Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call not-thread safe DLL in multi-thread in C++?

I want to parallelize with threads (not with multi-process) a native c++ code which use a DLL (Compute.dll) which is not thread-safe.

Actually, I have a kind of loop that I can parallelize :

for(int i = 0; i < x; i++) {
    ComputeDLL.DoWork(i); // DLL API Call not thread-safe
}

I identify a way to parallelize my native code : Clone and rename Compute.dll in Compute1.dll, Compute2.dll, ..., ComputeN.dll and use one dll by thread. So for the link, in the same way, i have to duplicate Compute.lib in Compute1.lib, Compute2.lib, ..., ComputeN.lib

With this solution, I have to duplicate code in my app to define multiple ComputeDLL class : ComputeDLL1, ComputeDLL2, ... ComputeDLLN with an explicit static link :

#pragma comment(lib,'Compute1.lib'); // for ComputeDLL1.h
#pragma comment(lib,'Compute2.lib'); // for ComputeDLL2.h
etc.

Can you tell me if this solution will work ?

In this solution :

  • the number of thread must be known before compilation
  • i have too much duplicated code

Is there an another cleaner good way to solve my problem ? May I use LoadLibrary() ?

Thanks

Nb : I don't want to use muli-processing because in my real case, I have to send big data in parameter to my DLL (so i don't want to use files for communication because I need performance) and a DoWork is very fast (10 ms). I want to easily work in memory without sockets, windows messages queues, etc... and in the future, i will perhaps need custom synchronization between threads => multi-threading pattern is the best for me

like image 583
Patrice Pezillier Avatar asked Dec 13 '13 11:12

Patrice Pezillier


1 Answers

Using many DLLs has several drawbacks:

  • You will have problems with duplicate symbols. That is the linker will have a difficult time knowing to which dll a function call refers to
  • Threading problems can still occur when the DLL uses some shared global data.
  • You need at least one DLL per thread which means you will have to copy the DLL every time if you want it to work with arbitrary number of threads. Very clunky.

A solution to the duplicate symbols problem is using LoadLibrary / GetProcAddress for every thread you create:

  • when a thread starts you would have to call LoadLibrary to a DLL not loaded before
  • then use GetProcAddress to set the thread-local function pointers
  • from now on, you will have to use these thread-local function pointers to call the functions in the DLLs

This can be a bad idea when the DLL you load will load another DLL. In this case wsock32 loads ws2_32.dll. Problematic:

In [1]: import ctypes

In [2]: k=ctypes.windll.kernel32

In [3]: a=k.LoadLibraryA('wsock32_1.dll')

In [4]: b=k.LoadLibraryA('wsock32_2.dll')

In [5]: a
Out[5]: 1885405184

In [6]: b
Out[6]: 1885339648

In [7]: k.GetProcAddress(a, "send")
Out[7]: 1980460801

In [8]: k.GetProcAddress(b, "send")
Out[8]: 1980460801

Here the send loaded from separate copies of wsock32.dll will just point to the same send function, because wsock32.dll is just a trampoline for ws2_32.dll.

However, you get different entry points for send, when you load ws2_32.

In [1]: import ctypes

In [2]: k=ctypes.windll.kernel32

In [3]: a=k.LoadLibraryA('ws2_32_1.dll')

In [4]: b=k.LoadLibraryA('ws2_32_2.dll')

In [5]: a
Out[5]: 1874853888

In [6]: b
Out[6]: 1874591744

In [7]: k.GetProcAddress(a, "send")
Out[7]: 1874882305

In [8]: k.GetProcAddress(b, "send")
Out[8]: 1874620161

Additional info: LoadLibrary loads a dll to the address space of the calling process. LoadLibrary remembers when you have already loaded a dll, so by using different dll names you can force loadlibrary to load the same dll to different places in the process address space.

A better solution would be to manually load the DLL code from memory, it can spare you the trouble of maintaining different copies of the same dll on disk.

http://www.joachim-bauch.de/tutorials/loading-a-dll-from-memory/

like image 57
George Avatar answered Sep 23 '22 00:09

George