2014年3月29日土曜日

遠回りで学ぶWin32 APIでOpenGL 004

遠回りで学ぶWin32 APIでOpenGL 003の続き

デバイスコンテキストの取得



ようやくOpenGLの環境設定に入る.まず,生成したウィンドウに対してデバイスコンテキストを取得する必要がある.これは,ウィンドウハンドル(HWND)に対して,GetDC関数(MSDNのドキュメント)を使うことで取得できる.GetDCで取得したデバイスコンテキストのハンドルは,ReleaseDC関数(MSDNのドキュメント)で解放する必要がある.

// 010_get_device_context.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"

using namespace std;

LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);

bool onCreate(HWND hWnd);
void printLastError();

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    using_console uc;

    // ウィンドウクラスの登録
    WNDCLASSEX wcx = { 0 };
    wcx.cbSize = sizeof wcx;
    wcx.lpszClassName = TEXT("hello");
    wcx.lpfnWndProc = WindowProc;

    if(RegisterClassEx(&wcx) == 0){
        printLastError();
        cout << "終了します.何か入力してください." << endl;
        cin.get();
        return 0;
    }

    // ウィンドウサイズの計算
    LONG const WIDTH = 640;
    LONG const HEIGHT = 480;
    RECT r = { 0, 0, WIDTH, HEIGHT };
    DWORD windowStyle = WS_OVERLAPPEDWINDOW;
    if(!AdjustWindowRect(&r, windowStyle, FALSE)){
        cout << "終了します.何か入力してください." << endl;
        cin.get();
        return 0;
    }

    // ウィンドウ生成
    HWND hWnd = CreateWindow(
        wcx.lpszClassName,
        TEXT("hello"),
        windowStyle,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        r.right - r.left,
        r.bottom - r.top,
        NULL,
        NULL,
        hInstance,
        NULL
    );
    if(!hWnd){
        printLastError();
        cout << "終了します.何か入力してください." << endl;
        cin.get();
        return 0;
    }

    // ウィンドウ表示
    ShowWindow(hWnd, nCmdShow);

    // メッセージループ
    MSG msg = { 0 };
    while(msg.message != WM_QUIT){
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
            DispatchMessage(&msg);
        }else{
            Sleep(100);
        }
    }

    cout << "終了します.何か入力してください." << endl;
    cin.get();

    return 0;
}

/*!
 *  ウィンドウに送られたメッセージを処理するためのコールバック関数
 */
LRESULT CALLBACK WindowProc(
    HWND hWnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
)
{
    switch(msg){
    case WM_CREATE:
        if(!onCreate(hWnd)){
            return -1;
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, msg, wParam, lParam);
    }

    return 0;
}

/*!
 *  ウィンドウ生成時の処理
 */
bool onCreate(HWND hWnd)
{
    // デバイスコンテキストの取得
    HDC hDC = GetDC(hWnd);
    if(hDC == NULL){
        return false;
    }

    // ここに色々な初期化処理を書く

    // デバイスコンテキストの解放
    if(!ReleaseDC(hWnd, hDC)){
        return false;
    }

    return true;
}

/*!
 *  GetLastError()によるエラーコードをFormatMessage()により
 *  文字列に変換し,標準出力に出力する.
 */
void printLastError()
{
    LPSTR pBuffer = NULL;
    DWORD error = GetLastError();
    DWORD result = FormatMessageA(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_IGNORE_INSERTS |
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL,
        error,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        reinterpret_cast(&pBuffer),
        0,
        NULL
    );

    if(result > 0 && pBuffer){
        cout << pBuffer << endl;
        LocalFree(pBuffer);
    }
}

ウィンドウプロシージャで処理するメッセージにWM_CREATE(MSDNのドキュメント)を追加している.MSDNのドキュメントによれば,-1を返せばウィンドウが生成されない,とのことなので,OpenGLの初期化に失敗した(つまりonCreateがfalseを返した)場合に-1を返すようにしてある.

コンパイル


cl /EHsc 010_get_device_context.cpp using_console.cpp /link User32.lib

デバイスコンテキストのクラス化



コンストラクタでデバイスコンテキストを取得し,デストラクタで解放するようなクラスを作っておく.
これで解放し忘れることが減る.

// device_context_handle.h
#ifndef DEVICE_CONTEXT_HANDLE_H_INCLUDED
#define DEVICE_CONTEXT_HANDLE_H_INCLUDED

#include <tchar.h>
#include <Windows.h>

class device_context_handle {
public:
    device_context_handle(HWND hWnd);
    ~device_context_handle();

    operator HDC() const;

private:
    // コピー禁止
    device_context_handle(device_context_handle const &);
    device_context_handle & operator=(device_context_handle const &);

    HWND mhWnd;
    HDC  mhDC;
};

#endif // DEVICE_CONTEXT_HANDLE_H_INCLUDED
// device_context_handle.cpp
#include "device_context_handle.h"

device_context_handle::device_context_handle(HWND hWnd)
    :   mhWnd(hWnd),
        mhDC(GetDC(hWnd))
{}

device_context_handle::~device_context_handle()
{
    if(mhDC){
        ReleaseDC(mhWnd, mhDC);
    }
}

device_context_handle::operator HDC() const
{
    return mhDC;
}

ピクセルフォーマットの選択



デバイスコンテキストに対してOpenGLがサポートされている状態にするために,ピクセルフォーマットを設定する必要がある.まず,ChoosePixelFormat関数(MSDNのドキュメント)を使って希望のピクセルフォーマットに近いピクセルフォーマットを選択する.
ここで,PIXELFORMATDESCRIPTOR(MSDNのドキュメント)のdwFlagsにPFD_SUPPORT_OPENGLを設定しておくことで,OpenGLが利用できるピクセルフォーマットが選択される.

// 011_choose_pixel_format.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"
#include "device_context_handle.h"

/* 色々省略 */

bool onCreate(HWND hWnd)
{
    device_context_handle hDC(hWnd);

    // ピクセルフォーマットの選択
    PIXELFORMATDESCRIPTOR pfd = { 0 };
    pfd.nSize = sizeof pfd;
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 24;
    pfd.iLayerType = PFD_MAIN_PLANE;

    int pixelFormatIndex = ChoosePixelFormat(hDC, &pfd);
    if(pixelFormatIndex == 0){
        printLastError();
        return false;
    }

    return true;
}

コンパイル

横に長くなってきたが,device_context_handle.cppとGdi32.libが追加されている.
cl /EHsc 011_choose_pixel_format.cpp using_console.cpp device_context_handle.cpp /link User32.lib Gdi32.lib

ピクセルフォーマットの設定

ピクセルフォーマットが選択できたら,そのピクセルフォーマットを設定してやる.設定には,SetPixelFormat関数(MSDNのドキュメント)を用いる.

// 012_set_pixel_format.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"
#include "device_context_handle.h"

/* 色々省略 */

bool onCreate(HWND hWnd)
{
    device_context_handle hDC(hWnd);

    // ピクセルフォーマットの選択
    PIXELFORMATDESCRIPTOR pfd = { 0 };
    pfd.nSize = sizeof pfd;
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 24;
    pfd.iLayerType = PFD_MAIN_PLANE;

    int pixelFormatIndex = ChoosePixelFormat(hDC, &pfd);
    if(pixelFormatIndex == 0){
        printLastError();
        return false;
    }

    // ピクセルフォーマットの設定
    if(!SetPixelFormat(hDC, pixelFormatIndex, &pfd)){
        printLastError();
        return false;
    }

    return true;
}

コンパイル


cl /EHsc 012_set_pixel_format.cpp using_console.cpp device_context_handle.cpp /link User32.lib Gdi32.lib

OpenGLのレンダリングコンテキストの生成



ピクセルフォーマットの設定が終われば,OpenGLのレンダリングコンテキストを生成できる.OpenGLのレンダリングコンテキストは,wglCreateContext関数(MSDNのドキュメント)を呼び出すことで生成できる.作ったレンダリングコンテキストは,wglDeleteContext関数(MSDNのドキュメント)で後始末する.

// 013_create_context.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"
#include "device_context_handle.h"

/* 色々省略 */

bool onCreate(HWND hWnd)
{
    device_context_handle hDC(hWnd);

    /* ピクセルフォーマットの選択から設定まで省略 */

    // OpenGLレンダリングコンテキストの生成
    HGLRC hGLRC = wglCreateContext(hDC);
    if(hGLRC == NULL){
        printLastError();
        return false;
    }

    // OpenGLレンダリングコンテキストの破棄
    if(!wglDeleteContext(hGLRC)){
        printLastError();
        return false;
    }

    return true;
}

コンパイル



とうとうOpenGL32.libというOpenGLのライブラリをリンクするところまできた.

cl /EHsc 013_create_context.cpp using_console.cpp device_context_handle.cpp /link User32.lib Gdi32.lib OpenGL32.lib

OpenGLレンダリングコンテキストの設定



OpenGLレンダリングコンテキストの生成が終われば,後はそれを設定するだけ.wglMakeCurrent関数(MSDNのドキュメント)を使う.また,メッセージループが終わった後に,OpenGLレンダリングコンテキストの破棄を行うようにしておく.

// 014_make_current.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"
#include "device_context_handle.h"

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    /* 色々省略 */

    MSG msg = { 0 };
    while(msg.message != WM_QUIT){
        / * 省略 */
    }

    // OpenGLレンダリングコンテキストの破棄
    HGLRC hGLRC = wglGetCurrentContext();
    wglMakeCurrent(NULL, NULL):
    wglDeleteContext(hGLRC);

    return msg.wParam;
}

bool onCreate(HWND hWnd)
{
    device_context_handle hDC(hWnd);

    /* ピクセルフォーマットの選択から設定まで省略 */

    // OpenGLレンダリングコンテキストの生成
    HGLRC hGLRC = wglCreateContext(hDC);
    if(hGLRC == NULL){
        printLastError();
        return false;
    }

    // OpenGLレンダリングコンテキストの設定
    if(!wglMakeCurrent(hDC, hGLRC)){
        printLastError();
        wglDeleteContext(hGLRC);
        return false;
    }

    return true;
}

コンパイル


cl /EHsc 014_make_current.cpp using_console.cpp device_context_handle.cpp /link User32.lib Gdi32.lib OpenGL32.lib

画面のクリア



ようやくOpenGLのAPIを使える状態になったので,OpenGLのヘッダファイルgl/GL.hを追加して,メッセージループでメッセージが来ていない場合に描画を実行する.今回は,glClearColorで色を指定し,glClearにGL_COLOR_BUFFER_BITを指定し,画面を指定した色で塗りつぶす.

// 015_clear.cpp
#include <tchar.h>
#include <Windows.h>
#include <GL/gl.h>
#include <iostream>
#include "using_console.h"
#include "device_context_handle.h"

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    /* 色々省略 */

    MSG msg = { 0 };
    while(msg.message != WM_QUIT){
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
            DispatchMessage(&msg);
        }else{
            glClearColor(1.f, 0.f, 0.f, 1.f);
            glClear(GL_COLOR_BUFFER_BIT);
            glFlush();

            Sleep(100);
        }
    }

    // OpenGLレンダリングコンテキストの破棄
    HGLRC hGLRC = wglGetCurrentContext();
    wglMakeCurrent(NULL, NULL):
    wglDeleteContext(hGLRC);

    return msg.wParam;
}

bool onCreate(HWND hWnd)
{
    /* 省略 */
}

コンパイル


cl /EHsc 015_clear.cpp using_console.cpp device_context_handle.cpp /link User32.lib Gdi32.lib OpenGL32.lib

第四回まとめ


  • デバイスコンテキストの取得はGetDC,破棄はReleaseDC
  • OpenGLのレンダリングコンテキストを生成する前に,ChoosePixelFormatでピクセルフォーマットを選択し,SetPixelFormatで設定
  • wglCreateContextでOpenGLレンダリングコンテキストを生成し,wglMakeCurrentで設定

たかが画面をクリアするだけのためにもこれだけの手順が必要な上に,OpenGLそのものを学ぶ上では知らなくても済む内容なので,GLUTやGLEWを使ってしまいたい気持ちがよく分かった.ただ,実際のゲームエンジンなどではライセンスの問題だったり,ゲーム専用ハードはサポートされていないためか,独自に実装していることもあるようなので,知っていて損はないのかなぁ,と.

2014年3月28日金曜日

遠回りで学ぶWin32 APIでOpenGL 003

遠回りで学ぶWin32 APIでOpenGL 002の続き

ウィンドウサイズの調整

今のままだとウィンドウサイズが不定なので,AdjustWindowRect(MSDNのドキュメント)で希望の描画領域のサイズに合わせたウィンドウサイズを計算する.

// 007_adjust_window_rect.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"

using namespace std;

void printLastError();

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    using_console uc;

    // ウィンドウクラスの登録
    WNDCLASSEX wcx = { 0 };
    wcx.cbSize = sizeof wcx;
    wcx.lpszClassName = TEXT("hello");
    wcx.lpfnWndProc = DefWindowProc;

    if(RegisterClassEx(&wcx) == 0){
        printLastError();
        cout << "終了します.何か入力してください." << endl;
        cin.get();
        return 0;
    }
    
    // ウィンドウサイズの計算
    LONG const WIDTH = 640;
    LONG const HEIGHT = 480;
    RECT r = { 0, 0, WIDTH, HEIGHT };
    DWORD windowStyle = WS_OVERLAPPEDWINDOW;
    if(!AdjustWindowRect(&r, windowStyle, FALSE)){
        cout << "終了します.何か入力してください." << endl;
        cin.get();
        return 0;
    }

    // ウィンドウの生成
    HWND hWnd = CreateWindow(
        wcx.lpszClassName,
        TEXT("hello"),
        windowStyle,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        r.right - r.left,
        r.bottom - r.top,
        NULL,
        NULL,
        hInstance,
        NULL
    );
    if(!hWnd){
        printLastError();
        cout << "終了します.何か入力してください." << endl;
        cin.get();
        return 0;
    }

    // ウィンドウの表示
    ShowWindow(hWnd, nCmdShow);

    cout << "終了します.何か入力してください." << endl;
    cin.get();

    return 0;
}

/*!
 *  GetLastError()によるエラーコードをFormatMessage()により
 *  文字列に変換し,標準出力に出力する.
 */
void printLastError()
{
    LPSTR pBuffer = NULL;
    DWORD error = GetLastError();
    DWORD result = FormatMessageA(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_IGNORE_INSERTS |
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL,
        error,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        reinterpret_cast<LPSTR>(&pBuffer),
        0,
        NULL
    );

    if(result > 0 && pBuffer){
        cout << pBuffer << endl;
        LocalFree(pBuffer);
    }
}

コンパイル

cl /EHsc 007_adjust_window_rect.cpp using_console.cpp

メッセージループ

今のままだと何もできずに終了してしまうので,Windowsからマウスのクリックといったイベントを受け取るためのメッセージループを追加する.PeekMessage関数(MSDNのドキュメント)でメッセージの有無をチェックし,メッセージがあればDispatchMessage関数(MSDNのドキュメント)でウィンドウにメッセージを送る.メッセージが特に来ていない場合,Sleep関数(MSDNのドキュメント)でしばらく待機させる.

// 008_message_loop.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"

using namespace std;

void printLastError();

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    using_console uc;

    /* ウィンドウクラスの登録からウィンドウの表示まで省略 */

    MSG msg = { 0 };
    while(msg.message != WM_QUIT){
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE){
            DispatchMessage(&msg);
        }else{
            Sleep(100);
        }
    }

    return msg.wParam;
}

/*!
 *  GetLastError()によるエラーコードをFormatMessage()により
 *  文字列に変換し,標準出力に出力する.
 */
void printLastError()
{
    /* 省略 */
}

実はこのままだとウィンドウを閉じてもプログラムが終了しないので,コンソール画面でCtrl+Cを押して強制終了する.

コンパイル

cl /EHsc 008_message_loop.cpp using_console.cpp

ウィンドウプロシージャ

ウィンドウを閉じた時にメッセージループを抜けるようにするため,独自のウィンドウプロシージャWindowProc(MSDNのドキュメント)を用意する.この関数は,型さえ合っていれば名前は何でも良い.最低限処理する必要があるのは,WM_DESTROYメッセージで,そのメッセージが送られてきたらPostQuitMessage関数(MSDNのドキュメント)でWM_QUITメッセージをイベントメッセージのキューに投げる.これで,ウィンドウを閉じるとメッセージループを抜けて終了するようになる.

// 009_window_proc.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"

using namespace std;

LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
void printLastError();

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    using_console uc;

    WNDCLASSEX wcx = { 0 };
    wcx.cbSize = sizeof wcx;
    wcx.lpszClassName = TEXT("hello");
    wcx.lpfnWndProc = WindowProc;

    /* ウィンドウの生成からウィンドウの表示まで省略 */

    MSG msg = { 0 };
    while(msg.message != WM_QUIT){
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE){
            DispatchMessage(&msg);
        }else{
            Sleep(100);
        }
    }

    return msg.wParam;
}

LRESULT CALLBACK WindowProc(
    HWND hWnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch(msg){
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, msg, wParam, lParam);
    }

    return 0;
}

/*!
 *  GetLastError()によるエラーコードをFormatMessage()により
 *  文字列に変換し,標準出力に出力する.
 */
void printLastError()
{
    /* 省略 */
}

コンパイル

cl /EHsc 009_window_proc.cpp using_console.cpp

第三回まとめ

  • クリックなどのイベントを処理するにはメッセージループが必要
  • メッセージループはPeekMessageでメッセージを確認し,DispatchMessageでウィンドウにメッセージを送る

次でようやくOpenGLの初期化に入れそう.

第四回へ

遠回りで学ぶWin32 APIでOpenGL 002

遠回りで学ぶWin32 APIでOpenGL 001の続き

Win32 APIでのエラー取得

Win32 APIの実行によりエラーが発生した場合,GetLastError関数(MSDNのドキュメント)でエラーコードを取得し,FormatMessage関数(MSDNのドキュメント)でメッセージを文字列に変換する.

// 002_print_last_error.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"

using namespace std;

//! Win32 APIでのエラーをコンソールに出力する
void printLastError();

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    // cout, cinが使えるようにコンソールを割り当てる
    using_console uc;

    // 敢えて失敗するように未設定のウィンドウクラスの登録を行う
    WNDCLASSEX wcx = { 0 };
    if(RegisterClassEx(&wcx) == 0){
        printLastError();
        cout << "終了します.何か入力してください." << endl;
        cin.get();
        return 0;
    }

    return 0;
}

/*!
 *  GetLastError()によるエラーコードをFormatMessage()により
 *  文字列に変換し,標準出力に出力する.
 */
void printLastError()
{
    LPSTR pBuffer = NULL;
    DWORD error = GetLastError();
    DWORD result = FormatMessageA(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_IGNORE_INSERTS |
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL,
        error,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        reinterpret_cast<LPSTR>(&pBuffer),
        0,
        NULL
    );

    if(result > 0 && pBuffer){
        cout << pBuffer << endl;
        LocalFree(pBuffer);
    }
}

コンパイル

前回実装したコンソール割り当て用クラスのcppを追加し,User32.libをリンクする.

cl /EHsc 002_print_last_error.cpp using_console.cpp /link User32.lib

ウィンドウクラスの登録

エラーを確認するための準備が整ったので,実際にウィンドウを表示する.まずは,ウィンドウクラスのWNDCLASSEX構造体(MSDNのドキュメント)を登録する必要がある.

// 003_register_window_class.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"

using namespace std;

//! Win32 APIでのエラーをコンソールに出力する
void printLastError();

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    using_console uc;

    // 登録が成功するための最低限の設定
    WNDCLASSEX wcx = { 0 };
    // 構造体のサイズ
    wcx.cbSize = sizeof wcx;
    // ウィンドウクラス名
    wcx.lpszClassName = TEXT("hello");

    if(RegisterClassEx(&wcx) == 0){
        printLastError();
        cout << "終了します.何か入力してください." << endl;
        cin.get();
        return 0;
    }

    return 0;
}

void printLastError()
{
    /* 省略 */
}

コンパイル

cl /EHsc 003_register_window_class.cpp using_console.cpp /link User32.lib

ウィンドウの生成

ウィンドウクラスの登録に成功したら,そのウィンドウクラスを使ってウィンドウを生成する.ウィンドウの生成には,CreateWindow関数(MSDNのドキュメント)を使う.ただし,今の状態のウィンドウクラスでウィンドウを生成しようとすると,強制終了してしまう.

// 004_fail_to_create_window.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"

using namespace std;

//! Win32 APIでのエラーをコンソールに出力する
void printLastError();

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    using_console uc;

    /* 
       ウィンドウクラスの登録 
       (003_register_window_class.cppと同じ)
     */

    HWND hWnd = CreateWindow(
        wcx.lpszClassName,
        TEXT("hello"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,
        NULL,
        hInstance,
        NULL
    );
    if(!hWnd){
        printLastError();
        cin.get();
        return 0;
    }

    return 0;
}

void printLastError()
{
    /* 省略 */
}

このコードが動くようにするためには,WNDLCASSEX構造体の初期化時にウィンドウプロシージャを登録しておく必要がある.

// 005_create_window.cpp
// 色々省略

WNDCLASSEX wcx = { 0 };
wcx.cbSize = sizeof wcx;
wcx.lpszClassName = TEXT("hello");
wcx.lpfnWndProc = DefWindowProc;

Win32 APIに用意されているデフォルトのウィンドウプロシージャDefWindowProc(MSDNのドキュメント)を登録しておくことで,無事ウィンドウが生成される.

コンパイル

cl /EHsc 004_fail_to_create_window.cpp using_console.cpp /link User32.lib
cl /EHsc 005_create_window.cpp using_console.cpp /link User32.lib

ウィンドウの表示

ウィンドウの生成に成功したら,ShowWindow関数(MSDNのドキュメント)を呼び出し,ウィンドウを表示する.初回の呼び出し時にはWinMain関数の引数のnCmdShowを与える必要がある.

// 006_show_window.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"

using namespace std;

//! Win32 APIでのエラーをコンソールに出力する
void printLastError();

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    using_console uc;

    WNDCLASSEX wcx = { 0 };
    wcx.cbSize = sizeof wcx;
    wcx.lpszClassName = TEXT("hello");
    wcx.lpfnWndProc = DefWindowProc;
    RegisterClassEx(&wcx);

    HWND hWnd = CreateWindow(/* 省略 */);
    if(!hWnd){
        printLastError();
        cin.get();
        return 0;
    }

    ShowWindow(hWnd, nCmdShow);

    cout << "終了します.何かキーを入力してください." << endl;
    cin.get();

    return 0;
}

void printLastError()
{
    /* 省略 */
}

コンパイル

cl /EHsc 006_show_window.cpp using_console.cpp /link User32.lib

第二回まとめ

  • ウィンドウを生成するにはまずRegisterClassExでウィンドウクラスを登録
  • CreateWindowでウィンドウの生成
  • ShowWindowでウィンドウの表示

遠回りなだけあってOpenGLが出てくるまでが長い.GLUTやGLEWが色々やってくれているのがよく分かる.

第三回へ

遠回りで学ぶWin32 APIでOpenGL 001

はじめに

敢えてGLUTもGLEWもIDEも使わずにOpenGLで描画をするための手順をまとめておく.

エントリポイント

Win32 APIを使う場合,プログラムの開始位置はmain関数ではなく,WinMain関数(MSDNのドキュメント)になる.

// 000_entrypoint.cpp
#include <tchar.h>
#include <Windows.h>

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    return 0;
}

tchar.hとWindows.hが必要になる.ここで,WinMainではなく_tWinMainとなっているのは,文字コード周りの諸々に対応するため.詳しくはこちら(MSDNのドキュメントへ)

コンパイル

Visual Studioに付属しているコンソールを起動するか,環境変数VSINSTALLDIRの位置にあるvcvarsall.batを実行し,環境を設定したら,以下のコマンドを実行する.

cl 000_entrypoint.cpp

コンソール割り当て

WinMain関数を利用する場合,コンソールウィンドウが表示されないため,標準入出力が利用できない.そこで,AllocConsole(MSDNのドキュメント)を使ってコンソールウィンドウを用意する.

// 001_allocate_console.cpp
#include <tchar.h>
#include <Windows.h>
#include <cstdio>
#include <iostream>

using namespace std;

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    if(!AllocConsole()){
        return 0;
    }

    freopen("CONOUT$", "w", stdout);
    freopen("CONIN$", "r", stdin);

    cout << "コンソール割り当てに成功しました." << endl;
    cout << "終了します.何かキーを入力してください." << endl;
    cin.get();

    FreeConsole();

    return 0;
}

AllocConsoleでコンソールウィンドウが用意できたら,freopenに"CONOUT$","CONIN$"という名前を指定してやると,それぞれコンソールへの出力,入力として結び付けられる.最後に,AllocConsoleをした場合はFreeConsoleでコンソールを解放する必要がある.

コンパイル

例外を有効にする必要があるので,/EHscのオプションを追加してビルドする.

cl /EHsc 001_allocate_console.cpp

コンソール割り当てのクラス化

AllocConsole後にFreeConsoleし忘れないように,コンストラクタでAllocConsoleをし,デストラクタでFreeConsoleするようなクラスを作る.

// using_console.h
#ifndef USING_CONSOLE_H_INCLUDED
#define USING_CONSOLE_H_INCLUDED

class using_console {
public:
    //! コンソールを割り当てる
    using_console();
    //! コンソールを解放する
    ~using_console();

    bool isConsoleAllocated() const;

private:
    // コピー禁止
    using_console(using_console const &);
    using_console & operator=(using_console const &);

    //! コンソールが割り当てられたかどうか
    bool mConsoleAllocated;
};

#endif // USING_CONSOLE_H_INCLUDED
// using_console.cpp
#include <tchar.h>
#include <Windows.h>
#include <cstdio>

/*!
    コンソールを割り当て,成功した場合には標準入出力と
    コンソールを結び付ける.
 */
using_console::using_console()
    :   mConsoleAllocated(AllocConsole())
{
    if(isConsoleAllocated()){
        freopen("CONOUT$", "w", stdout);
        freopen("COUIN$", "r", stdin);
    }
}

/*!
    コンソールが割り当てられている場合,
    コンソールを解放する.
 */
using_console::~using_console()
{
    if(isConsoleAllocated()){
        FreeConsole();
    }
}

bool using_console::isConsoleAllocated() const
{
    return mConsoleAllocated;
}

第一回まとめ

  • Win32 APIのエントリポイントはWinMain
  • コンソールを利用するにはAllocConsoleからCONOUT$とCONIN$でfreopen

第二回へ

2014年3月25日火曜日

C++でのファイルサイズの取得

C++でファイルのサイズを取得する方法.意外とateを利用した方法がなかったのでメモ.