現在実行中のプログラムがどこのモジュールに所属するか調べる

で、なぜ、そんなことをやっているかというと、スタティックリンクライブラリのある関数が、リンクされた結果できたモジュールを実行時に検出するという問題を解決するためだ。

何を書いているのか分かり難いな。

まぁ、会社の仕事なんだけけど。
うちはSDKを売っている会社で、さらにそのSDKがスタティックリンクライブラリで、ユーザーの会社が、リンクしてDLLやら、EXEにする。そのときに、その実行形式に対してライセンスチェックをしたいという要求。

あんまり考えずに実装すると、

WCHAR modulePath[MAX_PATH];
GetModuleFileName(NULL, modulePath, MAX_PATH);

なんだろうけど、これだとEXEのパスは取れても、実際にSDKをつかって作成したモジュールの名前は取れないかもしれない。たとえば、Photoshopのプラグインなどを作成した場合に、このコードだと、Photoshopの実行ファイルのパスを返してしまう。

プロセスにロードされたモジュール一覧は、CreateToolhelp32Snapshot, Module32First, Module32Nextで取得できる。Module32First/Module32Nextは、MODULEENTRY32という構造体に書くモジュールの情報を返してくれるが、この構造体の、modBaseAddr, modBaseSizeが、モジュールのロードされたアドレス、そしてサイズを示している。従って、

CString GetModuleNameFromAddress(void* address)
{
  HANDLE hSnapshot = CreateToolhelp32Snapshot(
    TH32CS_SNAPMODULE, GetCurrentProcessId());
  
  MODULEENTRY32W me;
  ZeroMemory(&me, sizeof(me));
  me.dwSize = sizeof(me);
  if(Module32FirstW(hSnapshot, &me))
  {
    do
    {
      if(me.modBaseAddr <= address &&
        address < me.modBaseAddr + me.modBaseSize)
      {
        CloseHandle(hSnapshot);
        return me.szExePath;
      }
    }
    while(Module32NextW(hSnapshot, &me));
  }
  CloseHandle(hSnapshot);
  return L"";
}

といった関数を書けば、任意のアドレスに対するモジュールを知ることができる。CreateToolhelp32Snapshotが返すモジュール一覧が十中八九、ソートされているという事実を加味すれば、二分探索もできるけど、別にそこまでしなくて良いだろう。

CString moduleName = GetModuleNameFromAddress(CloseHandle);

とかすると、C:\Windows\system32\kernel32.dllと返ってくる。しかしながら、

CString moduleName = GetModuleNameFromAddress(CreateToolhelp32Snapshot);

とすると、kernel32.dllとは返ってこない。すべてlib内で実装されているのか、それとも、実はAPIではないのか。このあたりは非常に悩ましい。Rtl系のAPIではないので、一応、kernel32.libからロードされていると思うのだが・・・。と思って、

HMODULE hKernel32 = LoadLibrary("kernel32.dll");
CString moduleName = GetModuleNameFromAddress(GetProcAddress(hKernel32, "CreateToolhelp32Snapshot"));
CloseHandle(hKernel32);

としてみると、ちゃんとkernel32.dllと返ってくる。本筋とは違うので、リロケーションのためのスタブでも入っているんだろう。と思うことにした。

ということで本来の作業に戻ると、

CString moduleName = GetModuleNameFromAddress(_ReturnAddress());

で現在の位置がどのモジュールに属しているかわかるはず。
一応、目的は達成できた・・・と思いきや、

1>c:\program files\microsoft visual studio 8\vc\include\intrin.h(912) : error C2733: second C linkage of overloaded function '_interlockedbittestandset' not allowed
1>  c:\program files\microsoft visual studio 8\vc\include\intrin.h(912) : see declaration of '_interlockedbittestandset'
1>  c:\program files\microsoft visual studio 8\vc\include\intrin.h(913) : error C2733: second C linkage of overloaded function '_interlockedbittestandreset' not allowed
1>  c:\program files\microsoft visual studio 8\vc\include\intrin.h(913) : see declaration of '_interlockedbittestandreset'

という謎のエラー群が。どうやら、_interlockedbittestandsetが何度も定義されているのが問題らしい。うーん。ググったけど、同じ症状の人が一人いることがわかったぐらい。VS2005 + Windows SDK RTMなんて、普通の環境だと思うのだけれどもなぁ。

組み込み関数なのでちょっと不安だなぁと思いながら思い切って、

#include <intrin.h>

extern "C" void * _ReturnAddress(void);

と置き換えてみる。ビルドが通った。少なくともリンクが正常にできたわけで、ちゃんと認識されているんだろう。実際、ちゃんと動作する。