IT이야기

XE2에서 COM이 손상되었을때 해결 방법

cyworld 2021. 9. 27. 21:36
반응형

XE2에서 COM이 손상되었으며 어떻게 해결할 수 있습니까?


업데이트: XE2 업데이트 2는 아래 설명된 버그를 수정합니다.

실제 프로그램에서 잘라낸 아래 프로그램은 XE2 예외와 함께 실패합니다. 이것은 2010년의 회귀입니다. 테스트할 XE가 없지만 프로그램이 XE에서 제대로 작동할 것으로 기대합니다(코드가 XE에서 제대로 실행되는지 확인해준 Primož 덕분에).

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는 '0x00dd6f3e에서 시스템 예외(코드 0xc000001d)' 메시지와 함께 예외 클래스 $C000001D를 발생시켰습니다.

의 두 번째 실행에서 오류가 발생합니다 UsedRange.Columns.

XE2 64비트에서 오류는 다음과 같습니다.

프로젝트 COMbug.exe에서 'c0000005 ACCESS_VIOLATION' 메시지와 함께 $C0000005 예외 클래스가 발생했습니다.

다시 말하지만, 의 두 번째 실행에서 오류가 발생한다고 생각 UsedRange.Columns하지만 64비트 디버거는 코드를 약간 이상한 방식으로 단계별로 실행 하므로 100% 확신할 수 없습니다.

문제에 대한 QC 보고서제출했습니다 .

델파이 COM/자동화/인터페이스 스택의 무언가가 완전히 망가진 것처럼 보입니다. 이것은 내 XE2 채택에 대한 완전한 쇼 스토퍼입니다.

누구든지이 문제에 대한 경험이 있습니까? 문제를 해결하기 위해 시도할 수 있는 방법에 대한 조언과 조언이 있는 사람이 있습니까? 여기에서 실제로 일어나는 일을 디버깅하는 것은 내 전문 영역을 벗어난 것입니다.


해결 방법

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;

분석

_Release를 실행할 때 DispCallByID의 System.Win.ComObj에서 충돌이 발생합니다.

varDispatch, varUnknown:
  begin
    if PPointer(Result)^ <> nil then
      IDispatch(Result)._Release;
    PPointer(Result)^ := Res.VDispatch;
  end;

Delphi XE(XE는 어셈블러 버전 사용)에서 이와 동일한 절차의 PUREPASCAL 버전은 다르지만 ...

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

버그는 이 임시 위치에 저장된 내부 인터페이스가 지워질 때 발생합니다. 이 인터페이스에 이미 무언가가 저장되어 있기 때문에 "for" 케이스가 두 번째로 실행될 때만 발생합니다. "for"가 호출될 때 거기에 놓였습니다. 처음으로. 두 번째 예에서는 이 내부 인터페이스가 항상 0으로 초기화되고 Release가 전혀 호출되지 않도록 두 위치가 사용됩니다.

진정한 버그는 이 내부 인터페이스에 쓰레기가 포함되어 있고 Release가 호출될 때 sh!t가 발생한다는 것입니다.

좀 더 파고 들자, 이전 인터페이스를 해제하는 어셈블러 코드가 동일하지 않다는 것을 알았습니다. XE2 버전에는 "mov eax, [eax]" 명령이 하나 빠져 있습니다. 아이오,

IDispatch(Result)._Release;

실수이고 정말 그래야 한다

IDispatch(Result.VDispatch)._Release;

불쾌한 RTL 버그.


이것의 대부분이 내 머리를 넘어 가고 있지만 CoInitialize에 대한 호출이 필요한지 궁금했습니다. 웹에서 CoInitialize를 검색하면 이 페이지가 반환됩니다.

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

해당 페이지가 .Release에 대한 호출과 관련하여 OP 및 gabr의 분석에서 문제를 설명하는 것처럼 보입니다. 코드의 기능을 자체 프로시저로 이동하면 도움이 될 수 있습니다. 테스트할 XE 또는 XE2가 없습니다.

편집: 쥐 - 위의 설명에 이것을 추가하기 위한 것입니다.

ReferenceURL : https://stackoverflow.com/questions/7886116/is-com-broken-in-xe2-and-how-might-i-work-around-it

반응형