【问题标题】:How to identify how many levels of CoInitialize have been called?如何确定调用了多少级 CoInitialize?
【发布时间】:2013-01-27 01:22:38
【问题描述】:

我正在对一个杂乱无章的项目进行调试,之前的开发人员不知道他们在做什么,主要问题是尝试多线程应用程序失败。我现在正在清理混乱并试图找出哪里出了问题。其中一个问题是为了使用 ADO 组件而对 CoInitialize 的调用不一致。

继续我的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;

【问题讨论】:

  • 您在 Google 或以前的问题中找不到答案,因为没有必要这样做,因此以前没有人问过。应用程序中每个线程的修复方法是在线程的Execute 开头调用CoInitialize,然后是包含代码处理循环的try 块并结束使用finally 调用CoUnitialize。无需计算调用次数,因为您可以确定每个线程都包含一对正确配对的调用。 (顺便说一句,这在您之前的问题中提到过。)
  • 在我开始改变事情之前,我正在整理分析代码的碎片化程度,然后提出一个修复它的工作计划,这不涉及荒谬的截止日期期望我完成这项任务。我很可能会从头开始重建它,但我需要首先证明它的设计有多可怕。这就是我想要这样做的原因。
  • 您无需分析或提出计划。正如肯所说,只有一个计划。实施。如果需要分析,我只需将 CoInitialize 和 CoUninitialize 路由到记录它们被调用的函数。
  • @DavidHeffernan 这段代码中的问题不仅仅是CoInitialize,这只是问题的一小部分。我知道你想告诉我什么。这不是重点。如果这里的人没有把它从我身上推开,我什至都不会提到我为什么需要它。

标签: delphi activex


【解决方案1】:

如果我必须解决这个问题,我会通过检测对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 非常好的解决方案(sry 之前刚刚添加评论,忘记投票了)
【解决方案2】:

你可以这样做:

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 应用程序中使用,只能在调试时回答特定问题。

【讨论】:

  • 您为每个 CoInitialize 调用了两次 CoUnInitialize。这可以很容易地测试。只需添加 ` Var UI,I:Integer;函数CoUnInitialize:整数;开始公司(UI); ActiveX.CoUninitialize;结尾;函数 CoInitialize(p:Pointer):HRESULT;开始公司(I);结果 := ActiveX.CoInitialize(p);结尾;到您的代码并尝试: procedure TForm1.Button2Click(Sender: TObject);开始用户界面:= 0;我:= 0; CoInitialize(nil); CoInitialize(nil); CoInitialize(nil); CoInitialize(nil);初始化计数; Showmessage(IntToStr(UI)+'-'+IntToStr(i));结尾; `
  • @JeroenWiertPluimers - 我不知道你在说什么线程安全问题。你能解释一下吗?
  • @Jeroen 的评论毫无意义。此代码是线程安全的。但是,它很可能会破坏应用程序,因为调用此函数时存在的任何 COM 对象现在都存在于不同的 COM 实例中。而且公寓模式可能已经改变!
  • @DavidHeffernan +1,该代码不应用于 COM 应用程序,仅用于在调试时回答特定问题。
  • 在我看来,@Serg 更干净的是挂钩 CoXXX 方法。
【解决方案3】:

如果我应该清理这样的项目,我会创建一个抽象线程祖先,并覆盖 Execute 并拆分为三个虚拟方法,例如BeforeExecuteTask、AfterExecuteTask 和抽象的 ExecuteTask。

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

【讨论】:

  • 这不是对所提问题的回答,虽然这可能是一个很好的建议,但不应将其作为答案发布。 (并且您所说的大部分内容已经作为对原始问题的评论发布,这是发布它的适当位置,并且杰里明确表示他对这些 cmets 中的此线程中的任何其他信息也不感兴趣。 )
  • 我很久以前为另一个项目创建了一个与此完全相同的线程(不同的命名)。不过,它有点特定于那个项目。但是,是的,这是评论,而不是答案。
【解决方案4】:

就必须按线程计算 Coinitialize 和 CoUninitialize 并且不应调用 CoUninitialize 进行计数,因为 COM 会被破坏,您可以使用以下代码进行调试。

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.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-10-26
    • 1970-01-01
    • 2010-09-06
    • 1970-01-01
    • 2014-06-29
    • 2022-01-26
    • 1970-01-01
    • 2011-09-22
    相关资源
    最近更新 更多