在 EXE 和 COM DLL 之间共享对象/接口时,访问冲突



当不同的模块(exe和DLL(有自己的对象/接口实例副本在它们之间传递时,程序在退出时会给出一个AV。在下面的代码片段中,当对 ReleaseManager(( 的调用(从 EXE 和 DLL(被注释掉时,AV 发生在GlobalUnit.pas 的终结部分。请参阅下面的简化和可重现的代码 (D-10.3.2(。有人可以解释为什么当对 ReleaseManager(( 的调用保持不变时代码有效吗?我也愿意听到解决这个问题的任何其他方法。

EXE 和 DLL 之间的共享接口 (SharedInft.pas:

unit SharedIntf;
interface
type
IIntfSharing = interface(IUnknown)
['{EA5862AA-973E-4CFA-A7AB-84D4A4F38A29}']
function SetManager(const AManager: IUnknown): HResult; stdcall;
function ShowUI: HResult; stdcall;
end;
IManager = interface(IUnknown)
['{6C408BB1-7CC8-4E71-B3E7-19508045E71C}']
function ManageSomething(const AResource: widestring): HResult; stdcall;
end;
ISecurity = interface(IUnknown)
['{0ED8E0BB-F2B3-46D8-8D07-411D71559508}']
function SecureSomething(const ALogin: widestring): HResult; stdcall;
end;
const
Class_IntfSharing: TGUID = '{B9187923-8B2E-469A-B5B4-1D67BA6C1D1F}';

所有模块的全局单位:

unit GlobalUnit;
interface
uses SharedIntf;
procedure SetManager(AManager: IManager);
procedure ReleaseManager;
implementation
var
ContainerControl: IManager;
SecurityControl: ISecurity;
procedure SetManager(AManager: IManager);
begin
ContainerControl := AManager;
SecurityControl := AManager as ISecurity;
end;
procedure ReleaseManager;
begin
ContainerControl := nil;
SecurityControl := nil;
end;
initialization
finalization
if Assigned(ContainerControl) then
ContainerControl := nil;
if Assigned (SecurityControl) then
SecurityControl := nil;
end.      

实现 IIntfSharing 的 COM DLL 代码

unit DLLMain;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
Windows, ActiveX, Classes, ComObj, StdVcl, SharedIntf, GlobalUnit;
type
TIntfSharing = class(TComObject, IIntfSharing)
protected
function SetManager(const AManager: IUnknown): HResult; stdcall;
function ShowUI: HResult; stdcall;
public
Destructor Destroy; override;
end;
implementation
uses ComServ, DLLUI;
destructor TIntfSharing.Destroy;
begin
GlobalUnit.ReleaseManager; // AV, if not called
inherited;
end;
function TIntfSharing.SetManager(const AManager: IUnknown): HResult;
begin
GlobalUnit.SetManager(AManager as IManager);
end;
function TIntfSharing.ShowUI: HResult;
begin
with TDLLFrm.Create(nil) do
try
ShowModal;
finally
Free;
end;
end;
initialization
TComObjectFactory.Create(ComServer, TIntfSharing, Class_IntfSharing,
'IntfSharing', '', ciMultiInstance, tmApartment);
end.

EXE 代码,调用 COM DLL 并实现 IManager 和 ISecurity

unit Main;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, SharedIntf, GlobalUnit,
ComObj, Vcl.ExtCtrls;
type
TController = Class(TCustomPanel, IManager, ISecurity)
public
{IManager}
function ManageSomething(const AResource: widestring): HResult; stdcall;
{ISecurity}
function SecureSomething(const ALogin: widestring): HResult; stdcall;
destructor Destroy; override;
End;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
FController: TController;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TController }
destructor TController.Destroy;
begin
inherited; // Just to make sure the object is being destroyed
end;
function TController.ManageSomething(const AResource: widestring): HResult;
begin
//
end;
function TController.SecureSomething(const ALogin: widestring): HResult;
begin
//
end;
procedure TForm1.Button1Click(Sender: TObject);
var
lDLLIntf: IIntfSharing;
begin
FController := TController.Create(Self);  // Will be destroyed when form gets destroyed
GlobalUnit.SetManager(FController as IManager);
lDLLIntf := CreateComObject(Class_IntfSharing) as IIntfSharing;
lDLLIntf.SetManager(FController);   // passed over to DLL
lDLLIntf.ShowUI;
FController.Color := clGreen;
FController.Parent := Self;
FController.Refresh;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
GlobalUnit.ReleaseManager; // AV, if not called
end;
end.

将接口设置为 nil 隐式调用对象_Release函数。我相信这里出现的问题是因为底层对象在全局单元最终确定之前被销毁了。(在本例中,在您的表单销毁中(。

如果我是对的,(我没有调试你的代码......(更改为TController.Create(nil)可能会解决访问冲突,但如果你的接口没有被引用计数,可能会导致内存泄漏。

FormDestroy中调用ReleaseManager会在表单销毁 TController 之前将接口指针设置为 nil,因此在调用release时不会发生访问冲突。

最新更新