問題描述
今天在做項目的時候碰到一個問題,就是用C++編寫CLR類庫dll的時候,C++的函數參數列表中包含一個char*的輸出型參數,然而在C#調用該dll時候,會自動將函數的中的char*參數“翻譯”為sbyte*, 使用了各種方法都不能調用函數,主要是不能合適的轉換為sbyte*。
簡單示例
舉個簡單的例子,比如我有一個CLR的類庫為MyDll.dll, 其中頭文件為Mydll.h,簡單的只有一個函數,其中我想要為char*的作為輸出參數, 代碼如下
1 // MyDll.h 2 3 #pragma once 4 #include <cstdio> 5 #include <cstring> 6 using namespace System; 7 8 namespace MyDll { 9 10 public ref class MyClass 11 { 12 // TODO: 在此處添加此類的方法。 13 public: 14 MyClass() 15 {} 16 public: 17 static bool TestFunc(char* outPutStr, int *p) 18 { 19 *p = 10; 20 char* s = "Hello world"; 21 strcpy_s(outPutStr, 32, s); 22 return true; 23 } 24 }; 25 }
源文件內容MyDll.cpp為空。【生成解決方案】后,得到MyDll.dll庫。
1 // 這是主 DLL 文件, MyDll.cpp 2 3 #include "stdafx.h" 4 5 #include "MyDll.h"
然后新建一個C# WPF項目,右鍵 【引用】->【添加引用...】->左側Tab【瀏覽】->右下角【瀏覽...】選擇剛才生成的 MyDll.dll 文件后,點擊【確定】。這樣我們就可以在WPF項目中調用 TestFunc 函數了。
我們在 WPF 的 MainWidnow 中隨便加入一個 Button,然后在 Button 的 Click 事件中嘗試調用 TestFunc 函數,Mainwindow 的 cs 文件如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Windows; 7 using System.Windows.Controls; 8 using System.Windows.Data; 9 using System.Windows.Documents; 10 using System.Windows.Input; 11 using System.Windows.Media; 12 using System.Windows.Media.Imaging; 13 using System.Windows.Navigation; 14 using System.Windows.Shapes; 15 using System.Runtime.InteropServices; 16 17 namespace MyWPF 18 { 19 /// <summary> 20 /// MainWindow.xaml 的交互邏輯 21 /// </summary> 22 public partial class MainWindow : Window 23 { 24 public MainWindow() 25 { 26 InitializeComponent(); 27 } 28 29 private void Button_Click(object sender, RoutedEventArgs e) 30 { 31 unsafe 32 { 33 string str = new string('A', 32); 34 IntPtr intPtrStr = (IntPtr)Marshal.StringToHGlobalAnsi(str); 35 sbyte* sbyteStr = (sbyte*)intPtrStr; 36 37 int p = 0; 38 MyDll.MyClass.TestFunc(sbyteStr, &p); 39 40 String tmp = new String(sbyteStr); 41 MessageBox.Show(tmp); 42 MessageBox.Show(p.ToString()); 43 } 44 } 45 } 46 }
注意引入第 15 行的命名空間, 同時【項目】->【屬性】->【生成】->勾選 【允許不安全代碼】。 主要轉換的部分出現在 33-35 行,首先建立一個普通的 string 變量,然后新建一個指針,指向該 string的內容,再講該指針強制類型轉化為sbyte*, 從而實現從 char* 中獲取內容。
問題討論
問題1: 為什么 C# 會將 C++ 中的 char* 轉換為 sbyte* 呢?
這是因為 C++ 中的 char 占用一個字節,而 C#中 char 占用兩個字節,所以不能講 char* 直接轉換為 C# 中的 char*, 而 sbyte 是有符號單字節,取值區間 [-128, 127],所以 C# 將 char* 轉換成 sbyte* 是合理的。
問題2:CLR 的 dll 和普通的 dll (Native dll) 有什么區別呢?
CLR 有點類似於 java 的 “虛擬機” , 提供運行環境,負責檢查 內存管理、安全、異常等比較容易出錯的問題,提高程序的安全性和健壯性,是 .NET Framework 的主要引擎。在 CLR 監視下的代碼稱為 “托管代碼 (managed)”, 而其他的不在其監管下的代碼稱為 “非托管代碼 (unmanaged)”。在 CLR 環境下生成的代碼是中間代碼,有點類似於 java 的字節碼。所以通過 CLR Dll 生成的 dll 類庫是中間代碼,普通的 dll 則是直接是本機化的二進制文件, 通過 CLR DLL 生成的 dll 是程序集 (Assembly), 可以在直接在C#工程中【添加引用】就可以直接調用,從前面的例子也可以看出來,不需要任何的 DllImport 之類的包含與結構體重定義,函數指針定義等等復雜的操作,使得調用更加簡單。
源碼下載
http://files.cnblogs.com/files/python27/MyWPF.zip
http://files.cnblogs.com/files/python27/MyDll.zip