7

I'm building a multi-threaded windows service application in Delphi XE2 which uses ADO database components to connect to SQL Server. I've used CoInitialize(nil); plenty times before inside threads, but in this case, I have a function which I'm unsure about.

This function is called TryConnect which attempts to connect to a database with a given connection string. It returns true or false on the connection success. The problem is that this function will be used both inside and outside the main service thread, and it will be creating its own temporary TADOConnection component, which requires CoInitialize...

My question is do I need to call CoInitialize inside this function also? If I do, and since the service's execute procedure uses CoInitialize also, will they interfere if I call this function from within the service? The TryConnect function is inside of an object which is created from the main service thread (but will eventually be moved to its own thread). I need to know if calling CoInitialize() twice from the same thread (and CoUninitialize) will interfere - and how to handle this scenario properly.

Here's the code below...

//This is the service app's execute procedure
procedure TJDRMSvr.ServiceExecute(Sender: TService);
begin
  try
    CoInitialize(nil);
    Startup;
    try
      while not Terminated do begin
        DoSomeWork;
        ServiceThread.ProcessRequests(False);
      end;
    finally
      Cleanup;
      CoUninitialize;
    end;
  except
    on e: exception do begin
      PostLog('EXCEPTION in Execute: '+e.Message);
    end;
  end;
end;

//TryConnect might be called from same service thread and another thread
function TDBPool.TryConnect(const AConnStr: String): Bool;
var
  DB: TADOConnection; //Do I need CoInitialize in this function?
begin
  Result:= False;
  DB:= TADOConnection.Create(nil);
  try
    DB.LoginPrompt:= False;
    DB.ConnectionString:= AConnStr;
    try
      DB.Connected:= True;
      Result:= True;
    except
      on e: exception do begin
      end;
    end;
    DB.Connected:= False;
  finally
    DB.Free;
  end;
end;

So to clarify what it's really doing, I might have an occasion of this:

CoInitialize(nil);
try
  CoInitialize(nil);
  try
    //Do some ADO work
  finally
    CoUninitialize;
  end;
finally
  CoUninitialize;
end;
Jerry Dodge
  • 26,858
  • 31
  • 155
  • 327
  • 3
    Did you read the article on [STAs](http://msdn.microsoft.com/en-us/library/windows/desktop/ms680112(v=vs.85).aspx)? Using `CoInitialize()` means you have STAs. COM has to be initialized for every thread and `CoInitialize()`/`CoUnitialize()` calls have to be balanced. I don't know how this works in Delphi, but you'd probably have to marshal pointers between threads there as well. – Georg Fritzsche Feb 15 '12 at 02:02

2 Answers2

23

CoInitialize has to be called in every single thread that uses COM, regardless of what thread it is, or whether it has a parent thread or child threads. If the thread uses COM, it must call CoInitialize.

The correct answer here is "it depends". Since you know the service thread has called CoInitialize, if TryConnect is called from the service thread it won't need to be called again. If the other threads that could call it have also called CoInitialize, it won't need to be called, as the function will run under the calling thread.

The MSDN documentation specifically addresses this question (emphasis added):

Typically, the COM library is initialized on a thread only once. Subsequent calls to CoInitialize or CoInitializeEx on the same thread will succeed, as long as they do not attempt to change the concurrency model, but will return S_FALSE. To close the COM library gracefully, each successful call to CoInitialize or CoInitializeEx, including those that return S_FALSE, must be balanced by a corresponding call to CoUninitialize. However, the first thread in the application that calls CoInitialize with 0 (or CoInitializeEx with COINIT_APARTMENTTHREADED) must be the last thread to call CoUninitialize. Otherwise, subsequent calls to CoInitialize on the STA will fail and the application will not work.

So the answer is: If you're not sure, call CoInitialize. Do it in a try..finally block, and call CoUnitialize in the finally, or initialize in a constructor and uninitialize in the destructor.

Community
  • 1
  • 1
Ken White
  • 123,280
  • 14
  • 225
  • 444
  • So that translates to yes `CoInitialize` can be called more than once, so long as it's wrapped in a `try` block and it should finally `CoUninitialize` after each. – Jerry Dodge Feb 15 '12 at 08:46
  • Yes, as described in the final paragraph of my answer. – Ken White Feb 15 '12 at 11:55
4

Your service thread should really not be doing any work at all. It should be used exclusively for responding to Service Manager calls. The OnExecute or OnStart/OnStop of the service should control the instantiation and execution of a "MainWorkThread" that represents the functionality of your service. See https://stackoverflow.com/a/5748495/11225 for an example.

The main work thread could do the real work and/or delegate it to other threads. Each thread that could use COM should have the CoInitialize/CoUninitialize calls and the easiest way to achieve that is to code them in the outer most try finally block of the thread's (overridden) Execute method.

TDBPool or any other class using COM should not concern itself with the CoInitialize and CoUninitialize calls. These methods need to be called in every thread that could use COM and a class doesn't and shouldn't know in which thread it will be executed.

Community
  • 1
  • 1
Marjan Venema
  • 19,136
  • 6
  • 65
  • 79