在我的类的实例中奇怪的内存覆盖问题

时间:2011-07-12 12:12:45

标签: delphi delphi-2009 access-violation

这个问题与this问题有关,我之前已经问过这个问题。 @RRUZ提供的代码正在运行,但似乎不太正确或 我做错了。

执行GetSharedFilesTMyObject实例发生了奇怪的事情。 ({应该})字段FMyEvent指向一些随机数据。

我在5分钟前发现的是,如果我关闭编译器选项中的优化,它在重建后工作正常。那么也许这是一些编译器错误?

这是一个代码快照(Delphi 2009 Windows 7 64位):

unit Unit17;

interface

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

type
  TForm17 = class(TForm)
    btnetst: TButton;
    procedure btnTestClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

type
  TMyEvent = procedure(Sender: TObject) of object;

type
  TMyObject = class(TObject)
  private
    FMyEvent: TMyEvent;
    function GetSharedFiles: TStringList;
  public
    property OnEvent: TMyEvent read FMyEvent write FMyEvent;
    procedure DoSomething;
  end;

var
  Form17: TForm17;

implementation

uses
  ActiveDs_TLB,
  ActiveX;

function ADsGetObject(lpszPathName:WideString; const riid:TGUID; out ppObject):HRESULT; safecall; external 'activeds.dll';

{$R *.dfm}

procedure TForm17.btnTestClick(Sender: TObject);
var
  MyObject: TMyObject;
begin
  MyObject := TMyObject.Create;
  try
    MyObject.DoSomething;
  finally
    if Assigned(MyObject) then
      MyObject.Free;
  end;
end;

{ TMyObject }

procedure TMyObject.DoSomething;
var
  TmpList: TStringList;
begin
  try

    TmpList := GetSharedFiles; //something is overwritting the memory in object and puts random data to FMyEvent?
    if Assigned(FMyEvent) then
      ShowMessage('WTF'); //this should not be called, and if you comment out GetSharedFiles it won't.

  finally
    if Assigned(TmpList) then
      TmpList.Free;
  end;
end;


function TMyObject.GetSharedFiles: TStringList;
var
  FSO           : IADsFileServiceOperations;
  Resources     : IADsCollection;
  Resource      : OleVariant;
  pceltFetched  : Cardinal;
  oEnum         : IEnumvariant;
begin
  Result := TStringList.Create;
  //establish the connection to ADSI
  if ADsGetObject('WinNT://./lanmanserver', IADsFileServiceOperations, FSO) = S_OK then
  begin
    //get the resources interface
    Resources := FSO.Resources;
    //get the enumerator
    oEnum:= IUnknown(Resources._NewEnum) as IEnumVariant;
    while oEnum.Next(1, Resource, pceltFetched) = 0 do
    begin
      Result.Add(LowerCase(Format('%s%s%s',[Resource.Path,#9,Resource.User])));
      Resource:=Unassigned;
    end;
  end;
end;    
end.

任何想法出了什么问题? 谢谢你的时间。

4 个答案:

答案 0 :(得分:2)

对此的调用约定应该是stdcall,而不是safecall

function ADsGetObject(lpszPathName:WideString; const riid:TGUID; out ppObject):HRESULT; safecall; external 'activeds.dll';

小结

典型的COM函数返回HRESULT结果;如果一切正常,他们会用它来传递错误代码或S_OK。使用这种类型的函数,通常会有这样的代码:

if CallComFunction(parameters) = S_OK then
  begin
    // Normal processing goes here
  end
else
  begin
    // Error condition needs to be dealt with here.
  end

由于通常无法处理错误条件,Delphi为我们提供了safecall伪调用约定。这不是一个真正的调用约定,因为事实上它在幕后使用stdcall。它的作用是自动生成S_OK的测试,并在失败时引发错误。因此,典型的COM方法可以声明为以下任何一种:

function TypicalComFunction(Parameters): HRESULT; stdcall;
procedure TypicalComFunction(Parameters); safecall;

如果您不打算处理任何潜在错误,请使用第二种形式(使用safecall)并忽略潜在的异常。如果确实发生错误,Delphi将引发异常,并且该异常将冒泡,直到它到达应用程序中可以处理错误的点。或者它会到达应用程序的异常处理程序,然后用于显示用户的错误。

使用safecall,上面的典型代码如下所示:

TypicalComFunction(Parameters); // raises exception on error    
// Normal processing goes here

另一方面,如果您 需要HRESUL,即使它与S_OK不同,请使用stdcall变体。

答案 1 :(得分:0)

不,这并不意味着编译器错误本身。更改编译器(Delphi< - > FPC),编译器版本或优化选项可以影响代码生成,并优化远离引用计数的临时值或更早地释放它们,或者改变使用的寄存器和寄存器分配。

这反过来可以使真正隐藏的错误弹出。

此类问题的一个示例是您调用外部函数。如果由于某种原因,他们的原型(相关单元中的声明)是错误的,寄存器可能会被删除,并且再次改变编译器选项会导致heisenbug行为。

同时重新计算自动类型的问题,或修改通过CONST传递的全局变量可能会导致此类问题。关于主要FPC maillist atm的后一个问题有一个巨大的线索。

记住:已经工作了很长时间的代码不一定正确。

答案 2 :(得分:0)

这不会是编译器错误。在字段上设置数据断点,您将找到覆盖该字段的代码。

答案 3 :(得分:0)

我过去曾遇到过类似的错误。它只在编译器优化开启时出现。否则,代码工作了大约2年没有问题。这是因为使用优化ON编译的代码与使用优化OFF编译的代码非常不同!!!!!!!由于这些机会,这个bug可以显示(或不显示)。

提示:

  • 使用FastMM(在积极的调试模式下设置)
  • 总是使用FreeAndNil而不是Free。这可能会帮助你很多,因为它可能会强制你的代码中的错误更快出现 - 也许在编译优化关闭的代码中。这实际上表明该bug一直存在。您可以在代码中对'.Free'进行大量的“搜索和替换”。

这两个技巧帮助我找到了这个错误。