std::unique_ptrでHDCとHGLRCを扱う。

ゲームフレームワーク的なものを作る。番外編1。
windowsプログラミングにおいて、HDCなどのハンドルの管理は非常に面倒ですよね?

    HWND hwnd = ...;

    HDC hdc = GetDC( hwnd );
    
    GDIを使った描画など;

    ReleaseDC( hwnd, hdc );     //使い終わったらReleaseするのが面倒!

std::unique_ptrを使う。

バイスコンテキストクラス的なものを作って、デストラクタでReleaseすれば良いわけですが、正直デストラクタも書きたくないのです。

こちらをお手本にして、std::unique_ptrを使ってみましょう。

deleterでWindowDCを解放する。
/**
 *  WindowDCを解放するためのdeleter
 */
struct dc_deleter
{
    typedef HDC pointer;

    void operator()( HDC hdc )const
    {
        ReleaseDC( hwnd, hdc );
    }

    dc_deleter( HWND hwnd ) : hwnd( hwnd ){}
    HWND hwnd;
};
記述が長くなるのでtypedefしておくと便利。
/**
 *  HDCのunique_ptr
 */
typedef std::unique_ptr< HDC, dc_deleter > dc_handle;
使い方
    {
        dc_handle dc( ::GetDC( hwnd ), deleter( hwnd ) );

        GDIなどで描画( dc.get(), ... );
    }//スコープから抜ける時にdeleterで解放される

Windowクラスにデバイスコンテキスト取得メソッドを追加。

class Window
{
/*略*/
public:
    /**
     *  WinodwDC取得
     */
    dc_handle get_dc()const{ reutrn dc_handle( ::GetDC( hwnd ), deleter( hwnd ) ); }
};
使い方
    Window* window = ...;   //既にウィンドウは生成済み

    {
        dc_handle dc( window->get_dc() );

        GDIなどで描画( dc.get(), ... );
    }//スコープから抜ける時にdeleterで解放される

OpenGLレンダリングコンテキストをstd::unique_ptrで管理する。

上記のウィンドウのデバイスコンテキストと同様に、レンダリングコンテキストもstd::unique_ptrに切り替えてみます。

deleterを作る
/**
 *  レンダリングコンテキストを破棄するためのdeleter
 */
struct glrc_deleter
{
    typedef HGLRC pointer;

    void operator()( HGLRC rc )const;
    {
        ::wglMakeCurrent( dc, nullptr );
        ::wglDeleteContext( rc );
    }

    glrc_deleter( HDC dc ) : dc( dc ){}
    HDC dc;
};

/**
 *  レンダリングコンテキストハンドル
 */
typedef std::unique_ptr< HGLRC, glrc_deleter > glrc_handle;
OpenGLの初期化
/**
 *  ViewのOpenGL実装
 */
class OpenGLView
    : public View
{
public:
    /**
     *  コンストラクタ
     */
    OpenGLView( const String& title, const Sizei& client_size );

private:
    /**
     *  OpenGLレンダリングコンテキストの生成
     */
    glrc_handle create_rendering_context( const dc_handle& dc );

private:
    dc_handle dc;
    glrc_handle rc;
};
/**
 *  コンストラクタ
 *
 *  @param  title       ウィンドウのタイトル
 *  @param  client_size ウィンドウのクライアントサイズ
 */
OpenGLView::OpenGLView( const String& title, const Sizei& client_size )
    : View( title, client_size )          //Viewの生成
    , dc( get_dc() )                      //ViewからDCを取得
    , rc( create_rendering_context( dc ) )  //ViewのDCを使ってGLRCを生成
{
}

/**
 *  OpenGLレンダリングコンテキストの生成
 *
 *  @param  dc  デバイスコンテキスト
 */
glrc_handle OpenGLView::create_rendering_context( const dc_handle& dc )
{
    //PIXELFORMATDESCRIPTORにピクセルフォーマットを設定し、
    //デバイスコンテキストにもっと適合するピクセルフォーマットを選択、設定後
    //レンダリングコンテキストを生成する
    PIXELFORMATDESCRIPTOR pfd = { sizeof( PIXELFORMATDESCRIPTOR ) };
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW    // support window 
                | PFD_SUPPORT_OPENGL    // support OpenGL 
                | PFD_DOUBLEBUFFER      // double buffered 
                ;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 32;
    pfd.cDepthBits = 16;
    pfd.iLayerType = PFD_MAIN_PLANE;
    int format = ::ChoosePixelFormat( dc.get(), &pfd );
    if( format == 0 )
        throw std::runtime_error( "ChoosePixelFormat" );
    if( !::SetPixelFormat( dc.get(), format, &pfd ) )
        throw std::runtime_error( "SetPixelFormat" );
    HGLRC rc = ::wglCreateContext( dc.get() );
    ::wglMakeCurrent( dc.get(), rc );

    return glrc_handle( rc, glrc_deleter( dc.get() ) );
}

ウィンドウの生成からOpenGLの初期化まで非常にシンプルに記述できていると思います。
また、初期化の途中で例外が発生しても、unique_ptrのデストラクタで確実に解放されます。

ダウンロード

  • unique_ptr01.zip 直
  • VC++2010 ExpressEdition、DirectX SDK(February 2010)で動作確認
  • OpenGL 1.2以上
    • VC++2010 ExpressEditionにはデフォルトで含まれていないので、こちら(OpenGL本家)から以下のファイルをダウンロードしてください。
      • glext.h、glxext.h、wglext.h