Не работает ли COM в XE2, и как я могу обойти это?

54

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

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

  • 0
    на какой линии он бросает?
  • 5
    Подтверждено - работает в XE, вылетает в XE2.
Показать ещё 7 комментариев
Теги:
com
delphi-xe2

2 ответа

82
Лучший ответ

Обход

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.

  • 0
    Хм, действительно интересно ...
  • 17
    Отличный анализ, очень полезно, спасибо!
Показать ещё 16 комментариев
1

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

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

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

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

  • 0
    Хм, я пропустил это в моем простом примере, спасибо. Это не имеет значения, хотя, возможно, потому что Excel работает вне процесса. Даже когда вы добавляете его, происходит то же самое. И, конечно, исходный код, из которого был взят этот пример, правильно инициализирован COM.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню