[C# VS C++] C#代码遇见了非托管dll如何处理


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

  • 方法一:DllImport

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

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

在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。// 解释和其他不同

2. 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")],用没成功。其他划线没用过

3. 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函数
View Code

 

继续上面的CharSet 

4. CharSet = CharSet.Ansi

4. 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


 

5. 非托管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风格。

6. .NET版本不支持编码转换:

C# 4.7版之前版本不支持UTF-8 string 编码,C++ dll使用UTF8编码,4.7版本支持string转码(编码),string前加MarshalAs(UnmanagedType.LPUTF8Str)。

解决办法:自定义UTF8Marshaler

public class UTF8Marshaler : ICustomMarshaler
    {
        public void CleanUpManagedData(object managedObj)
        {
        }

        public void CleanUpNativeData(IntPtr pNativeData)
        {
            Marshal.FreeHGlobal(pNativeData);
        }

        public int GetNativeDataSize()
        {
            return -1;
        }

        public IntPtr MarshalManagedToNative(object managedObj)
        {
            if (object.ReferenceEquals(managedObj, null))
                return IntPtr.Zero;
            if (!(managedObj is string))
                throw new InvalidOperationException();

            byte[] utf8bytes = Encoding.UTF8.GetBytes(managedObj as string);
            IntPtr ptr = Marshal.AllocHGlobal(utf8bytes.Length + 1);
            Marshal.Copy(utf8bytes, 0, ptr, utf8bytes.Length);
            Marshal.WriteByte(ptr, utf8bytes.Length, 0);
            return ptr;
        }

        public object MarshalNativeToManaged(IntPtr pNativeData)
        {
            if (pNativeData == IntPtr.Zero)
                return null;

            List<byte> bytes = new List<byte>();
            for (int offset = 0; ; offset++)
            {
                byte b = Marshal.ReadByte(pNativeData, offset);
                if (b == 0)
                    break;
                else
                    bytes.Add(b);
            }
            return Encoding.UTF8.GetString(bytes.ToArray(), 0, bytes.Count);
        }

        private static UTF8Marshaler instance = new UTF8Marshaler();
        public static ICustomMarshaler GetInstance(string cookie)
        {
            return instance;
        }
    }

7. 结合网上总结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方式

 

posted on 2021-02-06 13:25  码杰  阅读(1076)  评论(0)    收藏  举报