更新: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位中,错误是:
Project COMbug.exe引发了异常类$ C000001D,消息'系统异常(代码0xc000001d)位于0x00dd6f3e'。
第二次执行UsedRange.Columns
时发生错误。
在XE2 64位中,错误是:
Project COMbug.exe引发了异常类$ C0000005,消息为“c0000005 ACCESS_VIOLATION”
同样,我认为错误发生在UsedRange.Columns
的第二次执行,但是64位调试器以稍微有些奇怪的方式逐步执行代码,所以我不是百分之百确定。
我已为此问题提交了QC report。
我非常看好我,好像Delphi COM /自动化/接口堆栈中的某些内容被彻底打破了。对于我的XE2采用,这是一个完整的显示器。
有没有人有这个问题的经验?有没有人对我如何尝试解决这个问题有任何提示和建议?调试这里真正发生的事情超出了我的专业领域。
答案 0 :(得分:83)
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;
虽然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 - ???? 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。
真正的错误是这个内部接口包含垃圾,当调用Release时,就会发生这种情况。
经过多次挖掘后,我注意到释放旧接口的汇编程序代码不一样 - XE2版本缺少一个“mov eax,[eax]”指令。我,
IDispatch(Result)._Release;
是一个错误,它应该是
IDispatch(Result.VDispatch)._Release;
令人讨厌的RTL错误。
答案 1 :(得分:1)
大部分内容都超出了我的脑海,但我确实想知道是否需要调用CoInitialize。在网上搜索CoInitialize会返回此页面:
http://chrisbensen.blogspot.com/2007/06/delphi-tips-and-tricks.html
看起来该页面几乎描述了OP和gabr分析中的问题,因为它与.Release的调用有关。将代码的功能移动到它自己的过程可能会有所帮助。我没有XE或XE2来测试。
编辑:老鼠 - 意味着将其添加为对上述内容的评论。