XE2中COM是否被破解,我该如何解决它?

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

下面的程序,从真正的程序截断,失败,在XE2例外。 这是从2010年开始的一个回归。我没有XE来testing,但是我期望程序能够在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引发exception类$ C000001D消息'系统exception(代码0xc000001d)在0x00dd6f3e'。

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

在XE2 64位的错误是:

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

再次,我认为这个错误发生在UsedRange.Columns的第二次执行上,但是64位的debugging器以一种奇怪的方式遍历代码,所以我不能100%确定这一点。

我已经提交了这个问题的质量控制报告 。

我看起来很像我的Delphi COM /自动化/接口堆栈中的东西被全面打破。 这是我的XE2采用的完整展示。

有没有人有任何这个问题的经验? 有没有人有任何提示和build议,我可能会尝试解决这个问题? debugging真正发生的是我的专业领域之外。

解决方法

 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; 

这也是可行的(并且可以帮助您在更复杂的用例中find解决方法):

 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时,它在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; 

其原因是使用隐藏的局部variables。 在第一个例子中,代码编译为…

 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发生。

经过一番挖掘,我注意到释放旧接口的汇编代码是不一样的 – XE2版本缺less一个“mov eax,[eax]”指令。 IOW,

 IDispatch(Result)._Release; 

是一个错误,它应该是

 IDispatch(Result.VDispatch)._Release; 

讨厌的RTL错误。

大部分是在我头上,但我想知道是否需要打电话给CoInitialize。 在网上searchCoInitialize返回了这个页面:

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

它几乎看起来像那个页面描述OP和gabr分析的问题,因为它涉及到.Release的调用。 将代码的function移到自己的过程中可能会有所帮助。 我没有XE或XE2来testing。

编辑:大鼠 – 意味着添加这个作为上面的评论。