Reputation: 2334
I have a simple python script which loads a dll and calls exported function from two threads. Looks like script suddenly stops when second thread is started. Why is that? Is it unsupported to call dll functions from multiple threads?
My script:
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...')
Output:
$ 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;
}
Here is how I build this dll on cygwin:
$ g++ -Wall -Wextra -pedantic -c -fPIC ctypes-example.cpp -o ctypes-example.o
$ gcc -shared ctypes-example.o -o ctypes-example.dll
Upvotes: 1
Views: 956
Reputation: 177891
One problem is having both threads being daemons, when the only non-daemon thread exits, the daemon threads terminate.
Since your are writing a DllMain
you may want to read Dynamic-Link Library Best Practices as well. The code works for me on the Microsoft compiler, but other runtimes may create race conditions in DllMain (perhaps due to the printf?) if they trigger other DllMain calls.
This works in the Microsoft compiler:
test.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;
}
test.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
Output
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...
Upvotes: 2