COM 在 XE2 中是不是损坏,我该如何解决?

Posted

技术标签:

【中文标题】COM 在 XE2 中是不是损坏,我该如何解决?【英文标题】:Is COM broken in XE2, and how might I work around it?COM 在 XE2 中是否损坏,我该如何解决? 【发布时间】:2011-12-14 17:26:39 【问题描述】:

更新: 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.

在 XE2 32 位中,错误是:

项目 COMbug.exe 引发异常类 $C000001D,并带有消息“系统异常(代码 0xc000001d)在 0x00dd6f3e”。

第二次执行UsedRange.Columns时出现错误。

在 XE2 64 位中,错误是:

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

同样,我认为错误发生在第二次执行 UsedRange.Columns 时,但 64 位调试器以一种有点奇怪的方式逐步执行代码,所以我不能 100% 确定这一点。

我已为此问题提交了QC report。

在我看来,Delphi COM/automation/interface 堆栈中的某些东西被彻底破坏了。这对我采用 XE2 来说是一个完整的展示。

有人遇到过这个问题吗?有人对我如何尝试解决该问题有任何提示和建议吗?调试这里真正发生的事情超出了我的专业领域。

【问题讨论】:

它在哪一行抛出? 已确认 - 在 XE 中工作,在 XE2 中崩溃。 它在 DispCallByIDProc 内部崩溃。丑陋。 分配给 v := UsedRange.Item[Row, Col].Value;没有必要 - 没有它也会崩溃。 听起来和这里发生的一模一样:***.com/questions/7874995 【参考方案1】:

解决方法

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;

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

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;

分析

执行_Release in时,DispCallByID中System.Win.ComObj崩溃

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

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

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

...这两种情况下的汇编代码是相同的(编辑:不正确,见我的笔记在最后):

@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

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

for Row := 1 to UsedRange.Rows.Count do begin
  for Col := 1 to UsedRange.Columns.Count do begin
  end;
end;

...这不是。

row := UsedRange.Rows.Count;
col := UsedRange.Columns.Count;
col := UsedRange.Columns.Count;

这样做的原因是使用了隐藏的局部变量。在第一个示例中,代码编译为 ...

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

...这被调用了两次。

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

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

当清除存储在此临时位置的内部接口时会出现该错误,该错误仅在第二次执行“for”案例时发生,因为该接口中已经存储了一些东西 - 当“for”时它被放在那里“第一次被叫到。在第二个示例中,使用了两个位置,因此此内部接口始终初始化为 0,并且根本不调用 Release。

真正的错误是这个内部接口包含垃圾,当调用 Release 时,sh!t 发生。

经过进一步挖掘,我注意到释放旧接口的汇编代码不一样 - XE2 版本缺少一条“mov eax,[eax]”指令。爱荷华,

IDispatch(Result)._Release;

是一个错误,它真的应该是

IDispatch(Result.VDispatch)._Release;

讨厌的 RTL 错误。

【讨论】:

这是 100% 的编译器回归,需要在 Quality Central 中。 编译器是一项非常了不起的工作。它有错误。它总是会的。但它仍然是最令人难以置信的语言和可用的编译器技术。 (* 不,我不为 embarcadero 工作。*) @gabr 非常感谢您在这里所做的出色工作。我想我现在有足够的时间来解决这个错误。让我们希望 Emba 在下一次 XE2 更新中解决这个问题。 就技术准确性而言,这不是 RTL 错误而不是编译器错误吗?它到底是怎么发生的?为什么要更改该代码以及如何以引入如此严重错误的方式对其进行更改?有求知欲的人需要知道。 @Deltics “不会让你参加 XE2 测试计划”。显然他们优先考虑注册的 XE 用户,而我没有。他们为什么不想让我免费测试和调试他们的产品,这超出了我的理解。我会发现这个错误(以及许多其他错误)。对我来说毫无意义。【参考方案2】:

其中大部分都超出了我的想象,但我确实想知道是否需要调用 CoInitialize。在网上搜索 CoInitialize 时返回此页面:

http://chrisbensen.blogspot.com/2007/06/delphi-tips-and-tricks.html

看起来该页面描述了来自 OP 和 gabr 分析的问题,因为它与对 .Release 的调用有关。将代码的功能移动到它自己的过程中可能会有所帮助。我没有 XE 或 XE2 可供测试。

Edit: Rats - 意思是把它作为评论添加到上面。

【讨论】:

嗯,在我的简单示例中我确实错过了这一点,谢谢。不过没关系,也许是因为 Excel 正在运行程序外。即使您添加它,也会发生相同的行为。当然,从中挑选出这个例子的原始代码已经正确初始化了 COM。

以上是关于COM 在 XE2 中是不是损坏,我该如何解决?的主要内容,如果未能解决你的问题,请参考以下文章

如何在没有任何损坏的情况下将“ğ”填充到文本中?

DB2查询耗时很长我该如何解决

如何检测pdf文档是不是有损坏

输入希伯来语时,PayPal 捐赠说明显示字符损坏

Delphi XE2 编译ralease版本,无法添加UAC解决方法

django 中的会话数据损坏