読者です 読者をやめる 読者になる 読者になる

GetProcAddress を書く手間を減らすあれそれ

なんか唐突に思い出したのですが,GetProcAddress やるときに関数の型とか鬱陶しいですねというお話.

新しいWindowsでしか実行できない関数を使うときの1つの手段として、GetProcAdressがあります。C++0xのdecltypeは、そのやり方を大きく変えます。

HMODULE hmodKernel =
    GetModuleHandle(TEXT("kernel32"));

typedef decltype(CancelSynchronousIo)* PCancelSynchronousIo;
PCancelSynchronousIo pCancelSynchronousIo =
    reinterpret_cast<PCancelSynchronousIo>(
        GetProcAddress(hmodKernel, "CancelSynchronousIo"));

今までだったら、MSDNライブラリあるいはヘッダファイルを直接見るなどして、typedef BOOL (WINAPI *PCancelSynchronousIo)(HANDLE hThread);とtypedefを書く必要がありました。ところが、decltypeを使うと、このとおり関数名から直接、型を取り出せます。

あと、result_type (__stdcall *fn_ptr)(…)のようなstdcall関数へのポインタの書き方も、よく忘れるので毎度調べる羽目になります。よその掲示板でも時々この書き方についての質問を見るという印象を持っています。これから解放されるのも嬉しい点です。

boost とかほとんど知らんのですが,なんか Visual C++ 2008 とか Visual C++ 2005 とかでも書けていたようですごいですね.コンパイル時間とか等価交換されてる気もしますが.

#include <windows.h>
#include <tchar.h>
#include <typeinfo>

template <typename FunctionType>
class ScopedDllFunction {
 public:
  typedef FunctionType *FunctionPtrType;
  ScopedDllFunction(const TCHAR *dll_name, const char *function_name)
    : function_ptr_(NULL),
      module_(NULL) {
    module_ = ::LoadLibraryEx(dll_name, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
    function_ptr_ = reinterpret_cast<FunctionPtrType>(
        ::GetProcAddress(module_, function_name));
  }
  ~ScopedDllFunction() {
    if (module_) {
      ::FreeLibrary(module_);
    }
  }
  operator FunctionPtrType() const {
    return function_ptr_;
  }

 private:
  // noncopyable
  ScopedDllFunction(const ScopedDllFunction &);
  const ScopedDllFunction & operator =(const ScopedDllFunction &);

  HMODULE module_;
  FunctionPtrType function_ptr_;
};

#if _MSC_VER >= 1600
#define DEFINE_SCOPED_DLL_FUNCTION(instance_name, dll, function) \
  ScopedDllFunction<decltype(function)> instance_name((dll), (#function));
#else
#include <boost/typeof/typeof.hpp>
#define DEFINE_SCOPED_DLL_FUNCTION(instance_name, dll, function) \
  ScopedDllFunction<BOOST_TYPEOF(function)> instance_name((dll), (#function));
#endif

#include <dwmapi.h>

// API の型を見てるだけなのでリンク不要
// #pragma comment (lib, "dwmapi.lib")

int _tmain() {
  DEFINE_SCOPED_DLL_FUNCTION(MyDwmIsCompositionEnabled,
                             _T("dwmapi.dll"),  // ほんとはフルパスで指定するべき
                             DwmIsCompositionEnabled);

  if (MyDwmIsCompositionEnabled) {
    BOOL enabled = FALSE;
    HRESULT result = MyDwmIsCompositionEnabled(&enabled);
  }

  return 0;
}

現実的には,上記のように LoadLibrary や GetModuleHandleEx で DLL のリファレンスカウントを増やしておかないと危険なケースもあります.GetProcAdress と Loader Lock 絡みの悩みはセットで現れることが多く,そっちで悩んでいる時間の方もトータルではばかになりません.本当の (デッドロックとの) 戦いはここから始まります.
あと余談ですが, CreateWindow "関数" に対して decltype や BOOST_TYPEOF が使えない理由とか良い感じに脱力できていいですね.さらに気になった人は以下のコードの実行結果もご覧あれ.

void *ptr = ::GetProcAddress(::GetModuleHandleA("user32.dll"), "CreateWindowA");