[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); } }
下面代碼進行調用
// 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 ↑
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方式
浙公網安備 33010602011771號