3

The situation:

I have an application and a plugin dll both written in delphi 7.

The dll exports 3 functions: createobject:pointer, runobject(instance:pointer), freeobject(instance:pointer).

  • createobject:pointer creates an instance of an dll-internal workobject and returns the pointer to the object.

  • runobject(instance:pointer) takes this instance pointer as a parameter and uses the pointer to start some processingfunction in the objectinstance that this instance pointer points to.

  • freeobject(instance:pointer) takes the instance pointer and frees the internal object that this instance pointer points to.

I did this, so that i can create multiple workobject instances from the plugin dll.

Now, the application sets up 2 workerthreads. While setting up the 2 threads, the plugin dll is dynamically loaded twice via loadlibrary (one for each thread) and the exported functions are given to the thread. (Note: because it's the same DLL with the same filename, the DLL is loaded only once into my application and just the reference count of the loaded dll goes to 2.)

Each workerthread starts, calls CoInitialize(nil) to initialize the com system (because i want to use ado components) and then creates its own dll-internal object via the dllfunction createobject and then calls runobject with the returned instancepointer as parameter.

Now, the code inside runobject uses adoconnection + adoquery components to read from a database.

The adocomponents are created inside the workobject and nothing is shared between the 2 threads... no global vars used.

The problem:

I get strange random accessviolations while the 2 objectinstances, each on its own thread, use their own ado components to read from the DB...!?

Both threads start to read some Databaserows. Then, at some random time and "random place" in the adoquery read code, exceptions are being raised.

"Random place" means, that the exceptions sometimes occur in the call to adoquery.open, sometimes in the call to adoquery.next... the ado code is really simple... it looks like this:

with adoquery do
begin
    sql.clear;
    sql.add('select * from sometable');
    open;
    while not eof do
    begin
        test := fieldbyname('test').asstring;
        next;
    end;
    close
end;

I did some testing:

a) If I use only 1 thread (and so only 1 workobject inside the dll is beeing created) then everything works fine.

b) If I make a copy of the DLL File with another filename but the same code inside this file, and thread_1 loads dll_1 and thread_2 loads dll_2 then these 2 identical dlls are really both beeing loaded into my application and everything works fine.(Note: loadlibrary in this test was called from the context of the mainthread, not the context of each workerthread, but that seemed no problem because no exceptions did occur.)

c) If I don't use the DLL at all and just create my 2 workobjects directly on my 2 threads then everything works fine.

The exceptions only occur when I use adocomponents in 2 separate workobjects that are created on 2 threads and the creationcode of the workobject is inside a dll which is loaded only once into my application.

Questions:

If I call exported functions from a dll, does the loadlibrary call which loaded the dll has to be called from within the context of the thread? I don't think this is the case (see test b) ), but perhaps somebody knows better!?`Could this be causing my problems? If this is the case then there seems to be no way of using functions from one dll from multiple threads!?

Does anybody have an idea what causes these strange exceptions?

Any help/idea/explanation/suggestion greatly appreciated.

  • Are you creating each ADO object in their own thread context? (TADOQuery and TADOConnection) – EProgrammerNotFound Oct 31 '13 at 15:15
  • 1
    yes, the ado components are created inside the workobject and the workobject is created inside the dll with a call originating from the execute function of the tthread. so yes, they are created in the context of the thread. – user2703897 Oct 31 '13 at 15:18
  • May you provide a SSCCE for minimal reproduction? – EProgrammerNotFound Oct 31 '13 at 15:22
  • If you are executing queries in threads, you need to call `CoInitialize` before-hand, and `CoUninitialize` afterward. This requires the `ActiveX` unit in the `Uses` section. – LaKraven Oct 31 '13 at 15:27
  • @LaKraven OP is already doing this: "Each workerthread starts, calls CoInitialize(nil) to initialize the com system (because i want to use ado components) and then creates its own dll-internal object via the dllfunction createobject and then calls runobject with the returned instancepointer as parameter." – EProgrammerNotFound Oct 31 '13 at 15:33
  • @MatheusFreitas yes, but I'm trying to point out that `CoUninitialize` needs to be called *after* execution. The OP never specified whether he was doing this or not, so I was just trying to be helpful ;) – LaKraven Oct 31 '13 at 15:38
  • @LaKraven Indeed, it needs to be called to shutdown the COM dll! – EProgrammerNotFound Oct 31 '13 at 15:39
  • @Matheus: unfortunately not at the moment. the code is part of a bigger framework and is dependend on units that i can not make public now. i will have to extract/recode the relevant parts and set up a example project but that takes some time... perhaps i will do that next week... – user2703897 Oct 31 '13 at 15:39
  • @LaKraven, thanks, i will test that, but my problems start before there is a chance to shutdown com via CoUninitialize. Both Threads should first do their work, and shutdown after that... – user2703897 Oct 31 '13 at 15:41
  • Are both worker threads opening separate connections to the database, or do you have a single connection both are sharing? I ask because I've encountered pretty-much the exact same "random AV" issue when using separate connections in threads. – LaKraven Oct 31 '13 at 15:49
  • @LaKraven This should not be the problem. After all, it is not safe to cross thread boundary by sharing the connection. – EProgrammerNotFound Oct 31 '13 at 15:54
  • @MatheusFreitas yes, and when I have a single connection external to the threads, and point the ADO object(s) to that connection from inside my worker threads... everything works as expected. – LaKraven Oct 31 '13 at 15:55
  • @LaKraven: yes, both threads use own connections. do you think that sharind one tadoconnection object between the 2 threads would be better? – user2703897 Oct 31 '13 at 16:00
  • Can't hurt to try, right? It was the solution to my problem when I was multi-threading queries :) – LaKraven Oct 31 '13 at 16:00
  • Also: something to keep in mind is that your database server will have a connection limit, and depending on the number of client systems and the number of threads per client... you could hit that limit quite quickly. A single connection per client is usually considered "best practice". – LaKraven Oct 31 '13 at 16:01
  • @LaKraven May work, but it is breaking the rules. – EProgrammerNotFound Oct 31 '13 at 16:02
  • http://stackoverflow.com/questions/3266532/ok-to-use-tadoconnection-in-threads – LaKraven Oct 31 '13 at 16:05
  • @MatheusFreitas is right in that the ADO connection isn't specified as "thread-safe"... though the program I wrote with multi-threaded simultaneous queries didn't suffer as a result of having one connection. – LaKraven Oct 31 '13 at 16:07
  • @LaKraven: yes, it's worth a try i guess :) but i don't think that it is correct to share the connection between the threads – user2703897 Oct 31 '13 at 16:15
  • In the very least, it might give some additional insight... but yeah, it's not the elegant solution for sure. – LaKraven Oct 31 '13 at 16:17

1 Answers1

2

I found it. The Problem was, that i had to switch the delphi memorymanager to multithread mode with IsMultiThread := TRUE inside the dllcode! I already did this in the main application code, but the dll seems to use it's own version of the IsMultiThread flag or even it's own version of the delphi memorymanager. After adding IsMultiThread := TRUE to the dllcode everything works fine now.