我正在尝试Python和c++之间的互操作。
这是我的一个测试DLL方法的c++代码:extern "C" __declspec(dllexport) PEParserNamespace::PEParserBase& _cdecl test(PEParserNamespace::PEParserBase* base) {
printf("the C++ function was calledn");
base->bytes = 12345;
return *base;
}
我尝试在Python中这样使用它:
import ctypes
#DataStructures.py
class PEParserBase(ctypes.Structure):
_fields_ = [("hFile", ctypes.c_void_p),
("dwFileSize", ctypes.c_ulong),
("bytes", ctypes.c_ulong),
("fileBuffer",ctypes.c_void_p)]
class PEHEADER(ctypes.Structure):
xc = 0
#FunctionWrapper.py
def testWrapper(peParserBase, _instanceDLL):
_instanceDLL.test.argtypes = [ctypes.POINTER(PEParserBase)]
_instanceDLL.test.restype = PEParserBase
return _instanceDLL.test(ctypes.byref(pEParserBase))
pEParserBase = PEParserBase()
print("hallo welt")
_test = ctypes.CDLL('PeParserPythonWrapper.dll')
print(id(testWrapper(pEParserBase, _test)))
print(id(pEParserBase))
我期望testWrapper
返回原始PEParserBase
实例,但它没有-报告的id
值不同。c++代码没有创建任何新的PEParserBase
实例或其他任何东西,所以我相信问题必须在Python代码中。
为什么会发生这种情况,我如何修复它?
清单[Python。ctypes - Python的外部函数库。
你有U定义为(实际上是一堆):
-
CTypes(顾名思义)与C一起工作。参考是一个c++具体概念(C什么都不知道
-
由于一个引用实际上是一个内存地址(就像一个指针,但有一些关键的区别),你的Python函数原型(restype))不正确。检查[SO]:通过ctypes从Python调用的C函数返回不正确的值(@CristiFati的答案)了解更多细节。
注意:指定一个指针可以达到目的,但在技术上仍然是不正确的(如果想要严格的话)
CTypes对象是Python包装器。:通用对象结构- PyObject类型)超过实际的C。内置函数- id(object)返回Python包装对象的地址(这与包装的不同)。相反,您应该使用ctypes.addressof.
我准备了一个小例子(我根据Python和成员名定义了C结构——也得出了你在Win上的结论(但答案中的所有想法都与OS无关))。
-
dl00.cpp:
#include <stdio.h> #if defined(_WIN32) # include <Windows.h> # define DLL00_EXPORT_API __declspec(dllexport) #else # define DLL00_EXPORT_API #endif struct PEParserBase { HANDLE hFile; DWORD dwFileSize; ULONG bytes; LPVOID fileBuffer; }; typedef PEParserBase *PPEParserBase; #if defined(__cplusplus) extern "C" { #endif DLL00_EXPORT_API PPEParserBase func00ptr(PPEParserBase parg); #if defined(__cplusplus) } #endif PPEParserBase func00ptr(PPEParserBase parg) { printf(" C:n Address: 0x%0zXn", reinterpret_cast<size_t>(parg)); if (parg) { printf(" dwFileSize: %un bytes: %un", parg->dwFileSize, parg->bytes); parg->dwFileSize = 123; } return parg; }
-
code00.py:
#!/usr/bin/env python import ctypes as cts import ctypes.wintypes as wts import sys DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so") class PEParserBase(cts.Structure): _fields_ = ( ("hFile", wts.HANDLE), ("dwFileSize", wts.DWORD), ("bytes", wts.ULONG), ("fileBuffer", wts.LPVOID), ) def __str__(self): ret = [self.__repr__()] for field, _ in self._fields_: ret.append(" {:s}: {:}".format(field, getattr(self, field))) return "n".join(ret) PEParserBasePtr = cts.POINTER(PEParserBase) def print_ctypes_obj(obj): _id = id(obj) try: _addrof = cts.addressof(obj) except TypeError: _addrof = 0 print("Id: 0x{:016X}nAddressOf: 0x{:016X}n{:s}n".format(_id, _addrof, str(obj))) def main(*argv): dll = cts.CDLL(DLL_NAME) func00ptr = dll.func00ptr func00ptr.argtypes = (PEParserBasePtr,) func00ptr.restype = PEParserBasePtr use_ptr = 1 if use_ptr: print("Test pointer exportn") pb0 = PEParserBase(None, 3141593, 2718282, None) print_ctypes_obj(pb0) ppb0 = cts.byref(pb0) print_ctypes_obj(ppb0) ppb1 = func00ptr(ppb0) print() print_ctypes_obj(ppb1) pb1 = ppb1.contents print_ctypes_obj(pb1) if __name__ == "__main__": print("Python {:s} {:03d}bit on {:s}n".format(" ".join(elem.strip() for elem in sys.version.split("n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform)) rc = main(*sys.argv[1:]) print("nDone.n") sys.exit(rc)
输出:
[cfati@CFATI-5510-0:e:WorkDevStackExchangeStackOverflowq075885080]> sopr.bat ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ### [prompt]> [prompt]> "c:Installpc032MicrosoftVisualStudioCommunity2019VCAuxiliaryBuildvcvarsall.bat" x64 > nul [prompt]> cl /nologo /MD /DDLL dll00.cpp /link /NOLOGO /DLL /OUT:dll00.dll dll00.cpp Creating library dll00.lib and object dll00.exp [prompt]> [prompt]> "e:WorkDevVEnvspy_pc064_03.10_test0Scriptspython.exe" ./code00.py Python 3.10.9 (tags/v3.10.9:1dd9be6, Dec 6 2022, 20:01:21) [MSC v.1934 64 bit (AMD64)] 064bit on win32 Test pointer export Id: 0x0000013F2CB60740 AddressOf: 0x0000013F2C30FE30 <__main__.PEParserBase object at 0x0000013F2CB60740> hFile: None dwFileSize: 3141593 bytes: 2718282 fileBuffer: None Id: 0x0000013F2CBA72B0 AddressOf: 0x0000000000000000 <cparam 'P' (0x0000013F2C30FE30)> C: Address: 0x13F2C30FE30 dwFileSize: 3141593 bytes: 2718282 Id: 0x0000013F2CB607C0 AddressOf: 0x0000013F2CB60808 <__main__.LP_PEParserBase object at 0x0000013F2CB607C0> Id: 0x0000013F2CB60AC0 AddressOf: 0x0000013F2C30FE30 <__main__.PEParserBase object at 0x0000013F2CB60AC0> hFile: None dwFileSize: 123 bytes: 2718282 fileBuffer: None Done.
id()
在CPython中返回Pythonctypes
包装器对象的地址,而不是被包装对象的地址。请使用ctypes.addressof()
。
ctypes
也只理解C的普通旧数据(POD)类型和结构。它不知道c++名称空间或引用,但由于引用实际上是指针的c++语法糖,您可以使用.argtypes
和.restype
中的指针作为替代。
下面是一个简单的例子:
test.cpp
#include <stdio.h>
namespace PEParserNamespace {
struct PEParserBase {
void* hFile;
unsigned long dwFileSize;
unsigned long bytes;
void* fileBuffer;
};
}
extern "C" __declspec(dllexport)
PEParserNamespace::PEParserBase& _cdecl test(PEParserNamespace::PEParserBase* base) {
printf("the C++ function was calledn");
base->bytes = 12345;
return *base;
}
test.py
import ctypes as ct
class PEParserBase(ct.Structure):
_fields_ = (("hFile", ct.c_void_p),
("dwFileSize", ct.c_ulong),
("bytes", ct.c_ulong),
("fileBuffer",ct.c_void_p))
dll = ct.CDLL('./test')
dll.test.argtypes = ct.POINTER(PEParserBase),
dll.test.restype = ct.POINTER(PEParserBase) # Use a pointer here for the reference
base = PEParserBase()
pbase = dll.test(ct.byref(base))
print(hex(ct.addressof(base)), base.bytes)
print(hex(ct.addressof(pbase.contents)), base.bytes) # .contents dereferences the pointer
# so we get the address of the structure
# not the address of the pointer itself.
输出:
the C++ function was called, base=00000169712ADAF0
0x169712adaf0 12345
0x169712adaf0 12345