如何在 python 中为标准 Windows DLL 设置回调函数?

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

我正在开发一个小的 Python 程序来与数字信号调节器(Mantracourt 的FSU-SSBD)进行通信。基本上,该模块的工作原理是从连接的应变仪读取输入,并将其以 4800 Hz 的频率不断地流式传输到连接的计算机。

但也可以使用遵循ASCII协议的简单命令直接操作(无需专用软件工具包)。这就是我所追求的,因为我需要比 4800 Hz 低得多的速率,并且在导出工具包捕获的数据时,我需要记录比 30 分钟限制更长的数据。但是 - 剧透警报 - 它对我不起作用。

模块未设置为分配用于通信的 COM 端口,因此这不是一个选项。相反,Mantracourt 提供了一个 DLL 驱动程序以及一些 documentation,但我似乎无法在我的程序中正确使用它。

我的程序(最小工作示例):

import ctypes

class DLLCommunicator:

    @ctypes.CFUNCTYPE(None,ctypes.c_int,ctypes.c_float)
    def callback(station,value):
        print(f"Station: {station}, Value: {value}")

    def testDLL(self):
        DLLpath = "C:\\Program Files (x86)\\FSUDrvXX DLL Driver\FSUDrv64.dll"
        FSUDLL = ctypes.WinDLL(DLLpath)
        FSUDLL.INITIALISE(self.callback)
        res1 = FSUDLL.OPENPORT()
        print(f"OpenPort: {res1}")
        res2 = FSUDLL.DEVICECOUNT()
        print(f"DeviceCount: {res2}")
        FSUDLL.COMMANDMODE()
        res3 = FSUDLL.READCOMMAND("OVAL")
        print(f"OVAL: {res3}")

if __name__ == "__main__":
    DLLTest = DLLCommunicator()
    DLLTest.testDLL()

正如您在代码中看到的那样,第一步是初始化 DLL 并提供回调地址以接收 DLL 命令的输出。然后调用OPENPORT()函数打开FSU-SSBD接口进行通信。然后我调用 COMMANDMODE() 将 FSU-SSBD 从其以 4800 Hz 流式传输实时数据的默认状态切换到命令模式,然后我应该能够读/写不同的 ASCII 命令,如“OVAL”,它读取一个来自模块的单个值。

运行时,上述代码产生以下输出:

OpenPort: -500 #("Cannot list USB Devices. Problem with USB bus.")
DeviceCount: 1 #("A long value indicating the number of detected FSU Modules", 1 is correct) 
OVAL: -200 #("Error during read or write.")

如果我在 FSU-SSBD 断开连接时运行程序,则会得到以下输出:

OpenPort: -600 #("The calibration information cannot be retrieved so streamed values may be  incorrect.")
DeviceCount: 0 #("A long value indicating the number of detected FSU modules", 0 is now correct)
OVAL: -200 #("Error during read or write.")

我看到它的方式我可以清楚地与模块联系,因为它理解并响应我的DLL函数调用并且知道模块是否已连接。这让我觉得我尝试设置回调函数的方式有问题。

作为附加问题或奖励信息,程序在最后一个打印语句后崩溃。我不知道为什么。

我知道如果手边没有实际的 FSU-SSBD 模块,这很难(如果不是不可能的话)复制。但我希望在 ctypes 和 DLL 与 Python 通信方面有经验的人可以抽出时间查看 DLL 文档并找出我的代码中断的地方。文档末尾有一些 VBA、PureBasic 和 C 的示例声明。

感谢您的阅读!

python dll ctypes
1个回答
0
投票

你可以用

CFUNCTYPE
装饰一个类方法,如果它用
@staticmethod
装饰,所以它不需要
self
.

在类实例上使用方法是很棘手的。 bound 方法需要包装在一个 (CFUNCTYPE) 实例中,并且该实例必须在使用回调的生命周期内保存。

下面的DLL代码用于测试回调是否正确传递:

test.c(微软编译命令:cl /LD test.c)

#define API __declspec(dllexport)  // Windows requirement for function export.

typedef void (*CALLBACK)(int, float);

void API INITIALISE(CALLBACK cb) {
    cb(123, 4.56f);
}

以下工作并允许通过回调访问类实例(

self
):

import ctypes

CALLBACK = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_float)

class DLLCommunicator:

    def callback(self, station, value):
        print(f'Station: {station}, Value: {value}')

    def testDLL(self):
        DLLpath = './test.dll'
        FSUDLL = ctypes.WinDLL(DLLpath)
        FSUDLL.INITIALISE.argtypes = CALLBACK,  # Recommended so ctypes can typecheck parameters
        FSUDLL.INITIALISE.restype = None        # Declares return type.
        # Wrap the bound method and save it as an attribute.
        self.cb = CALLBACK(self.callback)
        FSUDLL.INITIALISE(self.cb)

if __name__ == '__main__':
    DLLTest = DLLCommunicator()
    DLLTest.testDLL()

输出:

Station: 123, Value: 4.559999942779541

如果不需要

self
访问权限,以下内容也适用:

import ctypes

CALLBACK = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_float)

class DLLCommunicator:

    @CALLBACK      # decorator order matters!
    @staticmethod
    def callback(station, value):
        print(f'Station: {station}, Value: {value}')

    def testDLL(self):
        DLLpath = './test.dll'
        FSUDLL = ctypes.WinDLL(DLLpath)
        FSUDLL.INITIALISE.argtypes = CALLBACK,  # Recommended so ctypes can typecheck parameters
        FSUDLL.INITIALISE.restype = None        # Declares return type.
        FSUDLL.INITIALISE(self.callback)

if __name__ == '__main__':
    DLLTest = DLLCommunicator()
    DLLTest.testDLL()

(相同的输出)

© www.soinside.com 2019 - 2024. All rights reserved.