C#呼び出し非管理C++DLL:管理C++DLLによる間接呼び出し


現在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(const wchar_t *pName, const wchar_t cSex, int iAge);
    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:
    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 
#include 
using namespace std;
CPerson::CPerson()
{
    wcscpy_s(m_szName, _T("No Name"));
    m_cSex = 'N';
    m_iAge = 0;
    wcscpy_s(m_szLastError, _T("No Error"));
}
CPerson::CPerson(const wchar_t *pName, const wchar_t cSex, int iAge)
{
    wcscpy_s(m_szLastError, _T("No Error"));
    SetName(pName);
    SetSex(cSex);
    SetAge(iAge);
}
void CPerson::SetName(const wchar_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(const wchar_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"
using namespace 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にはプライベートメンバー変数が1つしかありません.クラスCPersonのポインタです.クラスPersonのすべてのメンバー関数の実装は、このCPersonポインタによってクラスCPersonの対応するメンバー関数を呼び出すことによって実現される.具体的な実装コードは次のとおりです.
// ManageCppDll.cpp

#include "stdafx.h"
#include "ManageCppDll.h"
#include 

namespace ManageCppDll 
{
    //          CPerson                
    //                m_pImp   CPerson         
    Person::Person()
    {
        m_pImp = new CPerson();
    }
    Person::Person(String ^ strName, Char cSex, int iAge)
    {
        //  string   C++      
        pin_ptr wcName = PtrToStringChars(strName);
        m_pImp = new CPerson(wcName, cSex, iAge);
    }
    Person::~Person()
    {
        //         CPerson  
        delete m_pImp;
    }
    void Person::Name::set(String ^ strName)
    {
        pin_ptr 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());
    }
};

クラスPersonをC#で使用する場合は、まず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#でそれらの効用を発揮し続け,開発を半分の仕事倍にすることができる.