存储Delphi接口参考时的奇怪AV

RM.*_*RM. 6 delphi pointers interface access-violation

我在以下代码中收到意外的访问冲突错误:

program Project65;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  SysUtils;

type
  ITest = interface
  end;

  TTest = class(TInterfacedObject, ITest)
  end;

var
  p: ^ITest;

begin
  GetMem(p, SizeOf(ITest)); 
  p^ := TTest.Create; // AV here
  try
  finally
    p^ := nil;
    FreeMem(p);
  end;
end.
Run Code Online (Sandbox Code Playgroud)

我知道接口应该以不同的方式使用.但是我正在研究使用这种方法的遗留代码库.而且我很惊讶地发现,保留SizeOf(ITest)内存并将ITest放在那里是不够的.

现在有趣的是,如果我改变第一行

GetMem(p, 21);
Run Code Online (Sandbox Code Playgroud)

比AV消失了.(20个字节或更少的失败).对此有何解释?

(我使用的是Delphi XE2 Update 4 + HotFix)

请不要评论代码是多么可怕或建议如何正确编码.请回答为什么有必要保留21个字节而不是SizeOf(ITest)= 4?

Rem*_*eau 25

你有效写的是在幕后做以下逻辑:

var
  p: ^ITest;
begin
  GetMem(p, SizeOf(ITest));
  if p^ <> nil then p^._Release; // <-- AV here
  PInteger(p)^ := ITest(TTest.Create);
  p^._AddRef;
  ...
  if p^ <> nil then p^._Release;
  PInteger(p)^ := 0;
  FreeMem(p);
end;
Run Code Online (Sandbox Code Playgroud)

GetMem()不保证将其分配的内容归零.当您将新对象实例分配给可变的接口时,如果字节不是零,则RTL将认为已存在现有接口引用并将尝试调用其_Release()方法,从而导致AV,因为它不受真实对象的支持实例.您需要预先将分配的字节清零,然后RTL将看到一个nil接口引用而不再尝试调用其_Release()方法:

program Project65;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  SysUtils;

type
  ITest = interface
  end;

  TTest = class(TInterfacedObject, ITest)
  end;

var              
  p: ^ITest;              

begin              
  GetMem(p, SizeOf(ITest));               
  try
    FillChar(p^, SizeOf(ITest), #0); // <-- add this!
    p^ := TTest.Create; // <-- no more AV
    try
      ...
    finally
      p^ := nil;
    end;
  finally
    FreeMem(p);
  end;
end.
Run Code Online (Sandbox Code Playgroud)

  • 或者使用AllocMem而不是GetMem + FreeMem (6认同)
  • 或者使用`New`和`Dispose`. (4认同)