Подтвердить что ты не робот

Разбивается ли COM в XE2 и как я могу его обойти?

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

В XE2 32 бит ошибка:

Project COMbug.exe поднял класс исключений $C000001D с системным исключением (код 0xc000001d) с кодом 0x00dd6f3e.

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

В 64-битной версии XE2 ошибка:

Project COMbug.exe поднял класс исключений $C0000005 с сообщением 'c0000005 ACCESS_VIOLATION'

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

Я представил отчет QC для проблемы.

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

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

4b9b3361

Ответ 1

Обход

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;

... код ассемблера в обоих случаях одинаковый (EDIT: не верно, см. мои заметки в конце):

@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.

Ответ 2

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

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

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

Edit: Rats - означает добавить это как комментарий к предыдущему.