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

旧城冷巷雨未停 提交于 2019-12-03 13:02:06

问题


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;

回答1:


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.




回答2:


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.




回答3:


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.




回答4:


As far as Coinitialize and CoUninitialize would have to be counted per thread and CoUninitialize should not be called for counting as far as COM will be broken, you could use the following code for debugging.

unit CoinitCounter;

interface

uses Classes, Generics.Collections, ActiveX, SyncObjs, Windows;

Type
  TCoIniRec = Record
    ThreadID: Cardinal;
    Init: Integer;
    InvalidInit:Integer;
    CoInit: Integer;
    IsCoinitialized:Boolean;
  End;

  TCoIniList = TList<TCoIniRec>;

  TCoinitCounter = Class
  private
    FCS: TCriticalSection;
    FList: TCoIniList;

    Constructor Create;
    Destructor Destroy; override;
  public
    Function Coinitialize(p: Pointer): HRESULT;
    Procedure CoUninitialize;
    Function LeftInitCount: Integer;
    Function ValidInits: Integer;
    Function InValidInits: Integer;
    Function IsCoinitialized:Boolean;

  End;

var
  FCoinitCounter: TCoinitCounter;

implementation

{ TCoinitCounter }

function TCoinitCounter.Coinitialize(p: Pointer): HRESULT;
var
  r: TCoIniRec;
  i, x: Integer;
begin
  FCS.Enter;
  Result := ActiveX.Coinitialize(p);
  if  Succeeded(Result) then
  begin    
    x := -1;
    for i := 0 to FList.Count - 1 do
      if FList[i].ThreadID = GetCurrentThreadID then
        x := i;
    if x > -1 then
    begin
      r := FList[x];
      r.IsCoinitialized := true;
      if Result = s_OK then r.Init := r.Init + 1
      else r.InvalidInit := r.InvalidInit + 1;
      FList[x] := r;
    end
    else
    begin
      ZeroMemory(@r,SizeOf(r));
      r.ThreadID := GetCurrentThreadID;
      r.IsCoinitialized := true;
      if Result = s_OK then r.Init :=  1
      else r.InvalidInit :=  1;
      FList.Add(r);
    end;
  end;
  FCS.Leave;
end;

procedure TCoinitCounter.CoUninitialize;
var
  r: TCoIniRec;
  i, x: Integer;
begin
  FCS.Enter;
  x := -1;
  ActiveX.CoUninitialize;
  for i := 0 to FList.Count - 1 do
    if FList[i].ThreadID = GetCurrentThreadID then
      x := i;
  if x > -1 then
  begin
    r := FList[x];
    r.IsCoinitialized := false;
    r.CoInit := r.CoInit + 1;
    FList[x] := r;
  end
  else
  begin
    r.ThreadID := GetCurrentThreadID;
    r.IsCoinitialized := false;
    r.CoInit := 1;
    FList.Add(r);
  end;
  FCS.Leave;
end;

constructor TCoinitCounter.Create;
begin
  inherited;
  FCS := TCriticalSection.Create;
  FList := TCoIniList.Create;
end;

destructor TCoinitCounter.Destroy;
begin
  FCS.Free;
  FList.Free;

  inherited;
end;

function TCoinitCounter.InValidInits: Integer;
var
  i, x: Integer;
begin
  FCS.Enter;
  x := -1;
  for i := 0 to FList.Count - 1 do
    if FList[i].ThreadID = GetCurrentThreadID then
      x := i;
  if x > -1 then
    Result :=  FList[x].InvalidInit
  else
    Result := 0;
  FCS.Leave;
end;

function TCoinitCounter.LeftInitCount: Integer;
var
  i, x: Integer;
begin
  FCS.Enter;
  x := -1;
  for i := 0 to FList.Count - 1 do
    if FList[i].ThreadID = GetCurrentThreadID then
      x := i;
  if x > -1 then
    Result := FList[x].Init + FList[x].InvalidInit - FList[x].CoInit
  else
    Result := 0;
  FCS.Leave;
end;

function TCoinitCounter.IsCoinitialized: Boolean;
var
  i, x: Integer;
begin
  FCS.Enter;
  x := -1;
  for i := 0 to FList.Count - 1 do
    if FList[i].ThreadID = GetCurrentThreadID then
      x := i;
  if x > -1 then
    Result := FList[x].IsCoinitialized
  else
    Result := false;
  FCS.Leave;
end;


function TCoinitCounter.ValidInits: Integer;
var
  i, x: Integer;
begin
  FCS.Enter;
  x := -1;
  for i := 0 to FList.Count - 1 do
    if FList[i].ThreadID = GetCurrentThreadID then
      x := i;
  if x > -1 then
    Result :=  FList[x].Init
  else
    Result := 0;
  FCS.Leave;
end;

initialization

FCoinitCounter := TCoinitCounter.Create;

finalization

FCoinitCounter.Free;

end.

This

ThreadID 6968 deserved: 0 counted: 0 valid: 0 invalid 0
ThreadID 2908 deserved: 4 counted: 4 valid: 1 invalid 3
ThreadID 5184 deserved: 1 counted: 1 valid: 1 invalid 0
ThreadID 7864 deserved: 8 counted: 8 valid: 1 invalid 7
ThreadID 7284 deserved: 2 counted: 2 valid: 1 invalid 1
ThreadID 6352 deserved: 5 counted: 5 valid: 1 invalid 4
ThreadID 3624 deserved: 4 counted: 4 valid: 1 invalid 3
ThreadID 5180 deserved: 0 counted: 0 valid: 0 invalid 0
ThreadID 7384 deserved: 6 counted: 6 valid: 1 invalid 5
ThreadID 6860 deserved: 9 counted: 9 valid: 1 invalid 8

would be an example output of the following unit:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    procedure DispOnTerminate(Sender: TObject);
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation
uses CoinitCounter;
{$R *.dfm}
Type
  TTestThread=Class(TThread)
    private
    FCounted,FTestCoinits:Integer;
    FValidInits: Integer;
    FInValidInits: Integer;
    protected
    Procedure Execute;override;
    public
    Constructor Create(cnt:Integer);overload;
    Property TestCoinits:Integer read FTestCoinits;
    Property Counted:Integer Read FCounted;
    Property ValidInits:Integer Read FValidInits;
    Property InivalidInits:Integer Read FInValidInits;
  End;


{ TTestThread }

constructor TTestThread.Create(cnt: Integer);
begin
  inherited Create(false);
  FTestCoinits:= cnt;
end;

procedure TTestThread.Execute;
var
 i:Integer;
begin
  inherited;
  for I := 1 to FTestCoinits  do
     FCoinitCounter.Coinitialize(nil);
  FCounted := FCoinitCounter.LeftInitCount;
  FValidInits := FCoinitCounter.ValidInits;
  FInValidInits := FCoinitCounter.InValidInits;
  for I := 1 to FCounted do
       FCoinitCounter.CoUninitialize;
end;




procedure TForm1.DispOnTerminate(Sender: TObject);
begin
  Memo1.Lines.Add(Format('ThreadID %d deserved: %d counted: %d valid: %d invalid %d'
     ,[TTestThread(Sender).ThreadID, TTestThread(Sender).TestCoinits,TTestThread(Sender).Counted,TTestThread(Sender).ValidInits,TTestThread(Sender).InivalidInits]));
end;
procedure TForm1.Button1Click(Sender: TObject);
var
 i:Integer;
begin

  for I := 1 to 10  do
      with TTestThread.Create(Random(10)) do OnTerminate := DispOnTerminate;
end;

end.


来源:https://stackoverflow.com/questions/14543496/how-to-identify-how-many-levels-of-coinitialize-have-been-called

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!