c#调用C++的DLL找不到入口点以及衍生的相关问题

DllImport属性详解

API函数是构筑Windows的基石, 是Windows编程的必备利器。每一种Windows应用程序开发工具都提供间接或者直接的方式调用Win32API,C#也不例外。使用Win32API的一个好处就是,我们可以实现更多的功能。

首先,要引入命名空间:using System.Runtime.InteropServices;

然后,声明在程序中所要用到的API函数。注意方法体为空。

DllImport属性用于指定包含外部方法的实现的dll位置。

(1)DllImport属性只能放在方法声明上。

(2)DllImport具有单个定位参数:指定包含被导入方法的dll名称的dllName参数。

(3)DllImport具有6个命名参数:

a、CallingConvention参数:指示入口点的调用约定,如果未指定CallingConvention,则使用默认值CallingConvention.Winapi;

b、CharSet参数:指示用在入口点种的字符集。如果未指定CharSet,则使用默认值CharSet.Auto;

c、EntryPoint参数:给出所声明的方法在dll中入口点的名称。如果未指定EntryPoint,则使用方法本身的名称;

d、ExactSpelling参数:指示EntryPoint是否必须与指示的入口点的拼写完全匹配。如果未指定ExactSpelling,则使用默认值false;

e、PreserveSig参数:指示方法的签名应被应当被保留还是被转换。当签名被转换时,它被转换为一个具有HRESULT返回值和该返回值的一个名为retval的附加输出参数签名。如果未指定PreserveSig,则使用默认值false;

f、SetLastError参数:指示方法是否保留Win32上的错误,如果未指定SetLastError,则使用默认值false。

DllImport是一次性属性类,而且用DllImport修饰的方法必须具有extern修饰符。

例子:

[DllImport("kernel32")]
private static extern void GetWindowsDirectory(StringBuilder WinDir,int count);
 
[DllImport("user32.dll",EntryPoint = "FlashWindow")]
private static extern bool FlashWindow(IntPtr hWnd,bool bInvert);
 
[DllImport("ws2_32.dll")]
private static extern int inet_addr(string cp);
 
[DllImport("IPHLPAPI.dll")]
private static extern int SendARP(Int32 DestIP, Int32 SrcIP, ref Int64 pMacAddr, ref Int32 PhyAddrLen);

c#程序调用C++的dll的时候,经常出现这样的问题:

[c-sharp]  view plain  copy
  1. System.EntryPointNotFoundException:Unable to find an entry point named '函数名称' in Dll 'c++ dll文件名'  

之前也遇到过这个问题,可是怎么解决的就忘记了,这次遇到了,就写下这个问题的原因。

这个是我在网上查资料找到的:http://www.cnblogs.com/tallman/archive/2009/03/07/735948.html

原因就是:c++源代码中的函数在编译成DLL后,函数的名称就发生了改变:会在函数的前后产生一些字符。

我们能通过eXeScope软件来查看c++编译后的函数名称是什么,这里要提下,eXeScope中文版本无法在x64的环境下使用,最好下英文版本。

 

例如:c++中的函数名是GetSvsSize,编译后变成?GetSvsSize@@YA_NPA_WPAJ11111PAF@Z,这个时候,我们要是想调用这个函数,那么应该这样写:

[c-sharp]  view plain  copy
  1. [DllImport(@"svsReader.dll", EntryPoint = "?GetSvsSize@@YA_NPA_WPAJ11111PAF@Z")]  

       我的问题就解决了,然后我就开始想这个问题:c++在编译之后为什么要加上这些字符呢? 难道这是防止被Reflect?

在c#的代码执行过程中,首先源代码被编译成托管模块(分布在各自的dll中),托管模块里面包括IL代码、元数据、还有一些标志(头信息),那元数据里面记录了源代码中定义的各种类型和成员等信息,所以c# reflect出来,里面的类名,方法名都没改变。


C++源码如下: 
C++代码 
  1. —————————————————a.h—————————————————  
  2. #ifdef A_EXPORTS  
  3. #define A_API __declspec(dllexport)  
  4. #else  
  5. #define A_API __declspec(dllimport)  
  6. #endif  
  7.   
  8. A_API int F(void);  
  9. —————————————————a.cpp—————————————————  
  10.   
  11. #include "stdafx.h"  
  12. #include "a.h"  
  13. BOOL APIENTRY DllMain( HANDLE hModule,  
  14.                        DWORD  ul_reason_for_call,  
  15.                        LPVOID lpReserved  
  16.  )  
  17. {  
  18. switch (ul_reason_for_call)  
  19. {  
  20. case DLL_PROCESS_ATTACH:  
  21. case DLL_THREAD_ATTACH:  
  22. case DLL_THREAD_DETACH:  
  23. case DLL_PROCESS_DETACH:  
  24. break;  
  25. }  
  26.     return TRUE;  
  27. }  
  28.   
  29. A_API int F(void)  
  30. {  
  31. MessageBox(NULL, "1212""1212", MB_OK);  
  32. return 0;  
  33. }  

C#源码函数原型声明: 

[DllImport("a.dll")] 
public extern static int F(); 
调用后提示找不到入口点 

在命令行用dumpbin /exports 看函数名: 

dumpbin /exports a.dll 
函数名不是"F"?而是"?F@@YAHXZ" 

C#函数声明写成: 

C#代码 
  1. [DllImport("a.dll",EntryPoint="?F@@YAHXZ")]  
  2. public extern static int F();  

这样调用成功! 

原因:在C++函数声明时要将 extern "C" 添加在 DLL 函数声明之前 

主要注意包含 DllImport 的代码行。此代码行根据参数值通知编译器,使之声明位于 User32.dll 中的函数并将签名中出现的所有字符串(如参数或返回值)视为 Unicode 字符串。如果缺少 EntryPoint 参数,则默认值为函数名。另外,由于 CharSet 参数指定 Unicode,因此公共语言运行库将首先查找称为 MessageBoxW(有 W 是因为 Unicode 规范)的函数。如果运行库未找到此函数,它将根据调用约定查找 MessageBox 以及相应的修饰名。受支持的调用约定只有 __cdecl 和 __stdcall。 
当调用用户定义的 DLL 中所包含的函数时,,如下所示: 
// The function declaration in SampleDLL.h file 
extern "C" SAMPLEDLL_API int fnSampleDLL(void); 

Dumpbin.exe位于 VS的安装目录\VC\bin下,如果点击dumpbin.exe提示 
出现mspdb80.dll无法找到的情况,是因为VC\Bin\下没有 “msobj80.dll,mspdb80.dll,mspdbcore.dll,mspdbsrv.exe”这四个文件(在VS2005中并没有这四个文件),解决的方法: 
1>直接从Common7\IDE\下复制这四个文件到VC\Bin\下即可解决 
2>添加系统变量 (Path),这样:我的电脑->属性->高级->环境变量->系统变量,在path中添加C:\Program Files\Microsoft Visual Studio 8\Common7\IDE;,注意结尾最后用“;”隔开! 
这样在用ml编译就不会出现mspdb80.dll文件找不到的错误了 

DLL(动态库)导出函数名乱码含义  

DLL(动态库)导出函数名乱码含义  
C++编译时函数名修饰约定规则:    
  __stdcall调用约定:    
  1、以"?"标识函数名的开始,后跟函数名;   
  
  2、函数名后面以"@@YG"标识参数表的开始,后跟参数表;  
   
  3、参数表以代号表示:    
  X--void
  D--char
  E--unsigned char
  F--short
  H--int
  I--unsigned int
  J--long
  K--unsigned long
  M--float
  N--double
  _N--bool
  ....    
  PA--表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以"0"代替,一个"0"代表一次重复;    
  4、参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;    
   
  5、参数表后以"@Z"标识整个名字的结束,如果该函数无参数,则以"Z"标识结束。    
  其格式为"?functionname@@YG*****@Z"或"?functionname@@YG*XZ",例如    
                      int Test1(char *var1, unsigned long)-----"?Test1@@YGHPADK@Z"                      

                     void Test2()-----"?Test2@@YGXXZ"
     
  __cdecl调用约定:    
  规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YA"。    
  __fastcall调用约定:    
  规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YI"。   

  如果要用DEF文件输出一个"C++"类,则把要输出的数据和成员的修饰名都写入.def模块定义文件    
  所以...   通过def文件来导出C++类是很麻烦的,并且这个修饰名是不可避免的


C++编译器的命名规则是这样的:
因为c++支持函数名重载,所以编译器会根据自己的规则对函数名进行篡改,防止命名发生冲突。
解决办法是在你dll的.cpp 和.h头文件中在函数前 加关键字_stdcall
或者在.def文件中直接指定导出的函数名
这样你再用depends或者exescope 看dll导出函数时就不会出现名字改编的问题了


C#中DllImport用法汇总

C++ 动态库导出函数名乱码及解决

dll 导出函数名的那些事



你可能感兴趣的:(C#,C/C++)