Не работает ли COM в XE2, и как я могу обойти это? - PullRequest
54 голосов
/ 25 октября 2011

Обновление: XE2 Обновление 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.

В 32-битном XE2 ошибка:

Project COMbug.exe поднял класс исключения $ C000001D с сообщением «системное исключение (код 0xc000001d) в 0x00dd6f3e».

Ошибка возникает при втором выполнении UsedRange.Columns.

В 64-битном XE2 ошибка:

Project COMbug.exe поднял исключительную ситуацию класса $ C0000005 с сообщением «c0000005 ACCESS_VIOLATION»

Опять же, я думаю, что ошибка возникает при втором выполнении UsedRange.Columns, но 64-битный отладчик выполняет код немного странным образом, поэтому я не уверен на 100% в этом.

Я отправил отчет о контроле качества для этой проблемы.

Мне очень кажется, что что-то в стеке Delphi COM / автоматизации / интерфейса полностью сломано,Это полная демонстрация для моего принятия XE2.

У кого-нибудь есть опыт решения этой проблемы?У кого-нибудь есть какие-либо советы и рекомендации относительно того, как я могу попытаться обойти проблему?Отладка того, что на самом деле происходит здесь, выходит за рамки моей компетенции.

Ответы [ 2 ]

83 голосов
/ 25 октября 2011

Обход

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;

Анализ

Сбой в System.Win.ComObj в DispCallByID при выполнении _Release в

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 - ????):

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

Ошибка возникает, когда очищается внутренний интерфейс, сохраненный в этом временном местоположении, что происходит только тогда, когда случай «для» выполняется во второй раз, потому что в этом интерфейсе уже что-то есть - он был помещен туда, когда «для» был вызван впервые. Во втором примере используются два расположения, поэтому этот внутренний интерфейс всегда инициализируется равным 0, а Release вообще не вызывается.

Истинная ошибка в том, что этот внутренний интерфейс содержит мусор, и когда вызывается Release, происходит sh! T.

После еще нескольких копаний я заметил, что ассемблерный код, который освобождает старый интерфейс, не тот же - в версии XE2 отсутствует одна инструкция "mov eax, [eax]". IOW

IDispatch(Result)._Release;

это ошибка, и она действительно должна быть

IDispatch(Result.VDispatch)._Release;

Неприятная ошибка RTL.

1 голос
/ 26 октября 2011

Большая часть этого становится выше моей головы, но я действительно задавался вопросом, потребуется ли вызов CoInitialize.Поиск в сети для CoInitialize вернул эту страницу:

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

Похоже, что эта страница описывает проблему из OP и анализа Габра в том, что касается вызова .Release.Перенос функциональности кода в собственную процедуру может помочь.У меня нет XE или XE2 для тестирования.

Редактировать: Крысы - означало добавить это как комментарий к выше.

...