从 Python 调用 C++ 代码(使用 ctypes)时如何取回相同的对象(通过引用传递)?

问题描述 投票:0回答:2

我正在尝试在 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 c++ ctypes
2个回答
0
投票

清单 [Python.Docs]:ctypes - Python 的外部函数库.

你有Undefined Behavior(实际上是一堆):

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.

0
投票
CPython 中的

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
© www.soinside.com 2019 - 2024. All rights reserved.