安室 浩和(やすむろ ひろかず)

 大手コンピュータ・メーカー勤務。入社以来10数年をソフトウェア開発の現場で過ごし,その後ソフトウェア品質部へ異動。現場への技術支援や品質教育開発などを主に行っている。「APIで学ぶWindows徹底理解」(日経BP社)などを執筆。

 2007年1月末の一般向け出荷を控え,Windowsの世界では新バージョンVistaが注目を集めている。開発者にとってWindows Vistaの一番のポイントはと言えば, .NET Framework 3.0を標準で搭載する点を挙げる人が多いだろう。雑誌にせよWebにせよ,最近のWindowsプログラミングの記事のほとんどは .NET向けだ。マイクロソフトも,これまでネイティブ・コードでの開発が主流だったC/C++開発まで, .NETへの移行を促そうとしている。実際,マイクロソフトが無償で配布しているC/C++統合開発環境Visual C++ 2005 Express Editionには,ネイティブ・アプリケーション開発用のC++クラスライブラリMFC(Microsoft Foundation Classes)や,ダイアログボックスなどをデザインするためのリソース・エディタが付属してない。

 ではネイティブ・アプリケーション開発用のプログラミング・インタフェースであるWin32 API(Application Programming Interface)の知識は不要になるのかというと,決してそうはならないと筆者は考えている。理由の一つは,OSが動作する仕組みを知ることが,今後も開発者にとって重要であり続けることだ。プロセス管理,メモリー管理,同期化といった概念は,.NETで開発する場合でも必要だ。こうした知識を得るのに一番いいのは,やはりOSのカーネルに近いAPIのレベルで学ぶことである。

 将来どうなるかはともかく,現時点では .NET Frameworkの大部分はWin32 APIのレイヤー上に載るプログラミング・インタフェースに過ぎない。.NETプログラミングで問題に遭遇したときに,それがWin32 APIレベルでどう実現されているかを考えることは,問題解決の大きなヒントとなり得る。

 加えて分野にもよるものの,ネイティブ・アプリケーションを開発する機会は今後も少なくないはずだ。実際,現時点では2007 Microsoft Officeを含め,アプリケーションのほとんどがネイティブ・コードで動作する。

 今回から始まるこの連載では,そうした状況を踏まえ,Windowsのシステム・レベルにおける開発上の重要な機能やトピックをWin32 APIを通して詳しく解説していく。普段は .NETやMFCのプログラミングばかりで,Win32 APIには縁がないという方々にとっても,一段ステップ・アップできる内容を目指している。そのため,ごく基礎的な話題は,すでに理解しているものとして話を進めることがあるのでご了承願いたい。そうした話題については,書籍やインターネットなどに情報があふれているので,わからない点があれば自力で学習することが十分に可能なはずである。

 開発環境としては,Visual C++ 2005 Express EditionとWindows SDK(Platform SDKを含む)を組み合わせて使うことを前提にする。Visual C++ 2005 Express Edition自体はネイティブなGUIアプリケーションを作成する機能を備えていないが,Windows SDKと組み合わせることで一応作れるようになる。ただし,リソース・エディタの機能がないなど,Visual C++ Standard Edition以上と比べると制限が付く。いずれもマイクロソフトのWebサイトから無償でダウンロード可能なので,マイクロソフトのWebページを参照しながらインストールしてほしい。

 連載第1回となる今回は,Windowsの「プロセス」について取り上げる。Windowsでプログラムを実行すると,「プロセス」が作られる。スタート・メニューからアプリケーションを選択しても,デスクトップのアイコンをダブルクリックしても,コマンドラインから実行ファイル名を入力してEnterキーを押しても,すべて同じようにプロセスが作成され,処理を開始する。ではこの「プロセス」とはいったい何なのだろうか。Windowsをユーザーとして普通に使うだけなら,知らなくてもほとんど困ることのない「プロセス」だが,ソフトウエア開発者にとっては,必ず理解しておくべき重要な仕組みである。簡単なサンプル・プログラムを眺めながら,プロセスとそれにまつわるWindowsの仕組みについて学んでいくことにしよう。

プロセスはメモリー上に作られたプログラムの実体

 「プロセス(process)」は,英語で「処理」や「過程」といった意味を持つ単語だが,コンピュータの世界では「プログラム実行中のインスタンス」の意味で使われる。「インスタンス」もまたコンピュータの世界における専門用語で,何か元になるものがあって,そこから作り出される「実体」を意味する言葉として使われる。オブジェクト指向プログラミングの経験のある方は,クラスから生成されるオブジェクトのことをインスタンスと呼ぶのを思い出してほしい。Windowsでいう「プロセス」の場合,プログラムの実行ファイル(元になるもの)からメモリー上に実体化されたインスタンスが「プロセス」というわけだ。

 一つのクラスから複数のインスタンスを作成できるように,一つのプログラムの実行インスタンスが同時に複数存在することはめずらしくない。例えばユーザーがWindows付属の「メモ帳」を二つ起動すると,それらは別々のプロセスになる。各プロセスは,編集中のテキストやウィンドウの位置/サイズなど,プロセスごとに異なるデータを管理する。加えて,「今プログラムのどの部分を実行しているか」といったCPUのレジスタが保持する値もプロセスごとに異なる。こうした,その時点でのプロセスの状態をプロセスの実行コンテキスト,もしくは単にコンテキストと呼ぶ。

CreateProcess APIでプロセスを作成する

 Windowsでプロセスを作成するには,CreateProcessというWin32 APIを利用する。ユーザーがエクスプローラなどからプログラムを起動したときも,Windows内部で間接的にCreateProcessが呼び出されていると考えてよい。APIのプロトタイプはリスト1の通りである。

リスト1●CreateProcess APIのプロトタイプ。引数の数は多いが,大部分はNULLを渡してかまわない
リスト1●CreateProcess APIのプロトタイプ。引数の数は多いが,大部分はNULLを渡してかまわない

 リスト1の引数の数を見てわかる通り,プロセス作成時には実にたくさんの情報を渡すようになっている。もっとも,ポインタ型の引数のほとんどは,単に起動するだけならNULLを渡してかまわない。したがって,一番簡単なCreateProcessの呼び出し方は,リスト2のようになる。リスト2を含む,Visual C++ 2005 Express Edition+Windows SDK用のサンプル・プロジェクトの全ファイルはこちらからダウンロード可能だ。図1は実行後,メニューの「メモ帳を起動」を選択し,起動してみたところである。


  TCHAR cmdline[] = _T("notepad.exe");
  STARTUPINFO si;
  ZeroMemory(&si, sizeof(si));
  si.cb = sizeof(si);

  PROCESS_INFORMATION pi;
  ZeroMemory(&pi, sizeof(pi));
  if(CreateProcess(NULL, cmdline, NULL, NULL, FALSE, 0,
                   NULL, NULL, &si, &pi)){
    // 使用しないので,すぐにクローズしてよい
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
  }
  //・・・省略・・・
リスト2●CreateProcess APIの使用例。ZeroMemory APIを使ってSTARTUPINFO構造体の中身を0で初期化した後,サイズだけを設定してCreateProcessに渡している

図1●リスト2のサンプルを実行したところ
図1●リスト2のサンプルを実行したところ
[画像のクリックで拡大表示]

 では,APIの主要な引数を見ていこう。2番目の引数には,コマンドライン文字列へのポインタを渡す。起動するプログラムの実行ファイル名に,必要に応じて起動オプションを付加したものだ。リスト2では,Windowsに付属する「メモ帳」を起動している。変数宣言に含まれる「TCHAR」は,プログラム内部で文字列をUnicodeとして扱う場合とマルチバイト文字列(シフトJIS)として扱う場合のソースコードを共通化するためのマクロである。詳しくは,このページ下にあるカコミ記事「ANSI版とUnicode版のソースコードを共通化する」を参照してほしい。

 実行ファイル名にパスが指定されていない場合,Windowsはカレント・ディレクトリや環境変数PATHに指定したディレクトリなどを,定められた順序に従って探索する。実行ファイル名が半角スペースを含む場合は,ファイル名を2重引用符で囲む必要があることに注意してほしい。C/C++の場合には,その2重引用符を「\」でエスケープするのも忘れてはいけない。2番目の引数の代わりに,先頭の引数に実行ファイル名を指定することもできるが,この場合Windowsは探索を行わない。そのため,リスト2のように先頭はNULLとし,2番目の引数で実行ファイル名を指定するほうが便利だろう。

 3番目,4番目の引数は,セキュリティに関する情報などを指定するものだが,今回は説明を省略する。いずれも,特別なことをするのでない限りNULLを渡してかまわない。

 5番目の引数には,呼び出し側プロセスから新しいプロセスにハンドルを継承するかどうかを指定する。単にプロセスを起動するだけならリスト2のようにFALSEを渡しておけばよい。6番目の引数には,プロセスの起動の仕方についてのオプションを指定する。これもリスト2では必要ないので0を渡している。7番目,8番目については説明不要だろう。

 9番目と10番目はポインタ型だが,NULLでない値を渡す必要がある。9番目にはSTARTUPINFO構造体(リスト3)のポインタを指定する。この構造体は,名前からわかるように,アプリケーションを起動する際に必要な情報を指定するものだ。指定できる情報には,ウィンドウの大きさや表示位置などがある。何も指定しない場合は,リスト2のようにcb以外のメンバーをすべて0に初期化しておく。これには,C/C++の標準ライブラリ関数memsetや,ZeroMemoryというWin32 APIを使うのが簡単だ。

リスト3●STARTUPINFO構造体のメンバー。コメントが「コンソール・ウィンドウ・・・」で始まるメンバーはコンソール・プロセスの場合に意味を持つ。また「メイン・ウィンドウ」で始まるメンバーは,GUIプロセスでかつ最初のCreateWindow APIの呼び出し時にデフォルト値を使うように指定した場合だけ有効
リスト3●STARTUPINFO構造体のメンバー。コメントが「コンソール・ウィンドウ・・・」で始まるメンバーはコンソール・プロセスの場合に意味を持つ。また「メイン・ウィンドウ」で始まるメンバーは,GUIプロセスでかつ最初のCreateWindow APIの呼び出し時にデフォルト値を使うように指定した場合だけ有効

 注意が必要なのは,cbメンバーに必ず正しい構造体のサイズをセットしておくべきことだ。実は,この構造体のサイズ情報によって,構造体の仕様を拡張できるようにしてあるのである。例えばWindowsのバージョンが上がってより多くの初期化情報が必要になったような場合に,構造体の末尾に新しいメンバーを追加できるようになる。Windowsはサイズ情報を見ることで,アプリケーションが渡した構造体が追加メンバーを含むかどうかを判断するのである。

 Win32 APIで定義されている構造体には,同じような仕組みを持つものが多いので注意してほしい。うっかりサイズまで0に初期化したままだと,それだけでプログラムが正し く動作せずに悩むことがある。

ANSI版とUnicode版のソースコードを共通化する

 C/C++によるWindowsプログラミングに慣れていない方は,変数宣言などに頻繁に現れる「TCHAR」という型にとまどうかもしれない。これは「UNICODE」というプリプロセサ・シンボルが定義されているかどうかによって,自動的にwchar_t型(16ビット)かchar型(8ビット)のいずれかに切り替わるプリプロセサ・マクロである。文字変数をwchar_tやcharと宣言する代わりにTCHARと宣言することで,Unicode用とANSI/マルチバイト用のソースコードを共通化するのが目的だ。「_T( )」というマクロも同様で,文字列リテラルを定義する場合にこのマクロの引数として与えておくと,自動的に切り替わるようになる。

 Windowsは,文字列を扱う多くのWin32 APIについて,引数などをANSI/マルチバイト文字列で受け渡すANSI版と,Unicode文字列で受け渡すUnicode版の2種類を用意している。これらのAPIのどちらが呼び出されるかも,この「UNICODE」シンボルの定義の有無で決定される。

 Unicode版にするかどうかは,Visual Studioのプロジェクトの設定で切り替えられる。Visual Studio 2005(Visual C++ 2005 Express Editionを含む)では,Unicode版がデフォルトだ。

 ちなみにUnicode版の場合,CreateProcess APIの2番目の引数に文字列リテラルを直接指定すると実行時エラーになる。Unicode版は2番目の引数を内部で書き換えようとするからだ。これは,引数の型がconstでないことからもわかる。リスト2のように,変数を介して指定することに注意してほしい。