Comments
Description
Transcript
Windowsの概念の紹介
33 第 1章 Windowsの概念の紹介 1.1 Windowsとは何か? Microsoft Windows はグラフィカル環境である。バージョン 3.1 以前の初期の Windows は、 640K 以上のメインメモリ、全体で 2MB のメモリを搭載した Intel 8088 、8086 、80286 、80386 、 または 80486 プロセッサ上で動作した。一方、Windows 95 の最初のリリースは、80386 以上 のプロセッサと 4MB 以上のメモリを必要とした。初期のバージョンの Windows は、MS-DOS または PC-DOS version 3.1 以降の上で動作した。Windows 95 はシステムの基盤の部分を独 自にサポートしており、他のバージョンの DOS 上では動作しない。Windows NT は DOS を基 盤としていない完全なオペレーティングシステムで( DOS のかなりの部分をエミュレートする が、本物の DOS が含まれているわけではない)、80386 以上のプロセッサと 16 MB 以上のメモ リを必要とする。Windows 95 と Windows NT は、コンピュータのネイティブな 32 ビットの 命令セットを使ったプログラムをサポートしているという点で、以前のバージョンの Windows ( 3.1 と Windows for Workgroups 3.11 )から大幅に改善された新しいオペレーティングシステ ムである。このおかげでプログラミングは単純になり、プログラムの実行速度が上がった。 Windows は複数のアプリケーションを同時に実行できるグラフィカルなマルチタスクウィン ドウ環境である。各アプリケーションは、コンピュータ画面上のウィンドウと呼ばれる長方形 の領域にすべての出力を表示する。コンピュータ画面全体をデスクトップと呼ぶ。ユーザーは、 現実の机の上に紙を置くのと同様に、デスクトップ上にウィンドウを並べることができる。た とえば図 1-1 に示すように、横並びにしたウィンドウの中で 2 つのプログラムを実行し、他の プログラムを一時的に脇に取りのけておくというようなこともできる。図の中のすべてのプロ グラムは、アイコンとして脇にとりのけたものも含めて、実行を続けている。ほとんどのアプ リケーションは、ユーザーが操作をしていないときにはアイドル状態になっているが、中には アイコン化されている間も動作を続けるものもある。たとえば表計算プログラムは、アイコン 化されている間もスプレッドシートの値を再計算しているかもしれない。通信プログラム、電 子メールリーダーなどの通信プログラムは、アイコンの状態でもモデムやネットワークからの メッセージを読み込んで処理をすることがある。 プログラマにとっての Windows は、使いやすく一貫性のあるユーザーインターフェイスの 開発を強力にサポートしてくれるリッチなプログラミング環境である。Windows はメニュー、 ダイアログボックス、リストボックス、スクロールバー、プッシュボタンなどのユーザーイン 34 第 1 章 ・・・・・・・・・・・・Windows の概念の紹介 図 1-1 Windows デスクトップ ターフェイスコンポーネントを開発者に提供する。デバイスインディペンデントグラフィック スにより、プログラムが実行されるハードウェアプラットフォームについての詳細な知識がな くてもプログラムを書くことができる。このデバイスへの非依存性は、パーソナルコンピュー タの基本的なハードウェアデバイスにも当てはまり、プログラマはキーボード、マウスポイン タ、システムタイマー、シリアル通信ポートなどに、デバイスに依存しない形でアクセスする ことができる。 1.2 Win32とは何か? Win32 とは、一連の Windows プログラミングインターフェイスのファミリーのことである。 Windows 95 と Windows NT のための標準プログラミングインターフェイスである。Win32 ア の仕様は、Windows 3.x 以前の Windows プリケーションプログラミングインターフェイス ( API ) プログラミングに使われていた 16 ビット値ではなく、32 ビット値に基づいて作られている。32 ビットアドレスが採用されたことにより、整数のサイズが大きくなってパフォーマンスが上がっ ただけでなく、プログラミングの作業が大幅に単純化された。プログラマは、以前の Windows バージョンの“ セグメント化 ”された 16 ビットアドレスではなく、 “ フラット ”な 32 ビットの アドレス空間を扱うことができる。また、Win32 API にはさまざまな関数が新たに追加され ている。たとえば、Win95 API はベジェ曲線などのグラフィカルな操作や、( 制約はあるが) グラフィカルな変換行列の使用をサポートしており、カーネルに追加されたマルチスレッドの 能力によって強力で柔軟性の高いプログラムが書けるようになった。Windows NT API は、 Win95 API のすべての機能に加えて、さらに強力なグラフィックス機能( 65,536 単位を超える グラフィックス座標や汎用的な変換行列など)と、高度なファイル保護と共有の機能も備えてい る。また、Windows NT は複数のプロセッサ( CPU )が同じ物理メモリを共有するマルチプロ 1.3 ・・・・・・・・・・・・ユーザーインターフェイスの歴史 35 セッサシステムもサポートしている。 Win95 API と Windows NT API は、リッチテキストコントロールなどの強力なコントロー ルをサポートしており、これらを使えばきれいなユーザーインターフェイスを素早く作成する ことができる。Windows 3.1 で導入されて大きな成果を上げた“ コモンダイアログ”と同様に、 Win32 の“ コモンコントロール”を使うことでアプリケーションの“ルックアンドフィール”を 簡単に標準化できる。 また、Win32 は“ OCX コントロール”と呼ばれるスタイルのコントロールもサポートしてい る。OCX コントロールは OLE オートメーションを使ってインプリメントされている。Win16 で しか動作しなかった VBX コントロールとは異なり、OCX コントロールは 16 ビットと 32 ビッ トの両方のバージョンで作成できる。OCX コントロールを使えば、新しい種類のコントロール をアプリケーションに簡単に組み込むことができる。 これ以外にも、Win32 のバージョンの 1 つとして Win32s というものがある。これを使うと Windows 3.1 と Windows for Workgroups 3.11 上で動作する 32 ビットプログラムを作成でき るが、これらのプラットフォームに対する関心は急速に衰えつつあるようだ。 Windows 95 、Windows NT 、そして Win32s の細かい比較は本書の範囲を超えているが、 これらの API の間の大きな、また重要な違いについては随時指摘することにする。 1.3 ユーザーインターフェイスの歴史 初期の“パーソナル”コンピューティングには、トグルスイッチと電球を搭載したコンピュー *1 ユーザーはプログラムをコンピュータにロードするにあたって、一連のトグル タが使われた。 スイッチを正しいパターンに設定した上で、ボタンを 1 回押すごとに、1 つのキャラクタ、バイ ト、またはワードをコンピュータのメモリにロードしたものである。言うまでもないことだが、 この方法はきわめて遅く、間違いを犯しやすかった。コンピュータからの出力は、電球が配置 されたパネル上にバイナリ形式で表示されるのが一般的だった。 穿孔テープ、穿孔カード、そしてラインプリンタの登場によって事態は大きく改善された。 テープやカードに正しく孔をあけることさえできれば、プログラムは少なくともより正確にメ モリにロードできたし、読みやすい形式の出力が得られた。穿孔カードの山を床に落とした、 プリンタの紙が詰まったなどの出来事に目をつぶれば、コンピュータとの対話という作業の面 倒さと間違いやすさはずっと軽減されたのである。 それでも、コンピュータとの対話は簡単ではなかった。一般に、プログラムを記したカード のデックは、十分な量になるまで溜めおかれた後に一括して実行された。ジョブの種類によっ ては、コンピュータ上で 1 日に 2 回実行できたら祝杯に値したこともある。テレタイプ( TTY ) と、後の CRT( Cathode Ray Tube )や VDT( Video Display Terminals )端末(当時は“ガラス TTY ”と呼ばれた)の登場により、ユーザーはコンピュータと直接に対話を行えるようになり、 この時点でコンピュータに初めて触る人も増えた。この種のアクセス方式の先駆者として特に 重要なのが、1960 年代の MIT の CTSS( Compatible Time-Sharing System )と MULTICS ( MULTIplexed Computer System )である。ビデオ端末は 1970 年代の終わりに普及したが、 *1 米 DEC 社の PDP-8 は、最初の“パーソナル”コンピュータの 1 つだった。つまり、現実に 1 人の人間が買 うことができ、1 人の人間が専用で使ってもいいようなコンピュータである。このコンピュータではブート ストラップローダーをスイッチを使ってキー入力する必要があり、初期の PDP-8 プログラマはブートロー ドの際のトグルスイッチのシーケンスをいまでも暗唱することができる。 36 第 1 章 ・・・・・・・・・・・・Windows の概念の紹介 誕生は 1960 年代半ばにまで遡る。グラフィックディスプレイのコストが下がって広く使われる *2 ようになったのは 1980 年代に入ってからだった。 はじめのうち、ディスプレイ端末は、出力された最後の 24 行だけしか見えない印刷端末の ようなものでしかなかった。事実、IBM パーソナルコンピュータとその互換機上の DOS オペ レーティングシステムはいまでもディスプレイをこのように扱っている。ディレクトリリスト を出力すると、あっという間にリストが流れていき、長いディレクトリリストの最後の数行し か読めないこともある。 より優れたユーザーインターフェイスを求める絶え間ない探究の一環として、コンピュータ ディスプレイは、印刷端末のようなシーケンシャルなラインデバイスではなく、ランダムアク セスが可能なデバイスとして使われるようになった。スクリーン上にフォームを表示し、入力 フィールドで入力を受け付けるようなプログラムが登場した。しかし、プログラマは依然とし てさまざまな点で制約を受けていた。たとえば、ディスプレイは一般に 24 行×80 列から構成 されると仮定されていた。このような仮定はいまでもその影響を残している。最初の IBM パー ソナルコンピュータのテキストディスプレイは 25 行×80 列の表示だった。これは 24 行×80 列 のディスプレイに合わせて設計されたプログラムを、パーソナルコンピュータ用に簡単にコン バートできるようにとの配慮で、PC 用のプログラムは特別に 25 行目を利用することができた。 Color Graphics Adapater( CGA )、Enhanced Graphics Adapter( EGA )、Video Graphics Array( VGA )、super VGA 、8514 アダプタなどのディスプレイは、グラフィックスに加えて、 テキストをさまざまな行と列の数の組み合わせで表示できた。これらのディスプレイでも、一 部の古いプログラムは最初の 25 行と左の 80 列しか使わないなどの奇妙な動作を見せた。これ らのディスプレイのグラフィックモードは、プログラマにとってさらに扱いにくかった。グラ フィックスの組み込みサポートは事実上まったく存在しないので、個々のプログラマが同じルー チンを何度も作り直さなければならなかった。また、プログラムが特定のグラフィックディス プレイで正しく動作するようになっても、別のディスプレイ上でプログラムを実行するために は、新しいルーチンを苦労して作らなければならなかった。このプロセスは、 “ 標準 ”との“ 互 換性 ”をうたう多くのディスプレイカードが事実上は互換性を持たず、またそれらのディスプ レイカードの間でも互換性がないという事実によって、いっそう困難になっていた。 Microsoft Windows は、1983 年 11 月に、こうした問題に対する解決策として発表されたも のである。この製品は 1985 年 11 月に Windows version 1.01 としてリリースされ、その後の 2 年間に何回か改訂された。この時期にも、他のソフトウェアメーカー数社がグラフィカルま たはウィンドウ環境でプログラムを実行するソフトウェア製品をリリースしているが、そのほ とんどがいまでは忘れ去られている。 1987 年 11 月に Windows 2.0 がリリースされたのに伴い、Windows アプリケーションはバー ジョン 1.x の“ タイル”ウィンドウから、それ以降のすべてのバージョンで使われている“ オー バーラップ”ウィンドウに切り替わった。これも含めて、Windows 2.0 のユーザーインターフェ イスの外観と操作に関する各種の変更は、OS/2 プレゼンテーションマネージャとの一貫性を 保つために加えられたものである。 1990 年 5 月にリリースされた Windows 3.0 は、ユーザーと開発者の両方にとって重要な機 *2 初期のパーソナルワークステーションの歴史の概要については 、「 1.18 関連文献 」で紹介している 『 Proceedings of the ACM Conference on the History of Personal Workstations』が参考になる。 1.3 ・・・・・・・・・・・・ユーザーインターフェイスの歴史 37 能がいくつも追加されたバージョンだった。ディスプレイをひとめ見るだけで、以前のバージョ ンのデスクトップから外観が大きく改善されていることがわかる。さまざまなアイコンとボタ ンが“ 3 次元”の外観を持つようになった。メニューとダイアログボックスにはプロポーショナ ルフォントが使用されていた。Windows 3.0 はもはや 640K の境界に制限されておらず、拡張 メモリを使用することができた。拡張メモリとは、Intel 80286 以上の強力なプロセッサに搭載 されている、1 MB 境界の上にあるメモリのことである。複数の複雑なアプリケーションをカ ラーグラフィックディスプレイの上で横に並べて同時実行することができた。ユーザーインター フェイスは長い道をたどって、ようやくここまで来たのである。 1992 年 4 月にリリースされた Windows 3.1 によって、Windows 環境はさらに改善された。 ユーザーはインストールと構成をより簡単に行えるようになった。新しいユーザーのための チュートリアルが付属しており、すぐに Windows を使い始めることができた。プログラミン グの点から見ると、3.0 以前のバージョンでの Windows プログラミングをあれほど難しくして いた細かいメモリ管理上の概念がいくつも廃止されていた。プログラマは、1MB の大きさの ビットマップを割り当てるようなプログラムを書けるようになったし、事実書いたのである。 Windows 3.1 に新たに導入された概念の 1 つがコモンダイアログボックスである。[開く]、 [名前を付けて保存]、[フォントの選択]、[色の選択]、[通信]、[プリンタの設定]、[印刷] などのコモンダイアログボックスは、システム全体で共通の外観を持っている。これらのコモ ンダイアログは、プログラマが独自に開発する Windows アプリケーションの中でも使用でき る。一般的な機能の実行にコモンダイアログを使うようにしておけば、新しいユーザーでもす ぐにそのアプリケーションに慣れることができる。ユーザーは、他のアプリケーションを使っ た経験から、ファイルの保存やドキュメントの印刷などの一般的なユーザーインターフェイス の使い方を類推できるのである。 Windows 3.1 のファイルマネージャはそれまでのバージョンよりもはるかに高速に動作した。 また、ユーザーはファイルマネージャからファイルをドラッグし、別のアプリケーションにド ロップすることができた。このドラッグアンドドロップという概念は Windows 3.1 ではプログ ラミングが難しかったが、Win32 では当たり前のように使われており、プログラミングも簡単 である。プログラマは、Win32 アプリケーションを必要に応じてドラッグアンドドロップに対 応し、ユーザーによるファイルのドロップをサポートすることができる。 Windows 3.1 では TrueType というスケーラブルフォントが導入された。TrueType フォン トを使用することで、アプリケーションはスクリーン上にきれいな出力を表示し、任意のタイプ のプリンタに、そのプリンタが扱える最高の解像度を使って印刷することができる。TrueType フォントはスケーラブルである。つまり、ユーザーが各種のフォントのすべてのサイズのビッ トマップファイルを別々に用意していなくても、プログラマやユーザーは任意のフォントサイ ズを選択できる。TrueType はすべての Win32 インターフェイスでサポートされている。 また、Windows 3.1 では Win32s という新しいサブシステムが導入された。これは 16 ビッ トの Windows 3.1 プラットフォーム上で動作する 32 ビットのオペレーティング環境である。 Win32s は Windows 95 のサブセットで、 “ User モジュール”と“ GDI モジュール”におけるリ ソースの厳しい制限を含めて、下位の 16 ビットプラットフォームの持つ制限の多くに縛られて いた。適切な開発ツールがないことと、初期のバージョンにあった重大なバグのために、Win32s の有用性は限られていた。それでも、Win32s の後のリリースにより、開発者は 32 ビットコン パイラを使って、16 ビットプラットフォーム上で動作する 32 ビットアプリケーションを開発 38 第 1 章 ・・・・・・・・・・・・Windows の概念の紹介 できるようになった。だが、Office95 スイートや Visual C++環境などのいくつかの重要なアプ リケーションは Win32s 上では動作しない。Win32s 上で動作するプログラムを書くためには、 きわめて慎重になり、16 ビットの制限をつねに意識する必要がある。 1992 年 11 月には Windows for Workgroups version 3.1 がリリースされた。これは基本的 に、Windows 3.1 にネットワークサポートを追加したものである。両者の違いの大部分は、ネッ トワークインターフェイスと、ファイルおよびプリンタの共有機能を提供するモジュールに あった。1993 年 12 月には、これの改訂版である Windows for Workgroups 3.11 がリリース された。 1993 年 8 月になって、Windows NT version 3.1 がリリースされた。Windows NT は根底 の部分から新たに書き直された完全なオペレーティングシステムである。プリエンプティブな マルチタスクをサポートしており、1 つのプログラムがハングしても、システム全体をリブート する必要はなくなった。また、長い複雑な操作がコンピュータを何時間にもわたって占有して いる間も、他のアプリケーションを使うことができた。それ以前のバージョンの Windows は 協調型のマルチタスクに依存していた。このタイプのマルチタスクでは、時間のかかる演算を 行うときは、プログラマが明示的に制御を明け渡す必要があった。これを正しく行う方法につ いてはいろいろなことが語られ、特に従来の方法がラップトップの“ 電源管理 ”ソフトウェア と干渉することがわかったときには大きな論争になった。しかし、Windows NT のおかげで、 制御を明け渡す方法に悩む必要はなくなった。制御は必要に応じて他のタスクによってプリエ ンプト( 先取り)されるのである。また、Windows NT におけるタスクは、完全に独立したア ドレス空間の中で実行されているので、他の実行中のタスクやオペレーティングシステムその ものに損害を与えることはないし、異常を起こしたタスクから損害を受けることもない。 Windows NT では、ベジェ曲線、パス、変換行列などの強力なグラフィックス機能も新たに 導入された。さらに、Windows NT はセキュリティと保護をサポートしており、ファイルに対 するアクセス権の許可や制約を行うことができ、米国防総省の C2 セキュリティの要件も満た している。Windows NT は、複数のプロセッサが同じメモリを共有するマルチプロセッサシス テムにも対応している。ネットワークの存在を前提としており、ピアツーピアとクライアント サーバーのネットワーキングが組み込みでサポートされている。さらに、Windows NT はポー タブルでもある。Windows NT は従来の Intel プラットフォーム( 386 、486 、Pentium 、およ び Pentium Pro )だけでなく、MIPS R4000 、Digital Alpha 、および PowerPC を含むさまざ まな 32 ビットプラットフォーム上にインプリメントされている。どのプラットフォームも同じ API を提供しているので、アプリケーション内にプラットフォーム固有のコードを書かなけれ ば、これらすべてのプラットフォーム上でコンパイルし、実行できるのである。 Windows NT Advanced Server は 1993 年 7 月にリリースされた。Windows NT の 1 バー ジョンである Windows NT Server は、ファイルサーバーとしての用途に最適化されていた。 このバージョンはクライアントサーバーデータベースシステムを構築するための SQL Server などの製品をサポートすることができた。また、集中的なファイルシステムとしての役目を果 たすこともでき、Windows NT サーバー上で動作するテープバックアッププログラムで、ネッ トワーク上のすべてのワークステーションをバックアップすることができた。 1994 年 9 月には Windows NT Server 3.5 がリリースされ、1994 年 10 月には Windows NT Workstation 3.5 がリリースされた。その後まもなく、Windows NT Server 3.51 が 1995 年 5 月に、Windows NT Workstation 3.51 が 1995 年 7 月にリリースされた。API の点で見ると、 1.3 ・・・・・・・・・・・・ユーザーインターフェイスの歴史 39 これらはオリジナルの Windows NT API に対する小規模な拡張とバグフィックスが行われた バージョンに過ぎない。 Windows NT には 1 つの重大な欠点があった。動作するためにかなりの量のリソースを必 要としたのである。16 ビットの Windows 3.1 は、2 MB のメモリを持つローエンドの 386 プ ロセッサ上で動作したが、Windows NT 3.1 の最初のリリースは最低でも 16 MB のメモリを 必要とした。Windows NT 3.5 と 3.51 の最小必要メモリは 12 MB だが、アプリケーションに よってはこれよりも多くのメモリが必要な場合がある。Visual C++などのプログラミング環境 は最低でも 20 MB を要求し、32 MB を推奨していた。これらの要件のために、Windows NT はマシンをアップグレードする余裕のない大多数のユーザーのデスクトップ環境にたどりつく ことができなかったのである。この問題に対応すべく登場したのが、当時まだ初期の開発段階 にあった Windows 95 、コードネーム“ Chicago ”である。 1995 年 8 月にリリースされた Windows 95 は、主に 16 ビットで書かれている基盤の上で動 作する 32 ビットのハイブリッド環境である。これは Win32 API のサブセットをインプリメン トしている。かなりの部分が依然としてアセンブリ言語で書かれているので、ポータビリティは 低いが、4 MB という小さいメモリでも動作するという点が Windows NT との違いである( た 。 だし本格的な開発作業には、32 ビット開発環境を実行するために 16 MB 以上が必要だろう) Windows 95 は Windows NT と同様にプリエンプティブなマルチタスク、マルチスレッド、お よびタスクごとに独立したアドレス空間をサポートしているが、保護の点で見ると、アドレス空 間の間での保護はそこそこで、ファイルシステム上の保護はほとんど皆無である。Windows 95 では、Windows NT でも利用できる新しい標準コントロールと、古い“プログラムマネージャ” に代わる新たなインターフェイスが導入された。この新しいインターフェイスは Windows NT 上でも利用できる。また、Windows NT と同様に、Windows 95 は“ ネットワーク対応”であ り、Windows for Workgroups のすべての機能を取り込んでいる。パスやベジェ曲線といった Windows NT の高度なグラフィックス機能の多くが Windows 95 にもインプリメントされた。 Windows 95 は、実際には Windows NT 3.x の Win32 インターフェイスのサブセットでは なく、 “ レベル 4 ”の Win32 インターフェイスのサブセットとして位置付けられる。このイン ターフェイスには、Windows NT 3.x にはないが Windows NT 4.x には存在するかなりの数 の新しい API 機能が含まれている。これらの機能が Windows NT 3.x にないことが問題を引 き起こす可能性があるので、本書では可能な限り両者の違いを指摘している。 また、Windows 95 には、テレフォニーやテレフォニー API( TAPI )を通してのモデムサポー トなど、Windows NT 3.x にはないいくつかの能力が含まれている。 Windows 95 と Windows NT は今後も進化を続けていくだろう。Windows 95 は、多くの ユーザーにとって、32 ビットアプリケーションを低機能のハードウェアで実行できるようになっ たという意味で革新的なステップだったといえる。Windows NT は、特に情報のセキュリティ が必要となるヘビーデューティーなエンタプライズコンピューティングに勧められる。 1996 年 8 月には Windows NT Workstation 4.0 と Windows NT Server 4.0 がリリースさ れた。これらのリリースは 4.x レベルの API を完全にインプリメントしており、これによって Windows 95 API は Windows NT 4.0 API のサブセットとなった。プログラマは、そこそこ の注意を払うだけで、Windows 95 と Windows NT 4.x の両方で動作するアプリケーションを 書くことができる。Windows 95 上でインプリメントされていない関数が正常に実行されるこ とをテストするだけで済むのである。 40 第 1 章 ・・・・・・・・・・・・Windows の概念の紹介 Microsoft は Windows 95 をリリースする少し前に、Windows NT 4.0 は 10MB のメモリで 動作し、Windows 95 の次のリリースでは 8MB のメモリで動作するようにすると発表した。し かし、Windows 95 、Windows NT 3.51 、Windows NT 4.0 を使ってきたわれわれは、この小 さすぎる値に疑いの目を向けずにはいられない。メモリのコストの変動は、このような“小さな マシンの救済”という議論のほとんどを無意味にしている。読者は、プログラム開発を手掛ける のであれば、少なくとも 32MB のメモリが必要になると覚悟するべきである。かつて Windows NT の普及の大きな障壁として見られていたメモリのコストは、もはや重要な要因ではなくなっ ている。Windows NT 3.1 と Windows NT 4.0 のリリースの間に、メモリの価格は 25 倍以上 も低下したのだ。 Microsoft は Win32 API のサポートと発展に強く言明している。いわゆる“ パームトップ ” デバイス、 “ パーソナルデジタルアシスタント( PDA )”、さらには組み込みシステムのような 新しいアプリケーション領域が発展しつつあるが、Microsoft は Win32 API をこれらすべての システムの基盤に対する決定的なインターフェイスとして位置付けている。 本書の主題は、32 ビット Windows アプリケーションの開発である。Windows アプリケー ションは、おおまかに次の 4 つの種類に分類できる。 1. Win32s 上で動作しなければならないもの 2. Windows 95 と Windows NT のどちらでも動作するもの 3. Windows 95 を必要とするもの 4. Windows NT を必要とするもの これ以外にも、Windows NT 3.x ではなく Windows NT 4.x を必要とするというような制限 を付け加えることができる。アプリケーションによっては、Windows 95 と Windows NT 4.x を必要とし、3.x レベルのシステム上では動作しないものもある。16 ビット Windows から慌 ただしくコンバートされたアプリケーションには、依然として Windows 95 上でしか動作しな い 16 ビットのインターフェイスコードを使っているものもある。また、Windows NT にはま だコンバートされていないが、Windows 95 上では動作するデバイスドライバを要求するもの もある。 Win32s は、せいぜい Windows 95 と Windows NT への移行の中継点としての意味しか持た ないので、本書ではプログラムを Win32s 上で実行するときに生じるいくつかの制約に簡単に 触れるだけにする。また、Windows 95 と Windows NT の間の、GUI に影響を与える相違点 についても簡単に説明するにとどめる。マルチスレッドやセキュリティに関する詳細な議論は本 書の範囲を超えている。ただし、本書は Windows プログラミングの初心者だけでなく、Win32 を使ったプログラミングを始めようとしている Windows 3.x プログラマも対象にしているの で、Win16 API と Win32 API の間の大きな違いについては詳しく説明するし、既存の 16 ビッ トアプリケーションの Win32 への移植、さらにはプログラマの 16 ビットプログラミングに関 する知識の Win32 への移植という点に影響を与える箇所は細かく指摘する。本書は Windows 95 と Windows NT に適用できるテクニックを使った 32 ビット Windows プログラミングを教 える本である。本書のすべてのサンプルプログラムはどちらのプラットフォームでも正常に動 作するが、次の 2 点に注意する必要がある。第 1 に、Windows NT 固有の関数を呼び出してい るサンプルプログラムは、その関数を持っていない Windows 95 では当然ながらうまく動作し ない。第 2 に、Windows NT の能力、たとえば変換行列やファイルセキュリティなどに依存す 1.4 ・・・・・・・・・・・・Windows プログラムと典型的な DOS または UNIX プログラムとの違い 41 るアプリケーションは、Windows 95 ではその能力なしでも動作するように慎重なプログラミ ングを行う必要がある。実際、Microsoft はこれを 32 ビットアプリケーションの“ ロゴ認定 ” の条件の 1 つにしている。アプリケーションは、Windows 95 にはない Windows NT 固有の 機能を使ってもよいが、その場合でも Windows 95 と Windows NT の両方で実行できなけれ ばならないのである。 1.4 Windowsプログラムと典型的なDOSまたはUNIXプログラムとの違い 図 1-2 Windows の入力コントロールの例 Windows プログラムと典型的な DOS プログラムの間にはさまざまな違いがある。Windows アプリケーションはシステムリソースを共有しなければならないが、DOS アプリケーションは 一般に共有する必要がない。Windows アプリケーションはグラフィカルな出力を生成するが、 DOS アプリケーションは一般にテキスト表示を生成する。また、Windows アプリケーション は、入力の受け付けとメモり管理の点で DOS アプリケーションとは大きく異なる。Windows アプリケーションはハードウェアに関する詳細な知識を必要としないが、DOS アプリケーショ ンは特定のタイプのデバイスしか扱えないことが多い。 1.4.1 リソースの共有 Windows アプリケーションは、最初から、アプリケーションが実行されるシステムのリソー スを共有するように設計される。メインメモリ、プロセッサ、ディスプレイ、キーボード、ハー ドディスク、およびディスクドライブなどのリソースは、Windows 上で動作するすべてのアプ リケーションから共有される。この共有を実現するために、Windows アプリケーションは、コ ンピュータのリソースとの対話を行うときには必ず Windows のアプリケーションプログラミ ングインターフェイス( API )を通す必要がある。Windows がリソースを制御できるのは、リ 42 第 1 章 ・・・・・・・・・・・・Windows の概念の紹介 ソースへのアクセスが必ず Windows API を通ることがわかっているからである。この制約の おかげで、Windows 環境では複数のプログラムの同時実行が可能になっている。 これとは対照的に、典型的な DOS アプリケーションは、システムのすべてのリソースを自分 で利用できるものと期待する。DOS アプリケーションは他のアプリケーションを気にせずに、 使用可能なすべてのメモリを割り当てることができる。シリアルポートやパラレルポートを直 接に操作できる。さらに、キーボード割り込みを横取りし、キーダウンとキーアップの切り替 わりを直接に処理することさえできる。DOS アプリケーションはシステムリソースを共有する ように設計する必要はないのである。 一方 Unix プログラマは、この点に関しては、Win32 オペレーティング環境を目新しくは思 わないだろう。Win32 のメモリ環境は、Unix と同様にプライベートなアドレス空間という概 念をベースにしている。 1.4.2 グラフィカルユーザーインターフェイス Windows アプリケーションは一般にグラフィカルユーザーインターフェイス( GUI )を持 *3 複数のアプリケーションが同時に実行される可能性があるので、各アプリケーションは つ。 “ ウィンドウ ”を通してグラフィックディスプレイにアクセスすることになる。アプリケーショ ンはこのウィンドウを通してユーザーと対話を行う。Windows アプリケーションが持てるウィ ンドウの数は 1 つに制限されない。Windows アプリケーションは、グラフィックディスプレイ 上に複数のウィンドウを同時に表示することができる。 また、Windows はグラフィカルユーザーインターフェイスの一部として、豊富な入力コント ロールのセットを用意している。これらのコントロールの例としては、ダイアログボックス、コ ンボボックス、あらゆる種類のボタン、メニュー、およびスクロールバーがある。図 1-2 のダ イアログボックスは、タブコントロール、プッシュボタン、ラジオボタン、アップダウン(“ ス ピン ”)コントロール、エディットコントロール、標準のコンボボックス、グラフィックスを含 んだコンボボックス(“ オーナー描画 ”コントロールとして実現されている)、および 3 つのカ スタムコントロールを含んでいる。ダイアログボックスは、ユーザーに対して情報を表示し、 ユーザーからの入力を要求するためのものである。プログラマがユーザーとの対話またはダイ アログを“パッケージ化”するための手段ともいえる。ユーザーはプッシュボタン、チェックボッ クス、およびラジオボタンを使って、従来のようにユーザーとアプリケーションの間で質問と 答えのやりとりをすることなく、真/偽、オン/オフ、有効/無効などのタイプのプログラム オプションを選択することができる。メニューは、ユーザーがプログラムに対してアクション を命令するために使用される。スクロールバーは、ユーザーがファイル、ドキュメント、その 他の出力表示のどの部分をカレントウィンドウに表示するかを指示するために使用される。 および Macintoshについて 1.4.3 Unix、X 、Motif、OS/2、 Win32 に初めて触れるプログラマの多くは、Unix 、X 、Motif 、OS/2 、あるいは Macintosh のプログラミング経験がある。従来の Unix の“ シェル ”の世界からきたプログラマは、DOS プログラマと同じパラダイムシフトを経験することになる。つまり、ユーザーとの対話の方式 *3 コンソールアプリケーションや NT システムサービスのように、通常は GUI を持たずに動作する特殊な種 類のアプリケーションがある。これらのアプリケーションは、一般にユーザーと直接の対話を行わない。本 書ではこれらのアプリケーションは扱わない。 1.4 ・・・・・・・・・・・・Windows プログラムと典型的な DOS または UNIX プログラムとの違い 43 がまったく異なるのである。X Window 、Motif 、または Macintosh からきたプログラマには、 驚くようなことはほとんどない。スタイルの細かい部分が異なり、命名規約にも違いがあるの で、古いアイデアに付けられた新しい名前を学ぶ必要はある。また、API 呼び出しにも違いが ある。OS/2 プレゼンテーションマネージャ( OS/2 PM )からきたプログラマも、類似する点が 多いと感じるだろう。OS/2 PM は Win32 API にかなり似ており、いくつかの点ではまったく 同じである。 1.4.4 入力機能 ユーザー入力は、Windows 環境に初めて触れるプログラマが遭遇する最も大きな違いの 1 つ である。Windows は入力デバイスを制御し、これらのデバイスからの入力を、必要に応じて複 数の同時実行されているアプリケーションに分配する。Windows アプリケーションはマウスや キーボードなどの入力デバイスをオンデマンドで読むことはできない。Windows アプリケー ションは、アプリケーションが必要としたときに入力を要求するのではなく、ユーザーが入力 を生成したときにそれを受け付けるように作られる。従来の DOS や Unix のプログラミングか *4 らきた人は、getch() 関数が存在しないことに注意しなければならない。 ユーザーがキーを押したり離したりするたびに、Windows アプリケーションに対して通知が 行われる。マウスカーソルがアプリケーションのウィンドウを横切ると、一連のメッセージが アプリケーションに送られ、マウスカーソルの現在位置に関する最新の情報を与える。マウス ボタンが押されるか離されると、Windows はアプリケーションにそのイベントを通知し、イベ ントが発生したときにマウスカーソルがウィンドウ内のどの場所にあったかを知らせる。ユー ザーがメニューからの選択、チェックボックスのクリック、プッシュボタンのクリック、スクロー ルバーのスクロールといったさまざまな操作を行っている間、アプリケーションプログラムは ユーザーの操作を正確に知らせる一連のメッセージを受け取り続けているのである。 Windows アプリケーションのこの性質は、DOS や Unix の標準的なシェルアプリケーショ ンとはまったく異なっている。このため、プログラムの構造も大きく変わってくる。Windows アプリケーションは、DOS や Unix の標準的なシェルプログラムよりも書くのが難しいとは必 ずしもいえない。構造は目新しいし、古い習慣や手法は使えなくなるが、Windows プログラ ミングに関連する新しい概念をマスターすれば、多くの DOS や Unix のテキストベースのシェ ルアプリケーションよりも使いやすいアプリケーションを書けるようになる。プログラミング 環境、特に Microsoft Foundation Classes( MFC )のような豊富なライブラリとツールは、面 倒な部分を肩代わりし、プログラムの作成作業を従来の DOS や UNIX プログラミングよりも さまざまな面で簡単にしてくれるのである。 1.4.5 メモリ管理 メモリというリソースも、Windows 環境のすべてのアプリケーションから共有されるリソー スである。コンピュータのメモリ全体を自分のために使用できると仮定することが多い標準的 な DOS プログラムとは異なり、Windows では複数の Windows アプリケーションが使用可能 *4 API を使って DOS や Unix 風のシェルを作成する賢い方法については、Schulman 、Maxey 、Pietrek 著 『 Undocumented Windows』を参考にするとよい。「 1.18 関連文献」を参照のこと。ただし、この本は Windows 3.1 しか扱っていない。Windows NT では、 “コンソール”サブシステムを使って、Unix や DOS に似た“ シェル ”スタイルのアプリケーションや“ フィルタ ”を作成することができる。 44 第 1 章 ・・・・・・・・・・・・Windows の概念の紹介 なメモリを共有しなければならない。Windows アプリケーションは、自分が必要とするすべて のメモリを最初に予約して、アプリケーションが終了するまでそれを保持するというような方 法を取らず、その時点に必要なストレージだけを割り当てて、不要になったらすぐに解放しな ければならない。 初期のバージョンの Windows では、8088 の 640K のメモリ空間の制限に、のちには 286 や 386 以上の Intel プロセッサが持つ 16 ビットプロテクトメモリのセグメント化されたアーキテ クチャの制限に対応するために、細かいメモリ管理戦略を使う必要があった。Win32 プログラ マは、使い慣れた C の malloc() 関数や C++の new を使うことができる。ただし、後で述べる ように、例外もいくつか存在する。 1.4.6 デバイスインディペンデントグラフィックス 最後に、Windows はデバイスに依存しないグラフィックス操作を提供している。Windows プログラマがグラフィックディスプレイに線や円を描けるようになったら、同じプログラムを 使って、Windows がサポートしているすべてのグラフィックディスプレイ上に正しく図形を描 画することができる。このデバイスへの非依存性はディスプレイに限られたものではない。ディ スプレイ上に円グラフを表示するグラフィック処理は、ドットマトリクスプリンタ、レーザープ リンタ、プロッタ、または Windows がサポートしているその他の出力装置上に同じ円グラフを 出力することができる。また、DOS とは異なり、印刷のためのインターフェイスは 1 つ用意す るだけでよい。ユーザーのプリンタが大昔の FX-80 なのか、600 dpi のフルカラーレーザープ リンタなのかを心配する必要はほとんどないといってよい。 1.5 Windowsのプログラミングモデル Windows プログラミングは学ぶのが難しいという評価を得ている。このような評価の理由 の 1 つは、プログラミング環境の豊富さにある。人は Windows の API 関数の数に簡単に圧倒 されてしまうのだ。Windows プログラミングを学ぶ上では、2 つの大きな障壁がある。 1. Windows プログラムは、従来の DOS または Unix のシェルプログラムとは異なる概念 モデルを必要とする。 2. この概念モデルに基づいたプログラムを作成するために使われるツールが、これらの概 念を直接にはサポートしていない。 1.5.1 概念モデル Windows アプリケーションは、さまざまな形式のユーザー入力に反応し、ユーザーに対して 高度なグラフィック出力を提供するプログラムである。アプリケーションが表示するすべての ウィンドウは、ユーザーのアクションに反応しなければならない。たとえばメニューはポップ ダウンし、選択項目を表示しなければならない。プッシュボタンは押し下げられた状態になり、 マウスが離されたら元の状態に戻らなければならない。チェックボックスはオンとオフの状態 を表示しなければならない。Windows のオブジェクト一般は、操作が行われたら、それに対し て反応する必要がある。このような特徴のために、Windows アプリケーションは概念的にはオ ブジェクトの集まりと見なすのが適している。これは、まさにオブジェクト指向プログラミン グが提案している考え方である。 1.5 ・・・・・・・・・・・・Windows のプログラミングモデル 45 オブジェクト指向 GUI プログラミングの概念は、1970 年代半ばに、グラフィカルプログラミ ング環境の概念とともに Xerox Palo Alto Research Center( PARC )で生まれた。オブジェク ト指向プログラミングでは、プログラマは抽象データ型を作成する。これらの抽象データ型は 一般にオブジェクトと呼ばれ、データ構造と、このデータ構造を操作するための一般にメソッ ドと呼ばれる関連機能から構成されている。通常、オブジェクトのデータ構造は、外の世界に 対して公開しているメソッドを除けば、外からは完全に隠されている。このデータのカプセル 化と呼ばれるアプローチにより、オブジェクトの内部構造の変化をコントロールすることがで きる。プログラムの他の部分は、メソッドという形で提供される外部インターフェイスが変更 されない限り、オブジェクトの内部的な構造や機能のインプリメンテーションの細部を知る必 要がない。 これらの概念は、Windows アプリケーションにごく自然に適用できる。Windows には以下 のものをはじめとして多種多様なオブジェクトが存在する。 ● ペン=幅、色、およびダッシュスタイルを持ち、線を描くために使われるオブジェクト ● ブラシ=色を持ち、領域のペイントに使われると一定のパターンを残すオブジェクト ● メニューとダイアログボックス しかし第 1 の、最も基本的なオブジェクトは“ ウィンドウ ”である。 システムメニュー メニュー キャプション 最小化、 最大化、 クローズボタン サイズ変更境界 子ウィンドウ 背景色 図 1-3 ウィンドウの主な属性 スクリーン上に表示されるウィンドウに、各種のデータが関連付けられているのは一目瞭然 だろう。ウィンドウは背景色、タイトル、メニューなどをはじめとするさまざまな属性を持っ ている。図 1-3 はウィンドウの主な属性を示している。後で述べるように、Windows はこれ らの属性のインプリメンテーションを隠蔽しており、これらを変更するためには、ウィンドウ が提供している外部インターフェイスを経由するしかない。たとえば、カーソルがウィンドウ の中にあるときにユーザーがマウスボタンを押した場合には、ウィンドウに対してイベントの 通知を送り、ウィンドウにその処理を任せるというのがオブジェクト指向の考え方である。図 1-3 は、ウィンドウが他のウィンドウを(再帰的に)含むことができるということも示している。 この子ウィンドウも、( メニューを除く)すべてのウィンドウプロパティを持っている。子ウィ 46 第 1 章 ・・・・・・・・・・・・Windows の概念の紹介 ンドウの上でマウスがクリックされると、通知は子ウィンドウに対して送られる。その後、子 ウィンドウは必要ならば親ウィンドウに対して通知を行うこともできる。 ドッキングウィンドウ、小さなツールパレット、分割可能なウィンドウなどの便利な機能は どうなのだろうか? 実はこれらは Windows API の一部ではなく、アプリケーションコードの 中でインプリメントされるもので、一般には Microsoft Foundation Class( MFC )ライブラリ が使われる。MFC は高水準の Windows プログラミングインターフェイスを提供する C++の クラスとメソッドの集まりである。本書では MFC については触れていないので、Alan Feuer 著『 MFC Programming』 (「 1.18 関連文献」を参照)を参考にしてもらいたい。ツールバーな どのいくつかの機能は、以前は完全に MFC の中にインプリメントされていたが、現在ではコ モンコントロールライブラリの形で独立した機能として提供されている。Windows の進化の過 程ではこれ以外にも OLE ライブラリなどのいくつかのライブラリが追加されてきた。OLE ま たは ActiveX のプログラミングを専門にしている人は、これらのライブラリを“ Win32 API ” の重要な一部として見ているかもしれない。このように、 “ 純粋 ”な Win32 API とライブラリ *5 ベースの拡張機能の境目は見分けにくいことがあるが、これは必ずしも悪いことではない。 オブジェクト指向プログラミングは、ポリモーフィズムと呼ばれる、オブジェクトが複数の 挙動を持つ能力もサポートしている。図 1-3 の例でいえば、マウスボタンが押されたときの反 応はウィンドウごとにまったく異なるかもしれない。ウィンドウによってはイベントを完全に 無視することもありうる。 オブジェクト指向プログラミングは継承という概念もサポートしている。Windows プログ ラミングでは、機能が追加されている( または削減されているか、若干異なる)のを別にすれ ば“ あのウィンドウと同じような ”ウィンドウを作成できると便利なことが多い。新しいウィ ンドウを毎回作成するのではなく、既存のウィンドウをテンプレートとして使えるとよいだろ う。Windows はまさにこのような手法をサポートしている。これは組み込みコントロールの挙 動を変更したい場合にも重要になる。これができないと、コントロールのすべての機能をアプ リケーションの中でプログラミングしなければならなくなるが、継承を使えば、プログラマが 重要だと思った機能の変更箇所だけをプログラミングすればよいのである。 Windows アプリケーションの開発に使われるツールは、この種の概念モデルを直接にはサ ポートしていない。つまり、システムのオブジェクトベースのビューを、C 、Pascal 、またはア センブラの特定のプログラムステートメントに直接にマップすることはできない。たとえば、 オブジェクト指向プログラミングにおける“オブジェクト”と同じ意味を持つオブジェクトを宣 言するステートメントは C には存在しない。 このように、パラダイムシフトと、ツールの整備が不十分であるという理由から、Windows プログラマは Windows アプリケーションを動作させるという目標を達成するために、2 つの ハードルを越えなければならない。 *5 “純粋”な Win32 API は、3 つの中心的な Windows ライブラリ、すなわち Kernel 、User 、および GDI の 中にインプリメントされている API 関数の集まりであるということも可能だろう。しかし、コモンダイア ログやコモンコントロールなどの機能は非常に重要であり、これらが別途提供されているのは、Windows の進化の歴史上の偶然だともいえる。本書では、3 つのコアライブラリ、コモンダイアログ、コモンコント ロールのさまざまな側面を扱い、必要に応じて他のライブラリにも触れることにする。 1.5 ・・・・・・・・・・・・Windows のプログラミングモデル 47 1. どのようなプログラミングにも共通することだが、特定の問題を取り上げて抽象化し、具 体的なケースから、目的とする一般化された挙動に向けて進んでいかなければならない。 ほとんどのプログラマにとって、Windows プログラミングに使われる概念モデル( オブ ジェクト指向アプローチ)は最初のうちは慣れないものである。慣れ親しんだシーケン シャルなプロシージャ的アプローチは Windows 環境では使用できない。プログラミン グタスクに対するソリューションを新しい方法で構築するのに慣れるためには時間がか かる。 2. 最初のハードルを越えても、前方にはもう 1 つのハードルが待ち構えている。本書で扱っ ているプログラミング言語である C は、これらのオブジェクト指向の概念を直接に表現 するための手段を備えていない。このためプログラマは、オブジェクト指向に対応して いない言語を使って、ソリューションのオブジェクト指向デザインを正確にモデル化し たプロシージャ的なコードを書かなければならないのである。 この“ オブジェクト指向”アプローチは、プログラミングに C と C++のどちらを使うかとは 無関係なことに注意してほしい。Windows では、基本となる“ オブジェクト”はウィンドウで あり、 “ メソッド”はウィンドウに対して送られるメッセージである。一方、C++が提供するオ ブジェクトは、これとは違った観点に立っている。現実には、Microsoft 社や Borland 社など のベンダーが開発しているライブラリは、ウィンドウと、そのスクロールバー、エディットコ ントロールなどのあらゆるバリエーションを表現する C++オブジェクトを定義している。本書 では、C++ライブラリを使ったときのパラダイムの変わり方についても簡単に触れているが、 これらのライブラリについての細かい議論は本書で扱える範囲を超えている。C++と MFC の 関連文献」 入門書としては、Alan Feuer の MFC ライブラリに関する本をお勧めする(「 1.18 を参照)。MFC ライブラリは、オブジェクト指向言語を使って、Windows のオブジェクト指 向的な構造をモデル化しようと試みている。この 2 つのオブジェクトモデルは決して同じもの ではないが、実際には両者の違いの大部分を隠すことに成功している。 “オブジェクト指向 ”の Windows ライブラリは、下位の API の上に置かれた非常に薄い覆 いに過ぎないという点にも注意する必要がある。C++などの言語を使うことによって生じる利 点は大きいが、根本的なパラダイムは C と C++のどちらでプログラミングを行ってもきわめ て似ているのである。C++ではライブラリが細かい部分をある程度は隠してくれるが、下位の Windows のメカニズムと API の能力を理解していない限り、 “オブジェクト指向”の C++ライ ブラリは使うのが非常に難しいだろう。 この状況は、多くの点で、レポート作成プログラムを Visual Basic ではなく FORTRAN で 書くということに似ている。これは確かに可能ではあるが、そのために必要な労力の多くは、 特にその問題に適しているとはいえない言語を使って問題を表現するという作業に費やされる。 もちろん、その逆も成り立つ。FORTRAN が使えるのであれば、大量の演算を行うプログラム を Visual Basic で書くべきではない。 では、Windows はこのパラダイムの自分なりの仕組み( イベントの通知)をどのようにイン プリメントしているのだろうか? また、プログラマとしてのわれわれは、このタスクに特に適 しているとはいえない言語を使って、これらの通知に正しく反応するプログラムをどのように 書けばいいのだろうか? その答えは、ウィンドウ、それに関連付けられたウィンドウ関数、そ してメッセージに潜んでいる。 48 第 1 章 ・・・・・・・・・・・・Windows の概念の紹介 それに関連付けられたウィンドウ関数 1.5.2 ウィンドウと、 前に述べたように、プログラムがウィンドウを作成すると、そのウィンドウはスクリーン上 での位置、タイトル、サイズ、メニューといった一定の特性を持つようになる。これらは物理 的な特性と考えることができる。しかし、ウィンドウには、各種のイベントの通知にどのよう に反応するのかという別の種類の特性も持っている。これらは動作上の特性である。すべての ウィンドウには、イベントの通知に対する反応を決定するウィンドウ関数と呼ばれる関数が関 連付けられている。通知そのものはメッセージと呼ばれる。ウィンドウに含まれているボタン やスクロールバーコントロールなどは、それ自体がウィンドウである。したがって、これらに もボタンやスクロールバーコントロールの反応を制御するウィンドウ関数が関連付けられてい る。スクロールバー“ウィンドウ”は、マウスクリックに応えて、ウィンドウの外観を視覚的に 変更し、親ウィンドウにメッセージを送ってスクロールを行うように要求する。 前述のように、ウィンドウに影響を与える可能性があるイベントが発生すると、Windows は そのウィンドウに通知を行う。通常この仕組みは、メッセージがウィンドウに送られる、また 逆の視点からは、ウィンドウがイベントの発生を通知するメッセージを受け取るというふうに 表現される。実際には、Windows がウィンドウに関連付けられた関数を呼び出し、呼び出しの 引数として、発生したイベントを記述する情報を渡している。このように、ウィンドウ、それ に関連付けられたウィンドウ関数、そしてウィンドウが受け取るメッセージは互いに密接な関 係を持っている。この様子を図 1-4 に示す。この図には 3 つのウィンドウが描かれているが、 そのうちの 2 つは同じ種類(ウィンドウクラス)に属しており、もう 1 つは別の種類のウィンド ウである。 “ クラス A ”の 2 つのウィンドウには、送られてきたメッセージを処理する 1 つの関 数が関連付けられており、 “クラス B ”のウィンドウにもこれと似た関数が関連付けられている。 メッセージはどこからやってくるのだろうか? メッセージにはどのような種類があるのだろ うか? メッセージを受け取るにはどうすればいいのだろうか? メッセージの発生場所はさま ざまである。ほとんどのメッセージは Windows オペレーティング環境から生じるが、アプリ ケーション自身も 1 つのウィンドウから別のウィンドウに向けてメッセージを送ることができ る。ウィンドウは自分宛てのメッセージを送ることすらできる。Windows アプリケーションが ウィンドウ1: クラス A ウィンドウ3: クラス A ウィンドウ2: クラス B qwerty yuiop asdf ghjk l; zxcv bnm. クラスBをインプリメント するコード クラスAをインプリメント するコード 図 1-4 ウィンドウとウィンドウ関数 1.5 ・・・・・・・・・・・・Windows のプログラミングモデル 49 自分宛てのメッセージを送るのは、先の時点で何らかの操作を行うように自分自身に命令した いときである。アプリケーションはそのメッセージが到着した時点で目的のアクションを実行 する。アプリケーションはまったく別のアプリケーションにメッセージを送ることもできる。 Windows から受け取った 1 つのメッセージから複数のメッセージが分岐することも多い。で は、メッセージにはどのような種類があるのだろうか? ほとんどのメッセージは、マウスがク リックされた、キーが押されたなどの何らかの外部のイベントに関する通知である。この種の 通知は特定のウィンドウに送られる( どのウィンドウなのかという点については、後で詳しく 説明する) 。通知を受けたウィンドウは、これに反応して他のウィンドウに通知を送ることがあ る。メッセージの中には宛先のウィンドウに対して情報を要求するものもある。メッセージは ウィンドウに対して、キャプションの内容、ステータス、ウィンドウ自身を破棄していいかど うかといった事柄を尋ねることができる。そして、ウィンドウに対してキャプションや色といっ た外観の何らかの要素を変更するように指示するメッセージもある。最後に、メッセージを受 け取るにはどうすればいいのだろうか? メッセージを正しい順序で正しい宛先に配信するとい うことは、Windows の動作の中核にある事柄なので、この点について詳細に説明しておこう。 1.5.3 ウィンドウキューとメッセージループ Windows におけるメッセージの多くはデバイスから生じる。キーボードのキーを押して離 すと、キーボードデバイスドライバによって処理されるキーボード割り込みが生成される。マ ウスを移動したりマウスボタンをクリックすると、マウスデバイスドライバによって処理され る割り込みが生成される。これらのデバイスドライバは、Windows を呼び出して、ハードウェ アイベントをメッセージに変換する。その結果として得られたメッセージは Windows システ ムキューに入れられる。 Windows にはシステムキューとスレッドキューという 2 つのタイプのキューがある。シス テムキューは 1 つしか存在しない。メッセージに変換されたハードウェアイベントはシステム キューに入れられる。メッセージがシステムキューにとどまる時間は非常に短い。一方、実行中 の Windows アプリケーションは、それぞれ独自のスレッドキューを持っている( Win32 では、 実行中の個々のアプリケーションは複数の実行スレッドを持つことができ、個々のスレッドがそ れぞれ独自のスレッドキューでメッセージを処理することになる。マルチスレッドについては 後で詳しく説明する) 。Windows はシステムキューの中のメッセージを適切なスレッドキュー に転送する。プログラムのスレッドキューは、そのスレッドの中で動作しているすべてのウィ ンドウに対するすべてのメッセージを受け取り、これらのメッセージを先入れ先出し( FIFO ) の順序で処理していく。 Win16 アーキテクチャでは、各アプリケーションが独自のプライベートなアプリケーショ ンキューを持っており、システムキューが 1 つだけ存在していた。しかし、このシステム キューの扱いは若干異なっていた。Win32 の下で動作する Win16 サブシステムは、Win16 アプリケーションと相互作用するときには、このシステムキューの挙動をシミュレートする。 Windows はシステムキューを使って共有リソース( マウスやキーボードなど)の共有をイン プリメントしている。イベントが発生すると、メッセージはシステムキューに入れられる。その 後 Windows は、イベントの種類に応じて、どのスレッドキューがそのメッセージを受け取る べきかを決定する。Windows は、メッセージを受け取るスレッドキューを決定するために入力 50 第 1 章 ・・・・・・・・・・・・Windows の概念の紹介 フォーカスという概念を使用する。入力フォーカスとは、一度にシステム内の 1 つのウィンド ウだけが所有できる属性である。入力フォーカスを持つウィンドウはすべてのキーボード入力 のターゲットとなる。システムキューの中のキーボードメッセージは、その時点で入力フォー カスを持っているウィンドウに対応するスレッドのスレッドキューに移動される。入力フォー カスが別のウィンドウに移ると、Windows はそれに応じてキーボードメッセージの移動先を切 り替える。メッセージは 1 つずつ移動されるが、これはキーストロークの中に入力フォーカス を別のウィンドウに切り替えるための要求もあるからである。この場合、システムキューの中 のそれ以降のキーボードメッセージは別のスレッドキューに移動されることになる。 マウスメッセージはこれとは若干異なる方法で処理される。マウスもやはり共有リソースで ある。通常、マウスメッセージはマウスポインタの下にあるウィンドウに送られる。複数のウィ ンドウがオーバーラップしている場合は、一番上にあるウィンドウ、つまり表示されているウィ ンドウがマウスメッセージを受け取る。この規則の例外は、マウスのキャプチャが行われてい るときである。Windows アプリケーションが( Windows の関数呼び出しを使って)マウスを キャプチャすると、Windows はシステムキューの中のそれ以降のすべてのマウスメッセージ を、マウスがスクリーン上のどこをポイントしているかにかかわらず、キャプチャを行ったウィ ンドウのスレッドキューに移動する。このアプリケーションは、他のアプリケーションがマウ スを使用できるように、キャプチャしたマウスをいつかは解放しなければならない。 ハードウェアタイマーは、タイマーデバイスドライバによって処理される定期的な割り込み を生成する。タイマーデバイスドライバはキーボードデバイスドライバやマウスデバイスドラ イバと同様に、Windows を呼び出してハードウェアイベントをメッセージに変換する。ただ し、結果として得られたタイマーメッセージは、キーボードメッセージやマウスメッセージと は異なり、プログラムのアプリケーションキューに直接入れられる。複数のプログラムがタイ マーメッセージを要求している場合、Windows はタイマー割り込みが発生するたびに複数の アプリケーションキューにタイマーメッセージを入れることになる。Windows はこの方法で、 1 つしかないハードウェアタイマーを共有可能なデバイスに見せかけている。個々のウィンド ウは独自のプライベートなタイマーを持っていると考えることができる。 Windows は、プログラムが特定の Windows 関数を呼び出した結果として、そのプログラム のスレッドキューにメッセージを入れることがある。たとえば、プログラムはウィンドウの特 定の領域が最新の状態になっていないことを通知するために Windows 関数を呼び出す。これ に応えて Windows はそのウィンドウのスレッドキューにメッセージを入れ、プログラムはこ のメッセージに応えてウィンドウの古くなった領域を再描画する。 さて、プログラムのスレッドキューにメッセージが溜まっているとして、そのプログラムはス レッドキューからメッセージをどのように取り出し、適切なウィンドウ関数に配信するのだろ うか? プログラマはこのためにメッセージループと呼ばれる小さなコードを作成することにな る。メッセージループは、アプリケーションキューから入力メッセージを取り出し、それらを適 切なウィンドウ関数にディスパッチする。メッセージループは、ループの終了を指示する特殊 なメッセージが取り出されるまで、メッセージの取り出しとディスパッチを継続的に繰り返す。 Windows アプリケーションの本体は 1 つのメッセージループから構成されている。Windows アプリケーションは、初期化を行った後に、メッセージループのロジックを繰り返して実行し、 停止命令を受け取った時点で終了するという構造になっている。 プログラムは、スレッドキューからメッセージを取り出すために Windows の GetMessage 1.6 ・・・・・・・・・・・・Windows の動作モード 51 関数を呼び出す。これに応えて、Windows はキューの中のメッセージをプログラムの中のデー タ領域に移動する。 これでプログラムはメッセージを取り出すことができたが、このメッセージは適切なウィン ドウ関数に送ってやる必要がある。このために、プログラムは Windows の DispatchMessage 関数を呼び出す。だが、なぜ自分のウィンドウ関数にメッセージを送るのに Windows を呼び 出さなければならないのだろうか? プログラムは複数のウィンドウを作成することができる。 このとき、図 1-4 に示したように、各ウィンドウが独自のウィンドウ関数を持つこともあるし、 複数のウィンドウが同じウィンドウ関数を共有することもある。さらに、Windows が提供する ウィンドウタイプのウィンドウ関数の多くは、そもそもプログラマが書いたプログラムの中で はなく、Windows の中に存在している。Windows の DispatchMessage 関数は、こうした複 雑な事情をすべて隠蔽し、プログラムのウィンドウ関数や Windows の組み込みウィンドウ関 数のうちのどの関数がメッセージを受け取るのかを自動的に決定した上で、適切なウィンドウ 関数を直接に呼び出してくれるのである。図 1-5 は、キーボード入力がシステムの中を通ると きの経路を示している。 Windows アプリケーション int WINAPI WinMain(…) デバイスドライバ システムキュー while (!GetMessage(…)) { DispachMessage(…) } スレッドキュー GetMessage LRESULT CALLBACK myHandler(…) switch(message)... DispatchMessage 図 1-5 1 つの Windows アプリケーションに対するキーボード入力 1.6 Windowsの動作モード Windows 95 と Windows NT は、Intel 80x86 ファミリーのマイクロプロセッサ上で動作す る。現在のところ、このファミリーには 80386 、80486 、および Pentium マイクロプロセッサが 含まれており、8086 、8088 、80186 、80188 、および 80286 の古いマイクロプロセッサは含まれ ていない。Windows 95 と Windows NT は 80386 以上のマイクロプロセッサが持つプロテク トモードでの動作を必要とするのである。これらのプロセッサのハードウェア的な能力はロー エンドからハイエンドまでの広範囲にわたっているが、Windows の能力はローエンドのプロ セッサに縛られているわけではない。たとえば、Windows NT は 8 つのプロセッサを搭載した Pentium マルチプロセッサシステム上で、すべてのプロセッサをフルに活用することができる。 Windows 95 は現時点ではシングルプロセッサシステムにしか対応していない。なお、本書で は特に触れないが、Windows NT は Intel 以外のさまざまなプラットフォームでも動作する。 52 第 1 章 ・・・・・・・・・・・・Windows の概念の紹介 Windows 95 と Windows NT は、80386 、80486 、または Pentium プロセッサのいわゆる “ 386 拡張モード ”で動作する。仮想メモリの能力により、Windows はディスク領域を追加の (ただし遅い)メインメモリとして使用することができる。このため、Windows アプリケーショ ンは物理的に存在する以上の“ メモリ ”を使えるのである。Windows 上で動作する Windows アプリケーションは、プロセッサのプロテクトモードで実行される。 アプリケーションは Windows 95 と Windows NT の両方でテストするのが一番である。 Microsoft は“ロゴ認定”プログラムの一部としてこれを義務づけている。両方のプラットフォー ムでテストを行う必要があるのは、どちらの環境にも他方の環境には存在しない機能があるか らである。 1.7 プログラムのメモリモデル Win16 では、プログラマは“ミディアム”や“ラージ”などの“メモリモデル”を意識し、64K を超えるメモリブロックを参照するときにはセグメント化されたアドレスを扱い( huge ポイン タ)、16 ビットポインタ( near )と 32 ビットのセグメント化されたポインタ( far )の違いに注意 しなければならなかった。Win32 プログラミングでは、こうした配慮がいっさい不要になる。 Win32 の“ メモリモデル ”は統一的な 32 ビットアドレス空間の 1 つだけである。 これには良い面と悪い面がある。プログラミングが大幅に単純化され、サイズの制約が解消 され、パフォーマンスが大幅に改善される( Win16 の“ huge ”モデルでのポインタ演算を行う ためには、ランタイムサブルーチンを呼び出す必要があった) 。また、8,192 セクタの制限など、 Win16 のプログラマが対処せざるを得なかった制約も解消された( Windows プログラミングを 新しく始めた人は、このことを知らずに済むのをありがたく思わなければならない) 。ところで Win16 では、セグメントを割り当てて、それよりも後ろの位置をアドレスすると、通常は一般 保護違反( GPF )が発生した。一方 Win32 では、自分のメモリの中の考えもしなかった位置が 上書きされて、奇妙な挙動が生じる可能性がある。DOS 、メインフレーム、ミニコンピュータ、 ワークステーションなどの環境でのプログラミング経験がある方は、この問題のことをすでに ご存じだろう。しかし、Win16 が不正な書き込みをトラップしてくれることに慣れてしまった 人は、この機能がなくなっていることに注意する必要がある。幸いなことに、DOS の場合とは 異なり、Windows NT ではオペレーティングシステムを上書きすることはできない。Windows 95 では、オペレーティングシステムの上書きは不可能ではないが、困難である( つまり、偶然 に上書きすることは考えにくい) 。 いずれにせよ、セグメント化されたメモリと各種のメモリモデルに関する長たらしい退屈で 複雑な議論を本書から削ることができたのは、きわめてありがたいことである。 1.7.1 メモリモデルと16ビットコードの移植 Win16 プログラミングでは、プログラマはメモリモデルの細かい部分に注意を払う必要が あった。16 ビット環境は技術上の理由から、プログラムが“ラージ”モデルを使用していると、 そのプログラムの複数のコピーを実行することができなかったので、複数のインスタンスを持 てるアプリケーションを書こうとするプログラマは“ ミディアム ”モデルを使用した。そうい うこともあって、多くのプログラマは特に理由がなくてもミディアムモデルを採用していた。 Win32 にはメモリモデルが 1 つしかないのだから、こうした細かい事柄は無関係だと思うかも しれない。ところが残念なことに、古いプログラムを移植するときにはこれが問題になるので 1.8 ・・・・・・・・・・・・__cdecl と__stdcall の呼び出しシーケンス 53 ある。プログラマたちは、ポインタが 16 ビット値であると仮定して、1 つの 32 ビットワード に 2 つのポインタを入れたり、1 つの 32 ビットワードにポインタとウィンドウハンドルを 1 つ ずつ入れるというような巧妙なトリックをよく行った。しかし Win32 では、ポインタとハンド ルはどちらも 32 ビット値である。既存の Win16 アプリケーションを移植するときには、この 種の Win16 のトリックが問題を引き起こすことがある。 1.8 __cdeclと__stdcallの呼び出しシーケンス ほとんどの Windows 関数は、呼び出すときにいくつかのパラメータを渡す必要がある。関 数へのパラメータはスタックに積まれて渡される。パラメータのコピーをスタックに置く動作 は、 “ スタックにパラメータをプッシュする”という。また、パラメータを取り除く動作は、 “ス タックからパラメータをポップする”という。関数呼び出しのパラメータは、左から右の順( C 言語の関数呼び出しで記される順番)、または右から左の順にスタックにプッシュされる。C の 関数は、通常は右から左にパラメータをプッシュし( 一番左にある先頭のパラメータが最後に スタックにプッシュされる)、呼び出し側がスタックからパラメータを取り除く。一方、Intel プラットフォーム上での Windows API 呼び出しは、パラメータを自分でスタックから取り除 く。この 2 つのケースは、それぞれ__cdecl( デフォルト)と__stdcall のリンケージタイプに対 応している。Intel プラットフォーム上で動作するすべての Win32 API 関数は、パラメータが 右から左の順序でプッシュされると期待している。 Win16 API を知っているプログラマは、__cdecl と__pascal のリンケージタイプをご 存じだろう。Win16 の Windows API とコールバック関数は FAR PASCAL 型だったが、 Win32 では__stdcall である。また、__pascal リンケージタイプはパラメータを左から右 にプッシュし、呼び出し側がパラメータを取り除くことを期待していた点に注意すること。 関数が仕事を終えたら、プッシュされたパラメータをスタックから取り除かなければならな い。つまり、スタックから正しい数のパラメータをポップする必要がある。コンパイラは一般 に、特定の関数が何個のパラメータを受け取るのかをコンパイル時に知ることはできない。実 際、同じ C の関数が、呼び出されるたびに異なる数のパラメータを受け取ることもありうるの である。printf 関数はこの種の典型的な例だろう。呼び出された C の関数は、パラメータの数 が変動し、実行時まで不明であるという理由から、スタックに積んで渡されたパラメータを一 掃することができない。このため、一掃の責任は呼び出した側のプログラムに課せられる。プ ログラムは、関数の呼び出しから返った後で、スタックにプッシュしたのと同じ数のパラメー タをスタックから取り除く。 しかし、これよりも若干効率が高い呼び出しシーケンスが存在する。関数の引数の数を固定 しておけば、パラメータはどちらの順序でもプッシュできる。また、コンパイラはコンパイル時 に、関数が期待しているパラメータの数を知ることができる。この場合、コンパイラはスタック からパラメータを取り除くためのコードを、関数への個々の呼び出しの後に入れるのではなく、 呼び出される側の関数の末尾に埋め込むことができる。この方法だとコードの生成を 1 回で済 ませられるので、結果として得られるプログラムのスペースが節約できる。また、Intel を含む いくつかのアーキテクチャは、関数から返るのと同時にスタックからパラメータを取り除くと いう動作をハードウェアレベルでサポートしている。C 言語の仕様自体は、慎重にも、パラメー 54 第 1 章 ・・・・・・・・・・・・Windows の概念の紹介 タを左右どちらの順番でプッシュするかは定めていない。プログラマは、API 関数の呼び出し 方についてはきわめて慎重になる必要があり、パラメータが評価される順番についてはいかな る仮定も行ってはならない。これは、C 言語の柔軟性を利用して、パラメータを別の順序で評価 するコンパイラを装備した Intel 以外の Windows プラットフォームに移植される可能性がある からである( 現在の業界には、PowerPC 、MIPS 、DEC Alpha などの“ 公式”の NT プラット フォーム以外にも Windows API を採用しようという流れがある。たとえば、Open Software Foundation( OSF )は Windows API の仕様を持っており、少なくとも 3 社のベンダーが、既 存の Unix プラットフォーム上で動作するライブラリのセットとしてインプリメントされた、X ベースまたは Motif ベースのバージョンの Windows API を提供している) 。 Microsoft C コンパイラは、デフォルトでは、可変個のパラメータをサポートする__cdecl 呼 び出しシーケンスを使って関数呼び出しを生成する。これは C 言語の定義に従ったシーケンス である。しかし、ほとんどの Windows 関数はパラメータの数が固定されているので、これら の関数は効率を高めるために__stdcall 呼び出しシーケンスを使用している。Microsoft C コン パイラでは、__cdecl と__stdcall という 2 つのキーワードを使って、呼び出しシーケンスを関 数ごとに指定できる。__cdecl キーワードは次の関数が C の呼び出しシーケンスを使用するこ とを、また__stdcall キーワードは次の関数がより効率的な呼び出しシーケンスを使用すること をコンパイラに知らせる。各種の Windows 関数は、windows.h ファイルの中で、さまざまな typedef を通じて__stdcall 呼び出しシーケンスを使用するように定義されている。 プラットフォームに依存しないプログラムを書くためには、コードの中でこれらのキーワー ドを直接には使わず、一般に Windows API 関数はマクロ WINAPI で、Windows コールバッ ク関数はマクロ CALLBACK で宣言しなければならない。これらのマクロは、個々のプラット フォームに応じた(必ずしも Microsoft C と同じではない)形に定義される。一方、実際の予約 語である__cdecl と__stdcall をコードの中でそのままの形で使用していると、別のコンパイラ やハードウェアプラットフォームに移植できなくなる可能性がある。 1.9 静的リンクと動的リンク これまでは、ウィンドウ関数が自分に渡された情報を見つけられるように、正しい呼び出 しシーケンスでウィンドウ関数を呼び出す方法を説明してきた。しかし、まだ 2 つの疑問が 残っている。ウィンドウ関数はどこにあるのか? そして、アプリケーションの中の呼び出しは、 Windows 内のどこかに隠れている関数とどうやって接続されるのか? アプリケーションの中の呼び出しを呼び出された関数に接続するのはリンカの役目である。 リンカは、1 つまたは複数のオブジェクトモジュール( .obj ファイル)をリンクして 1 つの実行 可能プログラム( .exe ファイル)を作成するときに、オブジェクトモジュールの中のすべての呼 び出しを、同じまたは別のオブジェクトモジュールの中の関数と照合する。このステップを実 行した上で未解決の呼び出しが残ったら、リンカはライブラリファイルの中で足りない関数を 探す。リンカが探索するライブラリファイルは、LINK コマンドラインで指定されたファイル ( 通常は開発環境を通して指定することになる)と、リンク対象のオブジェクトモジュールの中 で明示的に指定されているファイルである。指定されたすべてのライブラリを探しても、呼び 出された関数が見つからなかった場合、リンカは“未解決の外部参照”メッセージを出力し、プ ログラムから呼び出されている関数が見つからなかったことを知らせる。リンカが関数を見つ けた場合には、そのオブジェクトコードがライブラリからコピーされ、実行可能ファイルに挿 1.9 ・・・・・・・・・・・・静的リンクと動的リンク 55 入される。これは通常のリンク形式であり、静的リンクと呼ばれる。静的リンクを行うために は、リンカがリンク時に、その関数のメモリ内の位置を知り、その関数を構成するオブジェク トコードにアクセスできなければならない。 しかしリンカは、Windows 関数については、そのメモリ内での位置をリンク時に知ることは できないのである。Windows は協調的な環境であり、複数のアプリケーションが同時に実行さ れる。同時に実行されるすべてのアプリケーションが、個々の Windows 関数の同じコピーを 共有できたら便利だろう。このような手段は実際に存在しており、動的リンクと呼ばれる。い ずれにしても、Windows 関数は、それを使用するプログラムごとにメモリ内の異なるアドレス に置かれる可能性があるので、リンカが 1 つのタスクでの関数の位置を知ったとしても、別の タスクから呼び出されたときにその関数が同じ位置にあるとは限らない。また、このアドレス は Windows のリリースごとに変わるし、Windows 95 と Windows NT でも異なる。 これを知って驚く読者もいるかもしれない。 “ オペレーティングシステム”は必ず同じ位置に あるものと思っている方も多いはずである。実際、同じリリースの Windows であれば、オペ レーティングシステムはすべてのプログラムに対して同じ位置に存在しているように見える。 しかし Windows では、われわれが“ オペレーティングシステム ”と見なすものの多くが数十 個のダイナミックリンクライブラリ( DLL )に分散しており、コモンダイアログ、コモンコント ロール、OLE サポートなどのさまざまな要素が、実際にはコアのオペレーティングシステムに は含まれていないのである。どの DLL が必要なのか、またどのような順序で必要になったかに 応じて、これらはタスクごとに異なる位置にロードされるし、同じアプリケーションであって も実行するたびに異なる位置にロードされる可能性がある。動的リンクはこのような状況にも 対処できなければならない。 動的リンクとは、リンカによる関数呼び出しの完全な解決をプログラムの実行時にまで遅らせ るテクニックである。リンカは、リンク時に呼び出しを固定するのではなく、未解決の関数に関 する情報を実行可能プログラムの中に挿入する。これらの据え置かれた呼び出しは、Windows がプログラムをロードした時点で Windows の中の関数に動的に解決されることになる。個々 の外部参照を動的リンクと静的リンクのどちらで解決するかを決めるのはインポートライブラ リである。 リンカは、リンク対象のプログラムの中に存在する未定義の外部参照を、オブジェクトコー ドライブラリと同様にインポートライブラリの中でも探索する。ただし、インポートライブラ リに含まれているのはコードではなく、その関数を含んでいる Windows モジュールの名前と、 そのモジュールの中での関数のエントリポイントの名前または番号のレコードである。この情 報が実行可能プログラムにコピーされれば、その外部参照はリンカによって解決されたと見な される。Windows は、後でプログラムが実行された時点で、この情報を使って呼び出しを解決 する。 Windows プログラムのリンクにはいくつものインポートライブラリが使われるが、特に重要な のは、Windows の主要なモジュールへのインターフェイスを提供するものである( kernel32.lib 、 user32.lib 、および gdi32.lib ) 。これらのライブラリはコードを含んでおらず、Windows アプリ ケーションが呼び出す主要な API Windows 関数のインポートレコードを含んでいる。また、 同じライブラリが、静的リンクに使われる関数のオブジェクトモジュールと、動的リンクに使 われるインポートレコードの両方を含んでいることもある。 56 第 1 章 ・・・・・・・・・・・・Windows の概念の紹介 1.9.1 ダイナミックリンクライブラリ リンカはプログラムモジュールまたはダイナミックリンクライブラリ( DLL )を生成すること ができる。プログラムモジュールはユーザーが実行できる実行可能ファイル( .exe )である。ダ イナミックリンクライブラリもやはり実行可能ファイル( .dll )だが、直接に実行されることは ない。この 2 つの間にはかなり大きな違いがある。 プログラムは Windows 上でタスクとして実行され、メッセージを受け取る。タスクは独立 したエンティティとして実行される。たとえば、メモ帳プログラムのコピーを 2 つ同時に実行 する場合を考えてみよう。Windows は 2 つのタスクを作成し、それぞれでメモ帳プログラムの コピーを実行するが、メモ帳プログラムのコードはメモリ内に 1 つしか存在していない。ただ し、各タスクは独自のプライベートデータを持っているので、それぞれ別のファイルを扱える のである。 DLL は、プログラムまたは他の DLL から使用できるルーチンの集まりに過ぎず、Windows 上でタスクとして実行されることはない。DLL 関数は、Windows 上でタスクとして実行され ているプログラムから、直接または間接に呼び出されなければならない。プログラムは前に述 べた動的リンクを通して DLL の中のサブルーチンを呼び出す。アプリケーションが呼び出す Windows 関数はいずれもダイナミックリンクライブラリに含まれている。Windows 本体は、 以下に示す 4 つの主要なダイナミックリンクライブラリから構成されているのである。 1. user32.dll。Windows 環境とアプリケーションのすべてのウィンドウを管理する。メニュー、 組み込みコントロールなどはここで管理されている。 2. kernel32.dll。Windows のシステムサービスを提供する。マルチタスク、マルチスレッド、 メモリ管理、リソース管理などのサービスがこれに含まれる。 3. gdi32.dll。ウィンドウに描画を行うときに必ず使用されるグラフィックデバイスインター フェイスを提供する。線やテキストなどを描画するときの操作はこれに含まれている。 4. ( kernel32.dll の中にある)コンソールサブシステムは、キャラクタベースのアプリケー ションのためのインターフェイスを提供する。本書は GUI に関連する Windows プログ ラミングに焦点を当てているので、この機能についてはほとんど触れていない。 Win16 システムを知っているプログラマは、gdi.exe 、kernel.exe、および user.exe と いうモジュール名に見覚えがあるだろう。事実、これらのモジュールは Windows のシス テムディレクトリの中に存在しており、実行中の Win16 アプリケーションと Win32 環境 の間のインターフェイスを提供している。 DLL の作り方は Windows プログラムの作り方によく似ている。リンカはオブジェクトモ ジュールをリンクして、実行可能なファイルを作成する。前に述べたように、この実行可能ファ イルは直接には実行されない。動的リンクが必要な外部参照を含んでいるプログラムがロード されたとき、これらの参照には、その参照を解決できる DLL の名前と、DLL の中のエントリ ポイントに関する情報が含まれている。Windows はその名前の DLL を探し、元のプログラム と一緒にロードする。この DLL に他の DLL への未解決の参照が含まれていたら、上記のプロ セスが、すべての参照が解決されるか、発見できない DLL が現れるまで繰り返される。 プログラマから見ると、静的リンクライブラリの代わりにダイナミックリンクライブラリを 1.10 ・・・・・・・・・・・・エクスポートとインポート、あるいは誰が誰を探しているのか? 57 使用することにはさまざまな利点がある。複数のアプリケーションが静的リンクライブラリの 同じライブラリルーチンを使用している場合、そのルーチンのコピーはすべてのアプリケーショ ンに含まれている。一方、複数のアプリケーションが DLL の中のルーチンを使用している場 合は、同じコピーがすべてのアプリケーションから共有される。さらに、静的リンクライブラ リでは、その中の関数を変更したときには、そのルーチンを使用しているすべてのアプリケー ションを再リンクする必要がある。再リンクだけでも大変だが、その前に、変更された関数を どのプログラムが使っているのかを調べなければならず、さもなければその作業を諦めてすべ てのプログラムを“念のため”に再リンクしなければならない。一方、DLL の中のルーチンを変 更した場合には、DLL の再リンクを行うだけで、そのルーチンを使用するすべてのアプリケー ションが次からアップデートされたバージョンを使うようになる。ライブラリルーチンが変更 されたからといって、アプリケーションをアップデートする必要はないのである。 もちろん、この方式にもそれなりの問題がある。たとえば、DLL をアップデートするときに は、それまでのバージョンとの互換性を確認しなければならない。プログラマが知らず、また コントロールすることもできない大量のコードが、その DLL を使っているかもしれないからで ある。また、プログラムは、自分が使用している DLL のバージョンを調べるための手段を持っ ていなければならない。DLL の特定のリリースレベルの機能に依存しているプログラムは、前 のバージョンが使われようとしたときにそのことを検出し、報告できなければならない。プロ グラマが提供するインストーラは、新しいバージョンの DLL を、配布ディスク上の古いバー ジョンで上書きしないように注意しなければならない。このような問題点があるにせよ、DLL は優れたパワーと柔軟性を持っており、ほとんどの状況に適したメカニズムである。 あるいは誰が誰を探しているのか? 1.10 エクスポートとインポート、 ダイナミックリンクライブラリはプログラムや他の DLL から呼び出される関数を提供してい る。関数を外部から呼び出せるようにするためには、その関数をエクスポートしなければなら ない。関数をエクスポートすると、実行可能ファイルの中にレコードが作成される。このレコー ドは、エントリポイントの名前と順序番号、そしてモジュール内での関数の位置を識別する。 一方、プログラムは、動的リンクを使って呼び出す関数をインポートしなければならない。関 数のインポートは、この参照が動的リンクであり、実行時まで解決されないということをリン カに通知する役目を果たしている。関数をインポートする方法は 2 つある。1 つは、アプリケー ションのモジュール定義ファイル(アプリケーションをリンクするときに必要なファイル)の中 で明示的なインポートステートメントを指定する方法である。もう 1 つの方法として、呼び出 された関数をリンカがライブラリの中で探索しているときに、リンカがその関数のインポート レコードを見つけると、その関数のインポートが行われる。いずれの場合も、リンカはプログ ラムの実行可能ファイルの中にレコードを挿入することで、動的リンクを行うことを Windows に通知する。Windows は、ロードしたプログラムの中に動的リンクのためのインポートレコー ドを発見したら、その時点で呼び出しの解決を行う。インポートレコードは、プログラムが呼 び出しているダイナミックリンクライブラリの名前と、ライブラリの中のルーチンの関数名ま たは順序番号を指定している。Windows は指定された DLL をロードし、指定された関数名ま たは順序番号のエクスポートレコードを探す。エクスポートレコードはライブラリの中の関数 の位置を Windows に知らせる役目を果たしている。このとき、Windows はプログラムの中の 呼び出しを DLL の中の関数に動的にバインドしているという。 58 第 1 章 ・・・・・・・・・・・・Windows の概念の紹介 Win16 を使用してきたプログラマは、コールバックと DLL エントリポイントをマークする ための__export キーワードをご存じだろう。Win32 では、コールバックは単なる__stdcall リンケージであり、特殊なエクスポート宣言は不要である。DLL エントリポイントは、モ ジュール定義ファイルかリンカのコマンドラインで指定するか、エントリポイントの宣言 に“ 記憶クラス修飾子 ”の__declspec( dllexport )を追加することで指定する。 1.11 Windowsのメモリ管理 Windows プログラムは 1 つ以上のコードセグメントと 1 つ以上のデータセグメントから構 成される。ダイナミックリンクライブラリも 1 つ以上のコードセグメントと 1 つ以上のデータ セグメントを持つことができる。複数の Windows プログラムを同時に実行でき、同じプログ ラムの複数のインスタンスを同時実行することもできる。Win32 では、同じ DLL を使用して いるすべてのプログラムが、これらのデータセグメントのいくつかを共有することができる(も ちろんデータセグメントを共有しないこともある) 。 Win16 の DLL を作成した経験があるプログラマは、Win16 DLL の制限、つまりデータセ グメントを 1 つまでしか持てず、それが DLL のすべてのクライアントから共有されるとい う制限に悩まされてきたはずである。Win32 の DLL はプライベートなデータセグメント と共有データセグメントの両方を持つことができ、スレッド固有のストレージを割り当て ることさえできる。 1.12 windows.hヘッダーファイル 次の章にはサンプルの Windows プログラムが掲載されている。大した機能もないプログラ ムではあるが、読者が最初に気づくのは、このプログラムが使っているデータ型の多くが C の 標準データ型でないということだろう。これらのデータ型は、すべての Windows モジュール がインクルードするインクルードファイルの中に定義されている。すべての Windows プログ ラムは次のステートメントで始まっていなければならない。 #include <windows.h> Windows API のかなりの部分が windows.h ファイルに定義されている。初期のバージョン の Windows では、API 呼び出し、これらの呼び出しに対応するシンボリック定数、そしてデー タ値を定義する構造体と typedef のすべての基本的な定義がこのファイルに含まれていた。し かし Windows が進化するにつれ、このアプローチにはいくつかの問題が生じてきた。 ● ファイルのサイズが大きくなりすぎた。 ● 新しいシンボルやインターフェイスが導入されるにつれ、新しいシンボルが既存のシンボ ルと衝突を起こして、既存のコードを新しい Windows リリースでコンパイルできなくな る可能性が生じた。 1.12 ・・・・・・・・・・・・windows.h ヘッダーファイル ● 59 システムのリビルドに必要なコンパイル時間に、ファイルのサイズが大きな影響を与える ようになった。このファイルには約 5,300 行のソースコードが含まれているが、これはア プリケーションコードのソースファイルの一般的なサイズよりもずっと大きい。しかも、 このファイルはすべてのコンパイル単位にインクルードされる。 ● 普通のモジュールでは、このファイルに対してコンパイラが費やす時間と労力のほとんど が無駄になる。というのも、定義されている数千のシンボルのうち、実際に必要となるの はせいぜい 10 個ぐらいだからである。 Microsoft はこれらの問題にいくつかの方法で対処した。1 つは、コンパイラを“ プリコンパ イル済みヘッダー ”に対応させるということである。このテクニックにより、ヘッダーファイ ルを実際に読み込んで解析する必要がなくなる。ヘッダーファイルはセミダイジェスト形式で 保存されており、コンパイラは必要な部分だけをバイナリ形式で読み込む。他のベンダーもこ れと似た戦略を採用している。シンボルの非互換性という問題は、より複合的な命名規約を採 用し、新しい機能は新しいヘッダーファイルに入れるという方針で対処された。Windows 3.1 以降、 “ シェルエクステンション ”、 “ ツールエクステンション ”、 “ 3D コントロール ”、OLE ( Object Linking and Embedding )といった機能は、別個のヘッダーファイルとして追加され、 プログラマはこれらを明示的にインクルードすることになった。不要な機能は、この章で後で 説明する条件コンパイルを使って除外できる。最後に、Win32 では windows.h 自体が約 30 個 のファイルに分割された。ただし互換性を保つために Windows 3.1 と同じ条件コンパイルオプ ションがサポートされており、単一の windows.h が、プログラマが指定した条件オプションに 従ってこれらの独立したファイルを選択的にインクルードするようになっている。 本書では、シンボルに言及するときに、それが含まれている具体的なファイルを指摘したり、 windows.h がインクルードする各種のヘッダーファイルの細部を論じるのは行き過ぎだと判断 した。実際、Microsoft はリリースを重ねるたびに、これらのファイルの構成を内部的に変更し ている。このため、本書では関数、シンボル、およびデータ型に言及するときに、 “ windows.h に定義されている”という表現を使っていることが多い。これは、 (条件コンパイルオプションを 定義するなどの)特殊な操作をせずに windows.h ヘッダーファイルをインクルードすると、そ の定義を利用できるようになるという意味である。実際の Win32 では、その定義は windows.h によって( 選択的に)インクルードされる別のファイルに含まれている。 windows.h を介してインクルードされるファイルは、主に#define 、typedef 、および関数プ ロトタイプの 3 つのタイプのステートメントから構成されている。windows.h とそのコンポー ネントは、インクルードファイルとしてはきわめて大きいもので、普通の Windows アプリケー ションは windows.h に含まれている情報の大部分を使用しない。このため、windows.h の多く の部分は条件的に処理から除外することができる。たとえば、本書のすべてのサンプルプログ ラムは、次のように始まる StdSDK.h ファイルを使用している。 #define STRICT #include <windows.h> 最初のステートメントはシンボル STRICT を定義するプリプロセッサディレクティブであ る。STRICT には値が指定されていないが、重要なのは、これが置換値を持たないシンボルと して定義されているということである。つまり、それ以降のソースコードに現れる“ STRICT ” という文字列の出現はすべて削除される。ところで、このシンボルは windows.h ヘッダーファ 60 第 1 章 ・・・・・・・・・・・・Windows の概念の紹介 イルの中のプリプロセッサディレクティブでも使用されている。 シンボル STRICT を定義し、その後に windows.h ヘッダーファイルをインクルードすると、 Windows プログラムの多くのデータ型は、このシンボルが定義されていないときとは異なる仕 様を持つようになる。シンボル STRICT はより厳密な型チェックを有効にするもので、新規に 作成する Windows ソースファイルでは必ず使用するべきである。古い Windows プログラム を厳密な型チェックを有効にして警告なしでコンパイルするためには、一般にある程度の、と きにはかなり大がかりな変更を加えなければならない。この種の警告は不正なコードが含まれ ている可能性を示している。どのような Windows アプリケーションコードでも、これらの警 告はすべて取り除くように努めるべきである。STRICT の型チェックを使えば、Win16 プログ ラムが Win32 で動作しないという移植上のエラーの多くを見つけることができる。 われわれは、コンパイルの際に、C コンパイラにできるだけ多くのチェックを行わせるように している。Microsoft C コンパイラの警告レベル 4 は、Windows アプリケーションをコンパイ ルするときにより厳密なチェックを行い、コード内の移植性に関連する問題を警告してくれる。 この種の警告の原因となるものの 1 つが、構造体の中のビットフィールドの使用である。通信 に関連する構造体はもともとビットフィールドを使用しているが、通信ルーチンを使わないの であれば、windows.h ヘッダーファイルをインクルードする前に#define NOCOMM を行って おけば、警告レベル 4 でもこれらの警告を消すことができる。このように define ステートメン トを使って処理から除外できる範囲を表 1-1 に示しておく。 さらに、警告メッセージの多くを、強制的にコンパイルエラーにしてしまうこともできる。 こうすればコンパイラは.OBJ ファイルを生成しないので、エラーを含んでいるプログラムのリ ンクに成功するということがなくなる。C 言語は多くの警告条件を強制的にエラーとは見なさ ないようにしている。さもないと、C 言語本来の寛容の精神に頼っていた既存のコードをコン パイルできなくなる可能性があるからである。だが、プログラマはコンパイラを保守的に振る 舞わせることもできる。Microsoft の C コンパイラでは#pragma warning() 宣言を使用する。 #pragma #pragma #pragma #pragma #pragma warning(error, warning(error, warning(error, warning(error, warning(error, 4002) 4003) 4020) 4021) 4024) // // // // // Too many actual parameters to macro Too few actual parameters to macro Too few actual parameters Too many actual parameters different types for formal and actual このテクニックは Microsoft の C コンパイラでのみ通用する。コードを他のコンパイラでコ ンパイルする可能性がある場合は、慎重に使用しなければならない。この種のファイルの例は Appendix D に示している。 “プリコンパイル済みヘッダー ”の使い方を学んでおくことを強くお勧めする。Borland と Microsoft をはじめとする数社のコンパイラがこれをサポートしている。プリコンパイル済み ヘッダーは、複数のヘッダーファイルから抽出された、素早くロードできるバイナリ形式の情 報の集まりである。プリコンパイル済みヘッダーを使用すればコンパイル時間が大幅に短縮で きる。 61 1.12 ・・・・・・・・・・・・windows.h ヘッダーファイル 表 1-1 windows.h の一部を除外するために使われるシンボル ºößî NOATOM NOCLIPBOARD NOCOMM ηîíäÐ ¥ËãGNÚð˧ÚP CF_*вíÆÚßVÌäƾV»þNʲíÆÚßVÌGNÚð˧ÚP ¬ DCBOCOMSTAT structOCE_*OIE_*OEV_*OCBR_*OCN_*OCSTF_*O êÕäKGíÐGNÚð˧ÚP WC_DIALOGODLGWINDOWEXTRAODS_*ODM_*ODC_*ODLGPROCO DWL_*ODLGC_*OCLTCOLOR_*OWM_CTLCOLOROWM_GETFONTO WM_SETFONTO ç¥ð³ ¶öËðVî ÚðËÂ§Ú O¶öË ðVî¼Â§îSS_*OBS_*OES_*OSBS_*OLBS_*OCBS_*P GetSysColorOSetSysColorsO COLOR_*P NOCOLOR NODEFERWINDOWPOS HDWPOBeginDeferWindowPosODeferWindowPosO EndDeferWindowPosP ʼ²ËÆÚºößî äÀÆÌ ª DESKTOP_* §DesktopP NODESKTOP DrawText DT_*P NODRAWTEXT Ôæë°¯ g h¾ÍÆ P O ¶VÌ NOEXTAPI NOCTLMGR ¬êÕ NOFONTSIG NOGDI NOGDICAPMASKS NOHELP NOICONS NOIME NOIMM NOKANJI NOKERNEL NOKEYSTATES NOLANGUAGE NOMB NOMCX NOMEMMGR NOMENUS NOMETAFILE NOMINMAX NOMSG NONCMESSAGES NONLS NOOPENFILE NOPROFILER NORASTEROPS NORESOURCE NOSCROLL NOSECURITY NOSERVICE NOSHOWWINDOW NOSOUND NOSYSCOMMANDS Ê ¬êÕ ÐGN Ê ¬êÕ Ê Ê Ê ÍtÐ ñB GN ñB®yɤì ÊÐ ÊÐÕ1® 2ºÈ¦íK3Í;»íP جö˺³ÍÄæiêêOTCI_*P »ÛÈÐ GíÐþ5ÊGNÚð˧ÚP DT_*OCC_*OLC_*OPC_*OCP_*OTC_*O¬êÕRC_*ÐÂP WinHelpOHELP_*OMULTIKEYHELPO¬êÕHELPWININFOP $¥§¶ö IDI_*P 4!äÀÆÌáÐV»æ ºößîÐIMC_*ÊIMN_*P 4!äÀÆÌáÐV»æ Imm*äÀÆÌÊ¿îÍGí»íiêêOWM_IME_*äÆ ¾V»O¬êÕIMC_*P |ɸàVËP WinMain »ÛÈÐkernelGíÐþ5ÊGNÚð˧ÚP ᩼äƾV»MK_*ÐÁãаV!Ñá¼²P ¸0¸àVË IsChar*P MessageBoxOMessageBeepO¬êÕMB_*P åÊã§öÂVت§¼ÐiêêÊxÕ¯¹P GMEM_*OGHNDOGPTROLHNDO¬êÕGí»íGNÚð˧ÚP MENUITEMTEMPLATEHEADEROMENUITEMTEMPLATEOMF_*O TMP_*OWM_MENUSELECTOWM_MENUCHARO¬êÕäÎèVGíÐG ÄÁÆ ÆÁ ª · ÆÊ · · ª NÚð˧ÚP HMETAFILEOMETA_*OHANDLETABLEOMETARECORDO METAFILEPICTOMETAHEADEROMPENUMPROCO äÂؤ§î GíÐGNÚð˧ÚP ¬êÕ ¬êÕ min maxá²ðP MSGOPM_*O äƾV» Úð˧ÚP ²ì§¥öËäƾV» ª HT_* SMTO_*P ËÉÐ¥ ¸àV˦ îVÄö ª MB_OWC_OCT_O C1_OC2_OC3_ONORM_OMAP_OLCMAP_OCTRY_O LOCALE_P OpenFileOOFSTRUCTOOF_*O Éöàìíؤ§î ÚðË a ¬êÕ Ï¸0 »ÛÈÐ þN ·GíÐGN Ê GíÐþ5Ê ¬êÕ ¬êÕ §ÚP Úðؤ§ìGíÐGNÚð˧ÚP ¿ RS_* O ¿O¬êÕ ¿Ðì¼ÂÝïVºêö¶VÌP ¯ ¥ ¦ ° GíÐGN ± RT_*P WM_HSCROLLOWM_VSCROLLOSB_*OESB_*O íÐGNÚð˧ÚP »ÛÈо°èíɦæóP ¬êÕ¼²ðVîÓVG »ÛÈиVÖ¼¶öËðVìîVÄöOSERVICE_ºößîP ShowWindowÔìäVÂSW_*ÊWM_SHOWWINDOWP S_*ʸ©öÌGíÐGNÚð˧ÚP º¼Éã¶áöÌ ª SC_*P 62 第 1 章 ・・・・・・・・・・・・Windows の概念の紹介 表 1-1 windows.h の一部を除外するために使われるシンボル ºößî NOSYSMETRICS NOSYSPARAMS ηîíäÐ GetSystemMetricsÊSM_*P º¼ÉãÔìäVÂlH ª GetSystemParametersInfoOSPI_*OSPIF_*O NONCLIENTMETRICSOARW_*OMINIMIZEDMETRICSO ICONMETRICSOANIMATIONINFOOSERIALKEYSOSERKF_*O HCF_*OCDS_*ODISP_*OChangeDisplaySettingsO SystemParametersInfoP GetTextMetricsOTEXTMETRICOTMPF_*ONEWTEXTMETRICO NOTEXTMETRIC NTM_*OOUTLINETEXTMETRICOGetOutlineTextMetricsO KERNINGPAIROGetKerningPairsOFONTENUMPROCOEnumFontsO EnumFontFamiliesO *_FONTTYPEP user P NOUSER V V ª VK_*P NOVIRTUALKEYCODES WH_*O*HOOKSTRUCTOHHOOKOHOOKPROCOHC_*OHCBT_*O NOWH HSHELL_*O P V V ª WINSTA_*O*WindowStationP NOWINDOWSTATION V ª WM_*OEM_*OLB_*O CB_* O NF_*O NOWINMESSAGES NFR_*OPWR_*O WMSZ_*P GWL_*OGCL_*OGWW_*OGCW_*O NOWINOFFSETS ¬êÕ NOWINSTYLES OEMRESOURCE ¬êÕ »ÛÈÐ GíÐþ5ÊGNÚðËÂ§Ú ° ¶ Ì ¬êÕØƲGíÐGNÚðËÂ§Ú ©¦öÌ©¼É ºêö¸à Ë ©¦öÌ©äƾ » ¬êÕ Ê ºößî ¬êÕ ¬êÕ©¦ö̩ʲì¼Ð ¦ÒÐ¥² ¾¼GNÐGNÚð˧ÚP ©¦öÌ©¼Â§î ª WS_*OCS_*OES_*OLBS_*OSBS_*O¬êÕCBS_*P ÌÂÊíÀV¼ ª OBM_*OOCR_*O¬êÕOIC_*P プリコンパイル済みヘッダーと表 1-1 のシンボルを併用すると、重大な問題が 1 つ発生す る。システムのどのシンボルをインクルードし、どのシンボルを除外するのかは、プリコンパ イル済みヘッダーのコンパイルを制御するファイルによって決定されるのである。たとえば、 OEM リソースをインクルードしたいときには、プログラムのコンパイル時に次のような#define OEMRESOURCE 行を追加すればいいと思うかもしれない。 #define OEMRESOURCE #include "StdSDK.h" // include OEM symbols しかし、これはうまく行かない。プリコンパイル済みヘッダーに OEMRESOURCE をイン クルードすることによって定義されるシンボルが含まれていなければ、プログラムのコンパイ ル時に“ 未定義のシンボル ”エラーが発生する。これと同じことが、名前の衝突を避けるため に、プログラムの中で一部のシンボルを除外しているときにも生じる。これを解決する 1 つの 方法は、プリコンパイル済みヘッダーを定義するファイルに OEMRESOURCE 宣言を追加し て、ソースシステム全体をリビルドするというものである。これでシンボルの定義または除外 は正しく行われるようになるが、これはコンパイル対象のすべてのファイルに適用されてしま う( 同じプリコンパイル済みヘッダーが使われるため) 。もう 1 つの方法は、特殊なシンボル定 義が必要となるファイルに対しては、プリコンパイル済みヘッダーを無効にするというもので ある。Microsoft Visual C++などの環境はこれをサポートしている。 表 1-2 は、現時点でのヘッダーファイルがどのように分割されているかを示している。表 1-1 の定義によって除外されるシンボルは、この新しいファイル構成にきっちりと対応しているわけ ではないので、条件コンパイルはどのファイルの中でも行われている。ただし、表 1-2 のファ イルのいくつかは、表の中に示しているシンボルを定義することでその全体をインクルードま たは除外することができる。各種のファイルの構成とその内容は、これまでも何度か変更され ており、コンパイラによる扱いもリリースごとに変わってきた。このため、表 1-2 の内容は、 将来も変更される可能性がある参考情報として扱っていただきたい。