versiones - delphi wikipedia
¿Está descompuesto COM en XE2 y cómo podría solucionarlo? (2)
Actualización: XE2 Actualización 2 corrige el error que se describe a continuación.
El programa a continuación, recortado del programa real, falla con una excepción en XE2. Esta es una regresión de 2010. No tengo XE para probar pero espero que el programa funcione bien en XE (gracias a Primož por confirmar que el código funciona bien en 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.
En XE2 32 bit el error es:
Proyecto COMbug.exe provocó la clase de excepción $ C000001D con mensaje ''excepción de sistema (código 0xc000001d) en 0x00dd6f3e''.
El error ocurre en la segunda ejecución de UsedRange.Columns
.
En XE2 64 bit el error es:
El proyecto COMbug.exe generó la clase de excepción $ C0000005 con el mensaje ''c0000005 ACCESS_VIOLATION''
De nuevo, creo que el error ocurre en la segunda ejecución de UsedRange.Columns
, pero el depurador de 64 bits UsedRange.Columns
por el código de una manera un poco extraña, así que no estoy 100% seguro de eso.
He enviado un informe de control de calidad para el problema.
Me parece mucho como si algo en la pila de COM / automatización / interfaz de Delphi estuviera completamente roto. Este es un completo stop-show para mi adopción de XE2.
¿Alguien tiene alguna experiencia con este problema? ¿Alguien tiene consejos y consejos sobre cómo puedo tratar de solucionar el problema? La depuración de lo que realmente sucede aquí está fuera de mi área de experiencia.
Solución
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;
Esto también funciona (y puede ayudarlo a encontrar una solución alternativa en casos de uso más complicados):
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;
Análisis
Se bloquea en System.Win.ComObj en DispCallByID al ejecutar _Release en
varDispatch, varUnknown:
begin
if PPointer(Result)^ <> nil then
IDispatch(Result)._Release;
PPointer(Result)^ := Res.VDispatch;
end;
Aunque la versión PUREPASCAL de este mismo procedimiento en Delphi XE (XE usa una versión de ensamblador) es diferente ...
varDispatch, varUnknown:
begin
if PPointer(Result)^ <> nil then
IDispatch(Result.VDispatch)._Release;
PPointer(Result)^ := Res.VDispatch;
end;
... el código de ensamblador en ambos casos es el mismo (EDITAR: no es cierto, vea mis notas al final):
@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
Curiosamente, esto se bloquea ...
for Row := 1 to UsedRange.Rows.Count do begin
for Col := 1 to UsedRange.Columns.Count do begin
end;
end;
... y esto no.
row := UsedRange.Rows.Count;
col := UsedRange.Columns.Count;
col := UsedRange.Columns.Count;
La razón de esto es el uso de variables locales ocultas. En el primer ejemplo, el código se compila para ...
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
... y eso se llama dos veces.
En el segundo ejemplo, se usan dos ubicaciones temporales diferentes en la pila (compensaciones de 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
El error se produce cuando se borra una interfaz interna almacenada en esta ubicación temporal, lo que ocurre solo cuando se ejecuta el caso "para" por segunda vez porque ya hay algo almacenado en esta interfaz: se colocó allí cuando se llamó "para" por primera vez. En el segundo ejemplo, se utilizan dos ubicaciones, por lo que esta interfaz interna siempre se inicializa en 0 y no se llama en absoluto a la versión.
El verdadero error es que esta interfaz interna contiene basura y cuando se lanza Release, ocurre sh! T.
Después de investigar un poco más, me di cuenta de que el código ensamblador que libera la interfaz anterior no es el mismo: a la versión XE2 le falta una instrucción "mov eax, [eax]". IOW,
IDispatch(Result)._Release;
es un error y realmente debería ser
IDispatch(Result.VDispatch)._Release;
Nasty error RTL.
La mayor parte de esto está por encima de mi cabeza, pero me pregunto si se requerirá una llamada a CoInitialize. La búsqueda en la web de CoInitialize devolvió esta página:
http://chrisbensen.blogspot.com/2007/06/delphi-tips-and-tricks.html
Casi parece que esa página describe el problema del OP y el análisis de gabr en lo que se refiere a una llamada a .Release. Mover la funcionalidad del código en su propio procedimiento podría ayudar. No tengo XE o XE2 para probar.
Editar: Ratas: la intención es agregar esto como un comentario al anterior.