C#  VS C++


C#代码遇见了非托管dll如何处理

问题:托管与非托管,兼容?

方法一:DllImport

托管调试助手 "PInvokeStackImbalance" Message=托管调试助手 "PInvokeStackImbalance":“对 PInvoke 函数“XXXX_Pub_Test!XXXX_Pub_Test.XxxxClient_temp::xxxxclient_config_init”的调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配。”

函数调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。(网上解释)

在DllImport中加入CallingConvention参数就行了,形如以下,

///////////////////////////////////////////////////////////////////////////////////////////////

下面并不能解决问题,只能勉强推送参数。需要推敲着用

///////////////////////////////////////////////////////////////////////////////////////////////


  • [DllImport(xxxx.dll, CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]

DllImport 部分反编译:

public class DllImportAttribute: Attribute
{
public DllImportAttribute(string dllName) {…} //定位参数为dllName
public CallingConvention CallingConvention; //入口点调用约定
public CharSet CharSet; //入口点采用的字符接
public string EntryPoint; //入口点名称
public bool ExactSpelling; //是否必须与指示的入口点拼写完全一致,默认false
public bool PreserveSig; //方法的签名是被保留还是被转换
public bool SetLastError; //FindLastError方法的返回值保存在这里
public string Value { get {…} }
}
网上找的说明:
1、DllImport只能放置在方法声明上。
2、DllImport具有单个定位参数:指定包含被导入方法的 dll 名称的 dllName 参数。
3、DllImport具有五个命名参数:
   a、CallingConvention 参数指示入口点的调用约定。如果未指定CallingConvention,则使用默认值CallingConvention.Winapi。
   b、CharSet参数指定用在入口点的字符集。如果未指定CharSet,则使用默认值CharSet.Auto。 // CharSet = CharSet.Ansi,试验用这个
   c、EntryPoint参数给出dll中入口点的名称。如果未指定EntryPoint,则使用方法本身的名称。
   d、ExactSpelling参数指示EntryPoint是否必须与指示的入口点的拼写完全匹配。如果未指定ExactSpelling,则使用默认值false。
   e、PreserveSig参数指示方法的签名被保留还是被转换。当签名被转换时,它被转换为一个具有HRESULT返回值和该返回值的一个
   名为retval的附加输出参数的签名。如果未指定PreserveSig,则使用默认值true。
   f、SetLastError参数指示方法是否保留Win32“上一错误”。如果未指定SetLastError,则使用默认值false。
4、它是一次性属性类。
5、用DllImport属性修饰的方法必须具有extern修饰符。
DllImport的用法示例(是用来写入ini文件的一个win32api):
#ifdef UNICODE
#define WritePrivateProfileString WritePrivateProfileStringW
#else
#define WritePrivateProfileString WritePrivateProfileStringA
#endif // !UNICODE
// !UNICODE
// typedef int                 BOOL;
WINBASEAPI
BOOL
WINAPI
WritePrivateProfileStringA(
_In_opt_ LPCSTR lpAppName,
_In_opt_ LPCSTR lpKeyName,
_In_opt_ LPCSTR lpString,
_In_opt_ LPCSTR lpFileName
);
[DllImport("kernel32")]
private static extern long WritePrivateProfileString(string mpAppName,string mpKeyName,string mpDefault,string mpFileName);
用此方法调用WinAPI的数据类型对应:C++:DWORD(BOOL/*最新*/)--C#:long/int/uint,C++:LPCTSTR(LPCWSTR或LPCSTR)--C#:string。// 解释和其他不同

///////////////////////////////////////////////////////////////////////////////////////////////

dll的位置,看了很多办法,总结一下:

DllImport会按照顺序自动去寻找的地方:

1、可运行文件exe所在目录 ,// 用过,用的这个

2、System32文件目录 ,// 用过,COM/DCOM注册的dll最好也放这

3、环境变量目录,// 没用过

只需要你把引用的DLL 拷贝到这三个目录下 就可以不用写路径了 或者

可以这样server.MapPath(.\bin\*.dll)web中的,同时也是应用程序中的 后来发现用[DllImport(@"C:\OJ\Bin\Judge.dll")]这样指定DLL的绝对路径就可以正常装载。 // [DllImport(@"C:\OJ\Bin\Judge.dll")],用没成功。其他划线没用过

Web 引用第三方非托管dll:

c#的dllimport使用方法详解未尝不是个解决办法,去里面多了解,简单总结一下。

具体做法如下:  

1. 首先我们在服务器上随便找个地方新建一个目录,假如为C:\DLL  

2. 然后,在环境变量中,给Path变量添加这个目录  

3. 最后,把所有的非托管文件都拷贝到C:\DLL中。或者更干脆的把dll放到system32目录  

对于可以自己部署的应用程序,这样未偿不是一个解决办法。然而,如果用的是虚拟空间,是没办法把注册PATH变量或者把我们自己的DLL拷到system32目录的。同时也不一定知道dll的物理路径。

DllImport里面只能用字符串常量,而不能够用Server.MapPath(@"~/Bin/Judge.dll")来确定物理路径。ASP.NET中要使用DllImport的,必须在先“using System.Runtime.InteropServices;”不过,我发现,调用这种"非托管Dll”相当的慢,可能是因为我的方法需要远程验证吧,但是实在是太慢了。经过一翻研究,终于想到了一个完美的解决办法,分别取得了LoadLibrary和GetProcAddress函数的地址,再通过这两个函数来取得我们的DLL里面的函数。

我们可以先用Server.MapPath(@"~/Bin/Judge.dll")来取得我们的DLL的物理路径,然后再用LoadLibrary进行载入,最后用GetProcAddress取得要用的函数地址。// 这个类似方法对C#托管dll使用过,动态加载dll,说的不对的请指正

[DllImport("kernel32.dll")]
private extern static IntPtr LoadLibrary(String path); [DllImport("kernel32.dll")]
private extern static IntPtr GetProcAddress(IntPtr lib, String funcName); [DllImport("kernel32.dll")]
private extern static bool FreeLibrary(IntPtr lib);

//  没验证过

LoadLibrary的装载和函数调用

public class DllInvoke
{
[DllImport("kernel32.dll")]
private extern static IntPtr LoadLibrary(String path); [DllImport("kernel32.dll")]
private extern static IntPtr GetProcAddress(IntPtr lib, String funcName); [DllImport("kernel32.dll")]
private extern static bool FreeLibrary(IntPtr lib); private IntPtr hLib; public DllInvoke(String DLLPath)
{
hLib = LoadLibrary(DLLPath);
} ~DllInvoke() // 还整出来析构??
{
FreeLibrary(hLib);
} //将要执行的函数转换为委托
public Delegate Invoke(String APIName,Type t)
{
IntPtr api = GetProcAddress(hLib, APIName);
return (Delegate)Marshal.GetDelegateForFunctionPointer(api,t);
}
}

LoadLibrary

下面代码进行调用

// delegate头顶上是不是也加点什么属性【】
// [...]
public delegate int Compile(String command, StringBuilder inf);
//编译
DllInvoke dll = new DllInvoke(Server.MapPath(@"~/Bin/Judge.dll"));
Compile compile = (Compile)dll.Invoke("Compile", typeof(Compile));
StringBuilder inf;
compile(@“gcc a.c -o a.exe“,inf);//这里就是调用我的DLL里定义的Compile函数

///////////////////////////////////////////////////////////////////////////////////////////////

继续上面的CharSet 

////////////////////////////////////////////////////////////////////////

CharSet = CharSet.Ansi才不会报错。//但不同机器结果也不同,难搞哦!

////////////////////////////////////////////////////////////////////////

改为:

  • [DllImport(xxxx.dll, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
  • 或者[DllImport(xxxx.dll, CharSet = CharSet.None, CallingConvention = CallingConvention.Cdecl)] // 其他机器报上述错误

System.BadImageFormatException

HResult=0x8007000B

Message=试图加载格式不正确的程序。 (异常来自 HRESULT:0x8007000B)

Source=XxxxClient_Test

StackTrace:

at XXXX_Test.XxxxClient_temp.xxxxclient_init()

at XxxxClient_Test.Program.Main(String[] args) in C:\Users\*****\source\repos\XXXX_Test\XxxxClient_Test\Program.cs:line 38


非托管dll的回调函数,在托管里如何表示:

C#默认情况下委托都是stdcall的调用方式,但可以通过UnmanagedFunctionPointer特性来修改

CallingConvention的值:

    public enum CallingConvention
{
Winapi = 1,// 默认平台调用约定
Cdecl, // C调用约定
StdCall, // 默认约定,这是使用平台invoke调用非托管函数的默认约定。
ThisCall, // 第一个参数是this指针,仅C++,用于对从非托管 DLL 导出的类调用方法
FastCall // 快?
}

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]

public static delegate {callback};

下面自己写了一个:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void on_message_back(int cfg, int messageId, string name, IntPtr load, int loadlen, int qos, bool retain);

用了默认CallingConvention.StdCall,情况是只调用了一次委托,绝无2次。修改为CallingConvention.Cdecl,估计dll是C风格。

结合网上总结C++与NET中数据类型的对应:

// c++:HANDLE(void *) ---- c#:System.IntPtr  

  // c++:Byte(unsigned char) ---- c#:System.Byte  

  // c++:SHORT(short) ---- c#:System.Int16  

  // c++:WORD(unsigned short) ---- c#:System.UInt16  

  // c++:INT(int) ---- c#:System.Int16

  // c++:INT(int) ---- c#:System.Int32  

  // c++:UINT(unsigned int) ---- c#:System.UInt16

  // c++:UINT(unsigned int) ---- c#:System.UInt32

  // c++:LONG(long) ---- c#:System.Int32  

  // c++:ULONG(unsigned long) ---- c#:System.UInt32  

  // c++:DWORD(unsigned long) ---- c#:System.UInt32  

  // c++:DECIMAL ---- c#:System.Decimal  

  // c++:BOOL(long) ---- c#:System.Boolean  

  // c++:CHAR(char) ---- c#:System.Char  

  // c++:LPSTR(char *) ---- c#:System.String  

  // c++:LPWSTR(wchar_t *) ---- c#:System.String  

  // c++:LPCSTR(const char *) ---- c#:System.String  

  // c++:LPCWSTR(const wchar_t *) ---- c#:System.String  

  // c++:PCAHR(char *) ---- c#:System.String  

  // c++:BSTR ---- c#:System.String  

  // c++:FLOAT(float) ---- c#:System.Single  

  // c++:DOUBLE(double) ---- c#:System.Double  

  // c++:VARIANT ---- c#:System.Object  

  // c++:PBYTE(byte *) ---- c#:System.Byte[]  

  // c++:BSTR ---- c#:StringBuilder

  // c++:LPCTSTR ---- c#:StringBuilder

  // c++:LPCTSTR ---- c#:string

  // c++:LPTSTR ---- c#:[MarshalAs(UnmanagedType.LPTStr)] string  

  // c++:LPTSTR 输出变量名
---- c#:StringBuilder 输出变量名

  // c++:LPCWSTR ---- c#:IntPtr

  // c++:BOOL ---- c#:bool   

  // c++:HMODULE ---- c#:IntPtr   

  // c++:HINSTANCE ---- c#:IntPtr  

  // c++:结构体
---- c#:public struct 结构体{};
 

  // c++:结构体
**变量名
---- c#:out 变量名
//C#中提前申明一个结构体实例化后的变量名

  // c++:结构体
&变量名
---- c#:ref 结构体
变量名

  // c++:WORD ---- c#:ushort

  // c++:DWORD ---- c#:uint

  // c++:DWORD ---- c#:int

// c++:UCHAR ---- c#:int

  // c++:UCHAR ---- c#:byte

  // c++:UCHAR* ---- c#:string

  // c++:UCHAR* ---- c#:IntPtr

  // c++:GUID ---- c#:Guid

  // c++:Handle ---- c#:IntPtr

  // c++:HWND ---- c#:IntPtr

  // c++:DWORD ---- c#:int

  // c++:COLORREF ---- c#:uint
  // c++:unsigned char ---- c#:byte

  // c++:unsigned char * ---- c#:ref byte

  // c++:unsigned char * ---- c#:[MarshalAs(UnmanagedType.LPArray)] byte[]

  // c++:unsigned char * ---- c#:[MarshalAs(UnmanagedType.LPArray)] Intptr

// c++:handle ---- c#:IntPtr
  // c++:hwnd ---- c#:IntPtr

  // c++:unsigned char & ---- c#:ref byte

  // c++:unsigned char 变量名
---- c#:byte 变量名

  // c++:unsigned short 变量名
---- c#:ushort 变量名

  // c++:unsigned int 变量名
---- c#:uint 变量名

  // c++:unsigned long 变量名
---- c#:ulong 变量名

  // c++:char 变量名
---- c#:byte 变量名
// C++中一个字符用一个字节表示,C#中一个字符用两个字节表示

  // c++:char 数组名[数组大小]
---- c#:MarshalAs(UnmanagedType.ByValTStr, SizeConst = 数组大小)]
public string 数组名;
ushort

  // c++:char * ---- c#:string // 传入参数

  // c++:char * ---- c#:StringBuilder // 传出参数

  // c++:char *变量名
---- c#:ref string 变量名

  // c++:char *输入变量名
---- c#:string 输入变量名

  // c++:char *输出变量名
---- c#:[MarshalAs(UnmanagedType.LPStr)] StringBuilder 输出变量名

  // c++:char ** ---- c#:string

  // c++:char **变量名
---- c#:ref string 变量名

  // c++:const char * ---- c#:string

  // c++:char[] ---- c#:string

  // c++:char 变量名[数组大小]
---- c#:[MarshalAs(UnmanagedType.ByValTStr,SizeConst=数组大小)]
public string 变量名;
 

  // c++:struct 结构体名
*变量名
---- c#:ref 结构体名
变量名

  // c++:委托
变量名
---- c#:委托
变量名

  // c++:int ---- c#:int

  // c++:int ---- c#:ref int

  // c++:int & ---- c#:ref int

  // c++:int * ---- c#:ref int  // C#中调用前需定义int 变量名
= 0;



  // c++:*int ---- c#:IntPtr

  // c++:int32 PIPTR * ---- c#:int32[]

  // c++:float PIPTR * ---- c#:float[]

   

  // c++:double** 数组名
---- c#:ref double 数组名

  // c++:double*[] 数组名
---- c#:ref double 数组名

  // c++:long ---- c#:int

  // c++:ulong ---- c#:int

    

  // c++:UINT8 * ---- c#:ref byte // C#中调用前需定义byte 变量名
= new byte(); 
       

  // c++:void * ---- c#:IntPtr   

  // c++:void * user_obj_param ---- c#:IntPtr user_obj_param

  // c++:void * 对象名称
---- c#:([MarshalAs(UnmanagedType.AsAny)]Object 对象名称

  // c++:char, INT8, SBYTE, CHAR ---- c#:System.SByte   

  // c++:short, short int, INT16, SHORT ---- c#:System.Int16   

  // c++:int, long, long int, INT32, LONG32, BOOL , INT ----
c#:System.Int32   

  // c++:__int64, INT64, LONGLONG ---- c#:System.Int64   

  // c++:unsigned char, UINT8, UCHAR , BYTE ---- c#:System.Byte
  

  // c++:unsigned short, UINT16, USHORT, WORD, ATOM, WCHAR , __wchar_t ----
c#:System.UInt16   

  // c++:unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD,
UINT ---- c#:System.UInt32   

  // c++:unsigned __int64, UINT64, DWORDLONG, ULONGLONG ----
c#:System.UInt64   

  // c++:float, FLOAT ---- c#:System.Single   

  // c++:double, long double, DOUBLE ---- c#:System.Double   



  // Win32 Types ---- CLR Type   

   

  // Struct需要在C#里重新定义一个Struct

  // CallBack回调函数需要封装在一个委托里,delegate
static extern int FunCallBack(string str);



  // unsigned char** ppImage替换成IntPtr ppImage

  // int& nWidth替换成ref
int nWidth

  // int*, int&, 则都可用
ref int 对应

  // 双针指类型参数,可以用
ref IntPtr

  // 函数指针使用c++:
typedef double (*fun_type1)(double); 对应 c#:public delegate double
fun_type1(double);

  // char* 的操作c++:
char*; 对应
c#:StringBuilder;

  // c#中使用指针:在需要使用指针的地方

unsafe
  // unsigned char对应public
byte

  /*

  * typedef void (*CALLBACKFUN1W)(wchar_t*, void* pArg); // 宽字符集

  * typedef void (*CALLBACKFUN1A)(char*, void* pArg);

  * bool BIOPRINT_SENSOR_API dllFun1(CALLBACKFUN1 pCallbackFun1, void*
pArg);

  * 调用方式为

  * [UnmanagedFunctionPointer(CallingConvention.Cdecl)]

  * public delegate void CallbackFunc1([MarshalAs(UnmanagedType.LPWStr)]
StringBuilder strName, IntPtr pArg); // 函数名有的可以不同

  * 

  */


方法二:C++/CLR方式

最新文章

  1. JS查看Object对象的内容
  2. win7桌面背景地址
  3. markdown语法集锦
  4. spring 怎样将枚举项注入到bean的数组字段中
  5. Ceph Jewel 10.2.3 环境部署
  6. C++获取本机IP地址
  7. NOI2014题解
  8. android Json解析详解(详细代码)
  9. Python之路:Python各个器
  10. docker rancher 体验 (未完待续.....)
  11. STL.vector.iterator的序号
  12. WPF中DataGrid中的DataGridCheckBoxColumn用法(全选,全否,反选)
  13. Solaris 10主机名和IP地址步骤
  14. 如何将在线电子书保存为pdf格式
  15. Java编程的逻辑 (32) - 剖析日期和时间
  16. 微服务之springCloud和docker-Eureka(一)
  17. mysql5.5版本和mysql 5.6版本具体有哪些区别?
  18. HeaderExchangeClient
  19. box2d 计算下一帧的位置/角度
  20. SPSS—回归—二元Logistic回归案例分析

热门文章

  1. linux kernel操作GPIO函数
  2. 【C++】《Effective C++》第五章
  3. 美业黑科技 ▏肌肤管家SkinRun V3S智能肌肤测试仪,实现“护肤”私人定制
  4. 【Tomcat 源码系列】认识 Tomcat
  5. Mac配置jmeter环境变量
  6. 【IMP】IMP导入表的时候,如果表存在怎么办
  7. AWD生存之道
  8. Jmeter(三十六) - 从入门到精通进阶篇 - 设置负载阶梯式压测场景(详解教程)
  9. Windows+.Net Framework+svn+IIS在Jenkins上的自动化部署入门
  10. 命令模式与go-redis command设计