Adam
Adam

Reputation: 2334

Is it possible to call dll using ctypes from multiple threads?

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

Answers (1)

Mark Tolonen
Mark Tolonen

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

Related Questions