当前位置: 首页 > 面试题库 >

Python ctypes cdll.LoadLibrary,实例化对象,执行其方法,私有变量地址被截断

乐正明辉
2023-03-14
问题内容

我用C语言编写了一个dll库,使用vs2017 64位进行编译,然后尝试使用python3.6 64位进行加载。但是,对象的成员变量的地址被截断为32位。

这是我的sim.c文件,已编译为sim.dll:

class Detector {
public:
    Detector();
    void process(int* pin, int* pout, int n);

private:
    int member_var;
};

Detector::Detector()
{
    memset(&member_var, 0, sizeof(member_var));
    myfile.open("addr_debug.txt");
    myfile << "member_var init address: " << &member_var << endl;
}
void Detector::process(int* pin, int* pout, int n);
{
    myfile << "member_var process address: " << &member_var << endl;
    myfile.close();
}

#define DllExport   __declspec( dllexport )

extern "C" {
    DllExport Detector* Detector_new() { return new Detector(); }
    DllExport void Detector_process(Detector* det, int* pin, int* pout, int n)
    {
        det->process(pin, pout, n);
    }
}

这是我的python脚本:

from ctypes import cdll
lib = cdll.LoadLibrary(r'sim.dll')

class Detector(object):
    def __init__(self):
        self.obj = lib.Detector_new()

    def process(self,pin, pout, n):
        lib.Detector_process(self.obj,pin, pout, n)

detector = Detector()

n = 1024
a = np.arange(n, dtype=np.uint32)
b = np.zeros(n, dtype=np.int32)

aptr = a.ctypes.data_as(ctypes.POINTER(ctypes.c_int))
bptr = b.ctypes.data_as(ctypes.POINTER(ctypes.c_int))

detector.process(aptr, bptr, n)

这是addr_debug.txt中member_var的地址:

member_var init address:    0000025259E123C4
member_var process address: 0000000059E123C4

因此,访问它会触发内存访问错误:

OSError: exception: access violation reading 0000000059E123C4

我试图理解该问题的一些尝试:

  • 将member_var定义为public而不是private,而不是help,地址仍被截断。
  • 将member_var定义为全局变量,那么地址就可以了。因此,我猜想member_var地址截断发生在将对象返回给python或将对象传递回dll时。

问题答案:

始终(正确) 为 C中 定义的函数指定 argtypesrestype __,否则( C89 风格)它们将 默认为
int
(通常为 32bit ),生成 _ ! 未定义的行为!!!。在 _64位上 ,地址(大于 _ 2 GiB_
)将被截断(这正是您所遇到的)。检查[SO]:从Python通过ctypes调用的C函数返回错误值(@CristiFati的答案)以获取更多详细信息。

另外,遇到问题时,请不要忘记[Python 3.Docs]:ctypes-
Python的外部函数库

下面是代码的改编版本。

detector.cpp

#include <stdio.h>
#include <memory.h>
#include <fstream>

#define C_TAG "From C"
#define PRINT_MSG_2SP(ARG0, ARG1) printf("%s - [%s] (%d) - [%s]:  %s: 0x%0p\n", C_TAG, __FILE__, __LINE__, __FUNCTION__, ARG0, ARG1)


using std::endl;

std::ofstream outFile;


class Detector {
    public:
        Detector();
        void process(int *pIn, int *pOut, int n);

    private:
        int m_var;
};


Detector::Detector() 
: m_var(0) {
    outFile.open("addr_debug.txt");
    outFile << "m_var init address: " << &m_var << endl;
    PRINT_MSG_2SP("&m_var", &m_var);
}

void Detector::process(int *pIn, int *pOut, int n) {
    outFile << "m_var process address: " << &m_var << endl;
    outFile.close();
    PRINT_MSG_2SP("&m_var", &m_var);
}


#define SIM_EXPORT __declspec(dllexport)

#if defined(__cplusplus)
extern "C" {
#endif

    SIM_EXPORT Detector *DetectorNew() { return new Detector(); }
    SIM_EXPORT void DetectorProcess(Detector *pDet, int *pIn, int *pOut, int n) {
        pDet->process(pIn, pOut, n);
    }
    SIM_EXPORT void DetectorDelete(Detector *pDet) { delete pDet; }

#if defined(__cplusplus)
}
#endif

code.py

import sys
from ctypes import CDLL, POINTER, \
    c_int, c_void_p
import numpy as np


sim_dll = CDLL("./sim.dll")

detector_new_func = sim_dll.DetectorNew
detector_new_func.restype = c_void_p

detector_process_func = sim_dll.DetectorProcess
detector_process_func.argtypes = [c_void_p, POINTER(c_int), POINTER(c_int), c_int]

detector_delete_func = sim_dll.DetectorDelete
detector_delete_func.argtypes = [c_void_p]


class Detector():
    def __init__(self):
        self.obj = detector_new_func()

    def process(self, pin, pout, n):
        detector_process_func(self.obj, pin, pout, n)

    def __del__(self):
        detector_delete_func(self.obj)


def main():
    detector = Detector()

    n = 1024
    a = np.arange(n, dtype=np.uint32)
    b = np.zeros(n, dtype=np.int32)

    aptr = a.ctypes.data_as(POINTER(c_int))
    bptr = b.ctypes.data_as(POINTER(c_int))

    detector.process(aptr, bptr, n)


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

注意事项

  • 正如我在开始时所说,问题是未指定 argtypesrestype (例如,对于 DetectorNew :comment detector_new_func.restype = c_void_p,您将再次遇到问题)
  • 问题中的代码缺少部分( #includeimport ,…),还存在一些语法错误,因此它无法编译,因此不遵循[SO]:如何创建最小化,完整且可验证的示例(mcve)准则。请在询问时确保拥有 mcve
  • 您分配(new Detector())的对象也必须被释放(否则,它将产生 内存泄漏 ),因此我添加了一个函数( DetectorDelete- 为此),该函数从( PythonDetector 的析构函数进行调用
  • 其他(非关键)更改(标识符重命名,一点重构,打印到 stdout 等)

输出

(py35x64_tes1)

e:\Work\Dev\StackOverflow\q052268294>”c:\Install\x86\Microsoft\Visual Studio
Community\2015\vc\vcvarsall.bat” x64

(py35x64_test) e:\Work\Dev\StackOverflow\q052268294>dir /b
code.py
detector.cpp

(py35x64_test) e:\Work\Dev\StackOverflow\q052268294>cl /nologo /DDLL

/EHsc detector.cpp /link /DLL /OUT:sim.dll
detector.cpp
Creating library sim.lib and object sim.exp

(py35x64_test) e:\Work\Dev\StackOverflow\q052268294>dir /b
code.py
detector.cpp
detector.obj
sim.dll
sim.exp
sim.lib

(py35x64_test)

e:\Work\Dev\StackOverflow\q052268294>”e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe”
./code.py
Python 3.5.4 (v3.5.4:3f56838, Aug 8 2017, 02:17:05) [MSC v.1900 64 bit
(AMD64)] on win32

From C - [detector.cpp] (28) - [Detector::Detector]:  &m_var:

0x0000020CE366E270
From C - [detector.cpp] (34) - [Detector::process]: &m_var:
0x0000020CE366E270



 类似资料:
  • 我有以下类要用模拟私有对象创建进行测试, 在上述场景中,如何使用EasyMock模拟私有“Dialog”变量及其实例化以及私有“listener”变量,以便在有条件的基础上继续执行其余操作。

  • 示例是带有 swap() 方法的 Card 类。实例化两个 Card 对象。该方法通过声明第三个 Card 变量来交换它们,但不实例化第三个对象。第三个变量用作临时保持器以支持交换。我预计交换不起作用,因为 temp 变量引用第一个对象,然后第一个对象被分配第二个对象,第二个对象被分配 temp,根据我的假设,它会拾取对第一个对象的更改。 输出: x y y x 我希望cards[0]和cards

  • 问题内容: 有这种行为的原因吗?我想知道内存级别有什么不同。编译器返回“无法获取复合文字的地址”,而我可以明确要求它执行此操作。 继承人去操场去操场链接 问题答案: 由于复合litteral是不可寻址直到它被分配给一个变量: 操作数必须是可寻址的,即变量,指针间接寻址或切片索引操作;或可寻址结构操作数的字段选择器;或可寻址数组的数组索引操作。 除可寻址性要求外,x还可为(可能带有括号的)复合文字。

  • 问题内容: 考虑示例: 我们怎么能做到这一点(而价值 确实 发生了变化)? 枚举实例不是隐式 静态的 并且是 final的 吗?另外,既然是,为什么我可以在其他课程之外访问它? 问题答案: 似乎没有人解决私人方面的问题。我的猜测是您正在从包含类型访问私有字段-您的枚举 实际上 是嵌套类型,如下所示: 这是完全合法和正常的-您 始终 可以从包含的类型访问嵌套类型的私有成员。 如果将枚举设置为顶级类型

  • 问题内容: 我有一个带有变量StudentID的班级Student: 我希望变量StudentID继续分配给每个Student创建的新ID号。每个ID号都应比上一个创建的ID号大一个,并且应等于已创建的对象总数。现在,每个对象的ID号为1。 问题答案: 将studentID设为静态成员 静态成员将在整个类的每个实例中保留,无论有多少个clas实例。

  • 在Ruby中,我有简单的旧源代码行来创建RSA公钥或私钥: 我的RSA公钥和私钥的格式如下,我对MRI和Jruby使用完全相同的密钥文件: 当我在MRI v2.4.0上运行它时-任何问题,我都可以实例化公钥和私钥: 但是,使用Jruby 9.0.4.0和9.1.8.0,我可以成功地启动公钥,但在初始化私钥时失败: 但创建私钥对象失败: 然后我尝试使用私钥,我已经从PEM文件中删除了-----BEG

  • 以下是使用对象进行批处理的典型步骤顺序 - 使用占位符创建SQL语句。 使用方法创建对象。 使用将自动提交设置为。 使用方法在创建的对象上添加SQL语句到批处理中。 在创建的对象上使用方法执行所有SQL语句。 最后,使用方法提交所有更改。 此示例代码是基于前面章节中完成的环境和数据库设置编写的。 以下代码片段提供了使用对象的批量更新示例,将下面代码保存到文件:BatchingWithPrepare

  • 以下是使用对象的批处理的典型步骤序列 - 使用方法创建对象。 使用将自动提交设置为。 使用方法在创建的对象上添加SQL语句到批处理中。 在创建的对象上使用方法执行所有SQL语句。 最后,使用方法提交所有更改。 此示例代码是基于前面章节中完成的环境和数据库设置编写的。 以下代码片段提供了使用对象的批量更新示例,将下面代码保存到文件:BatchingWithStatement.java - 编译上面代