Hello Word! の表示
まずは、「Hello Word!」という文字列を標準出力に表示するアセンブリの例(test1)を紹介します。

.586
.model flat, stdcall
include iolib.inc
includelib iolib.lib
include kernel32.inc
includelib kernel32.lib
.data
STR0 db "Hello Word!", 0
.code
_start:
invoke OutputString, NEAR32 PTR STR0
invoke ExitProcess, 0
end _start
これには、MASMの基本が全部ではありませんが詰まっています。まず、最初に目に付くのは
.586 .model flat, stdcall
ですが、この2行はプログラムの進行に特に関係ありませんので、丸暗記してください。
include/includelib
次の4行は、それぞれインクルードを表しています。すなわち、外部ファイルの内容をアセンブリで利用できるように取り込む作業です。
include iolib.inc includelib iolib.lib include kernel32.inc includelib kernel32.lib
includeとincludelibの違いは、次のとおりです。
- include
- includelib
これら4つのファイルがあるディレクトリについては、MASMをインストールしてから説明します。また、libのインクルードについては、アセンブル時にコマンドラインで指定することもできますが、VEFBLやKJではここでインクルードしておきます。
.data
.data
STR0 db "Hello Word!", 0
ここではアセンブリ内で使用するデータを宣言します。本稿で使用するデータ型は、次の2つです。
dd:数値db:文字列
変数の宣言
変数の宣言は次のように行います。
(変数名) (データ型) (変数の最初の内容)
例えば、上のアセンブリの例で次のように宣言しています。
STR0 db "Hello Word!", 0
- 変数名は「STR0」
- データ型は「db」(文字列)
- 変数の最初の内容は「Hello Word!」に0をつけたもの
文字列の最後に0を付けるのには理由があります。
例えば、ファイルを開くプログラムを作ってバイナリデータ(機械語のファイル)を開こうとした際に、途中ですぐに終了してしまい、残りの大半の部分が読み込めなかったという経験はありませんか。
それは、プログラムで呼び出しているWin32API(OSの機能を直接参照する機能、DLLによって定義されているものがほとんど)が、「ファイルの最後は0である」と定義しているからです(この0は文字としての0ではなく、文字コードの0です)。バイナリには文字コードの0が頻繁に現れるため、Win32APIが「0に到着したからこれで全部だ」と勘違いしてしまい、途中まてしか読めないのです(ちなみに「メモ帳」は違うアルゴリズムを使っているようですのできちんとファイルの最後まで読みます)。
文字列も同じように、Windowsでは(他のOSでもですが)「0」に到着すると文字列の終わりと判断します。そのため、文字列の終わりをアセンブラに教えるために、最後に0を付け加える必要があります。
このような面倒な作業はアセンブラが自動的に行ってくれればよさそうですが、アセンブリは、機械語をテキストに置き換えただけの低級言語なので、そうもいきません。
また、機械語は一句でも間違えると成り立ちません。アセンブリも、同様に気をつける必要があります。例えば、先ほどの文字列の末尾に付ける0を忘れると、その後のバイナリも文字列の一部と判断され、不具合を生じてしまいます。なお、dd(数値)では、最後に0をつける必要はありません。
.code/invoke
いよいよプログラム部分に到着しました。
.code
_start:
invoke OutputString, NEAR32 PTR STR0
invoke ExitProcess, 0
end _start
先ほどinclude、includelibを利用することで、DLLをアセンブリの中から呼び出せる、と書きました。また、変数宣言の説明では、Win32APIは普通DLLによって定義されていると書きました。つまり、この2つを組み合わせると、MASMからWin32APIを呼び出すことができるのです。
Win32APIの呼び出しにはinvokeを使います。
invoke (関数名), (パラメータを「,」で区切る)
例えば、Win32APIでプログラムの終わりを示すExitProcessは、次のように呼び出します。ExitProcessのパラメータ(引数)は1つです。
invoke ExitProcess, 0
- 関数名は「ExitProcess」
- 第1の引数は「0」
- 第2の引数以降は、第1の引数の後にコンマ(,)がないのでなし
ラベル
_start: end _start
一行目の「_start:」は、プログラマーなら想像ができると思いますが、ラベルといい、プログラムの中の特定の部分に名前を付けています。
メッセージボックス
次は、メッセージボックスを表示するアセンブリの例(test2)を紹介します。

.586
.model flat, stdcall
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.data
PROJECTNAME db "test2", 0
STR0 db "Hello Word!", 0
.code
_start:
invoke MessageBoxA, 0, offset STR0, offset PROJECTNAME, 0
invoke ExitProcess, 0
end _start
ライブラリが違いますね。それもそのはず、メッセージボックスを表示するWin32APIのMessageBoxAは、「user32.dll」で定義されているからです。
「あれ、MessageBoxのあとにAが付いているはなぜ」と思った人は、C++やActiveBasicなどでWin32APIの宣言をした事のない方がほとんどでしょう。一部のWin32APIでは最後にAが付いており、C++のインクルードファイルではAを取り除いた形で提供されています。
第2の疑問、「MB_OK」です。ここでは値を直接指定しています。C++でも、コンパイル時にMB_OKは数値に変換されてWin32APIに渡されるので、定義せずに最初から0を指定した方が手っ取り早いと思ったからです。
それに、もう1つ理由があります。MB_OKは、「windows.inc」で定義されているのですが、実はコンソールアプリケーションを作る時に「windows.inc」をインクルードすると、リンクの時にエラーが起きます。理由は筆者にもはっきり分かりませんが、Windowsアプリケーションからインクルードしたら問題なくリンクできました。ということで「windows.inc」はWindowsアプリケーションの作成までお預けにします。
offset
MessageBoxA関数を呼び出す時のoffsetでは、「変数のアドレス」を渡しています。CPUは、このアドレスが指し示すメモリを参照し、その値を受け取ります。
