XLW 2.1 : 苦痛なしに XLL をビルドするためのシステム 1

XLW 2.1 : 苦痛なしに XLL をビルドするためのシステム
MARK S. JOSHI
1. イントロダクション
金融工学での多くの数値計算は C++で行われる。また、ファイナンスでは多くの
数値演算を Excel 上で行う。そのため、Excel に関数を追加できるプラグインを使う
のが一般的で、これによってスプレッドシートからのインプットやスプレッドシート
へのアウトプットが簡単に行えるようになる。
これを実現するにはいくつかの方法がある。例えば、COM を使うことで実現で
きる。最も時代遅れは方法は Excel の C 言語 API を使う方法で、この原始的な多く
の方法を、マイクロソフトは Excel の初期のバージョンから変更していない。開発者
は、この安定したインターフェースに馴染み、それを包み込むさまざまなパッケージ
が開発された。このようなパッケージの一つが XLW(xlw.sourceforge.net) だ。プラ
グインを XLL として一般に知られている DLL として開発するための簡単なフレー
ムワークを XLW パッケージが提供する。
XLW2.1 パッケージは XLW の拡張で、その基本フレームワークは同じだが、ユー
ザビリティを向上させるためのさまざまな変更が追加されている。そのなかで最も大
きい変更は C++で登録やデータ変換を行うコードを書くのではなく、ヘッダーファ
イルを読み込んで登録やデータ変換のコードを含んだ C++のソースファイルを吐き
出すパーサが書かれたことだ。これは、C++のルーチンを XLL に変換するための特
別な知識をユーザが知る必要はない、ということだ。
さらに、元の XLW は Visual Studio6.0 で動作するように書かれていたが、Visual
Studio 6.0 のサポートは打ち切られた。このバージョンは Visual Studio7.1 と 8.0、9.0、
DevCpp の 4.9.9.2 のプロジェクトファイルが同梱されている。DevCpp は MinGW
g++コンパイラ向けのフリーの IDE で、XLL は g++を使ってビルドされる。
その他のいくつかのマイナーな変更が行われている。文字列とそれに類似するオ
ブジェクトの例外が発生した場合に、それらを捕まえて Excel に返す。引数を正し
い型へ変換するのに失敗した場合に’#VALUE’ ではなく失敗した引数を識別できる
文字列を返す。新しいデータ型である MyMatrix と CellMatrix が含まれた。特に、
CellMatrix は、数や文字列、bool やエラーコードなどの、あらゆるセル型のテーブ
ルを渡すことができる。また、すでに存在するデータ型から作成可能な、カスタマイ
ズされたデータ型を簡単に追加できるようになった。
2. 使用方法
XLW2.1 を使う前に XLW2.1 のライブラリとインターフェースジェネレータをビ
ルドしなくてはならない。インターフェースジェネレータのプロジェクトは使用する
コンパイラに応じたディレクトリの中から見つけることができる。
• DevCpp の場合は、build/devcpp フォルダの InterfaceGenerator.dev プロジェ
クトだ
Date: December 26, 2008.
Key words and phrases. Excel プラグイン, xlw, パーサ.
1
2
MARK S. JOSHI
• Visual Studio の場合は、ソリューションファイル build/vc?/xlw.sln(?はコ
ンパイラのバージョンによって異なり、7 か 8 か 9 だ)を開いて見つかる、
InterfaceGenerator プロジェクトだ
このプロジェクトをビルドすると InterfaceGenerator.exe という名前のコンソールア
プリケーションが生成される。あるコンパイラとそれとは異なるコンパイラでビルド
された、このアプリケーションのバージョンをトラブルなく使うことができるかもし
れないことに注意。
次に、XLW2.1 ライブラリをビルドする必要がある。プロジェクトファイルはコ
ンソールアプリケーションのプロジェクトファイルと同じ場所に置いていある。
• DevCpp 向けのプロジェクトファイルは xlw.dev で、このプロジェクトから
生成されるライブラリファイルは lib/xlw-gcc-s-3 0 0.a だ。
• Visual Studio 向けのプロジェクトは xlw で、このプロジェクト(リリース用
のコンフィギュレーション)から生成されるライブラリファイルは lib/xlwvc?-mt-3 0 0.lib だ。
それぞれのコンパイラ用に ExampleAutogenerated という名前のサンプルプロジェ
クトがあり、XLL にエクスポートされる関数が定義されている。それぞれのプロジェ
クトにはヘッダファイル Tesh.h とソースファイル Test.cpp、インターフェースファイ
ルである xlwTest.cpp が含まれている。これらのファイルは example/autogenerated
以下に含まれており、ペイオフを定義するいくつかのファイルとサンプルのスプレッ
ドシートも見つかるだろう。
xlwTest.cpp は自動的に生成されるインターフェースファイルである。InterfaceGenerator.exe をパスが通ったディレクトリに置くか、Test.h と同じディレクトに置
いてコマンドプロンプトで “InterfaceGenerator Test.h” と打てば再度インターフェー
スファイルを生成できる。
Visual Studio のソリューションは RunInterfaceGenerator という NMAKE のプ
ロジェクトファイルを含んでいる。これは InterfaceGenerator のインプットファイル
の変更を検知し、必要ならば自動的に再実行を行う。
XLL プロジェクトをビルドすると XLL が作られ、MyTestLibrary というライブ
ラリに新たな関数が作られ、これを Excel で開くことができる。
自分で書いた関数を使うために XLW2.1 を使う場合、C++の関数を最初に書い
て、インターフェースのコードを除いてコンパイルおよびビルドを行う。Excel に
エクスポートする関数のみをヘッダファイルに含め、InterfaceGenerator をヘッダ
ファイルに対して実行する。MyFile.h というファイル名のヘッダファイルの場合、
xlwMyFile.cpp というファイル名の新たなファイルが作られる。そして、この生成
された新しいファイルをプロジェクトに追加する。InterfaceGenerator は実行対象の
ヘッダファイル内のすべてのプリプロセッサコマンドを読み飛ばし、クラスや関数の
定義が書かれている場合にはエラーを投げるので注意が必要だ。また、不明なデータ
型が見つかったときにもエラーを投げる。
Excel の関数ウィザード向けの情報は、ヘッダファイルのコメントと変数の引数名
から抽出されるため、引数には引数名が必要だ。コメントはそれぞれの引数のすぐ後
に書こう。このコメントが関数ウィザードで引数を入力する際に表示される。関数の
概要説明は、関数の戻り値の型の宣言と関数名の間に記述しよう。
引数は参照渡し、値渡し両方可能で、const、非 const どちらでもよい。
(実際には、
これらの違いはインターフェースファイルのコーディングには何の影響も与えない)
一度インターフェースファイルをプロジェクトに追加しておけば、単にプロジェク
トをビルドして、出力される XLL ファイルを Excel を開くだけでよい。また、一つ
の XLL プロジェクトに複数のインターフェースファイルを追加することも可能だ。
新しい XLL プロジェクトを作成したい場合、以下のように行おう。
XLW 2,1
3
•
•
•
•
XLW フォルダを含むフォルダをインクルードパスに追加する
XLW ライブラリファイルを含むフォルダをリンクパスに追加する
XLW ライブラリファイルをリンクするファイルのリストに追加する
DevCpp の場合、プロジェクトを DLL プロジェクトにしなくてはならない
(DLL プロジェクトを作成して、DevCpp によって生成されたファイルを削
除し、自分のファイルを追加する)
• Visual Studio の場合、プロジェクトのコード生成の設定をマルチスレッド
DLL にしなくてはならない
• 出力ファイルの名前を MyName.xll に変更する
Visual Studio 8.0 Express を使っている場合、さらに以下の作業が必要だ。
•
•
•
•
Microsoft SDK をインストールする。
インクルードディレクトリに SDK のディレクトリを含める。
次のライブラリに対してリンクを行う: odbc32.lib odbccp32.lib User32.lib
XLW ヘッダファイルにあるディレクティブは自動的に、使用するコンパイラ
に応じた xlw ライブラリファイルの依存関係を生成する。例:lib/xlw-vc80mt-3 0 0.lib
• SDK ライブラリのディレクトリが、ライブラリディレクトリを検索するディ
レクトリのリストに含まれるようにする
• 現存するコードを使って新しいプロジェクトを作らなくてはならない場合、
後で DLL プロジェクトのオプションをオンにする(これは新しいコードか
ら新しいプロジェクトを作る場合にはオプションではない)
何人かのユーザは、InterfaceGenerator を自動的に実行するために、カスタムビル
ドステップを追加しているかもしれないため、注意が必要だ。
3. visual studio 8.0 express ユーザのためのステップ・バイ・ステップガイド
これはあるユーザによって書かれたもので、XLW を使って新しいプロジェクトを
作る方法について詳しく書かれている。
• 最初に SDK をインストールし、以下の指示に従おう
• InterfaceGenerator プロジェクトをフォルダから直接開くことができ、プロ
ジェクトをビルドすると InterfaceGenerator.exe ができる
• xlwLib をフォルダから直接開いてビルドする
• 今あるコードを使って、新しいプロジェクトを作り、DLL プロジェクトを選
択する
• これで関数の追加や TestFiles フォルダにあるファイルを追加できる
• C++ファイルを追加すると、C++メニューがプロジェクトのプロパティに
現れる。xlw フォルダが含まれるフォルダがインクルードディレクトリのリ
ストに加える
• ファイルを加える場合には、InterfaceGenerator.exe を同じフォルダに入れ
て、DOS コマンドウィンドウでそのフォルダに移動し、InterfaceGenerator
myfile.h を実行すると xlwmyfile.cpp が生成されるので、これをプロジェク
トに追加する
• SDK フォルダを Project Properties C/C++ Include メニューに追加する
• XLW フォルダを Project Properties C/C++ Include メニューに追加する
• SDK/Lib フォルダを Project Properties Linker Include libraries メニューに
追加する
• プロジェクトをデバッグモードで使っている場合、xlwLib/Debug フォルダ
を Project Properties Linker Include libraries メニューに追加する
4
MARK S. JOSHI
• リンカのインプットメニュに以下のライブラリを追加しなくてはならない:
odbc32.lib odbccp32.lib User32.lib デバッグモードで実行しているならば
xlwLib-Debug.lib も必要
• XLL ファイルの名前が何であったとしても、リンカの出力メニューを Debug/FDFunctions.xll に変更しなくてはならない
• プロジェクトを走らせて Debug フォルダを見ると、XLL ファイルが見つかる
• マクロのセキュリティレベルが高すぎると何も見えないので、中か低にしよ
う。そうすれば挿入されたメニューを使ったり、ライブラリや関数を見つけ
られるだろう。
4. 基本データ型
Excel にエクスポートされる関数はインターフェースジェネレータでサポートされ
ているデータ型しか使用できない。サポートされているデータ型には、double、short、
NEMatrix、MyMatrix、MyArray、CellMatrix、string、std::string、and bool など
の基本データ型と、int、unsigned long、ArgumentList、DoubleOrNothing、PayOff
などの拡張データ型がある。
XLOPER 型には short と double の 2 つの数値型があり、その他の数値型は double
を経由して変換されることに注意しよう。
MyMatrix クラスは MyContainers.h の中で MJMatrix に対して typedef すること
で定義されている。typedef 先のクラスを変更することで自分の好きな行列型を使う
ことができる。行列クラスは row() と columns() メソッド、行数と列数を引数に取る
コンストラクタ、a[i][j] のように行列の要素にアクセスできるインターフェースをサ
ポートしなくてはならない。もし使おうとしている行列クラスが operator() による
行列の要素へのアクセスしかサポートしていないならば、USE PARENTHESES マ
クロを定義しよう。
NEMatrix クラスは MyMatrix に typedef されたクラスだが、引数をこの型とし
て宣言すると、引数が空でない数の行列でない場合には関数は呼ばれない(その場
合、’#VALUE’ が返ってくる)。巨大な matrix を扱っている場合、データ型をシン
プルにした方が(K 型を P 型の変わりに使う)安定するだろう。
MyArray クラスも MyContainers.h で typedef によって定義されている。デフォル
トでは std::vector に typedef されている。MyArray クラスは size() メソッドと配列
のサイズを引数に取るコンストラクタ、operator[] が定義されていなくてはならない。
CellMatrix は CellMatrix.h で宣言された新しいクラスだ。これは Excel のセルの
値のテーブルの概念を抽象化したものだ。これによって、それぞれの行列のエント
リーは文字列、数、bool、エラー値や空の値を持つことができ、数値や文字の混在し
たテーブルを扱うことができる。CellMatrix への変換は、エラーコードも許可され
ているため、仮想的に、決して失敗しない。
std::string 型も string 型も両方使える。これらは同じクラスで、std ネームスペー
スが using によってすでに宣言されているか否かが単に異なるだけだ。
5. 拡張データ型
XLW2.1 は独自のデータ型を扱いやすいように設計されている。唯一の制約は、基
本型から構築可能なデータ型を引数として、新しいデータ型を作成する関数(やメ
ソッド)が存在しなくてはならない、ということだ。この目的のために、コンスト
ラクターは関数と等価になっている。例えば以下のように、TypeRegistrations.cpp
ファイルに単に宣言を追加すればよい。
XLW 2,1
TypeRegistry::Helper arglistreg("ArgumentList",
"CellMatrix",
"ArgumentList",
false,
true,
"",
"<xlw/ArgList.h>"
5
//
//
//
//
//
新しい型
古い型
コンバータ名
メソッド?
識別子を取るか?
// キーなし
// 強制的にインク
ルードするファイル
);
TypeRegistry::Helper payoffreg("Wrapper<PayOff>",
"ArgumentList",
"GetFromFactory<PayOff>",
false,
false,
"" ,
"<xlw/ArgListFactory.h>"
);
//
//
//
//
//
新しい型
古い型
コンバータ名
メソッド?
識別子を取るか?
// キーなし
最初の引数は新しい型の識別子だ。
2 番目の引数は新しい型が構築される元となる型だ。
3 番目の引数は古い型から新しい型を構築するための関数またはメソッドだ。
4 番目の引数の bool は変換関数が、古いクラスのメソッドか、単なる関数や古い
クラスのオブジェクトを一つ引数としてとるコンストラクターなのかを特定するも
のだ。
2 番目の bool は、エラーの際の識別子を表現する文字列の、2 番目の引数を引数
としてとる変換メソッドや関数か否かを指定する。これは引数があいまいな複雑な関
数を扱う場合に、とても便利だ。
一般に、基本型を定義するときなどの、XlfOper から変換する場合には、Excel に
型を伝えるキーが必要となる。典型的なのは R か P で、double は B として渡され、
空でない行列は K として渡される。
最後の引数は追加の#includes を.cpp のインターフェースファイルに加えること
ができる。これによって、変換関数を利用可能にすることができる。
新しい型を他の新しい型から定義する。この最大の深さは 26 で、これによって間
違ってループを作ってしまっても、パーサがこの上限を超えたときに教えてくれる。
説明のために追加された 3 つの主なデータ型は DoubleOrNothing と ArgumentList、
Wrapper<PayOff>だ。
DoubleOrNothing クラスによって、渡された数と空の引数を区別することができ
る。そのため、渡された数と、引数が空の場合のデフォルトの値を選ぶことができる。
引数リストはセルのテーブルをとり、それを引数のコレクションの中に返す。こ
れによって、一つの関数引数の中に、引数の数を変数として渡すことができる。テー
ブルの右上は PayOff のように、ストラクチャーの名前でなくてはならない。テーブ
ルの中では、数や文字列、配列、行列、セルやリストを使うことができる。これらの
一つ一つは、名前とその下にデータがある。配列の場合、配列のサイズは名前の下に
6
MARK S. JOSHI
直接指定しよう。行列やセル、リストの場合は、行と列の数を直接下に指定しよう。
型のセルは単にセルの値の行列だ。型の配列は ArgumentList だ。ArgumentList の
中で ArgumentList を使うと、デコレーションやコンポジットパターンに非常に便利
だ。ArgumentLists に渡されるものはすべて、小文字に変換されることに注意が必
要だ。
ArgumentLists はファクトリと一緒に使うと特に強力だ。ArgumentLists をテン
プレート引数として取る、テンプレート化されたファクトリを提供しているため、任
意の型に対して使うことができる。唯一の制約は、継承された型は ArgumentLists
を引数にとるコンストラクタを持ち、それらは共通の基底クラスを持つことだ。この
ことを PayOff クラスで説明している。
ファクトリは基底クラスへの生のポインタを返すため、これをすぐにスマートポイ
ンタに変換しよう(もしそうしなければ、メモリリークが起きてしまう)。これには
多態的なコピーポインタである Wrapper を使うとよい。替わりに boost::shared ptr
や std::auto ptr も使えるかもしれない。新しいデータ型は
Wrapper< PayOff >
となり、これは所有権をもち、適切なタイミングで delete を呼び出すことを保証
してくれる。
このクラスの使用例は examples/autogenerated/Example.xls のスプレッドシード
にある。
6. xlw コマンド
デフォルトの Excel のライブラリ名はヘッダファイル名だ。ヘッダファイルで以
下のように記述することで変更することが可能だ。
//<xlw:libraryname=MyTestLibrary
ヘッダファイルに含まれるすべての関数は、すべて、最後の libraryname コマン
ドで指定された、同じライブラリ名になるため注意が必要だ。
volatile な関数として宣言したい場合、以下のようにすることで可能だ。
double // system clock
//<xlw:volatile
SystemTime(DoubleOrNothing ticksPerSecond // number to divide by
);
volatile な関数は、引数が典型的なランダムな数や時刻でない場合でさえも、その
戻り値が変わりうる関数である。
また、以下のコマンドを同じ場所に挿入することで、時間依存関数にできる(も
ちろん、volatile な関数も時間依存関数にすることができ、この 2 つのコマンドの順
序は重要ではない)。
//<xlw:time
XLW 2,1
7
7. インターフェースファイル
インターフェースファイルを覗く必要はないが、直接編集することも可能だ。最初に
DummyFunction が無名ネームスペースに宣言されている。この関数は xlAutoOpen
と xlAutoClose を参照し、XLL ファイルにこの 2 つの関数を強制的に含める。これ
らの関数は Excel に関数を登録する際に実行されるため、必須の関数だ。
以下のような 1 行がある。
const char* LibraryName = "MyTestLibrary";
これによって、Excel 関数ウィザードで表示されるライブラリ名が決まる。この行は
無名ネームスペースで囲まれているため、複数のインターフェースファイルを同時に
扱うことができる。
それぞれの関数は 2 つの部分から構成される。前者は情報の登録で、後者は、Excel
と呼ばれる関数の間で呼ばれるラッパーである。
以下が登録情報の例だ。
namespace
{
XLRegistration::Arg
ConcatArgs[]=
{
{ "str1"," first string "},
{ "str2","second string "}
};
XLRegistration::XLFunctionRegistrationHelper
registerConcat("xlConcat",
"Concat",
" Concatenates two strings ",
LibraryName,
ConcatArgs,
"RR");
}
このコードは、他のリンケージに影響を与えないために、無名ネームスペースに
配置される。引数は最初のパートで、引数名と引数の説明と共に宣言されている。
2 番目のパートでは、グローバル変数が宣言されている。このグローバル変数の
作成により、関数はグローバルなシングルトンオブジェクトによって登録され、これ
は Excel に登録される。このアプローチによって、複数の分割されたファイルにまた
がって関数の登録が可能となる。
コンストラクタに渡される情報は、インターフェースファイルにある C++の関数
名と、Excel 関数としての名前、Excel でのライブラリ名、上記で宣言された引数お
よびその型である。型は以下のようなコードで表現される。
• R - XLOPER の参照渡し (LPXLOPER)
• P - OPER の参照渡し
• B - double
8
MARK S. JOSHI
• K - 浮動小数点の配列
XLW2.1 では P と B、K、R 型のみが使われている。P は CellMatrix と MyMatrix
に使われている。K は NEMatrix に使われている。B は double に使われている。R
はすべての基本型に使われる。bool のような型ははじめに LPXLOPER として渡さ
れた後、正しいデータ型に変換される。
インターフェース関数の定義の例は以下のようになる。
extern "C"
{
LPXLOPER EXCEL_EXPORT
xlConcat(
LPXLOPER xlstr1_,
LPXLOPER xlstr2_)
{
EXCEL_BEGIN;
if (XlfExcel::Instance().IsCalledByFuncWiz())
return XlfOper(true);
XlfOper xlstr1(xlstr1_);
std::string str1(xlstr1.AsString("str1"));
XlfOper xlstr2(xlstr2_);
std::string str2(xlstr2.AsString("str2"));
std::string result(
Concat(
str1,
str2)
);
return XlfOper(result);
EXCEL_END
}
}
extern ‘C’ コマンドは C 言語 API を使う場合必須で、C リンケージを使わなくて
はならない
関数の戻り値の型は常に、ポリモーフィックなデータ型の LPXLOPER で、妨害
にはならない。EXCEL EXPORT は以下のようなマクロだ。
#define EXCEL_EXPORT __declspec(dllexport)
これは関数が DLL をエクスポートしていることを意味し、動的にリンクされる可能
性がある。
型は LPXLOPER として渡され、XlfOpers や他の型ではないことに注意が必要
だ。XlfOpers は MinGW コンパイラを使った場合に POD オブジェクトではなくなっ
てしまい、クラッシュしてしまうため、使われていない。
XLW 2,1
9
EXCEL BEGIN マクロと EXCEL END マクロはすべての関数に対して共通の開
始情報と終了情報を含んでる。特に、EXCEL END は Excel に情報を返すための共
通データ型へのキャッチを含んでいる。
このルーチンは関数が関数ウィザードから呼ばれたか否かをチェックする。関数
ウィザードから呼ばれた場合、すぐに Excel に処理を戻す。これによって、データを
入力する際に、時間のかかる演算が呼ばれるのを避けることができる。
それぞれの引数は変換される。最初は XlfOper に変換され、その後 As メソッド
を呼び出すことで正しい型へと変換される。文字列を As メソッドに渡すことで、問
題のある引数を識別するためにエラーを投げることができる。拡張型を使う場合、こ
こに変換文字列が入る。
一度引数が変換されたら、元の関数が呼ばれ、結果が保存される。Excel に結果を
返すために、XlfOper に変換され、LPXLOPER として返される。
8. インターフェースジェネレータ
インターフェースジェネレータはシンプルな C++で書かれている。一つの引数を
取る、コンソールアプリケーションだ。出力ファイル名はオプション引数で、デフォ
ルトでは入力ファイル名に対して xlw を先頭に付け、.cpp を末尾につけたファイル
名となる。
インターフェースジェネレータは最初にファイルを読み込んで、利便性のために
文字列の vector に格納している。そして、これらはトークンに分解される。それぞ
れのトークンは、識別子やプリプロセッサ命令、コメントやデリミタだ。
const とアンパサンドに対応するトークンは削除されるため、これらはインター
フェース関数のコーディングには影響を与えない。この段階で、unsigned 識別子も
同時に処理される。
その後、引数名と型を伴った引数リストと、関数のリストに変換される。
そして、すべての型を識別し、変換関数を見つける。
さらに、出力ファイルが vector に書き出され、そして、ファイルに書き出される。
9. トラブルシューティング
もし XLL のビルドが可能でも、関数の登録ができない場合、XLL に関する一般
的な共通の問題がいくつか存在する。
もし、まったく何も起こらない場合、マクロのセキュリティ設定をチェックしよ
う。初期設定ではマクロを含むファイルを無視するようになっている。合理的なレベ
ルのセキュリティ設定を行うために、マクロを有効にするか尋ねられるだろう。もし
これが起こらない場合、セキュリティレベルが高すぎる(もしくは低すぎる)。
もし理解不能なフォーマットエラーが発生した場合、以下の可能性が考えられる。
• DLL がマシンに存在しない。これはあるマシン上で VC を使ってコンパイル
し、XLL を他のマシンに移動させた場合によく起こる。これは、DLL が必
要にならないように、コンパイラに対して対策を行うか、必要な DLL を新し
いマシンにコピーすることで解決できる(もしくは DevCpp に切り替える)。
• auto open と auto close の関数のエクスポートに失敗した。
dumpbin ユーティリティで、正しい関数がエクスポートされているかチェックす
ることができる。もしこの文書の説明に従っているならば、これは問題ではないは
ずだ。
Centre for Actuarial Studies, Department of Economics, University of Melbourne,
Victoria 3010, Australia
E-mail address: [email protected]