演算子の追加
では本題、「re-buildなしに機能を拡張」しますか。ソリューションに新しいプロジェクト:CLRクラスライブラリ"ExtendedOpertaions"を起こし、プロジェクト・プロパティの共通プロパティでSimpleCalculatorとSystem.ComponentModel.Compositionの参照を追加します。
あとはSimpleCalculatorで定義したOperations.cppとまったく同じ体裁で乗算/除算を定義します。
#include "stdafx.h"
namespace ExtendedOperations {
using namespace System::ComponentModel::Composition;
using namespace SimpleCalculator;
[Export(IOperation::typeid)]
[ExportMetadata(L"Symbol", L"*")]
ref class Multiple : IOperation {
public:
virtual int Operate(int left, int right) {
return left * right;
}
};
[Export(IOperation::typeid)]
[ExportMetadata(L"Symbol", L"/")]
ref class Subtract : IOperation {
public:
virtual int Operate(int left, int right) {
return left / right;
}
};
}
……おっとゴメンナサイ。拡張用アセンブリ(ExtendedOperations.dll)を読み込む部分を忘れてました。SimpleCalculator::CalculatorFormのコンストラクタに追加しなくちゃ。
CalculatorForm::CalculatorForm() {
InitializeComponent();
// Import/Export カタログをつくる
auto catalog = gcnew AggregateCatalog();
// まずは自分自身のアセンブリから
catalog->Catalogs->Add(gcnew AssemblyCatalog(CalculatorForm::typeid->Assembly));
// そして自分自身の置かれたディレクトリから見つけてくる (追加ここから)
String^ myLocation = System::IO::Path::GetDirectoryName(
System::Reflection::MethodBase::GetCurrentMethod()
->DeclaringType->Assembly->Location);
catalog->Catalogs->Add(gcnew DirectoryCatalog(myLocation));
// (追加ここまで)
// カタログから作られたコンテナを基にImport/Exportを結びつける
AttributedModelServices::ComposeParts(gcnew CompositionContainer(catalog), this);
// 得られた演算子(Symbol)をComboBoxに追加する
for each ( String^ symbol in calculator_->Symbols() ) {
cbxOpr->Items->Add(symbol);
}
cbxOpr->SelectedIndex = 0;
}
これにより、SimpleCalculator.exeの置かれたディレクトリにあるアセンブリがすべて読み込まれます。
ExtendedOperationsをビルドしたのち、SimpleCalculatorを実行するとComboBoxに「*」と「/」が追加されています。
当然ながら .NETアセンブリであれば、C++/CLIに限らずC#やVBで拡張しても構いません。C#クラスライブラリ・プロジェクトを起こし、
using System.ComponentModel.Composition;
using SimpleCalculator;
namespace ExtendedOperations {
[Export(typeof(IOperation))]
[ExportMetadata("Symbol", "%")]
class Modulus : IOperation {
public int Operate(int left, int right) {
return left % right;
}
};
[Export(typeof(IOperation))]
[ExportMetadata("Symbol", "MIN")]
class Minimum : IOperation {
public int Operate(int left, int right) {
return left < right ? left : right;
}
};
[Export(typeof(IOperation))]
[ExportMetadata("Symbol", "MAX")]
class Maxumum : IOperation {
public int Operate(int left, int right) {
return left > right ? left : right;
}
};
}
できたDLLをSimpleCalculator.exeと同じディレクトリに置けば……ほらね。
……面白いですねぇ、IOerationを定義したAddやSubtractなどは他のどこからも参照されず、ただ定義しExportしただけです。MEFはそれを手掛かりに動的に(実行時に)探し出し、インスタンスを生成してImportした受け皿に乗っけてくれるんですね。アセンブリを配置するだけで機能拡張できるのは、アプリケーションの提供者/利用者の双方に大きなメリットですよね。

