混合调用时接口类型中的陷阱

Posted 朝闻道

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了混合调用时接口类型中的陷阱相关的知识,希望对你有一定的参考价值。

[delphi] view plain copy
 
 技术分享技术分享
  1. function abc(A: Integer): IUnknown;  

这是一个Delphi的函数声明,看上去很简单,只有一个参数而已,但是真实情况呢?在编译成二进制代码后,实际上函数的参数已经有2个了!


为了更详细的说明问题,先用Delphi写一个DLL,导出一个接口,接口有一个Show方法。

[delphi] view plain copy
 
 技术分享技术分享
  1. library Project1;  
  2.   
  3. uses  
  4.   Windows;  
  5.   
  6. {$R *.res}  
  7.   
  8. type  
  9.   ITest = interface  
  10.     procedure Show(); stdcall;  
  11.   end;  
  12.   
  13.   TTest = class(TInterfacedObject, ITest)  
  14.   public  
  15.     procedure Show(); stdcall;  
  16.   end;  
  17.   
  18. function GetTest: ITest; stdcall;  
  19. begin  
  20.   Result := TTest.Create;  
  21. end;  
  22.   
  23. exports  
  24.   GetTest;  
  25.   
  26. { TTest }  
  27.   
  28. procedure TTest.Show;  
  29. begin  
  30.   OutputDebugString(‘Hello World‘);  
  31. end;  
  32.   
  33. begin  
  34. end.  


调用方用C++编写

[cpp] view plain copy
 
 技术分享技术分享
  1. #include "stdafx.h"  
  2. #include <iostream>  
  3. #include <Windows.h>  
  4.   
  5. interface ITest : public IUnknown  
  6. {  
  7.     virtual void __stdcall show() = 0;  
  8. };  
  9.   
  10. typedef ITest* (WINAPI *GetITest)();  
  11.   
  12. int _tmain(int argc, _TCHAR* argv[])  
  13. {  
  14.     HMODULE h = LoadLibrary(TEXT("Project1.dll"));  
  15.     if (h != 0)  
  16.     {  
  17.         GetITest get = (GetITest)GetProcAddress(h, "GetTest");  
  18.         ITest *test = get();  
  19.         test->show();  
  20.         test->Release();  
  21.     }  
  22.     system("pause");  
  23.     return 0;  
  24. }  


运行后直接弹出一个内存错误

技术分享

 

出错语句在DLL中

[delphi] view plain copy
 
 技术分享技术分享
  1. function GetTest: ITest; stdcall;  
  2. begin  
  3.   Result := TTest.Create;  
  4. end;  


以反汇编代码的形式查看这个函数就能发现问题

技术分享

可以看到,函数返回值是接口类型的时候,实际上返回值是一个隐式的参数,是一个二级指针类型。在Dephi中使用不会发现问题,因为它自动作出了优化。

而在多语言混合编程中,这样直接返回一个接口或对象的时候就会出现内存为空的错误。

 

修改后的调用代码

[cpp] view plain copy
 
 技术分享技术分享
  1. #include "stdafx.h"  
  2. #include <iostream>  
  3. #include <Windows.h>  
  4.   
  5. interface ITest : public IUnknown  
  6. {  
  7.     virtual void __stdcall show() = 0;  
  8. };  
  9.   
  10. // 正确的函数原型  
  11. typedef VOID (WINAPI *GetITest)(ITest**);  
  12.   
  13. int _tmain(int argc, _TCHAR* argv[])  
  14. {  
  15.     HMODULE h = LoadLibrary(TEXT("Project1.dll"));  
  16.     if (h != 0)  
  17.     {  
  18.         GetITest get = (GetITest)GetProcAddress(h, "GetTest");  
  19.           
  20.         // 修改后的调用方法  
  21.         ITest *test = nullptr;  
  22.         get(&test);  
  23.   
  24.         test->show();  
  25.         test->Release();  
  26.     }  
  27.     system("pause");  
  28.     return 0;  
  29. }  


最后可以总结出一点经验,当Delphi函数返回值为接口类型的时候,函数会认为第一个参数是一个接口缓冲区,用于接受接口的实例对象。

那么是否可以在不改变C++这边调用方式的前提下直接返回接口指针呢?答案也是肯定的,只要把返回数据类型改为基础类型即可

[delphi] view plain copy
 
 技术分享技术分享
  1. function GetTest: Pointer; stdcall;  
  2. var  
  3.   Temp: ITest;  
  4. begin  
  5.   Temp := TTest.Create;  
  6.   Temp._AddRef;  
  7.   Result := Pointer(Temp);  
  8. end;  


由于函数返回值已不再是一个接口类型,Delphi也不会去调用接口的AddRef方法把引用计数+1了,所以在创建接口后得手动调用AddRef方法

否则函数在结束后会自动释放Temp,导致返回值是一个野指针。

http://blog.csdn.net/aqtata/article/details/19079737

以上是关于混合调用时接口类型中的陷阱的主要内容,如果未能解决你的问题,请参考以下文章

调用 free() 时发生混合语言程序崩溃

2018-2019-1 20165304 《信息安全系统设计基础》第七周学习总结

Python混合编程:C语言接口ctypes

《C陷阱与缺陷》读书笔记

中断异常和系统调用

线程池的陷阱