是否在XE2中打破了COM,我该如何解决它?

Dav*_*nan 53 delphi com delphi-xe2

更新: XE2 Update 2修复了下面描述的错误.

下面的程序,从真实程序中减少,在XE2中失败.这是从2010年开始的回归.我没有XE可以测试,但我希望程序在XE上运行正常(感谢Primož确认代码在XE上正常运行).

program COMbug;

{$APPTYPE CONSOLE}

uses
  SysUtils, Variants, Windows, Excel2000;

var
  Excel: TExcelApplication;
  Book: ExcelWorkbook;
  Sheet: ExcelWorksheet;
  UsedRange: ExcelRange;
  Row, Col: Integer;
  v: Variant;

begin
  Excel := TExcelApplication.Create(nil);
  try
    Excel.Visible[LOCALE_USER_DEFAULT] := True;
    Book := Excel.Workbooks.Add(EmptyParam, LOCALE_USER_DEFAULT) as ExcelWorkbook;
    Sheet := Book.Worksheets.Add(EmptyParam, EmptyParam, 1, EmptyParam, LOCALE_USER_DEFAULT) as ExcelWorksheet;

    Sheet.Cells.Item[1,1].Value := 1.0;
    Sheet.Cells.Item[2,2].Value := 1.0;
    UsedRange := Sheet.UsedRange[LOCALE_USER_DEFAULT] as ExcelRange;
    for Row := 1 to UsedRange.Rows.Count do begin
      for Col := 1 to UsedRange.Columns.Count do begin
        v := UsedRange.Item[Row, Col].Value;
      end;
    end;
  finally
    Excel.Free;
  end;
end.
Run Code Online (Sandbox Code Playgroud)

在XE2 32位中,错误是:

项目COMbug.exe引发异常类$ C000001D,消息'系统异常(代码0xc000001d)位于0x00dd6f3e'.

第二次执行时发生错误UsedRange.Columns.

在XE2 64位中,错误是:

项目COMbug.exe引发了异常类$ C0000005,消息为"c0000005 ACCESS_VIOLATION"

再次,我认为错误发生在第二次执行UsedRange.Columns,但64位调试器以一种有点奇怪的方式逐步完成代码,所以我不是100%肯定.

我已就此问题提交了质量控制报告.

我非常看好我,好像Delphi COM /自动化/接口堆栈中的某些东西被彻底打破了.这是我XE2采用的完整展示.

有没有人有这个问题的经验?有没有人对我如何尝试解决这个问题有任何提示和建议?调试这里真正发生的事情超出了我的专业领域.

gab*_*abr 82

解决方法

rowCnt := UsedRange.Rows.Count;
colCnt := UsedRange.Columns.Count;
for Row := 1 to rowCnt do begin
  for Col := 1 to colCnt do begin
    v := UsedRange.Item[Row, Col].Value;
  end;
end;
Run Code Online (Sandbox Code Playgroud)

这也有效(并且可以帮助您在更复杂的用例中找到解决方法):

function ColCount(const range: ExcelRange): integer;
begin
  Result := range.Columns.Count;
end;

for Row := 1 to UsedRange.Rows.Count do begin
  for Col := 1 to ColCount(UsedRange) do begin
    v := UsedRange.Item[Row, Col].Value;
  end;
end;
Run Code Online (Sandbox Code Playgroud)

分析

在执行_Release时,它在DispCallByID中的System.Win.ComObj中崩溃

varDispatch, varUnknown:
  begin
    if PPointer(Result)^ <> nil then
      IDispatch(Result)._Release;
    PPointer(Result)^ := Res.VDispatch;
  end;
Run Code Online (Sandbox Code Playgroud)

虽然Delphi XE中这个相同程序的PUREPASCAL版本(XE使用汇编程序版本)是不同的......

varDispatch, varUnknown:
  begin
    if PPointer(Result)^ <> nil then
      IDispatch(Result.VDispatch)._Release;
    PPointer(Result)^ := Res.VDispatch;
  end;
Run Code Online (Sandbox Code Playgroud)

...两种情况下的汇编代码是相同的(编辑:不是真的,最后请看我的笔记):

@ResDispatch:
@ResUnknown:
        MOV     EAX,[EBX]
        TEST    EAX,EAX
        JE      @@2
        PUSH    EAX
        MOV     EAX,[EAX]
        CALL    [EAX].Pointer[8]
@@2:    MOV     EAX,[ESP+8]
        MOV     [EBX],EAX
        JMP     @ResDone
Run Code Online (Sandbox Code Playgroud)

有趣的是,这次崩溃......

for Row := 1 to UsedRange.Rows.Count do begin
  for Col := 1 to UsedRange.Columns.Count do begin
  end;
end;
Run Code Online (Sandbox Code Playgroud)

......而事实并非如此.

row := UsedRange.Rows.Count;
col := UsedRange.Columns.Count;
col := UsedRange.Columns.Count;
Run Code Online (Sandbox Code Playgroud)

原因是使用隐藏的局部变量.在第一个例子中,代码编译为......

00564511 6874465600       push $00564674
00564516 6884465600       push $00564684
0056451B A12CF35600       mov eax,[$0056f32c]
00564520 50               push eax
00564521 8D8508FFFFFF     lea eax,[ebp-$000000f8]
00564527 50               push eax
00564528 E8933EEAFF       call DispCallByIDProc
Run Code Online (Sandbox Code Playgroud)

......那叫两次.

在第二个例子中,使用了堆栈上的两个不同的临时位置(ebp - ???? offsets):

00564466 6874465600       push $00564674
0056446B 6884465600       push $00564684
00564470 A12CF35600       mov eax,[$0056f32c]
00564475 50               push eax
00564476 8D8514FFFFFF     lea eax,[ebp-$000000ec]
0056447C 50               push eax
0056447D E83E3FEAFF       call DispCallByIDProc
...
0056449B 6874465600       push $00564674
005644A0 6884465600       push $00564684
005644A5 A12CF35600       mov eax,[$0056f32c]
005644AA 50               push eax
005644AB 8D8510FFFFFF     lea eax,[ebp-$000000f0]
005644B1 50               push eax
005644B2 E8093FEAFF       call DispCallByIDProc
Run Code Online (Sandbox Code Playgroud)

当一个存储在这个临时位置的内部接口被清除时会发生这种错误,只有在第二次执行"for"情况时才会发生这种情况,因为这个接口中已存储了某些内容 - 当调用"for"时它被放在那里首次.在第二个示例中,使用了两个位置,因此此内部接口始终初始化为0,并且根本不调用Release.

真正的错误是这个内部接口包含垃圾,当调用Release时,就会发生这种情况.

经过一些挖掘,我注意到释放旧接口的汇编程序代码不一样 - XE2版本缺少一个"mov eax,[eax]"指令.IOW,

IDispatch(Result)._Release;
Run Code Online (Sandbox Code Playgroud)

是一个错误,它应该是

IDispatch(Result.VDispatch)._Release;
Run Code Online (Sandbox Code Playgroud)

讨厌的RTL错误.

  • 优秀的分析,非常有帮助,谢谢! (17认同)
  • 编译器是一项非常了不起的工作.它有bug.它总是会.但它仍然是最令人难以置信的语言和编译器技术.(*不,我不为embarcadero工作.*) (10认同)
  • 在技​​术准确性方面,这不是RTL错误而不是编译器错误吗?它到底是怎么发生的?为什么代码发生了变化?如何以引入如此严重错误的方式更改代码?询问的头脑需要知道. (4认同)
  • @Deltics"不会让你参加XE2测试计划".显然他们优先考虑注册的XE用户而我不是.他们不想让我免费测试和调试他们的产品的原因超出了我的想法.我会发现这个bug(还有很多其他的).对我毫无意义. (4认同)
  • 这是100%的编译器回归,需要在Quality Central中. (2认同)
  • @Warren,它已经是.请参阅问题中的链接. (2认同)
  • @gabr 非常感谢你在这里所做的出色工作。我想我现在已经足够解决这个错误了。我们希望 Emba 在下一个 XE2 更新中修复这个问题。 (2认同)