如何识别已调用多少级别的CoInitialize?

时间:2013-01-27 01:22:38

标签: delphi activex

我正在对一个凌乱的项目进行一些调试,这个项目是我以前开发人员不知道他们在做什么的,而主要的问题是尝试多线程应用程序失败了。我现在正在清理乱七八糟的东西,试图找出出错的地方。其中一个问题是对CoInitialize的调用不一致,以便使用ADO组件。

继续previous question,如何确定调用了CoInitialize级别?

例如,请考虑以下代码:

CoInitialize(nil);
try
  CoInitialize(nil);
  try
    //2 levels have been called, how to programatically check this?
  finally
    CoUninitialize;
  end;
finally
  CoUninitialize;
end;

4 个答案:

答案 0 :(得分:10)

如果我必须解决这个问题,我会通过调用CoInitializeCoInitializeExCoUninitialize来解决这个问题。我会挂钩对这些函数的调用,并使用线程局部变量来计算调用。

您可以通过将以下单位添加到项目中来完成此操作。

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.

与Serg的方法不同,这种技术不会改变程序的语义。

答案 1 :(得分:6)

你可以这样做:

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;

警告!由于以上功能关闭COM,因此无法在COM应用程序中使用,只能在调试时回答特定问题。

答案 2 :(得分:2)

如果我应该清理这样的项目,我会创建一个抽象线程祖先,它具有Execute overriden并分成三个虚拟方法,例如: BeforeExecuteTask,AfterExecuteTask和抽象ExecuteTask。

我将COM(un)初始化移动到Before / After方法中并删除所有其他出现的(DRY)。在每个后代中,我将从原始的Execute方法移动代码以覆盖ExecuteTask。

答案 3 :(得分:1)

就每个线程而言,必须计算Coinitialize和CoUninitialize时,如果COM被破坏,则不应调用CoUninitialize进行计数,您可以使用以下代码进行调试。

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.

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

将是以下单元的示例输出:

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.