Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to identify how many levels of CoInitialize have been called?

Tags:

delphi

activex

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;
like image 579
Jerry Dodge Avatar asked Jan 27 '13 01:01

Jerry Dodge


People also ask

When to call CoUninitialize?

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.

What is CoInitialize?

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.


3 Answers

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.

like image 128
David Heffernan Avatar answered Oct 17 '22 07:10

David Heffernan


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.

like image 45
kludg Avatar answered Oct 17 '22 08:10

kludg


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.

like image 22
pf1957 Avatar answered Oct 17 '22 08:10

pf1957