下面的程序,从实际程序中减少,失败,在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,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位的错误是:
Project COMbug.exe raised exception class $C000001D with message ‘system exception (code 0xc000001d) at 0x00dd6f3e’.
在UsedRange.Columns的第二次执行时发生错误。
在XE2 64位的错误是:
Project COMbug.exe raised exception class $C0000005 with message ‘c0000005 ACCESS_VIOLATION’
再次,我认为错误发生在UsedRange.Columns的第二次执行,但是64位调试程序以一种稍微奇怪的方式逐步通过代码,所以我不是100%肯定的。
我已经提交了一个QC report的问题。
我非常喜欢我,好像在Delphi COM /自动化/接口栈中的东西被全面打破。这是我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; 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时,它在DispCallByID中的System.Win.ComObj中崩溃
varDispatch,varUnknown: begin if PPointer(Result)^ <> nil then IDispatch(Result)._Release; PPointer(Result)^ := Res.VDispatch; end;
虽然PUREPASCAL版本的这个相同的过程在Delphi XE(XE使用汇编版本)是不同的…
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 – ???? 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
当存储在该临时位置的内部接口被清除时,该错误发生,这仅在第二次执行“for”情况时发生,因为在该接口中已经存储了一些东西 – 当“for”被调用时它被放在那里首次。在第二个示例中,使用两个位置,因此此内部接口始终初始化为0,并且根本不调用释放。
真正的错误是,这个内部接口包含垃圾,当Release被调用时,sh!t发生。
在一些更多的挖掘之后,我注意到释放旧接口的汇编代码是不一样的 – XE2版本缺少一个“mov eax,[eax]”指令。 IOW,
IDispatch(Result)._Release;
是一个错误,它真的应该是
IDispatch(Result.VDispatch)._Release;
讨厌的RTL错误。