下面的内容为个人分析结果, 目的是请各位高手斧正, 如发现有弱智,火星之类的内容, 请指出, 谢谢.今天在发现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的编译器对接口的支持还是不够好,造成在强制转化时,不能正确的识别出要求的接口类型来.
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的编译器对接口的支持还是不够好,造成在强制转化时,不能正确的识别出要求的接口类型来.
http://www.agui.googlepages.com
mailto: agui.cn(a)gmail.com
如果这样强制转换可以得到楼主想要的结果,那它就不是"强制"转换了.COM技术是由C++ 虚函数机制基本上发展演化而产生的,为了保持其尽可能的高效,
接口调用时是使用的内存位置约定,也就是楼主所观察到的,"..以方法的声明顺序..".按照COM最基本约定,对实现了多个接口的对象,从一个接口得到另一个接口的指针,
唯一方法:调用接口的QueryInterface方法. 尽管对于熟悉对象内存模型的程序员,
绕开这个方法,通过一些指针的强制转换也可以获得其它接口,但这是强烈反对的.DELPHI 定义了AS 运算,这是个比较灵活的运算符.当AS 用于从一个接口得到另一个接口时,
这时候就是调用了queryInterface方法.
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的比较好