【问题标题】:Why is the return value of this `ctypes` wrapper function `c_long(0)` and not `c_long(3)`?为什么这个 `ctypes` 包装函数的返回值是 `c_long(0)` 而不是 `c_long(3)`?
【发布时间】:2020-08-26 21:38:01
【问题描述】:

为什么这个ctypes包装函数的返回值是c_long(0)而不是c_long(3)?

// main.cpp

#include <iostream>

class AComplicatedCPPObj {

    int *d_;

public:

    explicit AComplicatedCPPObj(int *d)
            : d_(d) {
    }

    int *getD() const {
        return d_;
    }

    void setD(int *d) {
        d_ = d;
    }
};


#ifdef CTYPESTEST_EXPORTS
#if defined(_WIN64)
#define CTYPESTEST_API __declspec(dllexport)
#else
#define CTYPESTEST_API __declspec(dllimport)
#endif

#endif

extern "C" {
CTYPESTEST_API AComplicatedCPPObj *AComplicatedCPPObj_new(int *d) {
    return new AComplicatedCPPObj(d);
}

CTYPESTEST_API int *AComplicatedCPPObj_getD(AComplicatedCPPObj *aComplicatedCppObj) {
    return aComplicatedCppObj->getD();
}

CTYPESTEST_API void AComplicatedCPPObj_setD(AComplicatedCPPObj *aComplicatedCppObj, int *d) {
    aComplicatedCppObj->setD(d);
}

}

编译成共享库的cmake脚本

cmake_minimum_required(VERSION 3.15)
project(ctypesTest)

set(CMAKE_CXX_STANDARD 14)

add_library(ctypesTest SHARED main.cpp)
set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/INSTALL)
add_definitions("-DCTYPESTEST_EXPORTS=TRUE")
install(TARGETS ctypesTest)

在 python 端:

import ctypes as ct
import os, glob, sys

WORKING_DIRECTORY = os.path.dirname(__file__)

# find the shared library
if sys.platform == "linux":
    so_path = os.path.join(WORKING_DIRECTORY, "INSTALL/lib/libctypesTest.so")
else:
    so_path = os.path.join(WORKING_DIRECTORY, "INSTALL/bin/ctypesTest.dll")

if not os.path.isfile(so_path):
    raise FileNotFoundError(so_path)

# load sharedd library into python
lib = ct.CDLL(so_path)


class AComplicatedCPPObj:

    def __init__(self, d):
        self.set_function_types("AComplicatedCPPObj_new", [ct.POINTER(ct.c_int)], ct.c_void_p)
        # checking some ctyeps functions
        print("d", d, ct.c_int(d), ct.pointer(ct.c_int(d)), ct.pointer(ct.c_int(d)).contents)

        self.obj = lib.AComplicatedCPPObj_new(ct.pointer(ct.c_int(d)))

        self.set_function_types("AComplicatedCPPObj_getD", [ct.c_void_p], ct.POINTER(ct.c_long))
        self.set_function_types("AComplicatedCPPObj_setD", [ct.c_void_p, ct.POINTER(ct.c_long)], None)

    def set_function_types(self, func_name, argtypes, restype):
        f = lib.__getattr__(func_name)
        f.argtypes = argtypes
        f.restype = restype

    def getD(self):
        return lib.AComplicatedCPPObj_getD(self.obj).contents

    def setD(self, d):
        if isinstance(d, str):
            d = d.encode()

        return lib.AComplicatedCPPObj_setD(self.obj, d)


if __name__ == '__main__':
    o = AComplicatedCPPObj(3)
    print(o.getD())

这会返回:

d 3 c_long(3) <__main__.LP_c_long object at 0x000001F66F0E8248> c_long(3)
c_long(0)

当我希望看到时

d 3 c_long(3) <__main__.LP_c_long object at 0x000001F66F0E8248> c_long(3)
c_long(3)

编辑 - 一个错误

我不确定我现在做了什么不同但现在重新运行此代码时我得到的输出是这样的:

d 3 c_long(3) <__main__.LP_c_long object at 0x000002320B8A52C8> c_long(3)
c_long(193626992)

另外,根据 cmets 中的一个问题,当我运行时:

x = 3
o = AComplicatedCPPObj(x)
print(o.getD())

输出是

d 3 c_long(3) <__main__.LP_c_long object at 0x000002320B8A5248> c_long(3)
c_long(193627312)

现在还没有

d 3 c_long(3) <__main__.LP_c_long object at 0x000002320B8A52C8> c_long(3)
c_long(3)

?

【问题讨论】:

  • 如果x = 3、o = AComplicatedCPPObj(x)怎么办?
  • 嗨,克里斯蒂,再次感谢您的意见,我已经更新了问题

标签: python c++ shared-libraries ctypes language-interoperability


【解决方案1】:

您在这一行中创建了一个指向临时对象的临时指针:

self.obj = lib.AComplicatedCPPObj_new(ct.pointer(ct.c_int(d)))

此行执行后,pointer 和 c_int 对象都被释放。所以你有未定义的行为。传递给函数的指针不再存在。

我不知道您为什么要传递指向对象的指针,但要解决您当前正在做的事情,您需要保留对 c_int 对象的引用,只要 C++ 代码包含指向它的指针:

import ctypes as ct

lib = ct.CDLL('./ctypesTest')

class AComplicatedCPPObj:

    def __init__(self, d):
        self.set_function_types("AComplicatedCPPObj_new", [ct.POINTER(ct.c_int)], ct.c_void_p)
        self.set_function_types("AComplicatedCPPObj_getD", [ct.c_void_p], ct.POINTER(ct.c_int))
        self.set_function_types("AComplicatedCPPObj_setD", [ct.c_void_p, ct.POINTER(ct.c_int)], None)

        # keep reference to object and pass by reference to C++
        self.param = ct.c_int(d)
        self.obj = lib.AComplicatedCPPObj_new(ct.byref(self.param))

    def set_function_types(self, func_name, argtypes, restype):
        f = lib.__getattr__(func_name)
        f.argtypes = argtypes
        f.restype = restype

    def getD(self):
        return lib.AComplicatedCPPObj_getD(self.obj).contents

    def setD(self, d):
        # update local object and pass again by reference
        self.param = ct.c_int(d)
        return lib.AComplicatedCPPObj_setD(self.obj, ct.byref(self.param))

if __name__ == '__main__':
    o = AComplicatedCPPObj(3)
    print(o.getD())

但为什么不直接按值传递整数,这样就不必跟踪内存呢??

更新的 .cpp:

#include <iostream>

class AComplicatedCPPObj {
    int d_;
public:
    explicit AComplicatedCPPObj(int d)
            : d_(d) {
    }
    int getD() const {
        return d_;
    }
    void setD(int d) {
        d_ = d;
    }
};

// FYI, _WIN32 is defined for both 32-bit and 64-bit windows, unlike _WIN64.
#if defined(_WIN32)
#   define CTYPESTEST_API __declspec(dllexport)
#else
#   define CTYPESTEST_API __declspec(dllimport)
#endif

extern "C" {
    CTYPESTEST_API AComplicatedCPPObj *AComplicatedCPPObj_new(int d) {
        return new AComplicatedCPPObj(d);
    }

    CTYPESTEST_API int AComplicatedCPPObj_getD(AComplicatedCPPObj *aComplicatedCppObj) {
        return aComplicatedCppObj->getD();
    }

    CTYPESTEST_API void AComplicatedCPPObj_setD(AComplicatedCPPObj *aComplicatedCppObj, int d) {
        aComplicatedCppObj->setD(d);
    }
}

更新的 Python:

import ctypes as ct

lib = ct.CDLL('./ctypesTest')

def set_function_types(func_name, argtypes, restype):
    f = lib.__getattr__(func_name)
    f.argtypes = argtypes
    f.restype = restype

class AComplicatedCPPObj:

    def __init__(self, d):
        set_function_types("AComplicatedCPPObj_new", [ct.c_int], ct.c_void_p)
        set_function_types("AComplicatedCPPObj_getD", [ct.c_void_p], ct.c_int)
        set_function_types("AComplicatedCPPObj_setD", [ct.c_void_p, ct.c_int], None)
        self.obj = lib.AComplicatedCPPObj_new(d)

    def getD(self):
        return lib.AComplicatedCPPObj_getD(self.obj)

    def setD(self, d):
        return lib.AComplicatedCPPObj_setD(self.obj, d)

if __name__ == '__main__':
    o = AComplicatedCPPObj(3)
    print(o.getD())

【讨论】:

  • 谢谢马克-感谢您的回答。我传递 int 指针的原因只是为了学习如何去做。很有可能我必须将这个功能用于我正在编写的包中的一些更复杂的对象(或者不知道 - 我仍在寻找使用 ctypes 的最佳方法)。我也刚刚意识到可以只使用ct.c_int64 来传递指针。
  • @CiaranWelsh 使用指针来传递指针。如果使用得当,它们会维护对它们所引用的 Python/ctypes 对象的引用。如果你使用整数,你可能会发现你的对象在你想要之前就被删除了。
猜你喜欢
  • 2023-02-24
  • 2011-01-20
  • 1970-01-01
  • 2019-06-26
  • 1970-01-01
  • 2019-11-23
  • 2021-11-16
  • 1970-01-01
  • 2015-04-15
相关资源
最近更新 更多