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