XE2 中的 COM 是否损坏?我该如何解决它?
更新: 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% 确定。
我已针对该问题提交了质量控制报告。
在我看来,Delphi COM/自动化/接口堆栈中的某些东西似乎已全面损坏。这对我采用 XE2 来说是一个彻底的阻碍。
有人有这个问题的经验吗?有人对我如何尝试解决该问题有任何提示和建议吗?调试这里真正发生的事情超出了我的专业领域。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
解决方法
这也有效(并且可以帮助您在更复杂的用例中找到解决方法):
分析
当在 Delphi XE中执行 _Release 时,它会在 DispCallByID 中的 System.Win.ComObj 中崩溃
尽管在 Delphi XE 中此相同过程的 PUREPASCAL 版本(XE 使用汇编程序)版本)不同……
两种情况下的汇编代码是相同的(编辑:不正确,请参阅最后我的注释):
有趣的是,这会崩溃……
并且这个没有。
其原因是使用了隐藏的局部变量。在第一个示例中,代码编译为……
并且被调用两次。
在第二个示例中,使用了堆栈上的两个不同的临时位置(ebp - ?????? 偏移量):
当清除存储在该临时位置中的内部接口时,会发生该错误,这种情况仅在“for”情况为第二次执行是因为这个接口中已经存储了一些东西 - 当第一次调用“for”时它被放在那里。在第二个示例中,使用了两个位置,因此该内部接口始终初始化为 0,并且根本不调用 Release。
真正的 bug 是这个内部接口包含垃圾,当调用 Release 时,会发生 sh!t。经过更多挖掘,我注意到释放旧接口的汇编代码不一样 - XE2版本缺少一条“mov eax, [eax]”指令。 IOW,
这是一个错误,它确实应该是
令人讨厌的 RTL bug。
Workaround
This also works (and may help you find a workaround in more complicated use cases):
Analysis
It crashes in System.Win.ComObj in DispCallByID when executing _Release in
Although the PUREPASCAL version of this same procedure in Delphi XE (XE uses an assembler version) is different ...
... the assembler code in both cases is the same (EDIT: not true, see my notes at the end):
Interestingly enough, this crashes ...
... and this doesn't.
The reason for this is the use of hidden local variables. In the first example, the code compiles to ...
... and that is called twice.
In the second example, two different temporary locations on the stack are used (ebp - ???? offsets):
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,
is a mistake and it really should be
Nasty RTL bug.
其中大部分都超出了我的想象,但我确实想知道是否需要调用 CoInitialize。在网络上搜索 CoInitialize 返回此页面:
http://chrisbensen。 blogspot.com/2007/06/delphi-tips-and-tricks.html
看起来该页面几乎将 OP 和 gabr 的分析中的问题描述为它与对 .Release 的调用有关。将代码的功能转移到它自己的过程中可能会有所帮助。我没有 XE 或 XE2 来测试。
编辑:老鼠 - 旨在将其添加为上述内容的评论。
Most of this is getting above my head but I did wonder if a call to CoInitialize would be required. Searching the web for CoInitialize returned this page:
http://chrisbensen.blogspot.com/2007/06/delphi-tips-and-tricks.html
It almost looks like that page describes the problem from the OP and gabr's analysis as it relates to a call to .Release. Moving the functionality of the code into it's own procedure might help. I don't have XE or XE2 to test with.
Edit: Rats - meant to add this as a comment to the above.