Is COM broken in XE2, and how might I work around it?

前端 未结 2 1876
闹比i
闹比i 2020-12-03 00:51

Update: XE2 Update 2 fixes the bug described below.

The program below, cutdown from the real program, fails with an exception in XE2. This is a regr

相关标签:
2条回答
  • 2020-12-03 00:59

    Workaround

    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;
    

    This also works (and may help you find a workaround in more complicated use cases):

    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;
    

    Analysis

    It crashes in System.Win.ComObj in DispCallByID when executing _Release in

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

    Although the PUREPASCAL version of this same procedure in Delphi XE (XE uses an assembler version) is different ...

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

    ... the assembler code in both cases is the same (EDIT: not true, see my notes at the 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
    

    Interestingly enough, this crashes ...

    for Row := 1 to UsedRange.Rows.Count do begin
      for Col := 1 to UsedRange.Columns.Count do begin
      end;
    end;
    

    ... and this doesn't.

    row := UsedRange.Rows.Count;
    col := UsedRange.Columns.Count;
    col := UsedRange.Columns.Count;
    

    The reason for this is the use of hidden local variables. In the first example, the code compiles to ...

    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
    

    ... and that is called twice.

    In the second example, two different temporary locations on the stack are used (ebp - ???? offsets):

    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
    

    The bug occurs when an internal interface stored in this temporary location is being cleared, which happens only when the "for" case is executed for the second time because there's something already stored in this interface - it was put there when "for" was called for the first time. In the second example, two locations are used so this internal interface is always initialized to 0 and Release is not called at all.

    The true bug is that this internal interface contains garbage and when Release is called, sh!t happens.

    After some more digging, I noticed that the assembler code that frees the old interface is not the same - XE2 version is missing one "mov eax, [eax]" instruction. IOW,

    IDispatch(Result)._Release;
    

    is a mistake and it really should be

    IDispatch(Result.VDispatch)._Release;
    

    Nasty RTL bug.

    0 讨论(0)
  • 2020-12-03 01:17

    Most of this is getting above my head but I did wonder if a call to CoInitialize would be required. Searching the web for CoInitialize returned this page:

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

    It almost looks like that page describes the problem from the OP and gabr's analysis as it relates to a call to .Release. Moving the functionality of the code into it's own procedure might help. I don't have XE or XE2 to test with.

    Edit: Rats - meant to add this as a comment to the above.

    0 讨论(0)
提交回复
热议问题