下面的内容为个人分析结果, 目的是请各位高手斧正, 如发现有弱智,火星之类的内容, 请指出, 谢谢.今天在发现delphi里的接口是不能强制转化的, 比如如下代码:
  ITestInterface = interface(IUnknown)
  ['{DE8E7E63-BD1B-4BD8-B59A-43F4B2B3A3DC}']
    procedure Chk;
  end;  ITestInterface2 = interface(IUnknown)
  ['{3B47B113-4664-4CCC-9A69-E378F2D2363E}']
    procedure Chk2;
  end;var
  mInt: ITestInterface;
  mInt2: ITestInterface2;
begin
  mInt := TTestInterface2.Create;
  mInt.Chk;  ITestInterface2(mInt).Chk2;  mInt2 := mInt as ITestInterface2;
  mInt2.Chk2;
end;从运行情况可以看出,在第一种情况  ITestInterface2(mInt).Chk2; 下,实际运行的还是ITestInterface下的procedure Chk;而第二种情况  mInt2 := mInt as ITestInterface2; 就能正确的运行ITestInterface2下的procedure Chk2;了.这是为什么呢?
原来这都出自Delphi的编译之手,在进行强制转化  ITestInterface2(mInt).Chk2; 时, 代码是:
mov eax, [ebp-$04]
mov edx, [eax]
call dword ptr [edx+$0c]
而进行 mInt.Chk; 调用时的代码也是这个,这说明强制转化时,编译器根本就没有把ITestInterface转化为ITestInterface2,而是理解为直接的对象强制转化,然后希望能在方法表里找到对应的方法,而且在对接口进行方法的解析时,也只是以方法的声明顺序,而不是实际的方法名来进行解析.所以在Chk为ITestInterface的第一个方法,而Chk2也为ITestInterface2的第一个方法时,就会直接的调用了ITestInterface的Chk,而不是ITestInterface2的Chk2.这个结论也可能通过下面这个实验来表明.
  ITestInterface = interface(IUnknown)
  ['{DE8E7E63-BD1B-4BD8-B59A-43F4B2B3A3DC}']
    procedure Chk;
  end;  ITestInterface2 = interface(IUnknown)
  ['{3B47B113-4664-4CCC-9A69-E378F2D2363E}']
    procedure Chk2;
    procedure Chk22;
  end;var
  mInt: ITestInterface;
  mInt2: ITestInterface2;
begin
  mInt := TTestInterface2.Create;
  mInt.Chk;  ITestInterface2(mInt).Chk22;  mInt2 := mInt as ITestInterface2;
  mInt2.Chk2;
end;
在这种情况下,就会报地址出错,那是因为ITestInterface根本就没有第二个方法.当然下面的代码也能说明问题
  ITestInterface = interface(IUnknown)
  ['{DE8E7E63-BD1B-4BD8-B59A-43F4B2B3A3DC}']
    procedure Chk;
    procedure Chk1;
  end;  ITestInterface2 = interface(IUnknown)
  ['{3B47B113-4664-4CCC-9A69-E378F2D2363E}']
    procedure Chk2;
    procedure Chk22;
  end;var
  mInt: ITestInterface;
  mInt2: ITestInterface2;
begin
  mInt := TTestInterface2.Create;
  mInt.Chk;  ITestInterface2(mInt).Chk22;  mInt2 := mInt as ITestInterface2;
  mInt2.Chk2;
end;
在这种情况下,程序会调用ITestInterface的Chk1方法.好上面说了强制转化时发生的情况,下面说用as来转化时发生的情况.当用as来转化时,代码就变为:
lea eax, [ebp-$08]
mov edx, [ebp-$04]
mov ecx, $00452bc4
call @IntfCast
mov eax, [ebp-$08]
mov edx, [eax]
call dword prt [edx+$0c]
发现,在用as标志转化后,就会使编译器知道要在类实例中查找实际的接口ITestInterface2而不是用mInt所声明的ITestInteface.以上说明Delphi7的编译器对接口的支持还是不够好,造成在强制转化时,不能正确的识别出要求的接口类型来.

解决方案 »

  1.   

    楼主应该写成日志(blog)。--
    http://www.agui.googlepages.com
    mailto: agui.cn(a)gmail.com
      

  2.   

    是有写啊,但CSDN上的BLOG居然不支持DELPHI的CODE,真是晕死
      

  3.   

    佩服楼主仅靠经验和细心分析观察得到的结果.不过这样结果是正常现象,跟Delphi7的编译器对接口的支持无关,
    如果这样强制转换可以得到楼主想要的结果,那它就不是"强制"转换了.COM技术是由C++ 虚函数机制基本上发展演化而产生的,为了保持其尽可能的高效,
    接口调用时是使用的内存位置约定,也就是楼主所观察到的,"..以方法的声明顺序..".按照COM最基本约定,对实现了多个接口的对象,从一个接口得到另一个接口的指针,
    唯一方法:调用接口的QueryInterface方法. 尽管对于熟悉对象内存模型的程序员,
    绕开这个方法,通过一些指针的强制转换也可以获得其它接口,但这是强烈反对的.DELPHI 定义了AS 运算,这是个比较灵活的运算符.当AS 用于从一个接口得到另一个接口时,
    这时候就是调用了queryInterface方法.
      

  4.   

    其实Delphi为了速度的原因,对于接口是采用直接解析接口在实例中的偏移来得到的.这个可以从下面的代码中看到:var
      mInt: ITestInterface;
      mInt2: ITestInterface2;
    begin
      mInt := TTestInterface2.Create;
      mInt2 := TTestInterface2.Create;
    end;其中 mInt := TTestInterface2.Create 代码为:
    mov dl, $01
    mov eax, [$00452e4]
    call TObject.Create
    mov edx, eax
    jz +$03
    sub edx, -$0C
    lea eax, [ebp-$04]
    call @IntfCopy而 mInt2 := TTestInterface2.Create 代码为:
    mov dl, $01
    mov eax, [$00452e4]
    call TObject.Create
    mov edx, eax
    jz +$03
    sub edx, -$0C
    lea eax, [ebp-$08]
    call @IntfCopy但对于接口之间的转化时, halfdream(哈欠) 兄说的很对, Delphi在call @IntfCast里调用了@QueryInterface, 而这里面的代码, 其实是在System的TObject对象的GetInterface这个方法中, 但这个方法有个问题, 它需要GUID, 但我们实际可以在Delphi中使用无GUID的接口, 那么就会发现, 根本无法进行转化比如:
      INoGUIDInterface = interface
        procedure NoGUID;
      end;  INoGUIDInterface2 = interface
        procedure NoGUID2;
      end;var
      mInt: INoGUIDInterface;
      mInt2: INoGUIDInterface2;
    begin
      mInt := TTestNoGUIDInterface2.Create;
      mInt.NoGUID;  mInt2 := mInt as INoGUIDInterface2; 
      mInt2.NoGUID2;
    end;编译就会出错了, Operator not applicable to this operand type 根本就不支持. 所以表明接口时还是随手加上GUID的比较好