python - 使用 ctypes 为 C 函数传递和获取数组的问题

标签 python c dll ctypes

我正在尝试使用 ctypes 将二维数组和一维数组从我的 python 代码传递到 C 函数,然后该函数将一维数组返回到我的 python 终端。

1-我需要从 python 传递数组,正如您将在下面看到的(我是如何尝试做的)。 2-可能我没有正确设置参数类型和返回类型。我一直在搜索,但没有解决我的问题。

我的 C 代码名为 Thomas_PYTHON_DLL.c:

#include"stdio.h"
#include"stdlib.h"
#include"Thomas.h"


EXPORT double* Thomas(int dimension, double MatrizTridiagonal[dimension] 
[dimension],double vec_b[dimension]){


double* Thomas(int dimension, double MatrizTridiagonal[dimension] 
[dimension],double vec_b[dimension]){


double a[dimension];                            
double b[dimension];                            
double c[dimension];                            
double resp[dimension];                         

double *solution;
solution=(double *) malloc(dimension*sizeof(double));                       

for(int i=0;i<dimension;i++){resp[i]=vec_b[i];}


for(int i=0;i<dimension;i++){                    
        if(i==0){a[i]=0.0;}
        else{               
            a[i]=MatrizTridiagonal[i][i-1]; 
            }
}

for(int i=0;i<dimension;i++){                           
        b[i]=MatrizTridiagonal[i][i];       
    }

    for(int i=0;i<dimension;i++){               
        if(i==dimension-1){c[dimension-1]=0.0;}
        else{
            c[i]=MatrizTridiagonal[i][i+1];     
    }
}


for(int i=0;i<dimension;i++){                   
        if(i==0){
            c[i]=c[i]/b[i];                     
            resp[i]=resp[i]/b[i];               
                        }
        else{
            c[i]=c[i]/(b[i]-c[i-1]*a[i]);                           
            resp[i]=(resp[i]-a[i]*resp[i-1])/(b[i]-a[i]*c[i-1]);                
    }
}


for(int i=dimension-1;i>=0;i--){            

    if(i==dimension-1){
        solution[i]=resp[i];
    }

    else{

        solution[i]=resp[i]-c[i]*solution[i+1];
    }   

}

for(int i=0;i<dimension;i++){printf("x%d=|%0.2f| \n",i,solution[i]);}  

return solution;
//free(solution);
}

}

我的 C 代码名为 Thomas.h:

#define EXPORT __declspec(dllexport)

EXPORT double* Thomas(int dimension, double MatrizTridiagonal[dimension] 
[dimension],double vec_b[dimension]);

最后是我的 Python 代码,名为 Thomas_Python.py:

from ctypes import *

x=(c_double*5)
Tridiagonal = cdll.LoadLibrary('Thomas_dll.dll')
Tridiagonal.Thomas.restype=POINTER(x)
Tridiagonal.Thomas.argtypes=[c_int,((c_double*5)*5),(c_double*5)]

#arrays that i want to pass to C code
a=((c_double*5)*5)((2,-1,0,0,0),(-1,2,-1,0,0),(0,-1,2,-1,0),(0,0,-1,2,-1), 
(0,0,0,-1,2))
b=(c_double*5)(4,2,2,2,4)

r=Tridiagonal.Thomas(5,a,b)

print(r[2])

在上面的代码中,我希望在位置“2”处打印数组 r 的值,但打印结果显示:

<__main__.c_double_Array_5 object at 0x03A77350>

除了知道如何读取数组值,将整个数组作为列表获取之外,还有一个很大的帮助。非常感谢您的帮助和时间,我为我的英语道歉。

最佳答案

提到[Python.Docs]: ctypes - A foreign function library for Python .

您的代码存在许多问题。这里有一些:

  • 我不知道像这样的函数头 double *Thomas(int dimension, double MatrizTridiagonal[dimension][dimension], double vec_b[dimension]) 是如何编译的(因为维度)。但是,我没有用 gcc

    测试它
  • 您的 CPython 函数头(返回值)不同:double* vs. ctypes.POINTER(ctypes.c_double * 5)

  • 您从不释放返回的数组,导致内存泄漏

  • 代码风格(包括命名)可以大大改进

当处理数组(尤其是多维——因为维度需要在编译时知道)作为函数参数时,这意味着它们是从外部传递的,有两种处理方法:

  1. 为维度使用最大常数值。局限性非常明显

  2. 改用指针。缺点是函数头不是那么清晰,一般来说人们倾向于避开指针,尤其是当它们有不止一层的间接寻址时(2 星指针 :))

不过,我选择了后一种方式。我创建了一个虚拟 .dll,其中包含一个计算 2 个数组乘积的函数(将 1D 数组视为 2D 数组只有一列)。

dll00.c:

#include <stdlib.h>

#if defined(_WIN32)
#  define DLL00_EXPORT_API __declspec(dllexport)
#else
#  define DLL00_EXPORT_API
#endif


DLL00_EXPORT_API double *dll0Func0(int dimension, double **arr2D, double *arr1D)
{
    double *solution = (double*)calloc(dimension, sizeof(double));
    for (int i = 0; i < dimension; i++) {
        for (int j = 0; j < dimension; j++) {
            solution[i] += arr2D[i][j] * arr1D[j];
        }
    }
    return solution;
}


DLL00_EXPORT_API void dealloc(double *ptr)
{
    free(ptr);
}

code00.py:

#!/usr/bin/env python3

import sys
import ctypes as ct


DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")


def main(*argv):
    dim = 5
    DoubleArr = ct.c_double * dim
    DoubleArrArr = DoubleArr * dim

    DoublePtr = ct.POINTER(ct.c_double)
    DoublePtrPtr = ct.POINTER(DoublePtr)

    DoublePtrArr = DoublePtr * dim

    dll0 = ct.CDLL(DLL_NAME)

    dll0Func0 = dll0.dll0Func0
    dll0Func0.argtypes = (ct.c_int, DoublePtrPtr, DoublePtr)
    dll0Func0.restype = DoublePtr

    dealloc = dll0.dealloc
    dealloc.argtypes = (DoublePtr,)

    mat = DoubleArrArr(
        (2, -1, 0, 0, 0),
        (-1, 2, -1, 0, 0),
        (0, -1, 2, -1, 0),
        (0, 0, -1, 2, -1),
        (0, 0, 0, -1, 2),
    )
    vec = DoubleArr(4, 2, 2, 2, 4)

    
    res = dll0Func0(dim, ct.cast(DoublePtrArr(*(ct.cast(row, DoublePtr) for row in mat)), DoublePtrPtr), ct.cast(vec, DoublePtr))
    print("{0:s} returned {1:}".format(dll0Func0.__name__, res))
    for i in range(dim):
        print("{0:d} - {1:.3f}".format(i, res[i]))

    dealloc(res)


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    main(*sys.argv[1:])
    print("\nDone.")
    sys.exit(rc)

这里唯一棘手的事情是 DoublePtrArr 转换,因为 2D 数组不能转换为 double (**,不是类型)直接指针(我的意思是它可以,但是 2 个内存布局不同,所以它会生成未定义的行为,并且很可能该程序将段错误(访问冲突)),因此每个内部数组都在中间对象中单独转换,然后转换为 double (**) 指针(函数所期望的)。

输出:

cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q057295045]> sopr.bat
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[prompt]> "c:\Install\x86\Microsoft\Visual Studio Community\2017\VC\Auxiliary\Build\vcvarsall.bat" x64>nul

[prompt]> dir /b
dll00.c
code00.py
thomas.c
Thomas.h

[prompt]> cl /nologo /DDLL dll00.c  /link /NOLOGO /DLL /OUT:dll00.dll
dll0.c
   Creating library dll00.lib and object dll00.exp

[prompt]> dir /b *.dll
dll00.dll

[prompt]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code00.py
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 064bit on win32

dll0Func0 returned <__main__.LP_c_double object at 0x0000026CD4BEC4C8>
0 - 6.000
1 - -2.000
2 - 0.000
3 - -2.000
4 - 6.000

Done.

您还可以查看 [SO]: How to pass a 2d array from Python to C? (@CristiFati's answer)了解更多详情。

关于python - 使用 ctypes 为 C 函数传递和获取数组的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57295045/

相关文章:

c++ - 如何从给定日期找到星期几 `tm_wday`?

python - 'tf.data()' throwing 你的输入数据用完了;中断训练

python - 访问 pandas value_counts 的第一列

android - 错误 : OSCOKIRQ failed to assert

sql-server - 在生产环境中部署包时,如何避免在脚本任务的“添加引用”中手动浏览DLL?

windows - 在 DLL 中,函数表的结构是怎样的?

c# - .NET 控制台应用程序如何查找引用的程序集?

python - 使用 python requests 模块在 Github 中创建经过身份验证的 session

python - 在 Catplot 中叠加图像

c - 打印一些不需要的 ascii 代码的归档程序