第38回
プログラミングの周辺事項(1)~Cで書いたプログラムの仕組みと構造

Windowsとアプリケーション

おそらく最も一般的なパソコン用OSであるWindowsで動作するプログラム――Windowsアプリケーションの構造を見ておきましょう。

静的結合と動的結合

先に「プログラムはOSの用意した機能を使う」と書きました。WindowsではそれがAPI関数 ※5 であり、DLL――ダイナミックリンクライブラリと呼ばれる機能別のライブラリにまとめられています。

本コラムのテーマであるコンソールモードのCプログラムでは、ライブラリはコンパイルされたオブジェクトファイルとともにリンカで結合されます。しかし、WindowsのDLLは実行中のプログラムからその中のAPI関数が呼び出されます。前者のようにプログラムの生成時に結合される形が「静的結合――static link」であるのに対して、動作中に必要な関数を呼び出す形が「動的結合――dynamic link」という訳です。

APIは"Application Programming Interface"の略。ウィンドウの生成や文字列の出力、グラフィックスの描画などプログラムが必要とする基本機能が揃っています

イベント駆動とメッセージ

ここで、Windowsで動作するプログラム――Windowsアプリケーション ※6 の基本形を見ておきましょう。

Windowsアプリケーションは、主にマウスやキーボードなどユーザーの操作をきっかけにし、その操作に対応する動作を行う形で処理が進みます。これらユーザーの操作などはまずOSであるWindowsが関知し、その内容を『メッセージ』という形でアプリケーションに送信します。

アプリケーションはWindowsから送られてくるメッセージを待ち受け、メッセージを受け取ると、その内容に応じた処理を実行して、次のメッセージを待ちます。ユーザーの操作などアプリケーションの動作するきっかけを『イベント』と呼び、このようなイベントを待って動作を行う形式を『イベントドリブン(イベント駆動)』と呼びます。

Windowsアプリの3つの機構

Windowsアプリケーションの基本形は、以下のような形になります。

(1)Windowsからのメッセージを待つ
(2)メッセージを受け取る
(3)メッセージに対応する処理を実行する
  終了のメッセージならプログラムを終了する
  そうでなければ
(4)(1)に戻ってメッセージを待つ

つまり、Windowsから「終了せよ」というメッセージが届くまで、アプリケーションは
メッセージを待つ→処理を分岐する→処理を実行する
……という動作を繰り返すことになります。したがって、Windowsアプリケーションは以下のような3つの機構を持ちます。

1.メッセージを待つ
2.メッセージに応じた処理への分岐
3.メッセージに応じた実際の処理

1.の部分を『メッセージループ』(または「メッセージ待ちループ」)と呼び、この処理はどのようなアプリケーションでも変わりません。

2.の部分はアプリケーションにとって必要な処理と、必要とはしない処理とが存在しますが、分岐の構造自体はどのようなアプリケーションでも変わりません。

アプリケーション独自の処理は3.の部分に集約されます。つまり3.の部分こそが「アプリケーションの個性」であるということです。

ということは、アプリケーションの中でプログラマーが実際に組み立てなければならない部分は、「3.メッセージに応じた実際の処理」だけであると言えます。Visual BasicやVisual C++などでは1.と2.の部分を処理系が予め準備しており、プログラマーはアプリケーションの個性である3.の部分の組み立てに集中できるようになっている訳です。

Windows版Helloプログラム

Windows向けの統合開発環境がなかった時代には、Windowsアプリケーションでは先に紹介した「メッセージループ~分岐~実際の処理」のすべての構造をソースコードとして記述しなければなりませんでした。

もちろん、どのようなアプリケーションにも共通した「メッセージループと分岐」の部分はソースレベルでの再利用が可能ですが、ソース自体が長くなることは否めません。

ここで、Windows 3.1の時代に使われていたCで書いた「Windows版Hello worldプログラム」のソースを紹介しておきましょう。
Windowsでは、Windowsの制御下で動作する一般的なプログラムを「アプリケーション」と呼んでいます

リスト1:Windows 3.1時代のHelloプログラム(あくまで参考です)
/*----------------------------------------------------------------
    Windows版 Hello world -- C言語版
  ---------------------------------------------------------------- */
#include    <stdio.h>
#include    <string.h>
#include    <windows.h>

/* ウィンドウ関数のプロトタイプ宣言 */
long FAR PASCAL _export WndProc( HWND hWnd, WORD iMessage,
                                 WORD wParam, LONG lParam );

/* Windowsアプリの入り口 */
int PASCAL WinMain(HANDLE hInstance,
                   HANDLE hPrevInstance,
                   LPSTR  lpszCmdLine,
                   int    nCmdShow)
{
    static char     szAppName[] = "Hello Windows";
                    /* ウィンドウクラスの識別名 */
    MSG             msg;        /* メッセージ処理のための構造体 */
    HWND            hWnd;       /* ウィンドウハンドル */
    WNDCLASS        wndclass;   /*  ウィンドウ登録用の構造体 */

    /* 同じプログラムがロードされていなければ構造体の初期設定を行う */
    if ( ! hPrevInstance) {
        wndclass.style          = CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc    = WndProc;
        wndclass.cbClsExtra     = 0;
        wndclass.cbWndExtra     = 0;
        wndclass.hInstance      = hInstance;
        wndclass.hIcon          = LoadIcon(NULL, IDI_APPLICATION);
        wndclass.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wndclass.hbrBackground  = GetStockObject(WHITE_BRUSH);
        wndclass.lpszMenuName   = NULL;
        wndclass.lpszClassName  = szAppName;
        RegisterClass(&wndclass);
    }

    /* ウィンドウの生成 */
    hWnd = CreateWindow(szAppName,
                        "Windows example",
                        WS_OVERLAPPEDWINDOW,
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,
                        NULL,
                        NULL,
                        hInstance,
                        NULL);
    ShowWindow(hWnd, nCmdShow);     /* ウィンドウの表示 */
    UpdateWindow(hWnd);             /* WM_PAINT を送る */

    /* メッセージループ */
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;              /* 終了コードを返す */
}

/*
 ここで「引数 'lpszCmdLine' が WinMainないで使用されていない」
 といった意味の警告(Warning)が出るが動作に影響はない。
*/

/* ウィンドウ関数 -- ここでメッセージを処理する */
long FAR PASCAL _export WndProc(
                        HWND hWnd,     /* ウィンドウハンドル */
                        WORD iMessage, /* 送られてきたメッセージ */
                        WORD wParam,   /* メッセージに付随するパラメータ */
                        LONG lParam
                        )
{
    HDC             hDC;
    PAINTSTRUCT     ps;
    char    szHelloMsg[] = "Hello Windows world!";    /* 表示する文字列 */

    /* メッセージによって処理を分岐 */
    switch (iMessage)
    {
        case WM_CREATE:    /* ウィンドウの生成 */
            break;
        case WM_PAINT:     /* ウィンドウの描画 */
            /* 文字列を表示 */
            BeginPaint(hWnd, &ps);
            hDC = ps.hdc;
            TextOut(hDC, 0, 0, szHelloMsg, strlen(szHelloMsg));
            EndPaint(hWnd, &ps);
            return 0L;
        case WM_DESTROY:    /* ウィンドウの消去(終了) */
            PostQuitMessage( 0 );
            return 0L;
        default:    /* 上記以外のメッセージ */
            return DefWindowProc( hWnd, iMessage, wParam, lParam );
    }
}

Windowsと統合開発環境

どのようなプログラムでも、Windows環境下では先に掲げた1~3の部分は共通しています。そのため統合開発環境では、これら共通部分をあらかじめオブジェクトファイルとして準備しており、プログラマーは各メッセージに応じた『プログラム独自の処理』だけを記述すればいいようになっています。

その典型はバージョン6以前のVisual Basic(VB)で、デザインしたフォーム(ウィンドウ)上のボタンをクリックした、テキストボックスの内容が書き換えられた……といったイベントに対応する処理をプロシージャという形で記述するようになっています。

さらにVBでは、本来WM_LBUTTONDOWNのような形でOSからアプリケーションに送られてくるメッセージを、フォーム上のボタンなどのアイテム(VBでは「コントロール」)が操作されたことを示すメッセージに変換し、"cmdClear_Click"のような名前のプロシージャを生成するようになっています。

旧VBとランタイム

また、VBではVBで記述されコンパイルされたプログラムを実行するためのランタイムプログラムが用意され、その制御下でプログラムが動作するようになっています。そのためVBで記述されてコンパイルされたプログラムは、CPUが直接理解できる機械語ではなく、ランタイムプログラムが読み取って実行するための中間言語となっています。

Windowsプログラミングは .NETによって形が変わりましたが、これは、ちょうど旧VBのランタイムが .NET Frameworkに置き換わったようなイメージです。.NET FrameworkがWindowsとアプリケーションの間に入り、VBだけではなくC++やC#などの言語で作ったプログラムは .NET Frameworkの環境下で動作します。

これは、Javaアプレットがクライアントマシンで動作するブラウザの中のJava仮想マシン(VM)上で動作するのと似たイメージです。