1. 程式人生 > >在C#中使用C++編寫的類

在C#中使用C++編寫的類

     現在在Windows下的應用程式開發,VS.Net佔據了絕大多數的份額。因此很多以前搞VC++開發的人都轉向用更強大的VS.Net。在這種情況下,有很多開發人員就面臨瞭如何在C#中使用C++開發好的類的問題。下面就用一個完整的例項來詳細說明怎樣用託管C++封裝一個C++類以提供給C#使用。
    比如,現在有一個工程名為NativeCppDll的由C++編寫的DLL,裡面輸出了一個CPerson類。下面是具體的程式碼:

  1. // NativeCppDll.h
  2. #pragma once
  3. #ifndef LX_DLL_CLASS_EXPORTS
  4.     #define LX_DLL_CLASS __declspec(dllexport)
  5. #else
  6.     #define LX_DLL_CLASS __declspec(dllimport)
  7. #endif
  8. class LX_DLL_CLASS CPerson
  9. {
  10. public:
  11.     CPerson();
  12.     CPerson(constwchar_t *pName, constwchar_t cSex, int iAge);
  13. void SetName(constwchar_t *pName);
  14. wchar_t * GetName();
  15. void SetSex(constwchar_t cSex);
  16. wchar_t GetSex();
  17. void SetAge(
    int iAge);
  18. int GetAge();
  19. wchar_t * GetLastError();
  20. private:
  21. wchar_t m_szName[128];
  22. wchar_t m_cSex;
  23. int m_iAge;
  24. wchar_t m_szLastError[128];
  25. void ShowError();
  26. };
  27. // NativeCppDll.cpp
  28. #include "stdafx.h"
  29. #include "NativeCppDll.h"
  30. #include <iostream>
  31. #include <tchar.h>
  32. usingnamespace
     std;
  33. CPerson::CPerson()
  34. {
  35.     wcscpy_s(m_szName, _T("No Name"));
  36.     m_cSex = 'N';
  37.     m_iAge = 0;
  38.     wcscpy_s(m_szLastError, _T("No Error"));
  39. }
  40. CPerson::CPerson(constwchar_t *pName, constwchar_t cSex, int iAge)
  41. {
  42.     wcscpy_s(m_szLastError, _T("No Error"));
  43.     SetName(pName);
  44.     SetSex(cSex);
  45.     SetAge(iAge);
  46. }
  47. void CPerson::SetName(constwchar_t *pName)
  48. {
  49. if ((pName == NULL) || (wcslen(pName) == 0) || (wcslen(pName) > 127))
  50.     {
  51.         wcscpy_s(m_szName, _T("No Name"));
  52.         wcscpy_s(m_szLastError, _T("The length of the input name is out of range."));
  53.         ShowError();
  54. return;
  55.     }
  56.     wcscpy_s(m_szName, pName);
  57. }
  58. wchar_t * CPerson::GetName()
  59. {
  60. return m_szName;
  61. }
  62. void CPerson::SetSex(constwchar_t cSex)
  63. {
  64. if ((cSex != 'F') && (cSex != 'M') && (cSex != 'm') && (cSex != 'f'))
  65.     {
  66.         m_cSex = 'N';
  67.         wcscpy_s(m_szLastError, _T("The input sex is out of [F/M]."));
  68.         ShowError();
  69. return;
  70.     }
  71.     m_cSex = cSex;
  72. }
  73. wchar_t CPerson::GetSex()
  74. {
  75. return m_cSex;
  76. }
  77. void CPerson::SetAge(int iAge)
  78. {
  79. if ((iAge < 0) || (iAge > 150))
  80.     {
  81.         m_iAge = 0;
  82.         wcscpy_s(m_szLastError, _T("The input age is out of range."));
  83.         ShowError();
  84. return;
  85.     }
  86.     m_iAge = iAge;
  87. }
  88. int CPerson::GetAge()
  89. {
  90. return m_iAge;
  91. }
  92. wchar_t * CPerson::GetLastError()
  93. {
  94. return m_szLastError;
  95. }
  96. void CPerson::ShowError()
  97. {
  98.     cerr << m_szLastError << endl;
  99. }

    這是一個很典型的由C++開發的DLL,輸出一個完整的C++類。如果現在要求開發一個C#工程,需要用到這個DLL中輸出的C++類CPerson,該怎麼辦呢?針對這個例子來說,類CPerson非常小,可以用C#重新寫一個跟這個C++類一樣的類。可是,如果需要的C++類很大,或者很多的時候,重寫工程將非常龐大。而且這樣沒有對現有的程式碼進行重用,浪費了現有資源,開發起來費時費力。
    當然,還是有方法解決這個問題的。那就是用託管C++將C++類給封裝一下,然後再提供給C#來使用。下面就用程式碼來詳細說明怎樣用託管C++來封裝上面的那個C++類。
    首先,要建立一個託管C++的DLL工程ManageCppDll,然後在裡面新增下面的程式碼:   

  1. // ManageCppDll.h
  2. #pragma once
  3. #define LX_DLL_CLASS_EXPORTS
  4. #include "../NativeCppDll/NativeCppDll.h"
  5. usingnamespace System;
  6. namespace ManageCppDll 
  7. {
  8. public ref class Person
  9.     {
  10. // 包裝所有類CPerson的公有成員函式
  11. public:
  12.         Person();
  13.         Person(String ^ strName, Char cSex, int iAge);
  14.         ~Person();
  15.         property String ^ Name
  16.         {
  17. void set(String ^ strName);
  18.             String ^ get();
  19.         }
  20.         property Char Sex
  21.         {
  22. void set(Char cSex);
  23.             Char get();
  24.         }
  25.         property int Age
  26.         {
  27. void set(int iAge);
  28. int get();
  29.         }
  30.         String ^ GetLastError();
  31. private:
  32. // 類CPerson的指標,用來呼叫類CPerson的成員函式
  33.         CPerson *m_pImp;
  34.     };
  35. };

    從這個標頭檔案就能看出來,這是對C++類CPerson的包裝。類Person的所有公有成員函式都跟C++類CPerson一樣,只不過成員函式的引數和返回值就改成了託管C++的型別,這也是讓類Person能在C#中使用的首要條件。當然只需要對公有成員函式進行封裝,對於保護成員函式和私有成員函式則不必做任何封裝。
    類Person僅有一個私有的成員變數:一個類CPerson的指標。而類Person的所有成員函式的實現都是靠這個CPerson指標來呼叫類CPerson的相應成員函式來實現。
    下面是具體的實現程式碼:

  1. // ManageCppDll.cpp
  2. #include "stdafx.h"
  3. #include "ManageCppDll.h"
  4. #include <vcclr.h>
  5. namespace ManageCppDll 
  6. {
  7. // 在建構函式中建立類CPerson的物件並在解構函式中將該物件銷燬
  8. // 所有的成員函式實現都是通過指標m_pImp呼叫類CPerson的相應成員函式實現
  9.     Person::Person()
  10.     {
  11.         m_pImp = new CPerson();
  12.     }
  13.     Person::Person(String ^ strName, Char cSex, int iAge)
  14.     {
  15. // 將string轉換成C++能識別的指標
  16.         pin_ptr<constwchar_t> wcName = PtrToStringChars(strName);
  17.         m_pImp = new CPerson(wcName, cSex, iAge);
  18.     }
  19.     Person::~Person()
  20.     {
  21. // 在解構函式中刪除CPerson物件
  22. delete m_pImp;
  23.     }
  24. void Person::Name::set(String ^ strName)
  25.     {
  26.         pin_ptr<constwchar_t> wcName = PtrToStringChars(strName);
  27.         m_pImp->SetName(wcName);
  28.     }
  29.     String ^ Person::Name::get()
  30.     {
  31. return gcnew String(m_pImp->GetName());
  32.     }
  33. void Person::Sex::set(Char cSex)
  34.     {
  35.         m_pImp->SetSex(cSex);
  36.     }
  37.     Char Person::Sex::get()
  38.     {
  39. return m_pImp->GetSex();
  40.     }
  41. void Person::Age::set(int iAge)
  42.     {
  43.         m_pImp->SetAge(iAge);
  44.     }
  45. int  Person::Age::get()
  46.     {
  47. return m_pImp->GetAge();
  48.     }
  49.     String ^ Person::GetLastError()
  50.     {
  51. return gcnew String(m_pImp->GetLastError());
  52.     }
  53. };

    如果要在C#中使用類Person,首先要新增對ManageCppDll.dll的引用,然後就可以像用普通的C#類一樣的使用類Person了。比如下面這樣的程式碼:

  1. using ManageCppDll;
  2. Person person = new Person();
  3. person.Name = "StarLee";
  4. person.Sex = 'M';
  5. person.Age = 28;

    熟悉設計模式的看了上面的程式碼肯定會發現,這樣的設計跟BRIDGE模式如出一轍。其實,上面的方法也算是一種BRIDGE模式,由託管C++充當了C#中使用用C++開發的類的橋樑。另外,這種形式也可以理解為ADAPTER模式,託管C++類Person就是C++類CPerson的一個介面卡。通過這個橋樑,可以很容易的重用以前用C++開發的類,讓這些C++類繼續在C#中發揮它們的效用,讓開發變得事半功倍。