用C#掉用C++的dll直接import就可以之前有不同的類型對應,當要傳遞結構體的時候就有點麻煩了,這里有一個結構體里邊有char*類型,這個類型在C#中調用沒法聲明,傳string是不行的默認string是對應const char*,傳stringbuilder得指定大小,不然是沒法傳的,
查了好久,最后只能用unsafe代碼來實現了
用C/C++寫一個標准的動態鏈接庫:
頭文件,定義了三個接口函數,
#pragma once #define TESTDLL _declspec(dllexport) #ifndef PKI_DATA_ST #define PKI_DATA_ST typedef struct PKI_DATA_st { int size; char *value; }PKI_DATA, *PKI_PDATA; #endif extern "C" { TESTDLL double Add(double a, double b); TESTDLL double Subtract(double a, double b); TESTDLL int TestDn(PKI_DATA cert, PKI_DATA* dn); }
簡單實現:
#include "stdafx.h" #include "TestClass.h" double Add(double a, double b) { printf("Add \n"); return a + b; } double Subtract(double a, double b) { printf("Subtract"); return a - b; } int TestDn(PKI_DATA cert, PKI_DATA* dn) { printf("TestDn cert.value: %s\n", cert.value); dn->size = 10; //dn->value = "helloworld"; //這個種寫法需要轉換 dn->value =(char*) TEXT("helloworld");//這種寫法在選擇unicode字符集時C#是可以直接接受的 return 1; }
C#調用,路徑可以用當前程序的相對路徑,也可以是系統的環境變量中的路徑,聲明import
[DllImport("USBLoad.dll", EntryPoint = "Add", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)] static extern double Add(double a,double b); [DllImport("USBLoad.dll", EntryPoint = "TestDn", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)] static extern int TestDn(PKI_DATA cert, ref PKI_DATA dn); [DllImport("USBLoad.dll", EntryPoint = "Subtract", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)] static extern double Subtract(double a, double b);
聲明結構體:
internal unsafe struct PKI_DATA { internal int size; internal char* value; }
調用:記得屬性中勾上允許不安全代碼
[HandleProcessCorruptedStateExceptions]//這個可以捕獲C++中的異常
[SecurityCritical] static unsafe void TestUSBLoad()//加unsafe { try { var a = Add(10, 10); w(a); a = Subtract(15, 10); w(a); PKI_DATA cert = new PKI_DATA(); cert.size = 4; char[] test = "test".ToCharArray(); string s = ""; fixed (char * v=test)//傳值初始化。需要fixed關鍵字 { cert.value = v; PKI_DATA dn = new PKI_DATA(); TestDn(cert, ref dn); var encode = Encoding.ASCII; var c = dn.value; List<char> list = new List<char>(); for (int i=0;i<dn.size;++i,++c) { list.Add(*c); }
//兼容ansi編碼,先轉byte var bytes = Encoding.Unicode.GetBytes(list.ToArray()); s = Encoding.ASCII.GetString(bytes);//轉碼 w(new string(dn.value));//設置unicode字符集的情況下可以直接這么寫就能得到要字符 } s = s.Replace("\0","");//轉碼的時候后邊會有很多\0,移除 w(string.Format("[{0}]",s)); } catch (Exception ex) { w(ex.Message); } }
順便研究了下C++的動態調用、
如果是靜態調用需要頭文件和lib庫,直接引用頭文件,加入lib的鏈接路徑,引用后可以直接用,編譯就可以了
動態調用用loadlibrary,記得freelibrary,這些函數在windows.h中,這里建的是QT的項目所以引用的是qt_windows.h:
#include "qt_windows.h" #define DLLPATH (LPCTSTR)"USBLoad.dll" #ifndef PKI_DATA_ST #define PKI_DATA_ST typedef struct PKI_DATA_st { int size; char *value; }PKI_DATA, *PKI_PDATA; #endif
//聲明入口函數 typedef double(*Add_Func)(double, double); typedef int(*TestDn)(PKI_DATA, PKI_DATA*); int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); qDebug() << "hello world!"; HINSTANCE m_hModule = LoadLibrary(DLLPATH);//加載dll if (m_hModule == NULL) { printf("LoadLibrary %s failed,err %d\n", DLLPATH, GetLastError()); goto END; } Add_Func add; add = (Add_Func)GetProcAddress(m_hModule,"Add");//獲取方法地址,后邊調用 double c = add(10, 11); printf("Add_Func: %lf \n", c); TestDn test; test = (TestDn)GetProcAddress(m_hModule, "TestDn"); PKI_DATA cert; PKI_DATA dn; cert.size = 4; cert.value = (char*)"test"; int res = test(cert, &dn); printf("TestDn:%s\n", dn.value); END: if (m_hModule != NULL) { FreeLibrary(m_hModule); m_hModule = NULL; printf("FreeLibrary\n"); } return a.exec(); }
關於怎么把char*轉碼到正確的編碼格式下的C#string,
char* cchar=data IntPtr value = new IntPtr(cchar); var s = Marshal.PtrToStringAnsi(value);
可以用上邊的方法,看fcl的源碼可以看到上邊的方法實質是把char*強轉為sbyte*,然后直接new string,這樣可以避免C++和C#的byte差異
marshal還有touni的方法,只轉unicode的方法,實質是直接用char* new string,
char*和sbyte*兩種初始化參數string類型都接受。
