戻る
「ばけらった ハウス」トップページに戻る



4、WindowsプログラムとWin32 API


 4−1、Windowsの話
 4−2、Windowsでの最適化・高速化
 4−3、バッチ処理とかマクロで処理とか


VC++でWindowsプログラムを書く場合には、大きく分けて2通りの書き方があります
@MFC(Microsoft Foundation Class)
VC++に付属されている統合開発環境で、開発の生産性向上と簡易化を図るために使用される
MFCを使用した場合は、MFCに依存する(大きなクラスに依存する)ため実行速度が遅くなる
メインウインドウクラスをベースに、追加・拡張することで、アプリケーションの開発がスムーズに行われる
「Borland C++ Builder」などもフォームと呼ばれるメインウインドウクラスを元に拡張・追加する
「Microsoft Visual Basic」もこのようなスタイルになります
また、MFC・「Borland C++ Builder」・「Microsoft Visual Basic」で作成した実行可能ファイルは
ランタイム(C:\WINDOWS\SYSTEMフォルダに〜.dllを入れる)が必要になります
@−2ATL(Active Template Library)
  MSDN:ATLの概念
MFCだと、使い物にならないくらい、重たくなったり巨大化したりするので、
COM(Component Object Model))ベースの、軽量プログラム用なのが、<ATL>。 …らしいです。
COM自体、管理人は使わないので、ぜんぜん知りませんが、
昔は、ネット回線が細かったせいもあってか、
余計なデータが入っている「*.exe」よりも、「*.com」な、怪しいアプリがよくあったものでした。

最近(2011年06月)の、MSDN内のVS2010の講座でも
 ATL による COM の利用
 ATL による COM コンポーネントの基本構造の再確認
があるので、軽量なアプリなら、ATLで作れ…ってことなんでしょうか

管理人的には、
簡潔な文章・行数で済むプログラムは、
  1、一見、簡潔な初期化に見えるけど、内部処理を見てみると、かなり大量の初期化処理を行う(不具合回避のための処理がたくさんあったり)
  2、特殊な関数を使う場合、自分のプログラムには不用の引数も渡さなければならないので、結局手間がかかる(キャストしたり別の変数に移したり、なんかの関数の戻り値を渡したり)
  3、一見、簡潔な終了処理に見えるけど、内部処理を見てみると、かなり大量の終了処理を行う (1と3は同じ内容ですが)

外部コンポーネントにアクセスすると、必ずこれらが絡むのは間違いないので、
ある程度自作できる部分は、自作したほうが、確実に高速化・メモリ的に軽量化されます
適当に、ヘッダやライブラリ追加するより、
自分のプログラムに必要最小限の機能だけを使うように(使えるように組む)したほうがいいです。 めんどくさいですが

AWin32 API
WindowsのAPI(アプリケーション プログラム インターフェイス)をベースに作成する
プログラマは、使用したい機能をAPIから探して記述しなくてはならず、生産性は悪い
使用したい機能のみを記述するため、実行速度は速くなる
MFCを使用してもWin32APIの関数を使用することは出来ます
基本的にはAPIベースの場合はMFC関数を使用できないが、MFCを使用している場合はWin32APIを使用できます
リソースで作成したダイアログ機能(ポップアップのコメント等)が使用できなくなります

その他の拡張機能
DirectXやOpenGLなどがあります
DirectXは、通常のAPIでは使用できない「ハードウェアを直接操作」することができます(DirectX SDK)
OpenGLは、GeForce,RADEONなどでの3Dアクセラレータの拡張機能を使用するために必要です
これらは、MFCベース・APIベースどちらでも使用できますが、手間がかかります
また、MMX・SSE・SSE2・3DNow!などのCPU拡張命令を使用する場合には、
アセンブラ(__asm)で記述するので、上記@Aでも使用できます。
管理人は「VC.Net」を使用していないので詳しくは知らないのですが、
どうやら拡張命令をコンパイラが使用してくれるらしいです
しかし、MFCと同じく「.Net」用のランタイムが必要になります
「.Net」は全言語対応らしいですが使ってないので詳しくは知りません(行単位で混在できるらしい)
  管理人は、
  MFCは、基本的に使いませんが、
  WinMe時代は、VC++6・Win32API・DirectX7、DirectX 8 SDKを使用して記述してました。
  今は、WinXPで、VC++8(VS8 2005)、DirectX 9 SDKを使ったり、使ってなかったりしてます。


4−1、Windowsの話

1982年、Intel 80286 16bit CPUにはリアルモード(実アドレス)と
プロテクトモード(保護アドレス)があり、
プロテクトモードによるマルチタスク/マルチユーザーシステムが実現されます。
これにより、メモリ管理・保護機能・タスク管理などが可能になりました(Windows3.1とか)

1985年、Intel 80386 32bit CPUに32bitプロテクトモードが追加され、
32bitのWindows(Win95以降)はこちらを使用している

プロテクトモードでは実アドレス(パソコンについているメモリのデータ場所)を直接指定したり、
直接変更することは出来ません
Win32アプリケーションでアドレスを操作する場合には、プロセスごとに割り当てられた
4ギガの仮想アドレス空間のアドレスを操作・参照することになります
例外として、DirectXインターフェイスを使用しグラフィック・サウンドなどのメモリに直接アクセスする場合は、
実アドレスを指定して操作できます
この場合は、ロック→処理→ロック解除が基本的なアクセス方法になります


●Win32のプロテクトモード
○各アプリケーション(プロセス)には独自のアドレス空間(仮想アドレス)が
 4ギガバイト分割り当てられる

○システムはプロセスが使用するメモリを、システムメモリ(RAM)から割り当ててくれる

○メモリ使用量が多いなら、仮想メモリ(ページスワッピング)を使用し
 ハードディスクからメモリを割り当ててくれる

○仮想メモリからは計算できないので、一度メモリ(RAM)に写す作業が入る

○アプリケーションからは、システムが返す仮想メモリのアドレスしか参照できない

○実際には4ギガの空間をすべて使用できない。
 仮想メモリのマッピング(配置?)は、それぞれ決められた割り当てが行われているため(下図参照)
 しかし、メモリマップトファイルなどで別の仮想メモリ(ハードディスク)を作成し使用できる

これによって、個々のアプリケーションは個別のメモリ空間を持つことになり、
仮想アドレス内のデータを変更をしても、他のアプリケーションには影響を与えることがなくなりました

と、このようにアプリケーションはOSからメモリ空間が割り当てられます
これによってアプリケーション同士(プロセス間)の同期・通信処理は手間がかかるようになりました


上図のようにそれぞれ、各プロセスは独立して動いているため、「同期」という処理が必要になります
「同期」に関しては、ゲームプログラムというより、システム系のプログラムで使用されています
通信対戦をするならDirectPlayがありますし、同じパソコン内で同時起動して対戦するのではないなら、
さほど気にすることでもありません


●スレッド
「スレッドA君は山へ芝刈りへ」「スレッドBさんは川へ洗濯へ」と別々の処理ができるので、
スレッドAは描画のみ、スレッドBを入力監視などに使えば便利です
ですが、同一のメモリアドレスにアクセスするスレッド同士には同期が必要で、
これにはクリティカルセッションを使用します

クリティカルセッションはプロセス内のスレッドが同時にアクセスするのを防いでくれます
スレッドAで「変数Iを加算」・スレッドBで「変数Iを加算」・スレッドCで「変数Iを比較」
これは、問題のないようなこの処理は、スレッド同士が同時にアクセスすると変数の値がぶっ飛ぶ場合があります
各スレッドでこのような処理が一ヶ所でもあると、アクセスする確率は0%でないので、
@スレッド開始時にクリティカルセッションを作成(初期化)
Aスッレッド内の目的の行の前でクリティカルセッションを開始
B処理をする
Cクリティカルセッションの終了
Dスレッド終了時にクリティカルセッションを解放
をすることで回避できます

スレッドは、CreateThread関数で作成できます
が、WindowsNT系ではセキュリティ属性を指定しないと、スレッドを作成した時点で強制終了します(メッセージが出たはず)
このセキュリティ属性は、ちょっと初期化して、ポインタを渡せばOK


HANDL hThread_A = NULL;// スレッドAのハンドル
HANDL hThread_B = NULL;// スレッドBのハンドル
CRITICAL_SECTION Critical_Se; // クリティカルセッション



// メイン
int APIENTRY WinMain(HINSTANCE hinst,  HINSTANCE hprev_inst,  LPSTR Cmd_Line,  int Cmd_Show)
{
    InitializeCriticalSection( &Critical_Se );// クリティカルセッションの初期化

    // メッセージループなど処理
    DeleteCriticalSection( &Critical_Se);// クリティカルセッションの削除
}



void WINAPI Thread_A()
{
    EnterCriticalSection( &Critical_Se );// クリティカルセッション開始
    // クリティカルセッションが使用されているなら、↑ここで順番待ち


    // ↓ここから

    // ↑ここまでが、スレッドAで、安全な処理ができる部分


    LeaveCriticalSection( &Critical_Se );//クリティカルセッション終了

    DWORD Thread_ExitCode = 0;
    GetExitCodeThread( hThread_A, &Thread_ExitCode );// スレッド終了コード

    ExitThread( Thread_ExitCode );// スレッドの終了
    hThread_A = NULL;
}



void WINAPI Thread_B()
{
    EnterCriticalSection( &Critical_Se );// クリティカルセッション開始
    // クリティカルセッションが使用されているならここで順番待ち


    // ↓ここから

    // ↑ここまでが、スレッドBで、安全な処理ができる部分


    LeaveCriticalSection( &Critical_Se );//クリティカルセッション終了

    DWORD Thread_ExitCode = 0;
    GetExitCodeThread( hThread_B, &Thread_ExitCode );// スレッド終了コード

    ExitThread( Thread_ExitCode );// スレッドの終了
    hThread_B = NULL;
}



// 適当な関数
void ...()
{
// セキュリティ属性の初期化
    SECURITY_ATTRIBUTES Security = { sizeof(SECURITY_ATTRIBUTES), NULL, true };

    DWORD Thread_A_ID = 0;// スレッドID
    DWORD Thread_B_ID = 0;// スレッドID


// スレッドA作成
    hThread_A =
      CreateThread(
        &Security,
        0,
        (LPTHREAD_START_ROUTINE)Thread_A,
        NULL,
        0,
        &Thread_A_ID );
// この関数後にスレッドハンドル hThread_Aが NULL なら失敗


// スレッドB作成
    hThread_B = CreateThread(
        &Security,
        0,
        (LPTHREAD_START_ROUTINE)Thread_B,
        NULL,
        0,
        &Thread_B_ID );
// この関数後にスレッドハンドル hThread_Bが NULL なら失敗

}




●グラフィック
・GDI
通常目に見えている全ての物はGDIが表示してくれています
アドベンチャーゲームなどでは、ビットマップをシステムメモリ内で直接処理しても十分いけます


・DirectX7以前
DirectX7は2Dゲームなどでは現役です
3Dテクスチャと2Dサーフェイスが同じDirectDrawSurfaceなのでなにかと便利


・DirectX8・9
2D表示機能は、Direct3Dと切り離されています
ムービー再生用の「DirectDrawストリーミングインターフェイス」
2D用のDirectDrawインターフェイスも残っていますが、
ゲーム中にムービー再生する以外にはお世話になることはないでしょう
3Dを使用しないならDirectX7の方がええです
管理人はDx7のDirectDrawより、Dx8でのDirect3Dの方が覚えやすかったです
内部的にはこのようになってます(使ってても実感はないですね)
実際に困るのはこの段階ではなく「こんな感じならいいのになぁ」と思ったことを実装する時です
APIやDirectXの中に
思った通り関数があればいいのですが(探すだけでも結構大変ですが)、大抵ありません

とりあえずサンプルソースだけ置いてあります:6、DirectX



●ウインドウの構造
いつも目にしている「ウインドウ」ですが、構造にはいくつかの決まった形があります
これをウインドウスタイルといいます
HTMLのJAVAスクリプトで「window.open("gif\\walk.gif","","toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, close=no, width=32, height=32"); )」
などの命令にもあるように、ウインドウに何が付いているかを決めることができます

◇タイトルバー(Caption)
ウインドウの一番上の部分です
ここには、アプリケーションの名前や、作業中のファイル名などが表示されますが
これはプログラマが表示させているもので、表示させないことも出来ます
タイトルバーがあるウインドウが必ず細線の境界がついてます
右端には左から順に
_「最小化ボタン」 □「最大/復元化ボタン」 X「終了ボタン」
があります
終了ボタン以外はCreateWindow()で指定しないと使用不可になってます
タイトルバーの領域が無いウインドウも作成できますが、各ボタンも無くなってしまいます

◇最小化ボタン(MinimizeBox)

◇最大/復元化ボタン(MaximizeBox)

◇スクロールバー(Scroll)
 水平スクロール・垂直スクロールバーは、ウインドウ作成時に指定すると付く

◇境界線(Border)
 サイズ境界線の方が目立っているので分かりにくいですが、一応あります

◇サイズ変更枠(ThickFrame)
 ウィンドウのサイズ変更ができる太線の枠
 アイコンが「⇔」になる部分です

◇クライアント領域(Client)
 上記の部分以外の内側の領域です
 グラフィックの描画は基本的にこの領域にしかできません
 また、この領域のみのウインドウも作成できます

 リージョン・ビットマップ(DIB形式のファイルを直接読み込んでとか、DirectXサーフェイスでもいいですが)
 を使えば、特殊な形状のウインドウも作成できます
 ☆形とか○型とか人形などの、ウィンドウです
 が、
 いろいろと、めんどくさいので、ここでは説明しません。



●メッセージ
 Windowsではウインドウ処理にメッセージを使用しています
 これは、「ウインドウ内でマウスがクリックされた」とか「最大化ボタンを押した」など
 基本となるメッセージをOSが送ってくれるので、それをメイン関数内で受信・変換・送信して処理します
 メッセージはウインドウプロシージャと言う監視用のコールバック関数を使用して受け取ります
 また、プログラム内から各ウインドウにメッセージを送ることもできるので、
 キーボートの「E」を押すと終了するアプリケーションなどができます
 これらのメッセージはVCに付属されてるツール「SPY++」で確認することができます


 @WinMain関数内にfor文・while文などでループさせ、メッセージを受け取ります
 A受け取ったメッセージを変換します(TranslateMessage関数)
 B変換したメッセージをウインドウプロシージャに送ります(DispatchMessage関数)
 Cウインドウプロシージャで各メッセージ用の処理をします
  処理しないメッセージは、DefWindowProc関数でデフォルト処理します

// ウインドウプロシージャ
// hwnd:ウインドウハンドル msg:変換されたメッセージ
// wParam・lParam:メッセージパラメータ(各メッセージの情報)
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM  wParam, LPARAM  lParam)
{
    // このswitch文でメッセージを受け取ります
    switch (msg)
    {
    case WM_LBUTTONDOWN :
        // ここに左クリックしたときの処理を入れる
        break;

    // 右上のXボタンなどが押された場合
    case WM_CLOSE :
        DestroyWindow(hwnd);
        break;

    // これがないと終了できない
    case WM_DESTROY :
        PostQuitMessage(0); // 終了メッセージ
        break;


    // 処理をしないメッセージは、ここ↓で処理
    default :
        return ( DefWindowProc(hwnd, msg, wParam, lParam) );
    }

    return 0;
}



// メイン関数
int APIENTRY WinMain(HINSTANCE hinst,  HINSTANCE hprev_inst,  LPSTR Cmd_Line,  int Cmd_Show)
{
    // ウインドウの基本設定
    WNDCLASS wc;
    memset( &wc, 0, sizeof(WNDCLASS));

    wc.style = CS_DBLCLKS;    // ダブルクリックを使用可能に
    wc.lpfnWndProc = WndProc;    // ウインドウプロシージャの指定
    wc.hInstance = hinst;    // インスタンスの指定
    wc.hCursor =
        LoadCursor(hinst, MAKEINTRESOURCE(IDC_ARROW));    // リソース内のカーソルを指定
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);    // 背景色

    wc.lpszClassName = "登録名";    // この設定の登録名

    if( !RegisterClass(&wc) )    // 設定を登録
        return FALSE;


    // ウインドウを作成
    hWnd = CreateWindow(
            "登録名",    // 登録されている設定の名前
            "ういんどう",    // ウインドウのタイトル
            WS_CAPTION | WS_SYSMENU,    // タイトルバー・終了ボタン有効
            0, 0,    //  左上の位置をX:0 Y:0
            640, 480,    //サイズを640x480(クライアント領域+外枠)
            NULL,
            NULL,
            hinst,    // インスタンスの指定
            NULL);


    MSG msg;    // メッセージを格納するMSG構造体

    while( GetMessage(&msg, NULL, 0, 0) )    // WM_QUIT(終了)メッセージが入ると抜ける
    {
        TranslateMessage( &msg );    // 仮想キー メッセージを文字メッセージに変換
        DispatchMessage(  &msg );    // ウィンドウ プロシージャにメッセージを送る
        Sleep(1);    // プロセッサ使用率を軽減するため、Sleep()関数を入れておきます
    }

    return 0;
}

DialogBox関数・CreateDialog関数で作成されたダイアログはウインドウプロシージャのみで動作します
TranslateMessage関数・DispatchMessage関数を使用して監視しなくてもよい
ダイアログのメッセージの変換→転送は自動で行ってくれます
これは、ウインドウ内のボタン・エディット・リストなども同じく自動で行ってくれます

ウインドウ内にある「ボタン」なども、ウインドウと同じようになっています
ゲーム中のメニューや特殊なボタンは例外ですが、標準の灰色の「ボタン」などです
メッセージボックスなどで「はい」「いいえ」などで使用されています
ボタンは子ウインドウとなり(正確には違いますが)、
ボタンを押すと「ボタンの領域内でクリックした」とメッセージを親ウインドウに送り、
親ウインドウは「WM_COMMAND」メッセージとパラメータを、ウインドウプロシージャに送ります
親ウインドウ作成時に指定したプロシージャで、このメッセージを受け取り処理します

メニューなどはウインドウとは違いますが、ボタンと同じように「WM_COMMAND」メッセージを送ってきます



●ハンドル
Win32APIではなにをするにしても、ハンドルが必要です
「インスタンスのハンドル」「ウインドウのハンドル」とかです
GDIオブジェクトのペン・ブラシ・ビットマップ・デバイスコンテキストなどもハンドルを使用します
実際、「ハンドル」はポインタだと思って使えば問題ないと思います
通常、「int *i = new int; 」と「delete i;」はセットで使います
ハンドルは、「 ハンドル = Create〜(); 」と「 Delete〜(ハンドル); 」というセットになります
ハンドルは、Create〜時に無効な場合はNULLになり、
無効にするときは、初期化時またはDelete後にNULLを入れます
「delete」後のポインタは無効になるのと同様に、ハンドルもDelete〜関数で無効化され、リソースが解放されます
消さないとリソースがどんどんなくなってしまいます



●デバイスコンテキスト(DC)
GDIオブジェクトを表示するときに必要なのが、デバイスコンテキストです

「デバイス コンテキスト」
グラフィック オブジェクトと、
それに関連する属性・出力に影響するグラフィックモードを定義する構造体

 「グラフィック オブジェクト」には
  線描画用のペン
  塗りつぶし用のブラシ
  利用可能な色のセットを定義するブラシ
  画面のコピーやスクロールで使われるビットマップ
  クリッピングなどの操作で使われるリージョン
  描画操作で使われるパス
などがあります。

アプリケーションは
デバイス コンテキストを直接アクセスすることはなく、さまざまな関数を呼び出して間接的に構造体を操作します

使用する前にペン・ブラシ・ビットマップなどのオブジェクトを作成し、
これをデバイスコンテキストに登録して使用します(作成したオブジェクトのハンドルを渡す)
デバイスコンテキストに「ペンで線を引いてよ〜」と頼むと、関連付けされたペンで線を引きます

 注意しないといけないのが
変更前のオブジェクトを保存して元に戻さないと
アプリ終了後もその状態が残ります
画面が化け化けらったになったり、文字の色のデフォルト値も変更されてしまうので、再起動しないと直りません
使ったものは、ちゃんと元に戻しましょう(変更する前に事前の設定を保存しておかないとなりません)


通常のデバイスコンテキストを利用した描画方法を紹介します
WinMain()関数は除かれています
また、DIB(デバイスに依存しないビットマップ)を使ったDIBSECTIONでの描画方法もありますが、ここでは取り扱いません
この例では、カレントディレクトリにある「Bmp」フォルダに入っているビットマップCG1・CG2・CG1_M・CG2_Mを読み込み、 これをマスク処理して描画します
CG1など表示たくない部分を黒く(R0 G0 B0)したものを
CG1_Mなどは、CG1と同じ座標に表示したい部分を黒く(R0 G0 B0)して、透過したい部分を白く(R255 G255 B255)にしたファイルを作ったものとします

// 描画するウインドウのハンドル
HWND hWnd = NULL;

// 動作中は常に下記の「hVS」ビットマップを選択している
// hDC(ウインドウのDC)に「SRCCOPY」で転送される
HDC VSDC = NULL;

// ビットマップ用。ロードしたビットマップを選択している
// VSDCに「SRCPAINT」で転送される
HDC BmpDC = NULL;

// ビットマップ用。BmpDCの白黒マスクなどを入れる
// VDDCに「SRCAND」で転送される
HDC BmpDC_M = NULL;

HBITMAP hVS,// VSDCのビットマップ
    hVSOld,    // VSDCの前の状態
    hBmp1,    // ビットマップ1
    hBmp2,    // ビットマップ2
    hBmp1_M,    // ビットマップ1のマスク
    hBmp2_M;    // ビットマップ2のマスク



// ウインドウメッセージ処理
LRESULT CALLBACK WndProc(HWND hwnd,  UINT msg,  WPARAM wParam,  LPARAM lParam)
{
    switch (msg)
    {
    /* ウインドウ作成時にデバイスコンテキストとGDIオブジェクトを初期化・ロードしてます
    (別にここでなくてもいいです)
    先に作れるものは作り、オーバーヘッドを減らします*/
    case WM_CREATE :
        { // ←この括弧は、case内でローカル変数を使うため、{}でブロックします

            // ウインドウのDCを取得
            HDC hDC = GetDC(hwnd);

            // hDCと互換性のあるDCを作成します
            VSDC   = CreateCompatibleDC(hDC);
            BmpDC = CreateCompatibleDC(hDC);
            BmpDC_M = CreateCompatibleDC(hDC);

            // hDCのビットマップと互換性のあるビットマップを作成します
            hVS = CreateCompatibleBitmap(hDC,640,480);

            // ウインドウから取得したDCを解放します
            ReleaseDC(hWnd,hDC);


            // VSDCにビットマップハンドルを渡します
            hVSOld = (HBITMAP) SelectObject(VSDC, hVS);


            // ビットマップをファイルから読み込みます
            // 「Bmp」フォルダにあるCG1・CG2・CG1_M・CG2_Mを読み込みます
            hBmp1 = (HBITMAP)LoadImage(NULL, "Bmp\\CG1.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE| LR_CREATEDIBSECTION);
            hBmp2 = (HBITMAP)LoadImage(NULL, "Bmp\\CG2.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE| LR_CREATEDIBSECTION);
            hBmp1_M = (HBITMAP)LoadImage(NULL, "Bmp\\CG1_M.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE| LR_CREATEDIBSECTION);
            hBmp2_M = (HBITMAP)LoadImage(NULL, "Bmp\\CG2_M.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE| LR_CREATEDIBSECTION);
        }
        break;


    // 描画処理
    case WM_PAINT :
        {
            /* BmpDC BmpDC_Mにビットマップオブジェクトを渡します
            DCには指定されたビットマップが渡され、戻り値として以前のハンドルが戻ってきます
            この状態で初めてビットマップをGDIで操作できます
            つまり、ビットマップにアクセスすためにデバイスコンテキストを通さないといけないわけです*/
            HBITMAP hBmpOld   = (HBITMAP)SelectObject( BmpDC, hBmp1),
                      hBmpOld_M = (HBITMAP)SelectObject( BmpDC_M, hBmp1_M);

            // VSDCのビットマップを黒く塗りつぶす
            BitBlt( VSDC, 0, 0, 640, 480, NULL, 0, 0, BLACKNESS );

            /* hBmp1_MをAND転送(SRCAND)してから、hBmp1をOR転送(SRCPAINT)する
            すでにVSDCにはhVSビットマップが選択されているので、このビットマップに転送される*/
            BitBlt( VSDC, 0,0,32,32, BmpDC_M, 64, 64, SRCAND );
            BitBlt( VSDC, 0,0,32,32, BmpDC, 64, 64, SRCPAINT );

            /* デバイスコンテキストに別のビットマップを指定します
            以前の状態はhBmpOld・hBmpOld_Mに残しているので、ここではビットマップオブジェクトのハンドルを渡すだけです*/
            SelectObject( BmpDC, hBmp2);
            SelectObject( BmpDC_M, hBmp2_M);

            // hBmp2_MをAND転送(SRCAND)してから、hBmp2をOR転送(SRCPAINT)する
            BitBlt( VSDC, 0,0,32,32, BmpDC_M, 64, 64, SRCAND );
            BitBlt( VSDC, 0,0,32,32, BmpDC, 64, 64, SRCPAINT );

            // 転送が終わったので元に戻します
            SelectObject( BmpDC,   hBmpOld );
            SelectObject( BmpDC_M, hBmpOld_M );


            /* BeginPaint()によって再描画に必要な部分が更新されます
            BeginPaint() 〜 EndPaint()を使用しないとメニューなどが更新されません*/
            PAINTSTRUCT ps;
            HDC hDC = BeginPaint( hwnd, &ps );

            // VSDCに入っているhVSビットマップに転送されているので、これをWindowのDCに転送
            BitBlt( hDC,  0, 0, 640, 480, VSDC, 0, 0, SRCCOPY );

            // WindowのDCに文字を表示する
            TextOut( hDC, 0, 40, "文字表示だよ", lstrlen("文字表示だよ"));

            EndPaint( hWnd, &ps );
        }
        break;


    // アプリケーション終了
    case WM_DESTROY :
        SelectObject(VSDC,hVSOld);
        DeleteObject(hVS);

        DeleteObject(hBmp1);
        DeleteObject(hBmp2);
        DeleteObject(hBmp1_M);
        DeleteObject(hBmp2_M);

        DeleteDC(VSDC);
        DeleteDC(BmpDC);
        DeleteDC(BmpDC_M);
        break;
    }

}


DirectDrawやDirect3Dでは、抜き色・透過色と言われる色を指定して、その色の部分だけを抜くことも出来ます
また、Direct3Dではアルファブレンディングで透過率を決めることができます
GDIでDIBSECTIONを使用した場合や、DirectDrawSurfaceのポインタを取得した方法でもアルファブレンディングを自作できます




4−2、Windowsでの最適化・高速化

●メモリ
Win32では、メモリ領域がすべてWindowsによって管理されています
Windowsオブジェクト、クラス・関数・変数・配列の定義、メモリ割り当て命令などで割り当てられた領域(仮想メモリ)が
システムメモリなのかHDD(ハードディスク)上のディスクメモリなのかは分かりません
この割り当てを「コミット」といいます

HDDの割り当てでは処理が遅くなります
現行PCでのハードディスクは50Mbyte/sほどの速度を持っているので昔ほど気になることではなくなりましたが、
システムメモリもDDR時代になり、1Gbyte/s〜5.8Gbyte/sなどと恐ろしいほど速く
そしてプラットフォームが64bitの場合、メモリ4ギガ越えの大容量になりました
メモリはシステムに割り当ててもらえたほうが速くなります

ユーザーに不要なプログラム・サービスを停止してもらうことで、利用できるシステムメモリ量が増え
プロセス数・スレッド数も減るためパフォーマンスが向上します
某社のバトルフィールドシリーズも代々説明書にもそう書かれてます


また、DirectXのオブジェクトは、割り当て場所を指定できます
ローカルメモリ(グラフィックカードのメモリなど)・非ローカルメモリ(オンボードグラフィックカードのメモリなど)
システムメモリ・ディスクメモリが対象になります
この場合、ローカルメモリを指定しても容量が足りない場合は、
システムメモリ・ディスクメモリに割り当てを変更することができます


●速度低下を招く処理
メモリを直接編集する場合は速度が低下しやすいです
GDIでDIBSECTIONを使用して操作する場合
DirectDrawでサーフェイスを操作する場合
Direct3Dで頂点データを操作する場合
DirectSoundでサウンドバッファを操作する場合
などです
グラフィック・サウンドなどは、インターフェイス内のメモリ(ローカルメモリ)から
システムメモリにコピーして、その後レジスタにデータを一時写してから処理し、
処理後戻したほうが速くなります
レジスタに転送しない(アセンブラを使用しない場合)でも、システムに移すだけでパフォーマンスは向上します
また、システムメモリ内の場合、MMXなどの処理速度も向上します
つまり、CPUに近いメモリのほうが速くなります

しかし、グラフィックカードなどでは頂点シェーダー
サウンドではDolbySoundエンコーダなどのハードウェア拡張機能を使用する場合は
ローカルメモリのままのほうが速くなります
この場合は、インターフェイスカード内のメモリのほうが近いので速くなります



●ダイアログ(特にWinXP)
最近、最適化方法を知ったため調査中…

分かっている症状と回避方法

▼ダイアログにあるオブジェクトに対してのデータ転送
症状:
リストボックスなどにデータを転送する際に、
1つずつ転送する場合に起こる速度低下

回避方法:
リストボックスなどにテキストデータを転送する際に、
複数のデータをまとめて転送するように切り替える
または、オリジナルのリストボックスを作成する
結局、MFCを使用しないで、リソースからCreateDialog()で作成した場合は
SendDlgItemMessage()などで1個ずつ送るしかないので、MFCを使用したほうが手間がかからなそうです

▼ダイアログの描画
症状:
再描画する際に処理速度低下

回避方法:
リストボックスなどにデータを入れると自動で再描画されます
これを防ぐにはBeginPaint()・EinPaint()の間に、データを転送する命令を入れます

というのが、MSDNのオンライン講座(音声と静止画が見れる)でやってました
特に、Win9x系で楽々動いていたアプリが、WinXPになり突然速度低下した場合は、ダイアログ処理に問題があるようです






4−3、バッチ処理とかマクロで処理とか


●DOSみたいなバッチを使って作業効率を上げてみたりする

Windowsでも、DOSみたいなコンソールがあるので、バッチ処理ができます
例えば、
管理人は、ゲームのMODする場合などでよく使う、
  単品ファイルを、複数のフォルダに、こぴぺしたい場合
です。
テキストファイルを新規作成して、
XCopy A2OA_WF2_MS-06D1.Chernarus\MS_Script\_expFull.sqs A2OA_WF2_MS-06D1.Chernarus_lite\MS_Script /s /y
XCopy A2OA_WF2_MS-06D1.Chernarus\MS_Script\_expFull.sqs A2OA_WF2_MS-06D1.Porto\MS_Script /s /y

XCopy A2OA_WF2_MS-06D1.Chernarus\common A2OA_WF2_MS-06D1.Chernarus_lite\common /s /y
XCopy A2OA_WF2_MS-06D1.Chernarus\common A2OA_WF2_MS-06D1.Porto\common /s /y
と、テキストに書いて、このファイルをバッチの拡張子「.bat」にします。
実行する場所も、これらのフォルダがサブフォルダとしてある場所で実行するとします。
これを実行すると、
上の2行で、
サブフォルダ「A2OA_WF2_MS-06D1.Chernarus」の「MS_Script」フォルダ内の「_expFull.sqs」が、
 「A2OA_WF2_MS-06D1.Chernarus_lite\MS_Script」

 「A2OA_WF2_MS-06D1.Porto\MS_Script」
に入ります。

下の2行では、
サブフォルダ「A2OA_WF2_MS-06D1.Chernarus」の「common」フォルダの中身全部が、
 「A2OA_WF2_MS-06D1.Chernarus_lite\common」

 「A2OA_WF2_MS-06D1.Porto\common」
に入ります。

 「/s」は、サブフォルダもコピーの対象にするかで、
 「/y」は、上書き確認するかどうかです。
単純なファイル作業だけなら、バッチのほうが使いやすいです。



関係ないですが、
最近、知ってビックリした、分割ファイルの合体方法

Copy /b  abc.7z.001+abc.7z.002 def.7z

<abc.7z.001> <abc.7z.002>
という、分割ファイルがあった場合、
<def.7z>という、合成したファイルを作ります。
「/b」は、バイナリ扱いするオプションらしいです
パスさえ間違えてなければ、プロンプトからでも、バッチからでも、動くと思います。



●マクロを使ってまとめて処理する

マイクロソフト製品の場合、VBベースのマクロを使うことができます
Word・Excelなど、Officeの場合は、VBでのマクロになります。
一連の操作をまとめたり、変な関数とか使ったりするのにマクロを使います。
 「新しいマクロの記録」
を実行すると、
アプリ内で行われた、メニュー操作・キーボード操作・マウスのクリックで行った操作
などが、記録され、あとから実行できます。
  文字を検索 → 貼り付け
とか、
  選択されているセルから一定範囲を選択 → コピー → 数行下を選択 → 貼り付け
が一発でできます。
セル内に入れる数式からでは、他のセルの値を直接書き換えできなかったりしますが、
マクロを使うと、
  選択されているセルの値と、どこかのセルの値を変数に入れる → なんか計算する →
  計算結果を、どこかのセルに入れる が、
Word・Excelでは、マクロの実行をすると、いちいちマクロの確認が出てくるので、
とてつもなく、連続作業には向きません。
「Visual Studio」にも、
VBではないですが、同じような感じでマクロが付いているので、
一連の操作を覚えさせて、実行させることができます。
管理人が、よく使うのは、
「文字・数値の書き換え」です。
巨大な配列などに、固定値を入れる場合や、
若干違う、+1した数値を、連続で入れる場合などでも使えます。
  「検索 → 置き換え → 加算 → 加算したのをコピーする」 の操作をまずマクロに記憶させ、「加算」部分を後から手書きで追加すると
以前の数値と同じ具合で、連続で加算した値に変更できます。


Microsoft Excelを使う場合でも、
こういった、ある程度決まった数値を加算するだけなら、
マクロを作って
  ・選択されているセルの今入っている値
  ・に、違うセルの値分、加算
  ・して、次の別の下のセルを選択する
  ・ような、VBのマクロのボタンを作って置く
と、いうような感じにすると、
マクロ起動する自作ボタンさえ作っておけば、
最初に、
セルを選ぶだけで、その下の行に、次々と特定の値を加算していくことができるボタンが作れます
数値の加算だけではなく、文字列でも置き換えするのも作れます
管理人は、
TRPGの、ソードワールド用に、
レーティングシート読み用などの、複数のマクロを組み込んだ、表計算シートを作って使ってました。


WZえでったでも、
同様に、マクロ機能がありますが、ぜんぜん使ってません
WZは、
マクロよりも、
特定の文字(あいうえお。なら「あ…お」を検索できたり)
長い16進が検索しやすい(検索方法はめんどくさいですが「\xx数値」で、長い16進を検索できます)
ので、使いやすいです
どちらかと言えば、ただ単に軽いので、WZ使っているだけですけど。
ここのHTMLも、アームドアサルトのスクリプトも、WZで書いてます。


プログラムなんて、テキストなので、エディッタ自体は、なんでもいいとは思います
が、
VCも、WZも、文字・背景色も変更できて、
特定の文字だけ「色分け」できる機能のほうが重宝します
色分けできるかできないかが、エディッタの価値だと思ってます
WZでは、
テキスト用・HTML用・C言語用・アームドアサルト用
など、別々に色分けできるので、便利です





戻る


e-Words 情報・通信用語検索:
「ばけらった ハウス」トップページに戻る


inserted by FC2 system