我想从扭曲的应用程序中使用ctypes dll。这里的最小示例:
from ctypes import *
from threading import Lock
lock = Lock()
dll = windll.LoadLibrary('mydll.dll')
l = [1,2,3]
def callback():
lock.acquire()
l.pop()
lock.release()
return 0
C_CALLBACK = CFUNCTYPE(c_int)
c_callback = C_CALLBACK(callback)
# this is a non blocking function call starts a hardware task that fires a callback upon completion
dll.registerCallback(c_callback)
while(True):
# in reality this block gets called from a twisted server application
lock.acquire()
l.append(l.pop() + 1)
lock.release()
dll有一个函数(dll.registerCallback
),它接受一个ctypes回调函数,启动一个硬件事件,并在硬件指示硬件任务完成时触发一个回调。
来自API文档:
回调函数在DAQmx线程中调用。
网上有人试图解释什么是"DAQmx线程":
…您的回调将被调用并在DAQmx驱动程序线程中运行,并且将与您的程序异步运行(不在同一线程中)。
完整的文档可以在这里找到。为了简单起见,我更改了示例中的函数签名。
所以我们可以放心地假设dll正在生成一个线程。
锁是否可以确保回调函数在主循环中的pop
操作中间时不会尝试访问列表l
,反之亦然?或者这个方案只在使用threading
库创建的线程时才有效?这里推荐的做法是什么?
ctypes _CallPythonObject
所做的第一件事是调用PyGILState_Ensure()
,它将在必要时调用PyThreadState_New
来创建一个新的线程状态。除此之外,它是普通的Python线程代码,所以你的锁应该工作得很好。它在下面的例子(Linux, Python 2.7.3)中为我工作:
from ctypes import *
import threading
lock = threading.Lock()
lib = CDLL('./tmp.so')
callback_t = CFUNCTYPE(c_int)
def f():
with lock:
print threading.current_thread()
return 21
callback = callback_t(f)
lib.registerCallback(callback)
>>> lock.acquire()
True
>>> t = threading.Thread(target=lib.event)
>>> t.start()
>>> lock.locked()
True
>>> lock.release()
>>> <_DummyThread(Dummy-2, started daemon -1230402704)>
res: 21
DummyThread
输出是在回调中打印当前线程。在Python之外创建的线程获得一个'dummy'名称。
tmp.c:
#include <stdio.h>
#include <pthread.h>
typedef int (*callback_t)(void);
callback_t callback = NULL;
void *doCallback(void *arg)
{
int res = callback();
printf("res: %dn", res);
pthread_exit(0);
}
int event(void)
{
pthread_t callback_thread;
pthread_create(&callback_thread, NULL, doCallback, NULL);
pthread_join(callback_thread, NULL);
return 0;
}
int registerCallback(callback_t foo)
{
callback = foo;
return 0;
}