XE2中的COM是否损坏,我该如何解决? [英] Is COM broken in XE2, and how might I work around it?

查看:226
本文介绍了XE2中的COM是否损坏,我该如何解决?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



下面的程序从实际程序中减少,失败,出现异常在XE2。这是2010年的回归。我没有XE测试,但我希望程序在XE上工作正常(感谢Primož确认代码在XE上运行正常)。

 程序COMbug; 

{$ APPTYPE CONSOLE}

使用
SysUtils,Variants,Windows,Excel2000;

var
Excel:TExcelApplication;
Book:ExcelWorkbook;
工作表: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)作为ExcelWorkbook;
Sheet:= Book.Worksheets.Add(EmptyParam,EmptyParam,1,EmptyParam,LOCALE_USER_DEFAULT)作为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 ;
结束
结束
finally
Excel.Free;
结束
结束。

在XE2 32位中,错误是:



< blockquote>

项目COMbug.exe引发了异常类$ C000001D,消息为'system exception(code 0xc000001d)at 0x00dd6f3e'。


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



在XE2 64位错误是:


项目COMbug.exe引发异常类$ C0000005与消息'c0000005 ACCESS_VIOLATION'




再次,我认为错误发生在第二次执行 UsedRange.Columns ,但是64位调试器通过代码稍微奇怪,所以我不是100%肯定的。



我已经提交了一个 QC报告的问题。



我看起来非常像我一样一些在Delphi COM / automation / interface中被全面破坏这是我的XE2采用的完整展示。



有没有人有这个问题的经验?有没有人有任何提示和建议,我如何尝试解决这个问题?

解决方案

解决方法



  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;
结束
结束

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

 函数ColCount(const range:ExcelRange):integer; 
begin
结果:= range.Columns.Count;
结束

for Row:= 1 to UsedRange.Rows.Count do begin
for Col:= 1 to ColCount(UsedRange)do begin
v:= UsedRange.Item [Row,Col ]。值;
结束
结束



分析



它在系统中崩溃。在$ C $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $



$ )^ nil then
IDispatch(Result)._ Release;
PPointer(Result)^:= Res.VDispatch;
结束虽然Delphi XE(XE使用汇编器版本)的相同过程的PUREPASCAL版本是不同的,但是


$ b < ..

  varDispatch,varUnknown:
begin
如果PPointer(Result)^& nil then
IDispatch(Result.VDispatch)._ Release;
PPointer(Result)^:= Res.VDispatch;
结束

...这两种情况下的汇编代码是一样的(编辑:不真实,看我的笔记在结尾):

  @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开始
为Col:= 1到UsedRange.Columns.Count do begin
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 - ???? offset):

  00564466 6874465600推$ 00564674 
0056446B 6884465600推$ 00564684
00564470 A12CF35600 mov eax,[$ 0056f32c]
00564475 50 push eax
00564476 8D8514FFFFFF lea eax,[ebp- $ 000000ec]
0056447C 50 push eax
0056447D E83E3FEAFF调用DispCallByIDProc
...
0056449B 6874465600推$ 00564674
0056 44A0 6884465600推$ 00564684
005644A5 A12CF35600 mov eax,[$ 0056f32c]
005644AA 50 push eax
005644AB 8D8510FFFFFF lea eax,[ebp- $ 000000f0]
005644B1 50 push eax
005644B2 E8093FEAFF调用DispCallByIDProc

当存储在此临时位置的内部接口正在清除时,会发生此错误只有当第二次执行for的情况时,才发生这种情况,因为这个接口已经存在一些东西 - 当第一次调用for时,它被放在那里。在第二个示例中,使用两个位置,因此内部接口总是初始化为0,并且Release不被调用。



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



在进一步挖掘之后,我注意到,释放旧接口的汇编代码不一样 - XE2版本缺少一个mov eax,[eax]指令。 IOW,

  IDispatch(Result)._ Release; 

是一个错误,它应该是

  IDispatch(Result.VDispatch)._ Release; 

讨厌的RTL错误。


Update: XE2 Update 2 fixes the bug described below.

The program below, cutdown from the real program, fails with an exception in XE2. This is a regression from 2010. I don't have XE to test on but I'd expect that the program works fine on XE (thanks to Primož for confirming that the code runs fine on 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.

In XE2 32 bit the error is:

Project COMbug.exe raised exception class $C000001D with message 'system exception (code 0xc000001d) at 0x00dd6f3e'.

The error occurs on the second execution of UsedRange.Columns.

In XE2 64 bit the error is:

Project COMbug.exe raised exception class $C0000005 with message 'c0000005 ACCESS_VIOLATION'

Again, I think that the error occurs on the second execution of UsedRange.Columns, but the 64 bit debugger steps through the code in a slightly weird way so I'm not 100% sure of that.

I have submitted a QC report for the issue.

I looks very much to me as though something in the Delphi COM/automation/interface stack is comprehensively broken. This is a complete show-stopper for my XE2 adoption.

Does anyone have any experience of this problem? Does anyone have any tips and advice as to how I might attempt to work around the problem? Debugging what's really going on here is outside my area of expertise.

解决方案

Workaround

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;

This also works (and may help you find a workaround in more complicated use cases):

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;

Analysis

It crashes in System.Win.ComObj in DispCallByID when executing _Release in

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

Although the PUREPASCAL version of this same procedure in Delphi XE (XE uses an assembler version) is different ...

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

... the assembler code in both cases is the same (EDIT: not true, see my notes at the 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

Interestingly enough, this crashes ...

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

... and this doesn't.

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

The reason for this is the use of hidden local variables. In the first example, the code compiles to ...

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

... and that is called twice.

In the second example, two different temporary locations on the stack are used (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

The bug occurs when an internal interface stored in this temporary location is being cleared, which happens only when the "for" case is executed for the second time because there's something already stored in this interface - it was put there when "for" was called for the first time. In the second example, two locations are used so this internal interface is always initialized to 0 and Release is not called at all.

The true bug is that this internal interface contains garbage and when Release is called, sh!t happens.

After some more digging, I noticed that the assembler code that frees the old interface is not the same - XE2 version is missing one "mov eax, [eax]" instruction. IOW,

IDispatch(Result)._Release;

is a mistake and it really should be

IDispatch(Result.VDispatch)._Release;

Nasty RTL bug.

这篇关于XE2中的COM是否损坏,我该如何解决?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆