在面向对象的Python中使用ctypes调用DLL文件中的函数时遇到问题

2 投票
3 回答
917 浏览
提问于 2025-04-15 13:06

我希望这个问题不是已经有人回答过的,也不是个愚蠢的问题。最近我在用几种工具编程,想让它们之间进行通信,以便创建一个测试程序。不过,我在使用某个特定工具时遇到了一些问题,尤其是在调用我从工具的DLL文件中“屏蔽”掉的函数时。

当我在交互式的Python环境中使用这些函数时,一切都运行得很好(虽然有点繁琐)。但是当我用面向对象的方式来实现这些函数时,程序就出问题了。其实并不是崩溃,而是没有任何反应。以下是我调用的第一个方法:(ctypes和ctypes.util已经导入)

    def init_hardware(self):
    """ Inits the instrument """
    self.write_log("Initialising the automatic tuner")
    version_string = create_string_buffer(80)
    self.error_string = create_string_buffer(80)
    self.name = "Maury MT982EU"
    self.write_log("Tuner DLL path: %s", find_library('MLibTuners'))
    self.maury = WinDLL('MlibTuners')
    self.maury.get_tuner_driver_version(version_string)
    if (version_string.value == ""):
        self.write_log("IMPORTANT: Error obtaining the driver version")
    else:
        self.write_log("Version number of the DLL: %s" % version_string.value)
    self.ThreeTypeLong = c_long * 3

这个方法运行得很好,一切都很完美,我得到了完美的日志记录。

但是当我尝试在程序的后面运行一个叫做:

def add_tuner_and_controller(self, name, serial_number, tuner_number=0):
    """ Adds the tuner to the driver object, controller is inside the tuner """
    self.write_log("Adding tuner %d and the built-in controller" % tuner_number)
    TempType = self.ThreeTypeLong()
    self.maury.add_controller(c_short(tuner_number), c_char_p(self.file_path), c_char_p(name), c_int(0), c_int(0), 
                              c_long(0), c_short(serial_number), self.error_string)
    self.maury.add_tuner(c_short(tuner_number), c_char_p(name), c_short(serial_number), c_short(0),
                            c_short(1), pointer(c_double()), TempType, pointer(c_double()), pointer(c_double()),
                            pointer(c_double()), self.error_string)

的方法时,程序突然停止工作/继续运行,但在调用“self.maury”这一行时什么也没发生。当我把所有内容放在init_hardware方法里时,它就能正常工作,所以我猜可能是内存方面有点“小错误”,或者是面向对象结构的问题。我真的希望能保持这种方式,有没有办法像这样隔离这些函数?还是说我必须把代码写成一大块?


编辑:
文档信息:
[说明:星号表示指针,方括号表示数组]

add_tuner函数用于在调谐器驱动对象中添加或更新一个调谐器。

short add_tuner(short tuner_number, char model[ ], short serial_number, short ctlr_num, short ctlr_port, short *no_of_motors, long max_range[ ], double *fmin, double *fmax, double *fcrossover, char error_string[ ])

输出: no_motors, max_range(一个包含三个数字的数组), fmin, fmax, fcrossover, error_string(超过80个字符), function-return->Error flag



add_controller函数用于在调谐器驱动对象中添加或更新一个控制器。

short add_controller(short controller_number, char driver[ ], char model[ ], int timeout, int address, long delay_ms, char error_string[ ])

输出: error_string, function-return->Error flag

3 个回答

0

在add-controller或add-tuner中,有哪些参数实际上是返回值吗?

我强烈建议你定义函数的原型,而不是直接用所有参数的类型转换来调用它们。

我相信你已经看过这个页面了,但你需要关注的部分是函数原型。这会让代码看起来更整洁,也更容易追踪和调试。

另外,正如Mark Rushakoff提到的,使用pointer(c_double())这样的方式调用其实不太好。我发现使用POINTER()效果要好得多,并且再次建议你先把值声明为一个变量,然后在函数调用时传入这个变量。这样至少你可以在之后检查这个变量的值,看看有没有奇怪的行为。

编辑:所以你的原型和调用看起来会像这样:

prototype = WINFUNCTYPE(
    c_int,              # Return value (correct? Guess)
    c_short,            # tuner_number
    c_char_p,           # file_path
    c_char_p,           # name
    c_int,              # 0?
    c_int,              # 0?
    c_long,             # 0?
    c_short,            # serial_number
    c_char_p,           # error_string
)
# 1 input, 2 output, 4 input default to zero (I think; check doc page)
paramflags = (1, 'TunerNumber' ), (1, 'FilePath' ), (1, 'Name' ), ...
AddController = prototype(('add_controller', WinDLL.MlibTuners), paramflags)

这样你的调用就整洁多了:

arg1 = 0
arg2 = 0
arg3 = 0
AddController(tuner_number, self.file_path, name, arg1, arg2, arg3, 
    serial_number, self.error_string)
1

我不太确定你具体遇到的问题是什么,但这里有几个一般性的建议:

对于那些在构造函数外部调用的函数,我强烈建议你在构造函数中设置它们的 argtypes。一旦你声明了 argtypes,就不需要再把所有参数转换成 c_shortc_double 等类型了。此外,如果你不小心把错误的参数传给 C 函数,Python 会抛出运行时错误,而不是让 DLL 崩溃。

还有一个小细节,你应该使用 x = 0; byref(x),或者可能用 POINTER(c_double)(),而不是在调谐器和控制器中使用 pointer(c_double())。

我最近也在用 Python 2.6 写一些 ctypes 类,没遇到你描述的问题。而且似乎也没有相关的 Python bug 报告,所以我相信我们可能在你遇到问题的方法中忽略了一个小细节。

0

我发现调用导出的DLL中的函数的唯一方法是通过在每个方法中使用DLL作为参数。(这个程序导出DLL,在每个被调用的方法中都会把它作为参数传递)。这看起来挺难看的,但这是我找到的唯一有效的方法。我甚至尝试把DLL作为类的属性来导出。由于我正在处理的系统比较复杂,所以我猜里面可能有一些糟糕的代码导致它失败。感谢大家的反馈和建议!

/Mazdak

撰写回答