項目中要給其它客戶程序提供DLL做為接口,該項目是在.Net4.0平台下開發。終所周知.Net的各個版本之間存在着兼容性的問題,但是為了使用高版本運行平台的新特性,又不得不兼顧其它低版本平台客戶程序的調用。為了解決這個問題嘗試通過一個C++/CLI DLL對高版本的.Net DLL的接口加了一層包裝,對外暴露C風格的接口給客戶程序調用。
可支持的客戶語言平台:
- VB 6.0
- VC++
- .Net 1.0/.Net 1.1
- .Net 2.0
- .Net 3.5
創建C# .Net4.0的類庫
-
創建一個C#項目:Csharp
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Csharp { public class CsharpClass { public int DoTesting(int x, int y, string testing, out string error) { error = testing + " -> testing is ok."; return x + y; } } }
創建C++/CLI包裝類庫
-
創建項目C++/CLI項目:CsharpWrap
-
添加對Csharp的引用
-
CsharpWrap.h
// CsharpWrap.h #pragma once #include <windows.h> #include <string> using namespace System; using namespace Csharp; using namespace std; using namespace Runtime::InteropServices;
-
CsharpWrap.cpp
// This is the main DLL file. #include "stdafx.h" #include "CsharpWrap.h" extern "C" _declspec(dllexport) int DoTesting(int x, int y, char* testing, char* error) { try { CsharpClass ^generator = gcnew CsharpClass(); String^ strTesting = gcnew String(testing); String^ strError; int sum = generator->DoTesting(x, y,strTesting, strError); if(strError != nullptr) { char* cError = (char*)(Marshal::StringToHGlobalAnsi(strError)).ToPointer(); memcpy(error,cError,strlen(cError) + 1); } else { error = nullptr; } return sum; } catch(exception e) { memcpy(error,e.what(),strlen(e.what()) + 1); return -1; } }
-
項目輸出和使用
- Csharp.dll
- CsharpWrap.dll
如果要調用CsharpWrap.dll必須保證Csharp.dll也被調用程序可見(即應該放在進程EXE文件同一目錄下)
調用Demo代碼
-
C++
HINSTANCE hInst= LoadLibrary(_T("CsharpWrap.dll")); if(hInst) { pfunc DoTesting = (pfunc)GetProcAddress(hInst,"DoTesting"); if(DoTesting) { char error[100]= {NULL}; int sum = DoTesting(1, 2, "Hello", error); //show testing results char strSum[8]; _itoa_s(sum,strSum,16); ::MessageBoxA(NULL, error, strSum, MB_OK); } else { ::MessageBoxA(NULL, "Get function fail.", "Fail", MB_OK); } //free library FreeLibrary(hInst); hInst = nullptr; } else { ::MessageBoxA(NULL, "Load dll fail.", "Fail", MB_OK); }
-
C#低版本.Net
導出函數
[DllImport("CsharpWrap.dll")]
static extern int DoTesting(int x, int y, string testing, StringBuilder error);
調用
StringBuilder sb = new StringBuilder(200); int sum = DoTesting(1, 2, "Hellow", sb); MessageBox.Show(sb.ToString(), sum.ToString());
-
VB6.0
導出函數
Public Declare Function DoTesting Lib "CsharpWrap.dll" (ByVal x As Integer, ByVal y As Integer, ByVal testing As String, ByVal error As String) As Integer
調用
Dim error As String Dim testing As String Dim x As Integer Dim y As Integer Dim sum As Integer testing = "Hello" error = String(60000, vbNullChar) sum = DoTesting(x, y, testing, error)
常見問題及注意事項
- 平台工具集的問題
如果使用的是VS2010以上的版本編譯出來的C++/CLI DLL可能會遇到缺少依賴等運行錯誤。
如果要支持XP系統工具集盡量用_xp結尾的
平台工具集的依賴DLL,把下面的或其它相應版本的依賴DLL放到目標機器的C:\WINDOWS\SYSTEM32下
-
- msvcp100.dll
- msvcr100.dll
- msvcp110.dll
- msvcr110.dll
- 字符集的問題,因為.Net默認用的字符集是Unicode,但是客戶程序有可能是其它字符集,這樣也可能會造成字符串在程序間的兼容問題。
可選擇Unicode/Multi-Byte,根據不項目需求選擇相應的字符集
代碼內對不同字符集進行轉換
從char* to 寬字符
wchar_t *GetWC(const char *c) { const size_t cSize = strlen(c)+1; wchar_t* wc = new wchar_t[cSize]; MultiByteToWideChar(CP_ACP,0,(const char *)c,int(cSize),wc,int(cSize)); return wc; }
String^ to Char*
char* cError = (char*)(Marshal::StringToHGlobalAnsi(strError)).ToPointer();
- 調用的C++/CLI DLL的時候傳入參數的問題
C#調用的時候String參數對應的類型應該是StringBuilder,要注意StringBuilder的容量,默認是256個字符,如果返回的比較多的東西要注意初始化相應大小的容量。
- DLL多層嵌套的問題
如果用LoadLibrary加載DLL失敗,可以嘗試用LoadLibraryEx,同時保證所依賴的C#DLL放到進程EXE同級目錄。
LoadLibraryEx("DLL絕對路徑", NULL, LOAD_WITH_ALTERED_SEARCH_PATH);