Windows API関数を呼び出そう

Windows API

Windowsでは、プログラマが画面出力やユーザー入力などの処理を簡単に行うことができるように、基本的な機能をWindowsがはじめから用意しておき、プログラマがその機能を自由に使うことができるようにしています。これらの機能にアクセスするための手段が、『Windows API』というインターフェースの形で与えられています。これらのインターフェースの多くは「関数」と呼ばれるもののセットとして与えられていて、一般的には、この関数のセットまたは関数一つ一つのことを「Windows API」と呼びます。また、個々の関数を「API関数」と呼ぶこともあります。


Windows APIを使用することで実際に実行されるプログラムコードは、Windowsシステムが提供するDLL(ダイナミック・リンク・ライブラリ)に格納されています。

DLLとは、簡単にいえば、ある特定の機能を実現するプログラムコードが入っているバイナリファイルのことで、一般的にはファイル名の拡張子が「.dll」になっています。アプリケーションは、実行時にこのファイルをメモリ内に読み込んで、中に含まれているプログラムコードを実行させることができます。HSPの拡張プラグインも、このDLLであることは知っているでしょう。拡張プラグインは、DLLの中に拡張機能を実現するためのプログラムを含んでいて、HSPの実行時にDLLをメモリに読み込み、HSP側からこれらのプログラムを実行させることができるようになっているのです。

Windows API関数は、先に述べたとおり、Windowsが標準で持っているDLL(システムDLL)にそのプログラムコードが格納されています。特に、以下に示すDLLはWindowsの最重要なもので、これらのDLLが提供するAPIはWindowsアプリケーションの開発には不可欠です。

DLLファイル名 主なAPIの機能
kernel32.dll プロセス、メモリや周辺装置を管理
user32.dll ウィンドウベースのユーザー・インターフェースを管理
gdi32.dll 文字列やグラフィックスの描画に関するサービスを提供

また、これらのほかにも、Windowsシェルの中核をなすshell32.dllや、コモンコントロールの機能を提供するcomctl32.dllなど数多くのDLLがあり、それぞれが多くのAPI関数を提供しています。

関数とは

さて、Windows API関数を使うとは言ったものの、HSPでは『関数』という概念がなかったので、「そもそも、関数って何?」などと思っている方もいるでしょう。そこで、関数について簡単に説明してみることにしましょう。

最初に出てくるのは……えっ?数学?

高校で数学の授業を受けているならば、「関数」とはどのようなものかということを知っているかもしれませんね。数学の教科書の中でもっとも一般的に書かれている形としては、例えば

f(x) = 2x+1

となっていることでしょう。このように書いた場合の「f」は、「入力値xに対して2x+1(すなわちxを2倍して1を足した数)を返す関数」というわけです。関数fが上のように与えられている時に、「f(1)」と書くとそれは2×1+1(すなわち3)を表し、「f(5)」と書けばそれは2×5+1(すなわち11)を表しています。また、「y=f(x)」と書くことは「y=2x+1」と書くことと同じことになります。

高校の数学では主に、上の場合のように入力値がxひとつだけの関数、いわゆる1変数関数のみを扱うことが多いですが、2つ以上の入力値を持つ2変数関数、3変数関数などといった関数もあります。例えば、

f(x, y) = x+2y+3

というように与えられる関数では、「2つの入力値xyに対してx+2y+3を返す関数」ということになります。このときに例えば「f(1,2)」と書くとそれは1+(2×5)+3(すなわち14)を表すことになります。

というように、数学で言うところの関数は、与えられた数値(入力)に対してある特定の計算を行い、その結果の値(出力)を返すものであると考えることができるのです。

Windows API関数の基本はC言語の関数

数学の関数とは異なり、プログラミングの世界での「関数」は、独立して機能するひとまとまりのプログラムコードのブロックのことを指すことがあります。というのも、C/C++言語などのいくつかの言語ではプログラムコードのブロックを関数の形で書くことになっているのです。そして、そのプログラムブロックを実行させるためには、例えば「z=Func(x,y)」と数学の関数のように書かなければならないのです(このときの「Func」は実行させるプログラムブロックに付けられている名前です)。

C言語などのプログラミングにおける関数は、数学における関数での「特定の計算」を「特定の処理」に置き換えたものと考えることができます。すなわち、与えられた値(入力)(ただしこれは数値とは限らない)に対してある特定の処理を行い、その結果の値(出力)(これもまた数値とは限らない)を返すものである、と考えることができます。もちろん、その処理の内容は計算であってもいいので、プログラミングの関数で数学的な関数を書くこともできます。実際にC言語でもHSPでも標準で数学関数(sin, cos, expなど)がありますし、特定の数学関数を自分で作ることもできます。また、計算以外の処理も行うことができるので、関数を1つの処理単位として扱うことができるわけです。

ところで、プログラミング言語の中には、値を返さないものは「サブルーチン」、値を返すものは「関数」と明確に分けているものもあります(HSP3では#deffuncで定義されるユーザー定義命令と#defcfuncで定義されるユーザー定義関数の違いがこれに当たります。HSPで言うところのサブルーチンとは異なりますが)。しかし、C言語などではすべてのプログラムブロックを関数として書くことになっており、サブルーチンという概念がありません。そのため、Windows APIもそれにならって、提供する機能を関数と呼んでいるのです。

プログラムでの関数は「呼び出す」もの

C言語などのプログラミングでは、あるプログラムコード中で別の関数の処理を実行する(関数のコードブロックにジャンプしてくる)ことを、一般に「関数を呼び出す」といいます。「呼び出す」は英語で「call(コール)」であるため、「コールする」という場合もあります。

関数を呼び出す時に、もととなる情報を関数のコードに渡さなければならない場合があります。例えば、簡単な関数として、2つの整数の和を求める関数というものを考えてみましょう。この場合には、もとの2つの数を情報として渡さなくてはなりませんね。関数に渡されるこのような情報のことを引数(ひきすう)またはパラメータといいます。関数がどのような情報を必要とするかは関数の機能によりますから、引数の数や種類は関数によって異なります。10個以上もの引数を必要とするような関数もあれば、引数が必要ない(引数の数が0個の)関数もあります。

関数コードブロックでの処理が終わると、はじめに関数を呼び出したプログラムコードに戻ってきます。このことを「関数から(制御が)返る/戻る」とか、「関数が制御を返す」などと呼びます。「返る/返す」の英語が「return(リターン)」であることから、「リターンする」という場合もあります。このとき、関数によっては、呼び出し元のプログラムコードに情報を渡さなければならないものがあります。例えば、先ほどの2つの整数の和を求める関数の場合には、求めた結果(和)を渡さなければなりません。このように、関数が呼び出し側のコードに結果データを渡すことを、「関数が結果(値)を返す」または「関数から結果(値)が返る」と言い、返されたデータのことを戻り値と呼びます。戻り値を持たない(結果データを返さない)関数もありますが、戻り値を持つ関数でも、戻り値の数は1個までです。

関数とデータ型

ところで、関数においては、2つの点でデータ型が関わってきます。

まず1つは、関数に渡される引数のデータ型です。引数には、先ほどの例のような整数だけでなく、さまざまな型のデータが渡されます。どんな型のデータが渡されるかは関数によってさまざまです。例えば、Windows APIの中にはウィンドウに対して操作を行うものがありますが、そのような関数ではウィンドウハンドル(ウィンドウを識別する32ビット値)が必要になるので、必ずHWND型(ウィンドウハンドルのデータ型)の引数を持っています。また、メモリブロックに格納されているデータにアクセスすような関数は、そのメモリブロックのアドレスを引数に持ちます。例えば、文字列操作を行う関数は、文字列へのポインタ型(例えばLPSTR型など)の引数を持っていて、関数はこのポインタが指すメモリブロックに対して文字列操作を行います。

もう1つは、関数の戻り値のデータ型です。例えば、ウィンドウを作成するようなWindows API関数は、戻り値としてウィンドウハンドルを返しますから、この関数の戻り値はHWND型ということになります。他にも、メモリブロックを確保するようなAPI関数の戻り値の型はPVOID型(データへのポインタ型)であり、この関数は戻り値として確保したメモリブロックのアドレスを返します。

データ型についての詳細は、『データ型を知ろう』の項を参照してください。

C言語やWindows APIにおける関数の記述形式

次は、関数の記述形式について説明します。

例えば、先ほどの例にあった2つの整数の和を求める関数を仮定して考えましょう。この関数の名前がAddIntegersであるとします。この場合、C/C++などの言語では、関数の宣言/定義時などには、次のような記述がされます。

int AddIntegers(int n1, int n2);

上の記述は、この関数名が「AddIntegers」であり、2つのint型の引数n1n2を持つこと、そして、戻り値もまたint型であることを表しています。実際には、引数のn1n2という変数名はあまり意味はなく、データ型がint型であるということが重要なのです。引数の変数名は何でもいいのです。一般的には、何か引数の意味が分かるような変数名がつけられています。


では、今度は実在のWindows API関数を例にとってみましょう。Windows APIの1つにMessageBeep関数というものがあります。この関数はuser32.dllから提供されており、関数の宣言は以下のようになります。

BOOL MessageBeep(
    UINT uType     // サウンドの形式
);

ここでは複数行で記述されていますが、1行で記述した時と違いはありません。また、2つのスラッシュ「//」は、この行のそれ以降がコメントであることを表します。

この関数の場合は、関数名が「MessageBeep」であり、1つのUINT型の引数を持つこと、そして、戻り値の型がBOOL型であることを示します。BOOL型とは、真か偽かを表す型であり、0以外の値(通常は1)である場合を真とみなし、0の場合を偽とみなします。BOOL型の戻り値を返すWindows API関数の多くは、関数が成功すると真(0以外の値)を返し、何らかのエラーが発生して関数が失敗すると偽(0)を返します。

API関数のエラーについては、次の『Windows APIのエラー処理』の項で説明します。

Windows APIの呼び出し

HSPスクリプト中からWindows API関数を呼び出す手順を説明しましょう。

HSP3では、あらかじめ2種類のプリプロセッサ命令を使用してスクリプト中で使いたいAPI関数を登録しておき、API関数を呼び出したいところで、登録しておいた名前を引数の並びとともに指定することになります。具体的な手順としては、

  1. #uselib命令でDLL名を指定
  2. #func命令または#cfunc命令で任意の名前を付けて関数名と引数のタイプを登録
  3. 登録した名前で関数を呼び出し

というようになります。

先に言っておきますと、HSP3では2通りの呼び出し方法が存在します。具体的には、他のHSP命令と同じように関数を命令として記述し、戻り値がシステム変数に格納される「命令形式の関数呼び出し」と、数学の関数のように記述することのできる「関数形式の関数呼び出し」の2通りの呼び出し方法が存在します。これらはそれぞれ記述方法が異なってきます。

では、外部関数呼び出しの手順を具体的に説明していきましょう。

1.DLL名を指定

スクリプトの先頭で、プリプロセッサ命令#uselibを使用して、外部DLL名を指定する必要があります。

#uselib "DLLファイル名"

例として、user32.dllに含まれている関数を使用するためには、次のように記述することになります。

#uselib "user32.dll"

2.関数の登録

次に、プリプロセッサ命令#funcまたは#cfuncを使用して、使用する関数を登録していきます。ここで、関数を「命令形式」で呼び出すか、それとも「関数形式」で呼び出すかによって、どちらのプリプロセッサ命令を使用するのかが異なってきます。

関数を「命令形式」で呼び出す場合には#func命令で関数の登録を行います。この命令は次のような形で定義されています。

#func 新規名称 "関数名" 型指定1, 型指定2, …

一方、関数を「関数形式」で呼び出す場合には#cfunc命令で関数の登録を行います。

#cfunc 新規名称 "関数名" 型指定1, 型指定2, …

どちらの場合でも、「"関数名"」で指定される名前の関数を、指定した「新規名称」で登録します。関数呼び出し時には、ここで登録した新規名称を書くことになります。

「型指定1」、「型指定2」、… には引数の型を指定していかなければいけません。ここで指定できる型は、主に次のものがあります。(ここに書かれているもの以外にもいくつかありますが、Windows API関数の呼び出しではあまり使われないものなので、ここには書かれていません。)

タイプ意味
int 指定された整数(32ビット値)をそのまま渡す。
var 指定された変数のアドレスを渡す。
str 指定された文字列のアドレスを渡す。
sptr 指定された値が文字列である場合には文字列のアドレスを渡す。そうでない場合には指定された整数をそのまま渡す。
double 64ビット浮動小数点数を渡す。
nullptr 常にNULLポインタを渡す。

1個の同じDLLに含まれている複数の関数を登録する場合には、1つ1つ#uselib命令でDLL名を指定する必要はなく、#func命令を連続して指定することができます。

関数登録の例として、user32.dllに含まれているMessageBeep関数を登録してみましょう。これには、次のように記述します。

// 後で命令形式で呼び出す場合
#func MessageBeep "MessageBeep" int
// 後で関数形式で呼び出す場合
#cfunc MessageBeep "MessageBeep" int

MessageBeep関数は、整数型の引数を1つだけとる関数なので、上記のように関数名の後ろに引数の型指定「int」を1つだけ記述します。ここでは、関数名と登録名称が同じ「MessageBeep」ですが、別の名前で登録することも可能です。例えば、

#func msgbeep "MessageBeep" int

と記述すると、後で「msgbeep」という名前の命令でMessageBeep関数を呼び出すことができるようになります。ただし、通常はAPI関数名と同じにするのが良いでしょう。(HSPの標準命令と同じ名前のAPI関数も存在するので、そのようなものは別の名前にする必要があります。)

メモリアドレスが関係する型指定「var」「str」「sptr」については、次回に説明します。

3.関数の呼び出し

いったん登録したなら、その後で関数の呼び出しを行うことができるようになります。関数の呼び出し方は、「命令形式」と「関数形式」の場合で異なります。それぞれについてみていくことにしましょう。

命令形式の呼び出し

命令形式の呼び出しでは、登録した名称を通常の命令と同じように記述することで、関数が呼び出されます。この場合、関数の戻り値は、システム変数statに格納されることになります。

; 命令形式での MessageBeep 関数の呼び出し
MessageBeep 0
if (stat == 0) {
    mes "関数呼び出し時にエラーが発生しました。"
} else {
    mes "関数呼び出しは成功しました。"
}

上の例では、先ほど登録したMessageBeep関数を呼び出しています。この関数の引数1個(ここでは整数値0)をパラメータとして指定しています。

この関数は、呼び出しが成功すると、戻り値として0以外の値を返すので、成功した場合と失敗した場合とでそれぞれメッセージを表示しています。戻り値としてどのような値が返されるのかは、関数ごとに異なります。

関数形式の呼び出し

関数形式の呼び出しでは、登録した名称を数学関数と同じような形で記述することで、関数が呼び出されます。この場合には、記述した関数の値そのものが関数の戻り値となります。

; 関数形式での MessageBeep 関数の呼び出し
ret = MessageBeep(0)
if (ret == 0) {
    mes "関数呼び出し時にエラーが発生しました。"
} else {
    mes "関数呼び出しは成功しました。"
}

これは先ほどと同じくMessageBeep関数を呼び出す例です。戻り値を一時的に変数retに格納しています。ここで、関数形式の便利な点は、上記のスクリプトを以下のように書き換えることができるという点です。

; 関数形式での MessageBeep 関数の呼び出し
if (MessageBeep(0) == 0) {
    mes "関数呼び出し時にエラーが発生しました。"
} else {
    mes "関数呼び出しは成功しました。"
}

この例では条件式の一部として関数を記述することができています。

まとめ

以上、まとめてスクリプトを記述すると、次のようになります。

命令形式の呼び出し

; 使用する関数の登録
#uselib "user32.dll"
#func MessageBeep "MessageBeep" int

; 命令形式での MessageBeep 関数の呼び出し
MessageBeep 0
if (stat == 0) {
    mes "関数呼び出し時にエラーが発生しました。"
} else {
    mes "関数呼び出しは成功しました。"
}

関数形式の呼び出し

; 使用する関数の登録
#uselib "user32.dll"
#cfunc MessageBeep "MessageBeep" int

; 関数形式での MessageBeep 関数の呼び出し
if (MessageBeep(0) == 0) {
    mes "関数呼び出し時にエラーが発生しました。"
} else {
    mes "関数呼び出しは成功しました。"
}