プラグインのセットアップ
3. プラグインに遅延ローダーを追加しリンカーに動的リンクを指定する
次に、ストレージエンジンプラグインの遅延ローダーのコードで、存在しないmysqld.dllを遅延読み込みできるようにします。そのためには、mysqld.dllを使うようリンカーに指示する必要があります。MySQLではcmakeビルドシステムを使用するので、CMakeLists.txtファイルを適切に編集します。以下の行を追加し、適宜編集を加えます。
SET(DELAY_LDR_DIR ../../win/delayload) SET(DELAY_LOADER ${DELAY_LDR_DIR}/mysqld ${DELAY_LDR_DIR}/mapdelayldr) SET_TARGET_PROPERTIES _ (your_storage_engine PROPERTIES LINK_FLAGS "/DELAYLOAD:mysqld.dll") TARGET_LINK_LIBRARIES(your_storage_engine ${DELAY_LOADER})
遅延ローダーについて若干補足しておきます。
遅延ローダーは、ホストプロセス(この例ではmysqld.exe)のディレクトリの.mapファイルをハッシュテーブルに読み込みます。そして、このテーブルを使用してアドレスを解決します。しかし、遅延ローダーのモジュールには、ちょっとした問題点があります。
実は、厄介なことに、VC6のリンカーとVC7以降のリンカーでは、遅延ローダーに違いがあるのです。というのも、64ビットアーキテクチャが登場したときに、遅延ローダーのデザイナが、ImgDelayDescrでポインタではなくRVAを使うべきだったことに気付いたからです。したがって、適切なリンクのコードを使用して、両方のバージョンのリンカーに対応する必要があります。Visual Studioのバージョンによって、ヘッダーファイルは異なります。遅延ローダーのコードは、この点に対応して、正しいバージョンでコンパイルされるようになっています。この遅延ローダーはVC6のリンカーでのみテストしました。新しいバージョンの遅延ローダーで動作する保証はありませんが、理屈上は正しく動くはずです。
4. リンクする
次に、cmakeでmakefileを作成し、リンクしてみましょう。私はその作業にnmakeを使用しているので、MySQLのメインディレクトリでcmake . -G "NMake Makefiles"
を実行し、その後で、nmakeコマンドでプラグインをビルドします。でも、cmakeのターゲットが異なる場合でも、うまくいくはずです。
プラグインによっては、未解決の外部シンボルが原因でリンカーエラーが生じることがあります。これは通常、プラグインで使用しているグローバルデータ変数を示しています。たとえば、私が最初にプラグインをリンクしたときには、次のような未解決シンボルのエラーが表示されました。
ha_storage.obj : error LNK2001: Nichtaufgeloestes externes Symbol _ "class Bitmap<64> const key_map_empty" _ (?key_map_empty@@3V?$Bitmap@CODE_REPLACEMENT 4EA@@@B) ha_storage.obj : error LNK2001: Nichtaufgeloestes externes Symbol _ _my_charset_bin ha_storage.obj : error LNK2001: Nichtaufgeloestes externes Symbol _ "struct charset_ info_st * system_charset_info" _ (?system_charset_info@@3PAUcharset_info_st@@A) storage.dll : fatal error LNK1120: 3 unaufgeloeste externe Verweise
5. 外部データシンボルを手動で解決する
残念ながら、このリンカーが遅延読み込みできるのは関数だけで、データシンボルは読み込めません。そこで、ちょっとした裏技が必要になります。
どうにかして、遅延ローダーが解決できるようなポインタを用意し、これらのポインタがプラグインで逆参照されるようにしなければなりません。基本的には、シンボルをポインタに変換することが必要で、そのポインタを使用するときには必ずポインタを逆参照して正しいメモリ位置を参照するようにします。このようなシンボルはMySQLのヘッダーファイルで定義されることもあるので、一連の処理は透過的な方法で行わなければなりません。幸い、このからくりは、プリプロセッサを使用して実現できます。方法は次のとおりです。
まずは、オブジェクトへのすべての参照をポインタに変換し、逆参照する必要があります。そこで、そのための#defineをソースの冒頭部分に記述します(これはMySQLのヘッダーをインクルードする前に行います)。私の例の場合、次のような記述になります。
#ifdef WIN32 #define system_charset_info *ppsystem_charset_info #define my_charset_bin *pmy_charset_bin #endif
次に、必要なMySQLのヘッダーをインクルードします。
#include "mysql_priv.h"
次に、不足しているシンボルの宣言をMySQLのソースコード内で探し、プラグインファイルに記述します(この時点でプリプロセッサの処理は完了します)。
#ifdef WIN32 const key_map key_map_empty(0); CHARSET_INFO *system_charset_info; CHARSET_INFO my_charset_bin; #endif
key_map_empty
は常に、ここで宣言したように宣言されるので、元のシンボルを参照する必要はありません。しかし、他のエクスポートについては、これを同様に行います。この時点で、関数ポインタの宣言は済んでおり、使用時に逆参照されます。もちろん、この段階ではポインタはまだ無効です。したがって、モジュールの初期化時にポインタを初期化する必要があります。そのためには、遅延ローダーの動作について知っている必要があります。ここでは、そのための関数を2つ宣言します。1つは_LoadMyMapfile
で、もう1つはGetMapProcAddress
です。
_LoadMyMapfile
が行うのは、mysqld.mapファイルを調査および解析し、すべての関数および対応するアドレスを、高速に検索できる内部ハッシュテーブルに追加するという処理です。この関数の戻り値は、モジュールハンドルを示すHMODULE
です。GetMapProcAddress
で個別の関数の正しいアドレスを取得するときには、そのハンドルを使います。_LoadMyMapfile
の戻り値が0の場合は、エラーが発生した(.mapファイルが見つからなかった)という意味です。HMODULE
は常にモジュールの読み込みアドレスで、取得するハンドルはmysqld.exeのハンドルなので、個別のインポート関数について、正しく再配置されたアドレスを判断できます。
簡単にまとめると、まず必要なのは、遅延ローダーで2つの関数を次のように宣言することです。
#ifdef WIN32 extern "C" HMODULE _LoadMyMapfile(void); extern "C" FARPROC GetMapProcAddress (HANDLE hModule, _ const char *szImport); #endif
次に、プラグインのinit_funcで、リンカーでエラーとなったすべての外部シンボルを解決します。前述のエラーメッセージでは、妙な記号の付いた名前が示されていました。その名前をここで使います。今回の例では、プラグインの初期化関数に次のコードを追加します。
#ifdef WIN32 HMODULE hMod = _LoadMyMapfile(); if (!hMod) DBUG_RETURN(1);> pmy_charset_bin = (CHARSET_INFO*)GetMapProcAddress(hMod, _ "my_charset_bin"); ppsystem_charset_info = (CHARSET_INFO**)GetMapProcAddress(hMod, _ "?system_charset_info@@3PAUcharset_info_st@@A"); #endif
以上の面倒な手順が完了したら、ストレージエンジンを再度リンクしてみてください。今度はきちんとリンクできるはずです。
おつかれさまでした。これで、動的読み込みが可能なストレージエンジンプラグインの完成です。
補足
この記事で紹介した手法は、MySQLがWin32用のプラグインAPIに修正を加えるまでの回避策にすぎません。MySQLは、MySQLdのすべての機能をDLLに組み込んで、mysqld.exeはその単なるローダーにしてくれればよいのになあと思います。そうすれば、プラグインはそのDLLを使えるようになって、この記事の回避策は無用になります。