首页 > 解决方案 > 是否可以使用来自多个线程的 ctypes 调用 dll?

问题描述

我有一个简单的 python 脚本,它加载一个 dll 并从两个线程调用导出的函数。看起来当第二个线程启动时脚本突然停止。这是为什么?是否不支持从多个线程调用 dll 函数?

我的脚本:

import ctypes
import threading
import time

def tfunc():
    while True:
        my = ctypes.CDLL('/cygdrive/m/Workspace/py/src/cpp/ctypes-example.dll')
        my.test.restype = None
        my.test.argtypes = ()
        my.test()

t1 = threading.Thread(target=tfunc, daemon=True)
t2 = threading.Thread(target=tfunc, daemon=True)

t1.start()
time.sleep(1)
t2.start()

print('still here...')

输出:

$ python python-dll-thread.py
Load working...
Hello from dll: 0
Hello from dll: 1
...
Hello from dll: 1078
$

ctypes-example.hpp:

#pragma once

#include <windows.h>

extern "C" {

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved);
void test(void);

}

ctypes-example.cpp:

#include <stdio.h>

#include "ctypes-example.hpp"

void test(void) {
    static int a = 0;
    printf("Hello from dll: %i\n", a);
    ++a;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {

    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            // Code to run when the DLL is loaded
        printf ("Load working...\n");
            break;

        case DLL_PROCESS_DETACH:
            // Code to run when the DLL is freed
        printf ("Unload working...\n");
            break;

        case DLL_THREAD_ATTACH:
            // Code to run when a thread is created during the DLL's lifetime
        printf ("ThreadLoad working...\n");
            break;

        case DLL_THREAD_DETACH:
            // Code to run when a thread ends normally.
        printf ("ThreadUnload working...\n");
            break;
    }

    return TRUE;
}

这是我在 cygwin 上构建这个 dll 的方法:

$  g++ -Wall -Wextra -pedantic -c -fPIC ctypes-example.cpp -o ctypes-example.o
$  gcc -shared ctypes-example.o -o ctypes-example.dll

标签: pythonc++multithreadingdllctypes

解决方案


一个问题是两个线程都是守护进程,当唯一的非守护线程退出时,守护线程终止。

由于您正在编写一个DllMain,您可能还想阅读动态链接库最佳实践。该代码在 Microsoft 编译器上适用于我,但如果其他运行时触发其他 DllMain 调用,它们可能会在 DllMain 中创建竞争条件(可能是由于 printf?)。

这适用于 Microsoft 编译器:

测试.c

#include <windows.h>
#include <stdio.h>

#define API __declspec(dllexport)

API void test(void) {
    static int a = 0;
    printf("Hello from dll: %i\n", a);
    ++a;
}

API BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            // Code to run when the DLL is loaded
            printf ("Load working...\n");
            break;

        case DLL_PROCESS_DETACH:
            // Code to run when the DLL is freed
            printf ("Unload working...\n");
            break;

        case DLL_THREAD_ATTACH:
            // Code to run when a thread is created during the DLL's lifetime
            printf ("ThreadLoad working...\n");
            break;

        case DLL_THREAD_DETACH:
            // Code to run when a thread ends normally.
            printf ("ThreadUnload working...\n");
            break;
    }
    return TRUE;
}

测试.py

import ctypes
import threading
import time

my = ctypes.CDLL('./test')
my.test.restype = None
my.test.argtypes = ()

def tfunc():
    while True:
        my.test()
        time.sleep(.1) # slow down a bit

t1 = threading.Thread(target=tfunc, daemon=True)
t2 = threading.Thread(target=tfunc, daemon=True)

t1.start()
t2.start()

print('still here...')
time.sleep(1)  # illustrate threads stop after main thread exits

输出

Load working...
ThreadLoad working...
Hello from dll: 0
ThreadLoad working...
Hello from dll: 1
still here...
Hello from dll: 2
Hello from dll: 2
Hello from dll: 4
Hello from dll: 4
Hello from dll: 6
Hello from dll: 6
Hello from dll: 8
Hello from dll: 8
Hello from dll: 10
Hello from dll: 10
Hello from dll: 12
Hello from dll: 12
Hello from dll: 14
Hello from dll: 14
Hello from dll: 16
Hello from dll: 16
Hello from dll: 18
Hello from dll: 18
Unload working...

推荐阅读