简体   繁体   English

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

[英]Python 2.7 ctypes interfacing DLL providing char* as c_char_p leads to exception

i got a Dll where i have exported following function: 我得到了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 is a struct looking like this: (Variable names are modified) 参数是一个看起来像这样的结构:(变量名被修改)

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;

I use this dll within python by ctypes. 我通过ctypes在python中使用此dll。 I created a class to cover the Param structure. 我创建了一个类来介绍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) 
            ]

Next i have created a class to cover the DLL and its function: 接下来,我创建了一个涵盖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)

This is an example how I use the init function of the DLL. 这是我如何使用DLL的init函数的示例。 First a create an instance of Param, and put in some values. 首先创建一个Param实例,并放入一些值。 The problematic value - i will come later to that point - is the parameter LogFileName. 问题值-我稍后会讲到-参数LogFileName。

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

Next i create an instance of the DLL and call the function. 接下来,我创建DLL的实例并调用该函数。

mylittleDLL = MyDLL()
mylittleDLL.init(parameter)

The problem is, because of the LogFileName the function call is giving me an exception because of access violation. 问题是,由于LogFileName该函数调用由于访问冲突而给我一个异常。 When i remove the logfile from the structure in C++ and Python, the problem is gone. 当我从C ++和Python中的结构中删除日志文件时,问题就消失了。

Here the exception: 这里是例外:

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

If i modify python and c++ code and remove the LogFileName from both, it works fine. 如果我修改python和c ++代码并从两者中删除LogFileName ,它将正常工作。

Somehow it does not matter if i give the LogFilename by singlequotes or doublequotes or putting a 'b' in front of it like: b"SomeFileName" 无论如何,如果我通过单引号或双引号给LogFilename或在其前面放一个'b' b"SomeFileName"关系,例如: b"SomeFileName"

I have experimented by providing the logfilename as ctypes.by_ref (which won't work for sure). 我已经尝试通过将日志文件名提供为ctypes.by_ref(肯定不会起作用)来进行实验。 I have tried ctypes.create_string_buffer(b"filename") in different variations. 我尝试了ctypes.create_string_buffer(b"filename")的不同版本。 Recreated examples from stackoverflow and codeproject.com.. however.. i am stuck. 从stackoverflow和codeproject.com重新创建的示例。

I am quite sure I am doing a very silly mistake, but i do not get the hang of it where it is. 我很确定自己犯了一个非常愚蠢的错误,但是我不明白它的所在。 I just want to provide a filename from python to the dll.. that is actually all I want. 我只想提供一个从python到dll的文件名..实际上就是我想要的。

Any help is highly welcomed and appreciated! 任何帮助都将受到高度欢迎和赞赏!

Best regards, Tobias 最好的问候,托比亚斯

UPDATE UPDATE

With the help of eryksun, i have found different offsets of the location of LogFileName in the class in Python (52Bytes) and in the struct in C (51Bytes). 借助于eryksun,我在Python(52Bytes)类和C(51Bytes)结构中发现了LogFileName位置的不同偏移量。 I have also found some #pramga pack(push, 1) which sets the packaging to one byte. 我还发现了一些#pramga pack(push,1),它将包装设置为一个字节。 I have removed the pragma for testing purpose, but still the offsets are different (the same as before) and for sure the exception still happens.. I have checked once more for having the same structure in python class as well as in the c struct and also verified the datatypes.. All is fine. 我已经删除了用于测试目的的编译指示,但是偏移量仍然不同(与以前相同),并且可以确定仍然发生异常。.我再次检查了python类以及c结构中是否具有相同的结构并验证了数据类型。一切都很好。 It might happen because of some compiler optimization.... at least i will give a try by disabling optimization. 这可能是由于某些编译器优化而发生的。。。至少我会尝试禁用优化。

2nd UPDATE 第二次更新

Even with disabled pragma pack directives and disabled optimization it comes to different offsets in python (52Bytes) and C++(51Bytes) in the Param structure. 即使禁用了pragma pack指令和禁用了优化,它在Param结构中的python(52Bytes)和C ++(51Bytes)中也会出现不同的偏移量。 Well.. at least I could do some pointer arithmetic, but actually I want to avoid this... How can i ensure same offsets? 好吧..至少我可以做一些指针算术,但实际上我想避免这种情况。我如何确保相同的偏移量? In CTypes as well in C++ I am using std. 在CTypes和C ++中,我也在使用std。 data-types like uint32_t. 像uint32_t这样的数据类型。 I ordered the data by size in the struct (Except char* it is a 4Bytes type i quess). 我在结构中按大小排序了数据(char *除外,它是4字节类型的队列)。 I will reorder the LogFileName poniter just to check for any differences and put the results here.. maybe this does the trick.. 我将对LogFileName Poniter重新排序,以检查是否有任何差异并将结果放在此处。也许这可以解决问题。

3rd UPDATE -> Solution! 第三次更新->解决方案!

The problem has been the location of the char* (Pointer 4Byte Type) in the structure! 问题出在结构中char *(指针4Byte类型)的位置! I have changed the structure as follows (here Python, but the same is done in C++): 我更改了结构,如下所示(此处为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)
            ]

Thus i have put the 4Byte Type directly after the 8Byte Types int64_t . 因此,我将4Byte Type直接放在8Byte Types int64_t So the char* is directly on a 4Byte and 8Byte boarder (Byte allignment). 因此, char*直接位于4Byte和8Byte边界(字节分配)上。

My wild guess: In the former struct/class, the char* LogFileName has been the last element and i suspect 1 byte padding for the uint8_t type of "aaaa" thus it has been 51Bytes in C++ and 52Bytes in Python. 我的猜测:在以前的struct / class中, char* LogFileName是最后一个元素,我怀疑uint8_t类型的“ aaaa”填充了1个字节,因此在C ++中为51Bytes,在Python中为52Bytes。

Last update on this: I have found one more pragma pack(push,1) , this could also have caused the missing byte padding on the C++ side because of the order of the elements in the c struct Param. 对此的最新更新:我发现了另一个pragma pack(push,1) ,这也可能由于c struct Param中元素的顺序而导致C ++端缺少字节填充。

After this change char* LogFileName has offset of 32Bytes both in C++ and Python. 更改之后,在C ++和Python中char* LogFileName偏移量均为32Bytes。

Thank you soo much eryksun!!! 太谢谢你了!

The problem has been the location of the char* (Pointer 4Byte Type) in the structure! 问题出在结构中char *(指针4Byte类型)的位置! I have changed the structure as follows (here Python, but the same is done in C++): 我更改了结构,如下所示(此处为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)
            ]

Thus i have put the 4Byte Type directly after the 8Byte Types int64_t . 因此,我将4Byte Type直接放在8Byte Types int64_t So the char* is directly on a 4Byte and 8Byte boarder (Byte allignment). 因此, char*直接位于4Byte和8Byte边界(字节分配)上。

My wild guess: In the former struct/class, the char* LogFileName has been the last element and i suspect 1 byte padding for the uint8_t type of "aaaa" thus it has been 51Bytes in C++ and 52Bytes in Python. 我的猜测:在以前的struct / class中, char* LogFileName是最后一个元素,我怀疑uint8_t类型的“ aaaa”填充了1个字节,因此在C ++中为51Bytes,在Python中为52Bytes。

Last update on this: I have found one more pragma pack(push,1) , this could also have caused the missing byte padding on the C++ side because of the order of the elements in the c struct Param . 对此的最新更新:我发现了另一个pragma pack(push,1) ,由于c struct Param元素的顺序,这也可能导致C ++端缺少字节填充。

After this change char* LogFileName has offset of 32Bytes both in C++ and Python. 更改之后,在C ++和Python中char* LogFileName偏移量均为32Bytes。

Thank you soo much eryksun!!! 太谢谢你了!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM