Python 2.7 ctypes连接DLL提供char *作为c_char_p导致异常

时间:2017-07-10 12:43:21

标签: python dll ctypes

我有一个Dll,我已经导出了以下功能:

extern "C" __declspec(dllexport) uint32_t  __stdcall init(Param* InitParameter);


 extern "C" __declspec(dllexport) uint32_t  __stdcall init(Param* InitParameter)
    {  

      std::cerr << "Output test! " << std::endl;   
      std::string xy(InitParameter->log_filename);
      std::cerr  << xy << std::endl;
    ...
    }

Param是一个如下所示的结构:(变量名被修改)

typedef struct {
 int64_t a;
 int64_t b;
 int64_t c;
 int64_t d;
 uint32_t aa;
 uint32_t bb;
 uint32_t cc;
 uint16_t aaa;
 uint16_t bbb;
 uint8_t  aaaa;
 bool     Switch1
 bool     Switch2
 char*    LogFileName
}Param;

我通过ctypes在python中使用这个dll。我创建了一个类来覆盖Param结构。

class Param(ctypes.Structure):
    _fields_ = [            
            ("a",         ctypes.c_int64),
            ("b",         ctypes.c_int64),
            ("c",         ctypes.c_int64),
            ("d",         ctypes.c_int64),
            ("aa",        ctypes.c_uint32),
            ("bb",        ctypes.c_uint32),
            ("cc",        ctypes.c_uint32),
            ("aaa",       ctypes.c_uint16),
            ("bbb",       ctypes.c_uint16),                    
            ("aaaa",      ctypes.c_uint8),
            ("Switch1",   ctypes.c_bool),
            ("Switch2",   ctypes.c_bool),
            ("LogFileName", ctypes.c_char_p) 
            ]

接下来,我创建了一个类来覆盖DLL及其功能:

class MyDLL():   
    def __init__(self): #Constructor
        self.dll = ctypes.WinDLL('MyDll.dll')
        self.pf_init = self.dll['init']
        self.pf_init.restype = ctypes.c_uint32
        self.pf_init.argtypes = [ctypes.POINTER(Param)]

   def init(self, parameter_object):
        self.parameter = parameter_object
        try:
            self.pf_init(self.parameter)
            print "It works!"
        except WindowsError as e:
            print "Windows Error({0}): {1}".format(e.errno, e.message)

这是我如何使用DLL的init函数的示例。 首先创建一个Param实例,然后输入一些值。 问题值 - 我将在稍后到达 - 是参数 LOGFILENAME。

parameter = Param()
parameter.a = 0
parameter.b = 0
...
parameter.LogFileName = 'IAmGoingNuts'

接下来,我创建DLL的一个实例并调用该函数。

mylittleDLL = MyDLL()
mylittleDLL.init(parameter)

问题是,因为LogFileName函数调用 由于访问违规而给我一个例外。当我从C ++和Python的结构中删除日志文件时,问题就消失了。

这里有例外:

Windows Error(None): exception: access violation reading 0xC54D7C00

如果我修改python和c ++代码并从两者中删除LogFileName,它可以正常工作。

无论如何,如果我通过单引号或双引号给出LogFilename,或者在它前面放置一个'b',就像这样: b"SomeFileName"

我已经通过提供logfilename作为ctypes.by_ref进行了实验(这肯定不起作用)。我尝试了不同版本的ctypes.create_string_buffer(b"filename")。来自stackoverflow和codeproject.com的重新创建的例子..然而..我被卡住了。

我很确定我犯的是一个非常愚蠢的错误,但我不知道它在哪里。我只是想提供一个从python到dll的文件名..这实际上就是我想要的。

任何帮助都受到欢迎和赞赏!

祝你好运, 托拜厄斯

更新

在eryksun的帮助下,我找到了不同位置的偏移量 Python中的类(52Bytes)和C(51Bytes)中的结构中的LogFileName。 我还发现了一些#pramga pack(push,1),它将打包设置为一个字节。我已经删除了pragma用于测试目的,但是偏移量仍然不同(与之前相同)并且肯定异常仍然发生..我再次检查过在python类和c struct中具有相同的结构并且还验证了数据类型..一切都很好。它可能因为一些编译器优化而发生....至少我会通过禁用优化来尝试。

第二次更新

即使使用禁用的pragma pack指令和禁用的优化,它也会在Param结构中出现python(52Bytes)和C ++(51Bytes)中的不同偏移量。 好吧..至少我可以做一些指针算法,但实际上我想避免这个...我怎样才能确保相同的偏移?在CTypes以及C ++中我使用std。像uint32_t这样的数据类型。我按结构中的大小排序数据(除了char *它是一个4Bytes类型i quess)。我将重新排序LogFileName poniter只是为了检查是否有任何差异并将结果放在这里..也许这就是诀窍..

第3次更新 - &gt;溶液<!/强>

问题在于结构中char *(Pointer 4Byte Type)的位置!我已经改变了结构如下(这里是Python,但是相同 在C ++中完成:

class Param(ctypes.Structure):
    _fields_ = [            
            ("a",         ctypes.c_int64),
            ("b",         ctypes.c_int64),
            ("c",         ctypes.c_int64),
            ("d",         ctypes.c_int64),
            ("LogFileName", ctypes.c_char_p),
            ("aa",        ctypes.c_uint32),
            ("bb",        ctypes.c_uint32),
            ("cc",        ctypes.c_uint32),
            ("aaa",       ctypes.c_uint16),
            ("bbb",       ctypes.c_uint16),                    
            ("aaaa",      ctypes.c_uint8),
            ("Switch1",   ctypes.c_bool),
            ("Switch2",   ctypes.c_bool)
            ]

因此我将4Byte类型直接放在8Byte类型int64_t之后。 因此char*直接位于4Byte和8Byte寄存器上(Byte allignment)。

我的猜测:在前一个struct / class中,char* LogFileName是最后一个元素,我怀疑{a 1a}类型为“aaaa”的1字节填充因此在C ++中为51Bytes和Python中的52Bytes。

上次更新: 我发现了一个uint8_t,由于c struct Param中元素的顺序,这也可能导致C ++端缺少字节填充。

此更改后pragma pack(push,1)在C ++和Python中都有32Bytes的偏移量。

非常感谢eryksun !!!

1 个答案:

答案 0 :(得分:0)

问题在于结构中char *(Pointer 4Byte Type)的位置!我已经改变了结构如下(这里是Python,但在C ++中也是如此):

class Param(ctypes.Structure):
    _fields_ = [            
            ("a",         ctypes.c_int64),
            ("b",         ctypes.c_int64),
            ("c",         ctypes.c_int64),
            ("d",         ctypes.c_int64),
            ("LogFileName", ctypes.c_char_p),
            ("aa",        ctypes.c_uint32),
            ("bb",        ctypes.c_uint32),
            ("cc",        ctypes.c_uint32),
            ("aaa",       ctypes.c_uint16),
            ("bbb",       ctypes.c_uint16),                    
            ("aaaa",      ctypes.c_uint8),
            ("Switch1",   ctypes.c_bool),
            ("Switch2",   ctypes.c_bool)
            ]

因此我将4Byte类型直接放在8Byte类型int64_t之后。因此char*直接位于4Byte和8Byte寄存器上(Byte allignment)。

我猜测:在前结构/类中,char* LogFileName是最后一个元素,我怀疑uint8_t类型为“aaaa”的1字节填充因此它在C ++中为51Bytes,在Python中为52Bytes

上次更新:我发现了一个pragma pack(push,1),由于c struct Param中元素的顺序,这也可能导致C ++端缺少字节填充。

此更改后char* LogFileName在C ++和Python中都有32Bytes的偏移量。

非常感谢eryksun !!!