2013-07-28 74 views
3

我被告知將C++中的writen類導入到dll中,然後在c#應用程序中使用該dll。繼this guide我創建的DLL,但我不能簡單地用它在C#應用程序,因爲有關於它的一些問題:如何在基於C的dll或基於CLI的dll中打包C++類?

  1. 我應該把我的工廠函數的返回類型?

  2. 什麼是const wchar_t*這是我的構造函數參數類型?

  3. 如何檢索和使用我的函數返回類型是vector< wstring>

這些是阻止我在我的C#應用​​程序中使用我的C++ DLL的問題。我被告知需要用C++/CLI創建一個包裝器,然後在C#中使用它。但遺憾的是,我不知道它,我不知道C++ .net。

目前似乎對我更感性的唯一一件事是讓它與C兼容,然後創建一個C DLL並在我的C#應用​​程序中使用它。我已經讀過C語言,可以通過HANDLE來訪問類對象指針,所以我認爲這將是一個好主意,讓事情沒有太多變化。

所以問題是我該如何使用Handles來訪問C中的類對象並使用它們?我怎樣才能把vector<wstring>轉換成C對應的? 如果我想使用CLI爲我的C++ DLL創建一個包裝器(DLL?),並將其用於其他dotnet應用程序,我應該怎麼做?

+0

窗口'HANDLE'類型只是'void *'s – Pruyque

+0

'const wchar_t *'是一個非常模糊的參數說明。也許這是一個UTF-16LE編碼的Unicode字符串,它帶有一個被傳遞給構造函數的NUL終結符。如果是這樣,它接近.NET的'System :: String',這是一個代碼單元計數UTF-16LE編碼的Unicode字符串。 (至少它有'const',所以我們知道它是不可變的,因此不是一個輸入參數。) –

+0

.NET'char'編組到'wchar_t'。 – Joel

回答

2

爲了使C wrapperC++類可用於在例如C#應用程序,您可以執行以下操作。
在Visual Studio中選擇Win32 Console Application並輸入名稱,然後單擊下一步並在下一個窗格中選擇DLL
並單擊完成。
當你完成你代表一個DLL項目包括3個文件。

testdll.h 
testdll.cpp 
dllmain 

刪除您testdll.htestdll.cpp文件內部存在的一切,分別複製下面的內容,每個。 這些行添加到您的testdll.h

// Our C wrapper for creating a dll to be used in C# apps 

// The following ifdef block is the standard way of creating macros which make exporting 
// from a DLL simpler. All files within this DLL are compiled with the TESTDLL_EXPORTS 
// symbol defined on the command line. This symbol should not be defined on any project 
// that uses this DLL. This way any other project whose source files include this file see 
// TESTDLL_API functions as being imported from a DLL, whereas this DLL sees symbols 
// defined with this macro as being exported. 
#ifdef TESTDLL_EXPORTS 
#define TESTDLL_API __declspec(dllexport) 
#else 
#define TESTDLL_API __declspec(dllimport) 
#endif 

extern "C" 
{ 
    TESTDLL_API int OurTestFunction(int x, int y);       
} 

正是這種爲extern「C」塊,你定義的接口,功能來訪問你的類的成員函數原型之前functions.Note的TESTDLL內。所有的功能必須由此繼續。

這些添加到您的testdll.cpp文件:

#include "testdll.h" 
#include "ourClass.h" 

#define DLL_EXPORT 

extern "C" 
{ 
    OurClass ourObject; 
    TESTDLL_API int OurTestFunction(int x, int y) 
    { 
     return ourObject.Add(x,y); 
    } 
} 

編譯這一點,並得到一個基於C的DLL可以在C#應用程序中使用。
有幾件事情雖然注意到,其中比較重要的有:

  1. 你要明白,你作爲一個的Proxy-使用的代碼我的意思是你testdll.h內 函數定義,只能用C 兼容類型,它畢竟不是C++。
  2. 是你希望能夠分配你的 類的新對象,而不是僅僅使用一個全局對象來訪問所有的方法。

對於這一點,你,如果你需要傳遞成員函數之間的類的對象,則需要先將其轉換爲一個void*C能理解,然後通過它,並用它來訪問什麼讓你的成員函數永遠。

例如我會有這樣的事情我testdll.h內,以使用戶能夠間接管理的對象:

#ifdef TESTDLL_EXPORTS 
#define TESTDLL_API __declspec(dllexport) 
#else 
#define TESTDLL_API __declspec(dllimport) 
#endif 

extern "C" 
{ 
    TESTDLL_API int OurTestFunction(int x, int y);      

    TESTDLL_API void* CreateHandle(); 
    TESTDLL_API void* GetCurrentHandle(); 
    TESTDLL_API void DisposeCurrentHandle(); 
    TESTDLL_API void SetCurrentHandle(void* handle); 
    TESTDLL_API void* GetHandle(); 
    TESTDLL_API void DisposeHandle(void*); 
    TESTDLL_API void DisposeArrayBuffers(void); 
} 

而且我testdll.cpp內我會它們定義爲:

#include "testdll.h" 
#include "ourClass.h" 

#define DLL_EXPORT 

extern "C" 
{ 
    OurClass *ourObject; 

    TESTDLL_API int OurTestFunction(int x, int y) 
    { 
     //return ourObject.Add(x,y); -- not any more !! 
     ourObject = reinterpret_cast<OurClass *>(GetHandle()); 
    } 

    //Handle operations 
    TESTDLL_API void* CreateHandle() 
    { 
     if (ourObject == nullptr) 
     { 
      ourObject = new OurClass ; 
     } 
     else 
     { 
      delete ourObject ; 
      ourObject = new OurClass ; 
     } 
     return reinterpret_cast<void*>(ourObject); 
    } 

    TESTDLL_API void* GetCurrentHandle() 
    { 
     return reinterpret_cast<void*>(ourObject); 
    } 

    TESTDLL_API void DisposeCurrentHandle() 
    { 
     delete ourObject ; 
     ourObject = nullptr; 
    } 

    TESTDLL_API void SetCurrentHandle(void* handle) 
    { 
     if (handle != nullptr) 
     { 
      ourObject = reinterpret_cast<OurClass *>(handle); 
     } 
     else 
     { 
      ourObject = new OurClass ; 
     } 

    } 

    //factory utility function 
    TESTDLL_API void* GetHandle() 
    { 
     void* handle = GetCurrentHandle(); 
     if (handle != nullptr) 
     { 
      return handle; 
     } 
     else 
     { 
      ourObject = new OurClass ; 
      handle = reinterpret_cast <void*>(ourObject); 
     } 
     return handle; 
    } 

    CDLL_API void DisposeHandle(void* handle) 
    { 
     OurClass * tmp = reinterpret_cast<OurClass *>(handle); 
     delete tmp; 
    } 

    TESTDLL_API void DisposeArrayBuffers(void) 
    { 
     ourObject = reinterpret_cast<OurClass *>(GetHandle()); 
     return ourObject ->DisposeBuffers();//This is a member function defined solely for this purpose of being used inside this wrapper to delete any allocated resources by our class object. 
    } 
} 

當我們編譯這個Dll時,我們可以在我們的c#應用程序中輕鬆使用它。 在能夠使用我們在此dll中定義的函數之前,我們需要使用適當的[ImportDll()]
所以對於我們TestDll可以這樣寫:

[DllImport(@"TestDll.dll", CallingConvention = CallingConvention.Cdecl)] 
public static extern int OurTestFunction(int firstNumber,int secondNumber); 

最後使用它像:

private void btnReadBigram_Click(object sender, EventArgs e) 
{ 
    int x = OurTestFunction(10,50); 
    MessageBox.Show(x.ToString()); 
} 

這是我自己做了讓我的C++類成員C#應用程序中使用的功能沒有任何麻煩。

注意
當編譯你的C#應用​​程序請確保您選擇的x86平臺編譯項目不AnyCpu。你可以通過屬性改變你的平臺。

注2: 對於知道如何創建一個C++/CLI包裝爲您的本地C++類閱讀:
C++/CLI wrapper for your native C++ class

希望這是任何人在那裏誰具有相同的問題,因爲我的幫助:)

2

直接從C#中使用本地C++類在技術上是可行的,但這不是微不足道的,它甚至不是一個好主意。對於初學者,您必須知道用於從DLL導入的名稱,這將是C++名稱修改後的名稱。你也不能直接從C#中訪問像vector這樣的東西。

基本上有兩種很好的選擇:

首先是編寫與使用唯一可以被整理成CLR類型的類型有C接口的DLL。您可能會使用指針與IntPtr類型,但您無法真正取消引用這些指針。幾乎可以將它們存儲在C#代碼中,然後在需要時將它們傳遞迴本機DLL。你也可以使用簡單的struct類型,只要你不需要深拷貝來處理它們。該選項涉及使用P/Invoke。

第二個選擇是編寫一個混合模式的C++/CLI程序集,該程序集實現了所有需要訪問您的本機代碼的邏輯。這個程序集可以直接訪問你的C#代碼中的類和數據,也可以直接訪問你的本地代碼,儘管你應該預先警告你有兩個不能混合的煩人的休息時間。例如,C++/CLI中的ref class不能有shared_ptr成員。但是,它可以有一個原始的C++指針作爲成員。 (混合模式)本地類也可以訪問CLR句柄類型,並通過此方式調用C#代碼。該選項涉及使用C++ Interop。

值得注意的是,你也可以用C++ Interop的其他方式。您可以讓您的C#代碼訪問混合模式的C++/CLI程序集,該程序集爲某些本機代碼提供.NET接口。但是,在這種情況下,你仍然需要做一些翻譯,所以它不會比第一種方式好得多。

關於C++ Interop的完整教程將會相當長。我建議你閱讀here並在Google上進一步調查C++ Interop。

1

C++/CLI引入了託管對象,其中指針標記*應該替換爲^,並且'new'應該替換爲'gcnew',您不需要刪除這些對象他們將被垃圾收集,[編輯]託管類在其定義[/編輯]中有一個ref關鍵字。

中包裝C++ MyClass的類在C++/CLI的包裝類WrapperCLass可能是這個樣子:

#include <stdio.h> 

class MyClass 
{ 
public: 
    void ShowStuff(const wchar_t *a) 
    { 
     wprintf(a); 
    } 
}; 

public ref class WrapperClass 
{ 
    MyClass *wrapped; 
public: 
    WrapperClass() 
    { 
     wrapped = new MyClass; 

    } 
    ~WrapperClass() 
    { 
     delete wrapped; 
    } 
    void ShowStuff(IntPtr string) 
    { 
     wrapped->ShowStuff((const wchar_t *)string.ToPointer()); 
    } 
}; 

如果你生成這個DLL,您就可以使用它作爲C#項目 中的參考,您不必使用工廠函數機制。 在C++/CLI中可用,所以const wchar_t *也是如此。

要將系統::字符串轉換爲常量爲wchar_t *你可以使用這樣的事情:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

namespace Client 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      WrapperClass w = new WrapperClass(); 
      IntPtr tmp; 
      w.ShowStuff(tmp = System.Runtime.InteropServices.Marshal.StringToHGlobalUni("Test")); 
      System.Runtime.InteropServices.Marshal.FreeHGlobal(tmp); 
     } 
    } 
} 

(這裏很可能是更好的方式來做到這一點...)

對於您的返回類型,您必須在包裝類中進行轉換。創建一些.net集合,遍歷你的向量,將wstring轉換爲System :: String,並將其添加到.net集合中,然後返回該集合。

+1

從技術上講,'ref'不是關鍵字; 'ref class'是所謂的空白關鍵字之一。關鍵是'ref'不是'class'的修飾符,因爲'ref class'和'class'是完全不同的東西。 –

+1

對於編組字符串,我可能會建議將它們作爲'System :: String ^'傳遞給C++/CLI,然後使用基於RAII的編組類。否則,您會遇到異常安全問題。當然,你也可以在這裏使用'finally',但是我個人認爲RAI​​I更簡單。 – Joel

+0

@Pruyque:非常感謝。但是如果我準備好了一個dll並想用它來代替重寫我的託管類中的本地類,該怎麼辦? – Breeze