コマンドラインコンパイラを使ってみる

さて、前回までは VC++ の IDE (統合開発環境)でコンパイルを行なってきましたが、MS-DOSプロンプトからコマンドラインでコンパイルを行なうことができます。実は、HSPスクリプトで使用するためのマシン語作成には、こちらの方法が適していたりするのです。

ソースファイルの作成

コールバック関数のソースファイルは、前回と同じものを使用します。

ソースファイル enumwnd2.c

#include <windows.h>

// HSPからの情報を渡すための構造体
typedef struct {
    HWND *phwnd;    // ハンドルを格納する配列変数アドレス
    int   maxnum;   // 最大数(配列の要素数)
    int   cnt;      // カウンタ
} ENUMWND_DATA;

BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
    // lParam は ENUMWND_DATA 構造体アドレス
    ENUMWND_DATA *pData = (ENUMWND_DATA *)lParam;

    if (pData->cnt >= pData->maxnum)
        return FALSE;
    pData->phwnd[pData->cnt] = hwnd;
    pData->cnt++;
    return TRUE;
}

コンパイルを行なうための作業ディレクトリを決定し、そこにソースファイルを保存しておきます。今回は、ソースファイル『 D:\hspmcn\enumwnd2.c 』として保存してあることにします。

次に、MS-DOSプロンプトを起動してから、作業ディレクトリに移ります。通常のWindowsプログラミングの場合と違って、MS-DOSの場合、「カレントドライブ」の概念があり、「カレントディレクトリ」はそれぞれのドライブに1つずつ割り当てられるので、ディレクトリ移動はドライブとディレクトリで別々に行なう必要があります。作業ディレクトリに移る場合、まず、プロンプトのカレントディレクトリのあるドライブが、作業ディレクトリのあるドライブと別のドライブであった場合には、半角文字で『(作業ドライブ名):』と入力して [Enter] キーを押します。例えば、Dドライブなら『D:』というようにします。ドライブを移したら、次に cd コマンドを使用して、ディレクトリを移動します。『cd (作業ディレクトリ名)』と入力して、[Enter]を押します。このときのディレクトリ名は、絶対パス(フルパス)で指定しても、カレントディレクトリからの相対パスで指定しても、どちらでもよいです。

今回の場合のDOSウィンドウの表示は以下のようになります。強調表示されている部分は、プログラマが入力したものになります。ここではディレクトリ名を相対パスで指定しています。

Microsoft(R) Windows 98
   (C)Copyright Microsoft Corp 1981-1998.

C:\WINDOWS>d:

D:\>cd hspmcn

D:\hspmcn>

VC++ におけるコマンドラインコンパイラは、「cl.exe」という実行可能ファイルです。以下のようにすることで、コンパイルされてオブジェクトファイル(拡張子 .obj)が出来上がります。

cl /c (ソースファイル名)

ここで、『/c』は、「コンパイルはするがリンクはしない」ということを指定するオプションパラメータです。今回はマシン語を生成するのが目的であって、実行可能ファイルやDLLを作成するわけではないので、このオプションを指定しておく必要があります。

他にもいろいろとオプションを指定することができますが、ここでは詳しく説明しません。オプション指定についての細かい指定はコンパイラのリファレンスを参照してください。あえて例を挙げると、コードサイズ最適化オプション『/O1』(「ゼロ」ではなくて、アルファベットの大文字の「オー」です)や、速度最適化オプション『/O2』などがあります。

コンパイラのオプション指定は大文字・小文字が区別されるので、注意が必要です。また、オプション指定のスラッシュ『/』の代わりに、ハイフン『-』で記述することもできます。(例:「cl -c (ファイル名)」)

実際にコンパイルを行なうと、次のように表示されると思います。

D:\hspmcn>cl /c /O1 enumwnd2.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86
Copyright (C) Microsoft Corp 1984-1998. All rights reserved.

enumwnd2.c

D:\hspmcn>

上の例では、最適化オプションなんかも指定していますが、指定しなくでも別にいいです。マシン語コードのサイズが気になる方は、コードサイズ最適化オプション『-O1』を指定してみるのがいいかもしれないです。

コンパイルエラーが表示されなければコンパイルはできたことになります。コンパイルに成功していれば、同じ作業ディレクトリにオブジェクトファイル『 enumwnd2.obj 』が生成されているはずです。

VCのインストール時の選択によっては、必要な環境変数の登録などといった、コマンドラインコンパイラを使用するための環境ができていない場合があります。この場合、コンパイルを行なう前に、VCの「Bin」ディレクトリにある『VCVARS32.BAT』という名前のバッチファイルを実行する必要があります。例えば、筆者の環境の場合は、

"C:\Program Files\Microsoft Visual Studio\VC98\Bin\VCVARS32.BAT"

と入力することで、(そのDOSウィンドウ内でのみ)環境変数が登録され、コマンドラインコンパイラを使えるようになります。(パスにスペースが含まれる場合には、二重引用符(ダブルクォーテーション)で囲むか、短い形式のファイル名を使用する必要があります。『VCVARS32.BAT』ファイルをエクスプローラからDOSウィンドウにドラッグ&ドロップでも可。)

また、Windows 9x では、場合によって「環境変数のための領域が足りません。」というメッセージが現れることがあります。この場合には、いったんDOSウィンドウを閉じて、「DOS プロンプト」へのショートカットのプロパティを開き、「環境変数用のメモリ」を多めに設定してからもう一度やってみてください。

マシン語コードを取り出す

出来上がったオブジェクトファイルにはマシン語コードがバイナリ形式で含まれているのですが、オブジェクトファイルの中身は構造が複雑なので、単純には取り出せません。

そこで、VCに添付されている『DUMPBIN』というファイルダンプユーティリティを使います。このユーティリティは、オブジェクトファイルや実行可能ファイルをダンプして、構造を表示するというものです。これを使用して、オブジェクトファイルからマシン語コードの部分の情報を取り出します。DUMPBIN もコマンドラインツールなので、DOSプロンプト上で実行します。コマンドは次のようになります。

dumpbin /section:.text /rawdata /out:(出力ファイル名) (オブジェクトファイル名)

オプション指定『/section:.text』は、「.text と言う名前のセクションの情報を取り出す」というものです。オブジェクトファイルの中身は幾つかのセクションと呼ばれる部分に分かれており、関数の実行コードの情報は「.text」セクションに格納されているので、このオプションを指定します。

/rawdata』オプションは、セクションの raw データの出力を指定するものです。マシン語コードの情報を得るにはこのオプションが必要です。出力の1行あたりのデータ量を指定することができます。たとえば、1行につき16バイトぶんのデータを表示させたければ、『/rawdara:bytes,16』あるいは「bytes」を省略して『/rawdata:,16』などというようにします。大きめの値を指定しておくと、後でマシン語コードを抜き出す作業が少し簡単になるでしょう。逆に、数値を指定しなかった場合には、マシン語コード情報の横に、対応するANSI文字が表示されるようになり、邪魔に思うかもしれません。

/out:(ファイル名)』は、情報を出力するファイル名を指定するためのオプションです。

ほかにも、『/disasm』を指定すると、アセンブリコードが同時に出力されます。このオプションを指定して出力されたアセンブリコードを、前回のリストファイルのものと比較すると、同じ最適化処理をした場合には、コードが一致していることがわかります。

D:\hspmcn>dumpbin /section:.text /rawdata:,16 /out:enumwnd2.txt enumwnd2.obj
Microsoft (R) COFF Binary File Dumper Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


D:\hspmcn>

上の例では、ファイル『 enumwnd2.txt 』に結果を出力しなさい、と指定しています。作成されたこのファイルを開くと、以下のようになっています。

Dump of file enumwnd2.obj

File Type: COFF OBJECT

SECTION HEADER #2
   .text name
       0 physical address
       0 virtual address
      30 size of raw data
      DC file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
60501020 flags
         Code
         Communal; sym= _EnumWindowsProc@8
         16 byte align
         Execute Read

RAW DATA #2
  00000000: 8B 44 24 08 8B 48 08 8B 50 04 3B CA 7C 05 33 C0 
  00000010: C2 08 00 8B 10 56 8B 74 24 08 89 34 8A 8B 48 08 
  00000020: 41 5E 89 48 08 B8 01 00 00 00 C2 08 00 90 90 90 

  Summary

          30 .text

後は、マシン語コードの部分(上の緑色で塗られた部分)を、前回使用したマシン語変換プログラムにかけてやれば、マシン語コードをHSPで使える形で取り出すことができます。

バッチファイルで単純化

コンパイルのたびに、上のようなコマンドラインをいちいち入力していくのは面倒なので、バッチファイルを作成して、作業を単純化させてみましょう。まず、以下のファイルを作成して、作業フォルダに mcnvc.bat というファイル名で保存しておきます。

バッチファイル mcnvc.bat

cl /c /O1 %1.c
@if errorlevel == 1 goto END
dumpbin /section:.text /rawdata:,16 /out:%1.txt %1.obj
start %1.txt
:END

上のバッチファイルの「%1」の部分が、第1パラメータとして指定された文字列に置き換えられて実行されるようになります。例えば、今回の場合、

mcnvc.bat enumwnd2

または、コマンド入力時に拡張子『.bat』は省略できるので

mcnvc enumwnd2

とすることで、「%1」の部分はすべて「enumwnd2」に置き換えられて実行されます。このとき、まず、ソースファイルのコンパイルを実行した後、コンパイラがエラーを返さなければ、DUMPBINを実行し、最後に出力されたテキストファイルを開くようになっています。ファイルが自動的に開かれないようにしたい場合には、「start %1.txt」の行を削除してください。

実際の入出力は以下のようになります。

C:\WINDOWS>d:

D:\>cd hspmcn

D:\hspmcn>mcnvc enumwnd2

D:\hspmcn>cl /c /O2 enumwnd2.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86
Copyright (C) Microsoft Corp 1984-1998. All rights reserved.

enumwnd2.c

D:\hspmcn>dumpbin /section:.text /rawdata:,16 /out:enumwnd2.txt enumwnd2.obj
Microsoft (R) COFF Binary File Dumper Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


D:\hspmcn>start enumwnd2.txt
D:\hspmcn>

これで、だいぶ作業が簡単になるはずです。