当从Python调用c++代码(使用ctypes)时,我如何获得相同的对象返回(通过引用传递)? &g



我正在尝试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

最新更新