I'm doing some debugging on a messy project I picked up where the previous developer didn't know what they were doing, and the main issue is failed attempts to multi-thread the application. I'm now cleaning up the mess and trying to figure out where things are going wrong. One of the issues is inconsistent calls to CoInitialize in order to use ADO components.
Continued from my previous question, how can I identify how many levels of CoInitialize have been called?
For example, take this code into consideration:
CoInitialize(nil);
try
  CoInitialize(nil);
  try
    //2 levels have been called, how to programatically check this?
  finally
    CoUninitialize;
  end;
finally
  CoUninitialize;
end;
                CoUninitialize should be called on application shutdown, as the last call made to the COM library after the application hides its main windows and falls through its main message loop.
CoInitializeEx() replaces the other function, adding a parameter that allows you to specify the threading model of the thread--either apartment-threaded or free-threaded. A call to CoInitialize() simply sets the threading model to apartment-threaded.
If I had to solve this problem I'd tackle it by instrumenting calls to CoInitialize, CoInitializeEx and CoUninitialize. I would hook the calls to those functions and use a thread local variable to count the calls.
You can do this by adding the following unit to your project.
unit InstrumentCOMinit;
interface
uses
  SysUtils, Windows, ComObj, ActiveX;
threadvar
  COMinitCount: Integer;
implementation
function CoInitialize(pvReserved: Pointer): HResult; stdcall; external 'ole32.dll';
function CoInitializeEx(pvReserved: Pointer; coInit: Longint): HResult; stdcall; external 'ole32.dll';
procedure CoUninitialize; stdcall; external 'ole32.dll';
function InstrumentedCoInitialize(pvReserved: Pointer): HResult; stdcall;
begin
  Result := CoInitialize(pvReserved);
  if Succeeded(Result) then
    inc(COMinitCount);
end;
function InstrumentedCoInitializeEx(pvReserved: Pointer; coInit: Longint): HResult; stdcall;
begin
  Result := CoInitializeEx(pvReserved, coInit);
  if Succeeded(Result) then
    inc(COMinitCount);
end;
procedure InstrumentedCoUninitialize; stdcall;
begin
  CoUninitialize;
  dec(COMinitCount);
end;
procedure Fail;
begin
  raise EAssertionFailed.Create('Fixup failed.');
end;
procedure PatchCode(Address: Pointer; const NewCode; Size: Integer);
var
  OldProtect: DWORD;
begin
  if not VirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, OldProtect) then begin
    Fail;
  end;
  Move(NewCode, Address^, Size);
  FlushInstructionCache(GetCurrentProcess, nil, 0);
  if not VirtualProtect(Address, Size, OldProtect, @OldProtect) then begin
    Fail;
  end;
end;
type
  PInstruction = ^TInstruction;
  TInstruction = packed record
    Opcode: Byte;
    Offset: Integer;
  end;
procedure RedirectProcedure(OldAddress, NewAddress: Pointer);
var
  NewCode: TInstruction;
begin
  NewCode.Opcode := $E9;//jump relative
  NewCode.Offset := NativeInt(NewAddress)-NativeInt(OldAddress)-SizeOf(NewCode);
  PatchCode(OldAddress, NewCode, SizeOf(NewCode));
end;
initialization
  RedirectProcedure(@ActiveX.CoInitialize, @InstrumentedCoInitialize);
  RedirectProcedure(@ActiveX.CoInitializeEx, @InstrumentedCoInitializeEx);
  RedirectProcedure(@ActiveX.CoUninitialize, @InstrumentedCoUninitialize);
  ComObj.CoInitializeEx := InstrumentedCoInitializeEx;
end.
Unlike Serg's approach, this technique will not change the semantics of your program.
You can do it like this:
function CoInitializeCount: Integer;
var
  HR: HResult;
  I: Integer;
begin
  Result:= 0;
  repeat
    HR:= CoInitialize(nil);
    if (HR and $80000000 <> 0) then begin
      Result:= -1;
      Exit;
    end;
    CoUnInitialize;
    if (HR <> S_OK) then begin
      CoUnInitialize;
      Inc(Result);
    end
    else Break;
  until False;
  for I:= 0 to Result - 1 do
    CoInitialize(nil);
end;
Warning! Since the above function closes COM it cannot be used in COM applications, only to answer the particular question while debugging.
If I should clean such project, I would create an abstract thread ancestor having Execute overriden and split into three virtual methods e.g. BeforeExecuteTask, AfterExecuteTask and abstract ExecuteTask.
I'd move COM (un)initialization into Before/After methods and delete all other occurencies (DRY). In each descendant I'd move code from the original Execute method to overriden ExecuteTask.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With