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



我得到了一个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;
...
}

参数是一个看起来像这样的结构:(变量名称被修改(

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 的初始化函数的示例。 首先创建一个参数的实例,并输入一些值。 有问题的值 - 我稍后会谈到这一点 - 是参数 日志文件名。

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"

我已经通过提供日志文件名作为ctypes.by_ref进行了实验(这肯定不起作用(。我尝试了不同变体的ctypes.create_string_buffer(b"filename")。从堆栈溢出和 codeproject.com 重新创建示例。然而。。我卡住了。

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

任何帮助都非常欢迎和赞赏!

此致敬意 托比亚斯

更新

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

第二次更新

即使禁用了编译指示包指令和禁用优化,它也会在 python(52Bytes(和 C++(51Bytes( 结构中出现不同的偏移量。 井。。至少我可以做一些指针算术,但实际上我想避免这种情况......如何确保相同的偏移量?在 CTypes 中,我也在C++中使用 std. 数据类型,如 uint32_t。我在结构中按大小对数据进行排序(除了 char* 它是 4Bytes 类型的 i quess(。我将重新排序 LogFileName poniter 只是为了检查是否有任何差异并将结果放在这里。.也许这能解决问题..

第三次更新 ->解决方案!

问题在于结构中字符*(指针 4Byte 类型(的位置!我更改了结构如下(这里是 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++侧缺少字节填充,因为 c 结构 Param 中元素的顺序。

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

非常感谢你!!

问题在于结构中字符*(指针 4Byte 类型(的位置!我更改了结构如下(这里是 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++侧缺少字节填充,因为 cstruct Param中元素的顺序。

在此更改之后,char* LogFileName在C++和Python中的偏移量均为32Bytes。

非常感谢你!!

最新更新