我正在尝试在 Python 和 C++ 之间进行互操作。
这是我用于测试 DLL 方法的 C++ 代码:
extern "C" __declspec(dllexport) PEParserNamespace::PEParserBase& _cdecl test(PEParserNamespace::PEParserBase* base) {
printf("the C++ function was called\n");
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.Docs]:ctypes - Python 的外部函数库.
你有Undefined Behavior(实际上是一堆):
CTypes(顾名思义)适用于C。一个reference是一个C++特定的概念(C对此一无所知)
由于引用实际上是一个内存地址(就像一个指针,但有一些关键的区别),您的Python函数原型(restype)是不正确的。检查 [SO]:通过 ctypes 从 Python 调用的 C 函数返回不正确的值(@CristiFati 的回答) 以获取更多详细信息
CTypes 对象是 Python 包装器([Python.Docs]:公共对象结构 - 类型 PyObject)在实际的 C 上。
[Python.Docs]:内置函数 - 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%0zX\n", reinterpret_cast<size_t>(parg));
if (parg) {
printf(" dwFileSize: %u\n bytes: %u\n", 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 export\n")
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:\Work\Dev\StackExchange\StackOverflow\q075885080]> "e:\Work\Dev\VEnvs\py_pc064_03.10_test0\Scripts\python.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: 0x000001343FC50AC0 AddressOf: 0x000001343F7DFE30 <__main__.PEParserBase object at 0x000001343FC50AC0> hFile: None dwFileSize: 3141593 bytes: 2718282 fileBuffer: None Id: 0x000001343FCA85B0 AddressOf: 0x0000000000000000 <cparam 'P' (0x000001343F7DFE30)> C: Address: 0x1343F7DFE30 dwFileSize: 3141593 bytes: 2718282 Id: 0x000001343FC50B40 AddressOf: 0x000001343FC50B88 <__main__.LP_PEParserBase object at 0x000001343FC50B40> Id: 0x000001343FC50E40 AddressOf: 0x000001343F7DFE30 <__main__.PEParserBase object at 0x000001343FC50E40> hFile: None dwFileSize: 123 bytes: 2718282 fileBuffer: None Done.
id()
返回Python ctypes
包装器对象的地址,而不是被包装的对象的地址。为此,请使用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 called\n");
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)))
print(hex(ct.addressof(pbase.contents))) # .contents dereferences the pointer
# so we get the address of the structure
# not the address of the pointer itself.
输出:
the C++ function was called
0x25c0ee1daf0
0x25c0ee1daf0