DLLまとめ 05 C++クラスのエクスポート

DLLの簡単なまとめです。
C++クラスのエクスポートについて



DLLからクラスをエクスポートできます。
ただし、クラスのメンバ関数にextern "C"を指定できないため、同じベンダの(名前マングルの規則が同じ)コンパイラで作られた実行ファイルからしか呼び出せません。
更に大きな問題として、DLLのクラスのレイアウトが変更されると、それを使う実行ファイルを再コンパイルしなくてはいけないという問題があります。



RationalクラスをエクスポートするDLLで問題を確認します。

/*******************************************************************
	myDLL.h
*******************************************************************/

class __declspec(dllexport) Rational
{
public:
	Rational(int numerator = 0, int denominator = 1);
	
	...

	double asDouble() const;

private:
	int _numerator;
	int _denominator;
};
/*******************************************************************
	myDLL.cpp
*******************************************************************/
#include "myDLL.h"

double Rational::asDouble() const
{
	return static_cast<double>(_numerator) / static_cast<double>(_denominator);
}

実行ファイルは、DLLと同じくmyDLL.hをインクルードします。

/*******************************************************************
	main.cpp
*******************************************************************/
#include <myDLL.h>
#pragma comment(lib, "myDLL.lib")


int main()
{
	Rational r(2, 3);
	cout << r.asDouble() << endl;
	...

実行結果
f:id:skru_y:20140319170911j:plain
実行結果は、期待通りになります。



ここで、asDoubleが何度も呼び出されるため、除算をあらかじめ計算しておくように効率化したとします。
すると、DLLのコードは次のようになります。

/*******************************************************************
	myDLL.h
*******************************************************************/

class __declspec(dllexport) Rational
{
public:
	Rational(int numerator = 0, int denominator = 1);
	
	...

	double asDouble() const;

private:
	int _numerator;
	int _denominator;

	double _asDouble; //この値は_numeratorか_denominatorが変更されるたびに更新される
};
/*******************************************************************
	myDLL.cpp
*******************************************************************/
#include "myDLL.h"

double Rational::asDouble() const
{
	return _asDouble;
}

実行ファイルをコンパイルすることなくこのDLLを使うと、結果は下の画像になりました。
f:id:skru_y:20140319170813j:plain
原因は、実行ファイルの意図したクラスサイズとDLLの意図したクラスサイズが異なるためです。



解決策は、インタフェースクラスを作ることです。
共通のヘッダは下のようにします。

/*******************************************************************
myDLL.h
*******************************************************************/

class IRational
{
public:
	virtual double asDouble() const = 0;
};

class Rational : public IRational
{
	...
};


//createRationalは内部でRationalをnewする
extern "C" __declspec(dllexport)
IRational* __stdcall createRational(int numerator = 0, int denominator = 1);

//destroyRationalは内部でRationalをdeleteする
extern "C" __declspec(dllexport)
void __stdcall destroyRational(IRational* p);

実行ファイルは下のようにします。

#include <myDLL.h>
#pragma comment(lib, "myDLL.lib")



int main()
{
	IRational *p = createRational(2, 3);
	cout << p->asDouble() << endl;

実行ファイルが知る必要のあるサイズはIRationalへのポインタだけなので、Rationalのサイズが変更されても問題なく実行できます。


ちなみに、上記のインタフェースクラスに似たものとして、COM(Component Object Model)があります。
というわけで、次の記事からCOMをまとめようと思います。