简要说明
Python调用C动态库接口其实并不复杂,鉴于网上完整示例比较少,因此记录一下,这里不展开内部细节。
编译动态库
根据需求定义好相关接口,首先看一下函数声明的写法(C接口)例如:
DLL_PUBLIC int32_t getData(const CustomStructA *inParamA, const CustomStructB *inParamB, double inParamC, CustomStructA *outParamC );
这里声明了getData函数,两个入参,一个输出参数。在Python端调用的时候,Python端的实例会根据struct的内存转换成CustomStructA.例如这里将CustomStructA定义为:
struct CustomStructA
{
uint32_t size = 0;
uint32_t *buffer = nullptr;
};
struct CustomStructB
{
uint32_t memberA = 0;
uint32_t memberB = 0;
};
通过这个函数调用的例子说明如何传入数组,输出数组,内存管理问题。
#ifdef WIN32
#define DLL_PUBLIC _declspec(dllexport)
#else
#define DLL_PUBLIC __attribute ((visibility("default")))
#endif
DLL_PUBLIC int32_t getData(const CustomStructA *inParamA, const CustomStructB *inParamB, double inParamC, CustomStructA *outParamC )
{
if (nullptr == inParamA || nullptr == inParamB || nullptr == outParamC)
{
return -1;
}
printf("CustomStructA buffer size %d\n", inParamA->size);
for (uint32_t i = 0; i < inParamA->size; i ++)
{
printf("CustomStructA buffer buffer[%d] %d\n", i, inParamA->buffer[i]);
}
printf("CustomStructB memberA %d\n", inParamB->memberA);
printf("CustomStructB memberB %d\n", inParamB->memberB);
printf("inParamC %lf\n", inParamC);
outParamC->size = 9;
outParamC->buffer = new uint32_t[outParamC->size];
for (uint32_t m = 0; m < outParamC->size; m ++)
{
outParamC->buffer[m] = m;
}
return 0;
}
DLL_PUBLIC void releaseCustomStructA(CustomStructA *inParam)
{
if (nullptr != inParam && nullptr != inParam->buffer)
inParam->size = 0;
delete[] (inParam->buffer);
}
编译后会得到test.dll或者test.so
调用动态库
首先根据接口参数定义对应的Python端的对象:
from ctypes import *
class PyCustomStructA(Structure):
_fields_ = [("size", c_uint32),
("buffer", POINTER(c_uint32))]
class PyCustomStructB(Structure):
_fields_ = [("member_a", c_uint32),
("member_b", c_uint32)]
def __init__(self, val_a, val_b):
self.member_a = val_a
self.member_b = val_b
dllName = "../lib/Release/test.so"
dllHandle = cdll.LoadLibrary(dllName)
c_param_a = PyCustomStructA()
c_param_a.size = 6
input_buffer = c_uint32 * c_param_a.size
c_param_a.buffer = input_buffer()
c_param_a.buffer[0] = 2
c_param_a.buffer[1] = 0
c_param_a.buffer[2] = 2
c_param_a.buffer[3] = 1
c_param_a.buffer[4] = 0
c_param_a.buffer[5] = 3
c_param_b = PyCustomStructB(16, 39)
c_param_c = PyCustomStructA()
var_ret = dllHandle.getData(byref(c_param_a), byref(c_param_b), c_double(5.58), byref(c_param_c))
if 0 == var_ret:
for val_index in range(0, c_param_c.size):
print("c_param_c value %d" % c_param_c.buffer[val_index])
dllHandle.releaseCustomStructA(byref(c_param_c))
在类的定义中,其中_fields_说明具体成员,成员的具体类型形式为(“size”, c_uint32),除了是基础数据类型,还可以是自定义的类型如(“member_c”, PyCustomStructB)、(“member_c”, POINTER(PyCustomStructB))等等。
通过动态库的路径,调用cdll.LoadLibrary得到句柄;dllHandle加”.“加”接口函数名称“,可以直接调用该函数。
在参数中,指针可以使用byref函数得到Python端对象的地址,Python端传入的数值对象需要通过c_double()/c_uint64()等方式转换后才能作为double/uint64_t的值进行函数调用。
Python端传入数组时,可以通过以下方式:
input_buffer = c_uint32 * c_param_a.size
c_param_a.buffer = input_buffer()
构建数组
这里示例代码的调试输出结果为:
动态库:
CustomStructA buffer size 6
CustomStructA buffer buffer[0] 2
CustomStructA buffer buffer[1] 0
CustomStructA buffer buffer[2] 2
CustomStructA buffer buffer[3] 1
CustomStructA buffer buffer[4] 0
CustomStructA buffer buffer[5] 3
CustomStructB memberA 16
CustomStructB memberB 39
inParamC 5.580000
Python端:
c_param_c value 0
c_param_c value 1
c_param_c value 2
c_param_c value 3
c_param_c value 4
c_param_c value 5
c_param_c value 6
c_param_c value 7
c_param_c value 8
内存释放
从动态库中申请的内存需要主动释放:
dllHandle.releaseCustomStructA(byref(c_param_c))
c_param_c在释放后,这个实例就不要再继续使用了