用ctypes向dll传入的数组,返回python后,其中的数据有时正确有时错误,怎么办?

运行环境: win10 + anaconda5.3 + jupyter

python文件

import numpy as np
import pandas as pd

mylib = ctypes.cdll.LoadLibrary('mydll.dll')
C_fun = mylib.func_name
C_fun.restypes = None
C_fun.argtypes = pass_args_Struct, c_double*10

# pass_args_Struct 是继承ctypes.Structure定义的结构体,代码略
def generate_Struct(data, 其他参数略):
    # 生成 pass_args_Struct,代码略
    # data是个DataFrame,用于接收下面myClass的data属性

class myClass():
    # myclass有个data属性,data是一个DataFrame
    # 其他代码略
    def func1(self, x):
        # 删除self.data的一列,再根据x参数重新添加这一列,代码略
        struct_x = generate_Struct(self.data, 其他参数略)
        myArr = (c_double*10)()
        C_fun(struct_x, myArr)
        npArr = np.ctypeslib.as_array(myArr,(10,))
        return pd.Series(npArr, _column_names) # _column_names 定义略

    def func2(self): 
        # 生成df,df是个只有一列值的DataFrame,代码略
        return df.apply(lamba x: self.func1(x[0]), axis=1, result_type='expand'))

    # 其他代码略

mydll.dll中的代码

#define API extern "C" __declspec(dllexport)
typedef pass_args_Struct {
    // 对应于python中pass_args_Struct,代码略
}

API void func_name(pass_args_Struct* x, double arr[]) {
    // 对arr进行一些操作,代码略
}

在jupyter中:
导入前述Python文件并生成 myObject=myClass() 之后,
执行ret1 = myObject.func1()没什么问题,
但是ret2 = myObject.func2()的结果则有时正确有时错误,错误的时候,ret2中会出现一些NaN值和错误的值。

之前把generate_Struct()定义成myclass的一个方法,连ret1也会出错;
之前的func1(self, x)中采用:

func1(self, x):
    # 其他代码略
    myArr = np.ctypeslib.as_array(myArr,(10,)) # 左边不用新名而直接用myArr
    return pd.Series(myArr, _column_names)

则ret1会频繁出错,基本上是对一次就错一次。

程序一直能运行,只是结果有时不正确。

请教各位大牛,正确的写法是什么样子的?

=====2018年11月30日更新====================================

我可能发现问题了:
Python文件TestX.py(放在PYTHONPATH下):

import numpy as np
from ctypes import Structure, c_double, c_int, POINTER

class struct_args(Structure):
    _fields_ = [('data',POINTER(c_double*2)),
                ('rows',c_int)]
class test():
    def __init__(self):
        self.data = None
    def get_args_2C(self):
        arr = np.ascontiguousarray(self.data[['foo','bar']].values, dtype=np.float)
        rows = c_int(arr.shape[0])
        return struct_args(arr.ctypes.data_as(POINTER(c_double*2)), rows)

在jupyter中:

import TestX
import pandas as pd
import numpy as np
mydata = pd.DataFrame(np.arange(1600).reshape(800,2),columns=['foo','bar']) # 行数不要太小
mytest = TestX.test()
mytest.data = mydata
args = mytest.get_args_2C()
np.ctypeslib.as_array(args.data,(800,))

输出的值经常是错误的。

=====2018年12月3日更新====================================
不知道为什么,但总算是不出错了:

python文件:

from ctypes import Structure,POINTER,c_double
def Struct_A(ctypes.Structure):
    _fields_ = [(), # 其他成员略
                ('my_arr',POINTER(c_double)] # 这个地方用c_double*10后面也会出错
class myClass():
    def makeStructA(self, 其他参数):
        arr = (c_double*10)()
        SA = Struct_A(……,arr) # 其他成员略
        return SA
    def myMethod(self, 其他参数):
        SA = self.makeStructA(其他参数)
        # myCfun是dll中的函数,功能是利用SA中数据进行一些计算,然后把结果写入SA.my_arr,具体代码略
        myCfun(SA)
        ret = pd.Series(np.ctypeslib.as_array(SA.my_arr,(10,)), _columns_name)
        ret['odd'] = 1 # 这里随便新加点什么就不会出错了
        return ret

以上代码如果没有ret['odd']=1那一行,则myObject.myMethod()返回的Series中都是错误的值(看着像是内存未初始化,比如-2.24934335e308之类),而随便给ret添加点什么内容,返回值就是正确的了。
但是在以下计算中仍然会出错,只是出错的频率变小了,而且多运行几次就会正确:

class myClass():
    # 接上文
    def myOptimize(self, arg_name, arg_range):
        ret = pd.DataFrame({arg_name:arg_range})
        _optimize = lambda arg: self.myMethod(**{arg_name:arg[arg_name]})
        return ret.join(ret.apply(_optimize, axis=1, result_type='expand'))

=====2018年12月7日更新====================================
又出错了!!!12月3日写的:

        ret['odd'] = 1 # 这里随便新加点什么就不会出错了

那个函数确实不出错了,但是别的函数用同样的写法(先生成c_double*shape再传入dll在C中写入值)得到的ret无论添加行还是添加列,多运行几次总会出错(内存被清理)。
换一种写法:

    略
    arr=np.zeros(shape)
    略
    # 然后传入arr.ctypes.data_as(POINTER(c_double*10))

目前暂时不出错了。
=====2018年12月13日更新====================================
前面的写法有问题:用函数生成结构体(比如makeStructA)再传递给dll就会出错,直接将makeStuctA的代码放到myMethod中就不会出错。
另外,传递结构体时用byref就不会出错了,在dll中用malloc给结构体的成员赋值都不会出错。

2个回答

shrek911
shrek911 你好。我看了你给的例子,传递数组部分,我写的和它一样,但是我的结果有时会出错。我的代码是放在类里面的,这个会有影响吗?
一年多之前 回复

自己回答吧:
func1(self, x)中不直接修改self.data,传参不用结构体,循环一万次没有出错。
补充一点:
将func1改成返回一个DataFrame(而不是直接将结果写入self.data中)时,必须这样写:

    def func1(self, x):
        # ~~删除self.data的一列,再根据x参数重新添加这一列,代码略~~ 改为:
        ret = pd.DataFrame(index=self.data.index, columns=[所有的列名全部列出]) # 只有一列
        # 根据x参数和self.data对ret各列进行赋值
        r2c = ret.values.ctypes.data_as(相应的ctype)
        struct_x = generate_Struct(self.data, r2c, 其他参数略) # 结构体定义已作相应的更改,代码略
        # 后面的代码略

如果不是先将所有的列名全部列出,而是后来往ret里添加列,则无论是用self.data.计算结果.copy()还是用self.data.计算结果.values,最终的结果都会出错。

Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问