在那篇《在C#中使用C++編寫的類》中我介紹了如何在C#中使用C++編寫的類。可是由於C#在使用者介面設計、資料庫儲存和XML檔案讀取等方面的優勢,有時候也會出現要在C++中使用C#編寫的類的情況。下面就用一個完整的執行個體來說明怎樣在C++中使用C#編寫的類。
比如說,現在有一個用C#編寫的DLL工程CsharpDll裡面有一個Person類:
- // Person.cs
- using System;
- namespace CsharpDll
- {
- public class Person
- {
- public Person()
- {
- Name = "No Name";
- Sex = 'N';
- Age = 0;
- m_strLastError = "No Error";
- }
- public Person(string strName, char cSex, int iAge)
- {
- m_strLastError = "No Error";
- Name = strName;
- Sex = cSex;
- Age = iAge;
- }
- public string Name
- {
- get
- {
- return m_strName;
- }
- set
- {
- if ((String.IsNullOrEmpty(value)) || (value.Length > 127))
- {
- m_strName = "No Name";
- m_strLastError = "The length of the input name is out of range.";
- return;
- }
- m_strName = value;
- }
- }
- public char Sex
- {
- get
- {
- return m_cSex;
- }
- set
- {
- if ((value != 'F') && (value != 'M') && (value != 'm') && (value != 'f'))
- {
- m_cSex = 'N';
- m_strLastError = "The input sex is out of [F/M].";
- return;
- }
- m_cSex = value;
- }
- }
- public int Age
- {
- get
- {
- return m_iAge;
- }
- set
- {
- if ((value < 0) || (value > 150))
- {
- m_iAge = 0;
- m_strLastError = "The input age is out of range.";
- return;
- }
- m_iAge = value;
- }
- }
- public string LastError
- {
- get
- {
- return m_strLastError;
- }
- }
- private string m_strName;
- private char m_cSex;
- private int m_iAge;
- private string m_strLastError;
- }
- }
如果需要在C++中使用這個C#編寫的Person類,就需要用託管C++來對這個C#進行封裝,將它封裝成一個C++能用的類。
首先,要建立一個託管C++的DLL工程ManageCppDll。並且,要添加對CsharpDll.dll的引用。然後對C#類所有的公有屬性和方法進行封裝。下面是具體的代碼:
- // ManageCppDll.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(const wchar_t *pName, const wchar_t cSex, int iAge);
- ~CPerson();
- void SetName(const wchar_t *pName);
- wchar_t * GetName();
- void SetSex(const wchar_t cSex);
- wchar_t GetSex();
- void SetAge(int iAge);
- int GetAge();
- wchar_t * GetLastError();
- private:
- // 用一個void指標指向Person的對象
- // 所有公有成員函數的實現都是通過這個對象來實現
- void *m_pImp;
- wchar_t m_szName[128];
- wchar_t m_szLastError[128];
- };
- // ManageCppDll.cpp
- #include "stdafx.h"
- #include "ManageCppDll.h"
- #include <vcclr.h>
- #include <string.h>
- #include <stdlib.h>
- using namespace System;
- using namespace System::Runtime::InteropServices;
- using namespace CsharpDll;
- // 將GCHandle轉換成為void指標
- #define __GCHANDLE_TO_VOIDPTR(x) ((GCHandle::operator System::IntPtr(x)).ToPointer())
- // 將void指標轉換為GCHandle
- #define __VOIDPTR_TO_GCHANDLE(x) (GCHandle::operator GCHandle(System::IntPtr(x)))
- // 輔助函數
- // 將void指標指向的對象轉換成為Person對象
- inline Person ^ GetImpObj(void *pHandle)
- {
- Person ^ person = nullptr;
- if (pHandle != NULL)
- {
- person = static_cast<Person^>(__VOIDPTR_TO_GCHANDLE(pHandle).Target);
- }
- return person;
- }
- CPerson::CPerson()
- {
- m_pImp = NULL;
- Person ^ person = gcnew Person();
- // 建立GCHandle並將它轉換成void指標儲存到成員變數中
- GCHandle handle = GCHandle::Alloc(person);
- m_pImp = __GCHANDLE_TO_VOIDPTR(handle);
- }
- CPerson::CPerson(const wchar_t *pName, const wchar_t cSex, int iAge)
- {
- m_pImp = NULL;
- Person ^ person = gcnew Person();
- person->Name = gcnew String(pName);
- person->Sex = cSex;
- person->Age = iAge;
- GCHandle handle = GCHandle::Alloc(person);
- m_pImp = __GCHANDLE_TO_VOIDPTR(handle);
- }
- CPerson::~CPerson()
- {
- if (m_pImp == NULL)
- return;
- // 釋放GCHandle
- GCHandle handle = __VOIDPTR_TO_GCHANDLE(m_pImp);
- handle.Free();
- m_pImp = NULL;
- }
- void CPerson::SetName(const wchar_t *pName)
- {
- // 將void指標轉換成Person指標
- // 並用該指標調用相應的公有屬性或方法
- Person ^ person = GetImpObj(m_pImp);
- person->Name = gcnew String(pName);
- }
- wchar_t * CPerson::GetName()
- {
- Person ^ person = GetImpObj(m_pImp);
- // 將C#返回的字串轉換為wchat_t*指標能指向的地址
- wchar_t * pName = static_cast<wchar_t*>(Marshal::StringToHGlobalUni(person->Name).ToPointer());
- wcscpy_s(m_szName, pName);
- Marshal::FreeHGlobal(System::IntPtr(pName)); // 釋放記憶體
- return m_szName;
- }
- void CPerson::SetSex(const wchar_t cSex)
- {
- Person ^ person = GetImpObj(m_pImp);
- person->Sex = cSex;
- }
- wchar_t CPerson::GetSex()
- {
- Person ^ person = GetImpObj(m_pImp);
- return person->Sex;
- }
- void CPerson::SetAge(int iAge)
- {
- Person ^ person = GetImpObj(m_pImp);
- person->Age = iAge;
- }
- int CPerson::GetAge()
- {
- Person ^ person = GetImpObj(m_pImp);
- return person->Age;
- }
- wchar_t * CPerson::GetLastError()
- {
- Person ^ person = GetImpObj(m_pImp);
- wchar_t * pLastError = static_cast<wchar_t*>(Marshal::StringToHGlobalUni(person->LastError).ToPointer());
- wcscpy_s(m_szLastError, pLastError);
- Marshal::FreeHGlobal(System::IntPtr(pLastError));
- return m_szLastError;
- }
現在對上面代碼中所用到的一些相關背景知識進行一下介紹。
GCHandle結構提供從非託管記憶體訪問託管對象的方法。
GCHandle.Alloc方法(Object)為指定的對象分配Normal控制代碼。它保護對象不被記憶體回收。當不再需要GCHandle時,必須通過Free將其釋放。Normal控制代碼類型表示不透明控制代碼,這意味著無法通過此控制代碼解析固定對象的地址。可以使用此類型跟蹤對象,並防止它被記憶體回收行程回收。當非託管用戶端持有對託管對象的唯一引用(從記憶體回收行程檢測不到該引用)時,此枚舉成員很有用。
上面的代碼中,在類CPerson的建構函式中用GCHandle為C#類Person的對象分配一個控制代碼,並將該控制代碼轉換為void指標存放在成員變數中,以保證這個對象不會被記憶體回收行程回收。然後在類CPerson的解構函式中釋放這個控制代碼,將C#類Person的對象的回收權交給系統。
Marshal類提供了一個方法集,這些方法用於分配非託管記憶體、複製非託管記憶體塊、將託管類型轉換為非託管類型,此外還提供了在與Unmanaged 程式碼互動時使用的其他雜項方法。
Marshal..::.StringToHGlobalUni方法向非託管記憶體複製託管String的內容。StringToHGlobalUni對於自訂封送處理或者在混合託管和Unmanaged 程式碼時很有用。由於該方法分配字串所需的非託管記憶體,因此應始終通過調用FreeHGlobal釋放記憶體。
(更多關於上面介紹的背景知識可以搜尋MSDN。說實在的MSDN真是一個寶庫!VS能在Windows平台開發中取得絕大多數的份額,除了因為它和Windows都是微軟開發的之外,MSDN的完備性功不可沒!)
通過上面的方法,就把一個C#編寫的類Person用託管C++給封裝成了一個C++可以使用的CPerson類。我們可以在C++的工程中像使用一般的C++類一樣使用類CPerson。比如下面的代碼。
- CPerson person(_T("StarLee"), 'M', 28);
- person.SetName(_T("StarLee"));
- person.SetSex('M');
- person.SetAge(28);
- wcout << "Name: " << person.GetName() << " Sex: " << person.GetSex() << " Age: " << person.GetAge() << endl;
- wcout << "Error: " << person.GetLastError() << endl;
這裡的方法跟《在C#中使用C++編寫的類》一樣,都是借用託管C++這個橋樑來溝通C++編寫的類和C#編寫的類,使在C++中使用C#編寫的類和在C#中使用C++編寫的類成為現實。