在C#中使用C++編寫的類
現在在Windows下的應用程式開發,VS.Net佔據了絕大多數的份額。因此很多以前搞VC++開發的人都轉向用更強大的VS.Net。在這種情況下,有很多開發人員就面臨瞭如何在C#中使用C++開發好的類的問題。下面就用一個完整的例項來詳細說明怎樣用託管C++封裝一個C++類以提供給C#使用。
比如,現在有一個工程名為NativeCppDll的由C++編寫的DLL,裡面輸出了一個CPerson類。下面是具體的程式碼:
- // NativeCppDll.h
- #pragma once
- #ifndef LX_DLL_CLASS_EXPORTS
- #define LX_DLL_CLASS __declspec(dllexport)
- #else
- #define LX_DLL_CLASS __declspec(dllimport)
- #endif
- class LX_DLL_CLASS CPerson
- {
- public:
- CPerson();
- CPerson(constwchar_t *pName, constwchar_t cSex, int iAge);
- void SetName(constwchar_t *pName);
- wchar_t * GetName();
- void SetSex(constwchar_t cSex);
- wchar_t GetSex();
- void SetAge(
- int GetAge();
- wchar_t * GetLastError();
- private:
- wchar_t m_szName[128];
- wchar_t m_cSex;
- int m_iAge;
- wchar_t m_szLastError[128];
- void ShowError();
- };
- // NativeCppDll.cpp
- #include "stdafx.h"
- #include "NativeCppDll.h"
- #include <iostream>
- #include <tchar.h>
- usingnamespace
- CPerson::CPerson()
- {
- wcscpy_s(m_szName, _T("No Name"));
- m_cSex = 'N';
- m_iAge = 0;
- wcscpy_s(m_szLastError, _T("No Error"));
- }
- CPerson::CPerson(constwchar_t *pName, constwchar_t cSex, int iAge)
- {
- wcscpy_s(m_szLastError, _T("No Error"));
- SetName(pName);
- SetSex(cSex);
- SetAge(iAge);
- }
- void CPerson::SetName(constwchar_t *pName)
- {
- if ((pName == NULL) || (wcslen(pName) == 0) || (wcslen(pName) > 127))
- {
- wcscpy_s(m_szName, _T("No Name"));
- wcscpy_s(m_szLastError, _T("The length of the input name is out of range."));
- ShowError();
- return;
- }
- wcscpy_s(m_szName, pName);
- }
- wchar_t * CPerson::GetName()
- {
- return m_szName;
- }
- void CPerson::SetSex(constwchar_t cSex)
- {
- if ((cSex != 'F') && (cSex != 'M') && (cSex != 'm') && (cSex != 'f'))
- {
- m_cSex = 'N';
- wcscpy_s(m_szLastError, _T("The input sex is out of [F/M]."));
- ShowError();
- return;
- }
- m_cSex = cSex;
- }
- wchar_t CPerson::GetSex()
- {
- return m_cSex;
- }
- void CPerson::SetAge(int iAge)
- {
- if ((iAge < 0) || (iAge > 150))
- {
- m_iAge = 0;
- wcscpy_s(m_szLastError, _T("The input age is out of range."));
- ShowError();
- return;
- }
- m_iAge = iAge;
- }
- int CPerson::GetAge()
- {
- return m_iAge;
- }
- wchar_t * CPerson::GetLastError()
- {
- return m_szLastError;
- }
- void CPerson::ShowError()
- {
- cerr << m_szLastError << endl;
- }
這是一個很典型的由C++開發的DLL,輸出一個完整的C++類。如果現在要求開發一個C#工程,需要用到這個DLL中輸出的C++類CPerson,該怎麼辦呢?針對這個例子來說,類CPerson非常小,可以用C#重新寫一個跟這個C++類一樣的類。可是,如果需要的C++類很大,或者很多的時候,重寫工程將非常龐大。而且這樣沒有對現有的程式碼進行重用,浪費了現有資源,開發起來費時費力。
當然,還是有方法解決這個問題的。那就是用託管C++將C++類給封裝一下,然後再提供給C#來使用。下面就用程式碼來詳細說明怎樣用託管C++來封裝上面的那個C++類。
首先,要建立一個託管C++的DLL工程ManageCppDll,然後在裡面新增下面的程式碼:
- // ManageCppDll.h
- #pragma once
- #define LX_DLL_CLASS_EXPORTS
- #include "../NativeCppDll/NativeCppDll.h"
- usingnamespace System;
- namespace ManageCppDll
- {
- public ref class Person
- {
- // 包裝所有類CPerson的公有成員函式
- public:
- Person();
- Person(String ^ strName, Char cSex, int iAge);
- ~Person();
- property String ^ Name
- {
- void set(String ^ strName);
- String ^ get();
- }
- property Char Sex
- {
- void set(Char cSex);
- Char get();
- }
- property int Age
- {
- void set(int iAge);
- int get();
- }
- String ^ GetLastError();
- private:
- // 類CPerson的指標,用來呼叫類CPerson的成員函式
- CPerson *m_pImp;
- };
- };
從這個標頭檔案就能看出來,這是對C++類CPerson的包裝。類Person的所有公有成員函式都跟C++類CPerson一樣,只不過成員函式的引數和返回值就改成了託管C++的型別,這也是讓類Person能在C#中使用的首要條件。當然只需要對公有成員函式進行封裝,對於保護成員函式和私有成員函式則不必做任何封裝。
類Person僅有一個私有的成員變數:一個類CPerson的指標。而類Person的所有成員函式的實現都是靠這個CPerson指標來呼叫類CPerson的相應成員函式來實現。
下面是具體的實現程式碼:
- // ManageCppDll.cpp
- #include "stdafx.h"
- #include "ManageCppDll.h"
- #include <vcclr.h>
- namespace ManageCppDll
- {
- // 在建構函式中建立類CPerson的物件並在解構函式中將該物件銷燬
- // 所有的成員函式實現都是通過指標m_pImp呼叫類CPerson的相應成員函式實現
- Person::Person()
- {
- m_pImp = new CPerson();
- }
- Person::Person(String ^ strName, Char cSex, int iAge)
- {
- // 將string轉換成C++能識別的指標
- pin_ptr<constwchar_t> wcName = PtrToStringChars(strName);
- m_pImp = new CPerson(wcName, cSex, iAge);
- }
- Person::~Person()
- {
- // 在解構函式中刪除CPerson物件
- delete m_pImp;
- }
- void Person::Name::set(String ^ strName)
- {
- pin_ptr<constwchar_t> wcName = PtrToStringChars(strName);
- m_pImp->SetName(wcName);
- }
- String ^ Person::Name::get()
- {
- return gcnew String(m_pImp->GetName());
- }
- void Person::Sex::set(Char cSex)
- {
- m_pImp->SetSex(cSex);
- }
- Char Person::Sex::get()
- {
- return m_pImp->GetSex();
- }
- void Person::Age::set(int iAge)
- {
- m_pImp->SetAge(iAge);
- }
- int Person::Age::get()
- {
- return m_pImp->GetAge();
- }
- String ^ Person::GetLastError()
- {
- return gcnew String(m_pImp->GetLastError());
- }
- };
如果要在C#中使用類Person,首先要新增對ManageCppDll.dll的引用,然後就可以像用普通的C#類一樣的使用類Person了。比如下面這樣的程式碼:
- using ManageCppDll;
- Person person = new Person();
- person.Name = "StarLee";
- person.Sex = 'M';
- person.Age = 28;
熟悉設計模式的看了上面的程式碼肯定會發現,這樣的設計跟BRIDGE模式如出一轍。其實,上面的方法也算是一種BRIDGE模式,由託管C++充當了C#中使用用C++開發的類的橋樑。另外,這種形式也可以理解為ADAPTER模式,託管C++類Person就是C++類CPerson的一個介面卡。通過這個橋樑,可以很容易的重用以前用C++開發的類,讓這些C++類繼續在C#中發揮它們的效用,讓開發變得事半功倍。