Kuroda Software Service
最初のプログラム
 

コンテンツ内メニュー

表示モード 全項表示  部分表示



マークの色と認証の関係

一般公開

無料認証

購入認証


認証についての説明

無料アカウント作成フォーム

有料ページ購入フォーム


ほかのコンテンツトップページ

最初のプログラム

コンソールプログラミングについて理解します。

/*--------------------------------------------------------------------------------

WIN32環境における最初のプログラム

Kuroda Software Service 黒田英之

--------------------------------------------------------------------------------*/

#include <windows.h>

#include <comdef.h>

class Console

{

    HANDLE CONIN; HANDLE CONOUT;

    TCHAR Buf[0x800];DWORD Len;

 

    public:

    Console()

    {

        AllocConsole();

        CONIN  = CreateFile(TEXT("CONIN$"),  GENERIC_READ,  0, NULL, OPEN_EXISTING, 0, NULL);

        CONOUT = CreateFile(TEXT("CONOUT$"), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

    }

    void SetColor(WORD color){SetConsoleTextAttribute(CONOUT, color);}

    _bstr_t Input()

    {

        //\r\nの\rを踏み潰し改行無しの文字列としていることに注意

        ReadConsole(CONIN, Buf, sizeof(Buf), &Len, NULL); Buf[Len - 2] = 0x00; return(Buf);

    }

    void Print(const _bstr_t &str)

    {

        //長大な書き込みは無視されるので自身バッファ長に分割して出力

        LPTSTR st = (LPTSTR)str; LPTSTR en = st + lstrlen(str); DWORD size = sizeof(Buf);

        while(st < en){WriteConsole(CONOUT, st, (st + size < en)? size: en - st, &Len, NULL); st += Len;}

    }

    void Println(const _bstr_t &str){Print(str);Print("\r\n");}

    void Printf(const _bstr_t format, ...){wvsprintf(Buf, format, (va_list)(&format + 1));Print(Buf);}

    ~Console()

    {

        if(CONIN  != NULL) CloseHandle(CONIN);

        if(CONOUT != NULL) CloseHandle(CONOUT);

        FreeConsole();

    }

}CONSOLE;

 

//コンソール呼び出しの簡略化

#define SetColor CONSOLE.SetColor

#define Input    CONSOLE.Input

#define Print    CONSOLE.Print

#define Println  CONSOLE.Println

#define Printf   CONSOLE.Printf

 

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

{

    MSG Msg;

    try

    {

        SetColor(0x0A); Println("<<< 最初のC++プログラム >>>");

        SetColor(0x0F); Println("キーボードから文字を入力しEnterを押してください");

        SetColor(0x07); _bstr_t str = Input() + "が入力されました";

        SetColor(0x0F); Println(str);

        SetColor(0x0A); Println("<<< ウインドウを閉じてください >>>");

 

        //Consoleウインドウに対するWM_CLOSEはスレッドにWM_QUITを送るのでメッセージループは終了する

        while(GetMessage(&Msg, NULL, 0, 0 ) != 0){TranslateMessage(&Msg); DispatchMessage(&Msg);}

    }

    catch(...)

    {

        MessageBox(NULL, TEXT("Unknown Erroe"), TEXT("Console C++"), 0);

    }

    return(0);

}

実行結果

 

l         クラスとは何か?

 乱暴な言い方を許していただければ、クラスとはコピーペーストして使いまわすことを容易にする工夫です。また、Printというような直感的な命名をした関数は名前が衝突しがちですが、クラス内の関数であれば問題ありません。

 クラス定義部をヘッダファイルにしているサンプルをよく目にしますが、これは複数の人間がプロジェクトに関った時、不用意に実装関数の中身を変更できないようにする工夫ですので、個人が管理するプロジェクトではヘッダファイルを作成せず単一のCPPファイルとしたほうが可読性は向上します。


l         TCHARとは何か?

 VC++ではマルチバイト環境で「_MBCS」ユニコード環境で「_UNICODE」が定数として宣言されています。両環境で同一のソースが利用できるようにTCHARは前述定数の存在によってchar又はwchar_tに置き換わります。また、TEXTマクロは_MBCS時には単にTEXTという記述が削除され、_UNICODE時にはLに置き換わることで文字列定数を正しく表現します。

l         #defineとは何か?

 C/C++ではコンパイラがソースを解釈する前にソースを置き換える機会が与えられています。#defineは半角スペースを区切りと認識し#define op1 op2 の場合op1の文字を機械的にop2に置き換えます。

l         WinMain関数とは何か?

 WIN32アプリケーションのエントリーポイントです。EXEファイルの実行を要求されたWindowsシステムはこの関数を探して実行します。また、Windowsシステムは複数のプログラムが協調して動作できるように主スレッドに対してメッセージを送信してきます。WIN32アプリケーションは最低限でも停止要求(WM_QUIT)を受信すべくメッセージループを備えているべきです。

l         例外処理とは?

 動作の局面ごとに細かく例外処理を行う必要はありません。しかしプログラムの動作全体を通じて何らかの例外をcatch(...)で監視することは大変重要です。特にCOMプログラミングに発展した場合、エラー停止はしないがその後の動作に影響を及ぼすエラーが発生する場合があります。それに気づかないと動作の度に挙動が違うといった最も深刻な事態を味わうことになります。

l         Consoleクラス解説

 ConsoleクラスはWindowsシステムに定義されているConsoleWindowClassを利用する例です。AllocConsoleでコンソールを取得しCreateFileで入出力ハンドルを取得しています。GetStdHandleを使用するサンプルが多いのですが、GetStdHandleは文字通り標準入出力ハンドルの取得であり、子プロセスとして呼ばれた場合は親との通信ハンドルとなりますのでコンソールへのハンドルとはならないことに注意が必要です。

 Consoleクラスの公開関数は文字列引数として_bstr_tを利用します。本ソースコードではこの_bstr_tを使う目的でcomdef.hがインクルードされています。_bstr_tは柔軟にchar*、wchar_t*を判断しますのでソースコード中にTEXTマクロを記述する必要がなくなるというメリットがあります。

 WriteConsoleはコンソールへの文字列出力ですが、長大な(数メガ程度)ファイル内容を出力するような場合は正しく動作しません。これは内部のバッファがオーバーフローするため
と推測されますので、小刻みに出力する工夫が必要となります。


l         本プログラムで実践すべき課題

 ConsoleクラスにはPrintf関数が存在します。C++プログラミングを理解する最大のポイントは変数やクラスの「名前」は単にソースコード中にしか存在せず、すべてアドレス参照によって解決されている事実を理解することです。つまり「ポインターが理解できない」という表現は不適切で「変数という概念に惑わされている」という表現が適切なのです。

Printf("hInstance %p\n", hInstance);はどんな表示を行うでしょうか?
Printf("WinMain %p\n", WinMain);はどんな表示を行うでしょうか?

 新たな変数や関数を追加しそれがメモリ中にどう配置されどう呼ばれるのかを理解してください。またソースコード中にF9でブレークポイントを設定し変数やオブジェクト(クラス実体)がどのアドレスに存在するのかを確認する方法に習熟してください。

 もし余裕があれば独自の文字列クラスを設計し動作を確認してください。この独自文字列クラスというテーマは大変奥深いものですが、WIN32環境でコピーコンストラクタや代入演算子に正しく応答する文字列クラスを設計することはC++に対する理解を深める最も有効な手段です。



 C++でのコーディングではメモリ中に何がどう配置されているのかのイメージを持つことが重要です。ある程度CPU寄りの配慮をすることによって他の環境では実現できない切れ味が実現されていることを理解してください。

 

©クロダ ソフトウェア サービス programmed by hidebou