Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When do I need to call CoInitialize() in this scenario?

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;
like image 571
Jerry Dodge Avatar asked Feb 15 '12 01:02

Jerry Dodge


1 Answers

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.

like image 196
Ken White Avatar answered Nov 15 '22 08:11

Ken White