ゆとりーなの日記

日記的な事を書いて行くと思はれる

この期に及んでWin32APIとXlibAPI

 昨日全ゲ連に行ったわけですよ。懇談会で名刺を幾つか頂いて、そこに載っているTwitterIDをフォローする作業を行うわけです。さて、ここでありがたいことにフォロー返しを頂いたとしましょう。ここの日記の在り処を私のTwitterのプロフィールのところに貼り付けていますのでここの場所は割れるわけです。そこで万が一こちらの方へ足を運んでいただいたときにトップが鬱日記だと「何ぞこの人は?」と思われてしまうことに気づいたので、手遅れ感はありますが少しプログラム的なサムシングの話をしてお茶を濁すことにします。
 それでは早速お茶を濁しに行きましょう。

#include <windows.h>

// コールバックプロシージャ
LRESULT CALLBACK WndProc(HWND handle, UINT message, WPARAM wp, LPARAM lp) {
  if (message == WM_DESTROY) {
    PostQuitMessage(0);
    return 0;
  }
  return DefWindowProc(handle, message, wp, lp);
}

int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int show_command) {
  // ウィンドウクラス登録
  const WNDCLASSEXA window_class  = {
    sizeof(window_class),
    CS_HREDRAW | CS_VREDRAW,
    &WndProc,
    0,
    0,
    instance,
    reinterpret_cast<HICON>(LoadImage(nullptr, 
                                      IDI_APPLICATION, 
                                      IMAGE_ICON, 
                                      32,
                                      32,
                                      LR_DEFAULTSIZE | LR_SHARED)),
    reinterpret_cast<HCURSOR>(LoadImage(nullptr,
                                        IDC_ARROW, 
                                        IMAGE_CURSOR, 
                                        0,
                                        0,
                                        LR_DEFAULTSIZE | LR_SHARED)),
    reinterpret_cast<HBRUSH>(GetStockObject(WHITE_BRUSH)),
    nullptr,
    "HOGE",
    nullptr
  };
  if (!RegisterClassExA(&window_class)) {
    return 0;
  }
  // ウィンドウ作成
  const HWND window = CreateWindowA("HOGE", 
                                    "",
                                    WS_OVERLAPPEDWINDOW,
                                    0,
                                    0, 
                                    640,
                                    480,
                                    nullptr,
                                    nullptr,
                                    instance,
                                    nullptr);
  if (!window) {
    return 0;
  }
  // ウィンドウの表示
  ShowWindow(window, show_command);
  UpdateWindow(window);
  MSG mes;
  // メッセージループ
  for (;;) {
    // メッセージが来たら取得
    const BOOL result = GetMessage(&mes, nullptr, 0, 0);
    if (!(result && ~result)) {
      break;
    } 
    // メッセージ処理
    TranslateMessage(&mes);
    DispatchMessage(&mes);
  }
  return mes.wParam;
}

 いつものWin32APIですね。窓出して、×ボタン押したら終了する単純なやつです。ウィンドウクラス登録して、それを元に窓を作って表示、どこかでこけなければメッセージループに突入して、なんかメッセージがあればウィンドウクラスに関連付けたコールバックが呼ばれてるよっていう流れですね。
 さてさて、続いては今回手を出してみたXlibAPIを使ったやつですね。なぜ今更こんな地雷を踏みに行ったのかというと、gtk系列だとちょっとゲームに不向きな抽象化がされすぎていて?若干使いにくい部分があったからですね。多分なかなかMFCでゲーム作ろうと思わないのに近い感じだと思います。で、多分WindowsでのWin32APIに当たるものはLinuxではXlibAPIなのかなとか思ったので両足で踏みに行ったというわけです。

#include <memory>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

struct display_delete {
  void operator ()(Display * const display) const {
    XCloseDisplay(display);
  }
};

int main() {
  // ディスプレイと接続
  std::unique_ptr<Display, display_delete> display(XOpenDisplay(nullptr));
  if (!display) {
    return 0;
  }
  // ウィンドウ作成
  const Window window = XCreateSimpleWindow(display.get(),
                                            DefaultRootWindow(display.get()),
		                            0, 
                                            0, 
                                            640,
                                            480,
                                            1,
		                            BlackPixel(display.get(), DefaultScreen(display.get())),
		                            WhitePixel(display.get(), DefaultScreen(display.get())));
 // ×ボタン関係の設定
  const Atom atom1 = XInternAtom(display.get(), "WM_PROTOCOLS", False);
  const Atom atom2 = XInternAtom(display.get(), "WM_DELETE_WINDOW", False);
  XSetWMProtocols(display.get(), window, &atom2, 1);	
  // ウィンドウの表示
  XMapWindow(display.get(), window);
  XFlush(display.get());
  XEvent event;
  bool run_flag = true;
  // メッセージループ
  while(run_flag) {
    // メッセージが来たら取得
    XNextEvent(display, &event);
    // メッセージ処理
    if (event.type == ClientMessage) {
      if (event.xclient.message_type == atom1 && event.xclient.data.l[0] == atom2) {
        XDestroyWindow(display.get(), window);
        run_flag = false;
      }
    }
  }
  return 0;
}

 こちらも窓出して、×ボタン押したら終了する単純なやつです。エントリポイント的サムシングやウィンドウのサイズが窓全体なのか、描画領域なのか、窓の左上座標の指定が無視されることがあるなど違いもありますが、そういうところは取り敢えずスルーの方向で行きます。
 で、Xlibではディスプレイとの接続を最初にするっぽいです。これがDisplay*型らしいです。終了するときにはきちんと接続を絶たないといけないみたいなので、unique_ptr余裕でした。多分これはWin32APIでいうところのHINSTANCEなのかなとか思ってたりします。まぁこちらはエントリポイントに引数から取って来れますし、特にリソース管理なども必要ないのでここまでではWin32APIが一歩リードですかね。
 ウィンドウの指定方法は別に大差ない感じですね。強いて言えば、Win32APIは親がなければNULLでいいのが、Xlibだとデスクトップを明示的に指定してやらないといかんということですかね。
 Xlibで一番面倒なのは、メッセージ処理のところなんじゃないですかね。×ボタンで閉じるって書くだけでも、いちいち×ボタンの関係の定義とかを自分で書かないといけません(因みに何も書かなくても×ボタン押せばプログラムは終了しますが、なにやらよからぬメッセージを吐いてきます)。で、この関係の定義のとこれで出てくるAtom型、これ何じゃとか思うわけですが、そういえば実は普段さりげなく使っている(Win32APIが普段さりげなく使う代物なのかどうかはおいておいて)RegisterClassExAの戻り値って実はATOM型なんですね。もう関係ありそうな要素しかないじゃないですか。多分恐らくWin32APIでは、メッセージの関係とかは全てRegisterClassEx系の関数が勝手にやってくれて、それらを纏めたものの識別子がアトムとして帰ってくる、一方でXlibAPIではメッセージの関係ひとつひとつをアトムの識別子で扱うってことなのかなとか思ってたりしますがあくまで予想なので悪しからず。
 最後に、メッセージループの部分ですね。Win32APIでは、ウィンドウクラスに登録したプロシージャが関連付けられたウィンドウ毎に自動的に呼ばれるのでいい感じにオブジェクト指向してますね。ウィンドウハンドルもウィンドウの方で登録したクラスのプロシージャに勝手に飛んでくるのでそこらへんの管理は気にしなくてもいいのです。一方でXlibAPIでは、非常に手続き型チックな書き方になってるように見えます。ウィンドウハンドルも、自分で管理してどのウィンドウに対して処理を行うのかをきちんと自前で分岐しなければいけなそうです。
 結論としては後発(多分)のWin32APIの方がXlibAPIよりはマシってことになるんですかね。