2019年7月10日水曜日

デスクトップアプリ用のテンプレート

デスクトップアプリ用テンプレートです。
一応GitHubにも上げています(リンク)。

Windowsプログラミングで、私が勝手にコツと感じたことを覚書程度に残しておこうと思います。
あくまでIDEを使わずにターミナルで開発することにこだわりたいと思います。

ディレクトリ構成

  • Makefile
  • icon.ico
  • main.cc
  • resource.rc

最初にフォルダの中に、4つのファイルを準備します。
それぞれ説明します。

メインソースファイル


//
// @file main.cc
// @brief Template project for Windows desktop application.
// @author Mamoru Kaminaga
// @date 2019-07-02 15:46:34
// Copyright 2019 Mamoru Kaminaga
//
#include <stdio.h>
#include <wchar.h>
#include <windows.h>
#include <windowsx.h>
#include "./resource.h"

namespace {
constexpr wchar_t WINDOW_NAME[] = L"Template window";
constexpr wchar_t CLASS_NAME[] = L"Template class";
}  // namespace

BOOL Cls_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) {
  (void)hWnd;
  (void)lpCreateStruct;
  return TRUE;
}

void Cls_OnDestroy(HWND hWnd) {
  (void)hWnd;
  PostQuitMessage(0);
}

void Cls_OnClose(HWND hWnd) { DestroyWindow(hWnd); }

void Cls_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify) {
  (void)hWnd;
  (void)id;
  (void)hWndCtl;
  (void)codeNotify;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
                         LPARAM lParam) {
  switch (message) {
    HANDLE_MSG(hWnd, WM_CREATE, Cls_OnCreate);
    HANDLE_MSG(hWnd, WM_DESTROY, Cls_OnDestroy);
    HANDLE_MSG(hWnd, WM_COMMAND, Cls_OnCommand);
    HANDLE_MSG(hWnd, WM_CLOSE, Cls_OnClose);
    default:
      return DefWindowProc(hWnd, message, wParam, lParam);
  }
}

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPTSTR lpsCmdLine, int nCmdShow) {
  (void)hPrevInstance;
  (void)lpsCmdLine;

#ifdef DEBUG
  FILE* fp = nullptr;
  AllocConsole();
  _wfreopen_s(&fp, L"CONOUT$", L"w", stdout);
  _wfreopen_s(&fp, L"CONOUT$", L"w", stderr);
  _wfreopen_s(&fp, L"CONIN$", L"r", stdin);
#endif

#ifdef DEBUG
  fwprintf(stdout, L"Hello world to stdout!\n");
  fwprintf(stderr, L"Hello world to stderr!\n");
#endif

  WNDCLASS wc;
  wc.style = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc = WndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInstance;
  wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  wc.lpszMenuName = 0;
  wc.lpszClassName = CLASS_NAME;
  if (!RegisterClass(&wc)) {
    return FALSE;
  }

  HWND hWnd = CreateWindow(CLASS_NAME, WINDOW_NAME, WS_OVERLAPPEDWINDOW,
                           CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                           CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
  if (hWnd == NULL) {
    return FALSE;
  }
  ShowWindow(hWnd, nCmdShow);
  UpdateWindow(hWnd);

  MSG msg;
  while (GetMessage(&msg, NULL, 0, 0) > 0) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }

#ifdef DEBUG
  FreeConsole();
#endif
  return 0;
}

ポイント
  • Windows APIにこだわる
    ライセンスの問題などの理由から、MFCは使いません。
    Windows APIの関数をそのまま使います。
  • メッセージクラッカ
    windowsx.hをインクルードし、メッセージクラッカを使用します。
    メッセージクラッカとは、ウィンドウプロシージャ中の肥大化しがちなswitch文をスリムに書くための、マクロの集まりみたいなものです。
    Cls_OnCreateなどの関数の定義の仕方は、windowsx.hにマクロにコメントとして記述されています。
  • コードフォーマッタ
    C++のコードフォーマッタを用いてソースコードを整形しました。
    IDEを使わないのであれば、フォーマッタを別途用意する必要があります。
    私はclang-formatを導入していますが、Google style guideほか複数のコーディング規約をカバーしている点、vimからプラグインを介して呼び出せる点などが気に入っています。
    clang-formatはLLVMのコンパイラを導入することで、付属品として付いてきます。
  • AllocConsole()、FreeConsole()
    ウィンドウデスクトップアプリ開発でコマンドプロントを表示する関数です。
    知っているとデバッグで役に立ちます。

メイクファイル


#
# Makefile
# Date: 2019-07-02 10:38:40
# Copyright 2019 Mamoru Kaminaga
#
TARGET = main.exe
PDB = main.pdb
MAP = main.map
RES = resource.res
SRC = main.cc
TMPDIR = build
OBJ = $(TMPDIR)\main.obj

CC = "$(VCINSTALLDIR)\cl.exe"
LINK = "$(VCINSTALLDIR)\link.exe"

CPPFLAGS = /nologo /W4 /Zi /O2 /MT /EHsc /Fd"$(TMPDIR)/" /DUNICODE /D_UNICODE \
      /DDEBUG
LFLAGS = "kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" \
     "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" \
     "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" "libcmt.lib" \
     /NOLOGO /SUBSYSTEM:WINDOWS /DEBUG

ALL: $(TARGET)

$(TARGET): $(OBJ) $(RES)
 $(LINK) $(LFLAGS) /OUT:$(TARGET) /PDB:"$(PDB)" /MAP:"$(MAP)" \
 $(OBJ) $(RES)

.cc{$(TMPDIR)}.obj:
 @[ -d $(TMPDIR) ] || mkdir $(TMPDIR)
 $(CC) $(CPPFLAGS) /Fo"$(TMPDIR)\\" /c $<

clean:
 rm $(TARGET) $(OBJ) *.map *.pdb *.ilk *.obj *.lib



ポイント
  • VCINSTALLDIR
    Visual C++のコンパイラやリンカの実行可能ファイル(*.exe)が置いてあるディレクトリの環境変数です。
    開発者コマンドプロンプトを使用すれば、環境変数の設定などが楽で済みますが、cygwinから呼び出すとなるとひと手間かかります。
    環境変数は、一度は頑張って設定する必要があります。
  • CC、LINK
    cygwinのgccパッケージに含まれるlink.exeと名前が衝突するので、フルパスで設定しています。
  • nmake
    Visual Studioについてくるmakeです。
    gccのドキュメントに比べ情報がヒットせず、使い方を調べるのに苦労しました。
  • TMPDIR
    一時フォルダです。
    ここにオブジェクトファイルなどの一時ファイルが入ります。
    (このフォルダは.gitignoreに指定しておきます)
  • CPPFLAGS
    コマンドラインオプションです。
    ドキュメントはここ
  • LFLAGS
    リンカオプションです。
    ドキュメントはここ

アイコンファイル


ポイント
  • デザイン
    ちゃんとしたアイコンがあるだけで、自作ソフトウェアが駄作でもそれっぽく見えてしまうから不思議です。
    このサンプルはペイントのやっつけ仕事です。
  • IconWizard
    ペイントはiconファイルを出力できないので、一度PNGやJPEGで出力しておき、それをアイコンファイルに変換する必要があります。
    私はフリーソフトのIconWizardを使わせてもらっています。
    簡単にアイコンが作れるので助かっています。

リソースファイル(resource.rc)

// Generated by ResEdit 1.6.6
// Copyright (C) 2006-2015
// http://www.resedit.net

#include 
#include 
#include 
#include "resource.h"
#pragma code_page(65001)




//
// Icon resources
//
LANGUAGE 17, SUBLANG_DEFAULT
IDI_ICON1          ICON           ".\\icon.ico"


ポイント

  • ResEdit
    リソースを編集するために、フリーソフトのResEditを使います。
    本サンプルでは、アイコンをリソースに埋め込むためだけに用いていますが、かなり使い勝手の良いリソースエディタです。
    ボタンやチェックボックスなどのGUIパーツは、ソースコード中で関数を使って配置するのではなく、リソースファイルに記述するのがファイル分けとして綺麗です。
    ResEditを用いることで、ペイントやパワポと同じ感覚でresrouce.rcを編集し、GUIパーツを配置することができます。
    (パスの設定が悪いと大量のエラーを吐くるので、気を付けてください)
  • resource.h
    ResEditでresrouce.rcを編集して保存すると、それに対応したヘッダファイルであるresource.hが自動で作られます。
    resource.hはソースコードからインクルードしてください。
    リソーススクリプトで定義したGUIパーツやアイコンは、専用の関数を使いEXEにおいて初期化してください。
  • resource.res
    リソースが入った中間生成ファイルです。
    Makefileでは変数RESで扱っています。
    変数RESがTARGETの依存ファイルに指定されていると、ビルド時に自動でリソースコンパイラが呼び出されます。

0 件のコメント:

コメントを投稿

コメント表示は承認制に設定しています