|
生APIプログラミング主義
この連載では,W-ZERO3で動くTiny BASICインタプリタ「ワンべぇWM(Windows Mobile)」に関する技術的なトピックを解説していく。だが,これは普通のWindos Mobileのプログラミング技術解説ではない。なぜかといえば,特異的な特徴が三つあるからだ。
- 時代遅れのCで書かれている
- もっと時代遅れのTiny BASICインタプリタを作ろうとしている
- 生APIプログラミングを行っている
このうち,最初の二つについては前回その理由を説明した。要するに「自分にとって面白いから」「モバイルデバイスの乏しいリソース節約できるから」という二つに理由があったのである。
今回は,最後の一つについて取り上げたい。便利なライブラリやフレームワークを使わず,なぜ生APIプログラミングを行うのかである。
生APIプログラミングの長所と短所
先に合理的な話題を片付けておこう。まず,なぜ生APIプログラミングを行うのかという理由である。
一見,その理由は非常に単純なことに思える。そもそも,ほとんどのライブラリはオブジェクト指向プログラミングが前提であり,オブジェクト指向言語ではないCで活用できるものは多くはないのである。Cで書くと決めた時点で,生APIプログラミングは避けられないことのように思えるかもしれない。
だが,その考えは正しくはない。なぜなら,以下のような選択肢は常に存在するからだ。
- インタプリタ本体と環境依存部分は別の言語で書く
- 特にCとC++の組み合わせなら,非常に簡単にできる
- 自前でC用のコンパクトなフレームワークを書くのも難しくはない(実際,Ajaxの世界では数え切れないほどのフレームワークが生まれているのだから,同じことができるはず)
- インタプリタ本体はさほど大きなプログラムではないので,必要とあれば別言語に書き換えることも不可能ではない。本当に価値あるライブラリがあれば,それだけの労力を費やす価値がある
結局のところ,ある程度の水準以下のサイズならば,プログラムをいくらでも書き換えられるし,すべて捨てて作り直すこともできる。手間は増えるとしても,あらゆる選択肢は否定されていない。
それにもかかわらず,生APIプログラミングを採用した理由は二つある。一つは,それがリソース節約の手段として有効だからだ。ライブラリを使うということは,メモリーと実行速度の双方にインパクトを与えてしまう。どれほど効率の良いライブラリであろうとも,どうしても使用メモリー量は増えてしまう。また,ライブラリを経由したAPI呼び出しは,どうしても直接呼び出しよりも遅くなってしまうのである。
これは,面白いけれど実用性が希薄な居候プログラムにとっては大問題である。メモリーを食い過ぎたら追い出されてしまうからだ。また,インタプリタ言語はただでさえ処理速度が遅いのだから,スローダウンする要因は排除するに越したことはないのである。
もう一つの採用理由は,「それには魅力がある」からだ。だが魅力は一つではない。あえて生産性の高いライブラリやフレームワークをさしおいて,生APIプログラミングを行うことには,大きく分けて二つの魅力が存在する。
第1の魅力は,言うまでもなく生のOSと触れ合う快楽である。ライブラリやフレームワークは,実際には存在しない環境があたかもそこにあるかのように見せかけることを通じて,生産性の向上を目指すものと言えるが,それはプログラマをOSの真の姿から遠ざける働きを持つ。だが,いくら理想の環境を見せかけたところで,中身の実体が変わるわけではない。たとえ困難であろうと,「みせかけ」を排除して真実に迫るのは一つの魅力ある行為と言える。
ただし,生APIプログラミングが常に「真実」に迫るわけではない。Windows Vistaになると,Win32 APIのほうが互換性のための「みせかけ」に近い位置付けに後退しつつある。
第2の魅力は,APIも一種のライブラリとみなして比較したとき,他のライブラリと比較してAPIも好き…と思える感性から生まれる。
APIの実体とは,たいていの場合Windowsに含まれるDLLそのものである。OSの外部で作られる後付けのライブラリやフレームワークの実体がDLLであることは珍しくないが,それと比較すれば両者は本質的に同じものとみなすことができる。そして,他のライブラリと同様に,APIも直接利用されることを前提とした様々な工夫が凝らされている。その工夫を堪能するというのも,一つのプログラミングの楽しみ方である。
さて,ここで念のために補足しよう。筆者は,.NET Compact FrameworkとC#を使ったプログラミングも行っている。そして,それにも魅力があることを知っている。
そのような観点から,生APIプログラミングに短所があることもよく承知している。生APIを使うと実行の効率は高いが,コーディングの効率は落ちるし,安全性も低下する。だが,そこそこ小さなプログラムであれば,これらは致命的な問題にはならない。そこそこ小さければ,破綻して完成しないということはないし,ソースコードをレビューして最低限の安全性を確保することも難しくはないのである。
そのようなわけで,ワンべぇWMは生APIプログラミングを採用したのである。
絶対生APIプログラミングの時代
今時のWindowsプログラマであれば,「APIも直接利用されることを前提とした様々な工夫が凝らされている」という表現に違和感を感じるかもしれない。
おそらく,C++プログラマならMFCを,C#/Visual Basic(VB).NETプログラマなら.NET Frameworkを,VB 6.0プログラマならVBにビルドインされた機能を使うことが常識であり,APIを呼び出す頻度は低いものに過ぎないのではないかと思う。まして,全面的にAPIに依存したプログラミングなど,想像の外にあるかもしれない。
だが,最初からそのような環境があったわけではない。Windowsが生まれた時代まで遡ってみよう。
1985年に発売された初めてのWindowsは,当たり前だがWindows 1.0という名前だった。この時点でWindowsアプリケーションを開発する手段は,事実上Cによる生APIプログラミングしか存在していなかった。当時,NECのPC-9801シリーズ上のWindows用として,N88-BASICも存在していたようだが,Windowsならではの機能を活用するプログラムを開発するには十分ではなかったようだ。
その後,Windowsはバージョンアップを繰り返した。しかし,1991年に発売されるWindows 3.0の時代まで,いくつかの開発言語製品が発売されるが,いずれも決定打とはならなかった。Cと生APIの呪縛から解放されるのは,事実上1991年に発売されたVB 1.0を待たねばならない。その後,Windowsで利用できるC++とそのための様々なクラスライブラリも出現し,一気に状況は変化していく。
だが,ここで注意すべき点は,1985年から1991年までの6年間,WindowsプログラマはCによって生APIを呼び出すプログラムを書き続けたということである。これが,事実上生APIプログラミング以外の選択肢が存在しない絶対生APIプログラミングの時代である。
おそらく,Windowsプログラマは,この時代へのかかわり方で三つに分類できると思う。
- 生APIプログラミングにたっぷり浸った
- 生APIプログラミングに挑戦したが,困難さに撤退した
- その後から始めたので,この時代のことは知らない
ここで注意が必要なのは,この3種類は下に行くに従って人数が多いことである。そして,2番目のグループの者達が生APIプログラミングについて,実際以上に困難でわかりにくい作業だと主張した可能性がある。その結果として,生APIプログラミングは著しく困難であるという誤った俗説が生まれた可能性があるのではないかと考えている。
それは俗説であって事実ではないと思う。生APIプログラミングは,全体的な構造の概要さえ把握してしまえば難しいものではないし,構造が複雑というわけでもない。それにもかかわらず,なぜ俗説が生まれたのかといえば,それは生APIプログラミングを学ぶための良書が決定的に欠けていたからだと感じる。つまり,良書がないためにわかりにくさを生み,挑戦しながら敗退するという悲劇を招いたに過ぎないということだ。
この時代に最もメジャーだった生APIプログラミングの入門書はCharles Petzold(チャールズ・ペゾルド)の『プログラミングWindows』(アスキー発行)だろう。この書籍は名著と褒めたたえる人も多い。実際に「これでWindowsプログラミングが理解できました!」という喜びの声もよく耳にする。
だが,その評価は類書がほとんど存在しないという特異的な状況で行われたものに過ぎないことに留意する必要がある。実際,子細に調べていくと問題のある記述も多く,全体的な構成もWindows APIをわかりやすく理解するには適切とは言い難い。
ちなみに,筆者が1991年から「きた あきら」名義でソフトバンクのWindows専門誌に連載を始め,『はじめてのWindowsプログラミング』(ソフトバンクパブリッシング発行)というタイトルで単行本化された書籍は,部分的に『プログラミングWindows』の悪い部分をフォローする意図を持っていた。しかし,全体として「Windowsプログラミングの入門書が難しすぎる」という問題に対処することが主目的であったため,あくまで入り口の問題にしか対処できていない。
少し話がそれてしまった。さて,筆者は上記の三つの分類のうち「生APIプログラミングにたっぷり浸った」に該当する。Windows 2.1~3.0の日本語化の仕事をしていたが,その作業の多くはAPIの日本語対応そのものであった。それゆえに,APIを実装したコードを修正した後,それを実際に動作させるプログラムを作成することは,まぎれもなく生APIプログラミングそのものであった。それは慣れてしまえば,なんら苦痛を感じる作業ではなかった。
事実として,すでにこの時代には全世界的に面白いWindows対応の小物プログラムがあふれていた。当然,これらはほとんどすべて生Windowsプログラミングによって作り出されたものである。慣れてしまえば,生APIプログラミングは決して困難なものではないという証拠と言えよう。
APIの基本的な構造は当時の16ビットWindowsも,W-ZERO3(Windows Mobile)も大差はない。W-ZERO3で生APIプログラミングができない理由は何もない。
生APIプログラミングの愛好者達
ここで,少し遠回りになるが,なぜ生APIプログラミングは主流の開発スタイルから没落したのか,そして,没落したにもかかわらずなぜ生APIプログラミング愛好者はその後も存在したのか,彼らは生APIプログラミングの何に価値を見出しているのかを確認してみよう。それを確認することで,生APIプログラミングを活かすべき場面と,そうではない場面の区別が付けやすくなるからだ。
そう,ここでは生APIプログラミングが最強であり,それを常に使うべきだという主張をするつもりはない。すべての技術は適材適所で使われるべきものであり,何もかも上手く扱う万能の技術などは存在し得ないのである。
さて,なぜ「絶対生APIプログラミングの時代」は終焉を迎えたのだろうか。その理由は,プログラムの複雑さの爆発にある。
まず複雑さが大きくなる一例を示そう。ウィンドウとマウスで操作するプログラムは,キーボードと文字で操作するプログラムと比較して,複雑さが格段に上がる。なぜなら,キーボードと違ってマウスは画面のどこでもクリックすることができ,どこをクリックしても適切に反応(あるいは無視)を行わねばならない。しかし,どこをクリックされるかは利用者の自由であり,プログラム側で予測することはできない。つまり,予測できない操作に対応するために,必然的にプログラムは複雑化してしまうのである。
このような問題に対処する方法として採用されたのがオブジェクト指向プログラミングである。Windowsでは主にC++が使われるようになった。しかし,構造化プログラミング言語であるCから呼び出されることを前提としたAPIは,オブジェクト指向プログラミングにはなじまなかった。
そこで,APIを隠ぺいしてオブジェクト指向的に見せかけるクラスライブラリが作られるようになった。このようなライブラリはいくつかあるが,最も有名なのは米Microsoft自身が作ったMFC(Microsoft Foundation Class)だろう。
おそらく,MFCの出現とVBの流行が,「絶対生APIプログラミングの時代」に引導を渡したと言っても良いのではないだろうか。これ以後,生APIプログラミングは脇役へと追いやられていく。
しかし,MFCの評判は必ずしも良いものではなかった。アンチMicrosoft派がこぞって嫌ったという話ではない。実は,MicrosoftのOSや開発言語を愛用する者達の中にも,MFCを好まない者は珍しくなかったのである(なお,Windowsプログラミングを学び始めた時点ですでにMFCがあった世代は,MFCに抵抗感がないケースも多い。傾向に世代差があることは留意しておく価値があるだろう)。
なぜMFCは嫌われたのだろうか。いくつか典型的な理由を見てみよう。
- 小さなプログラムでも巨大なライブラリが必要(容量を食う)
- 巨大なMFCは,全体が把握しきれないので不安
- ツール抜きではうかつに書き換えられないマクロの塊を必要とする
- マニュアルの説明が足りない(MFCのソースがマニュアル)
- MFCのソースを見ないと書けないが,MFCのソースに依存するコードを書くと,MFCのバージョンアップ時に動かない
このような不満を持つ者達の一部は,確信犯的に生APIプログラミングの世界に回帰している。例えば,筆者がソフトバンクのWindows専門誌で連載し,後に単行本が3冊出た『API散歩道』(ソフトバンクパブリッシング発行)のサポート用として開設した「API散歩道メーリングリスト」は,ある時期において,紛れもなく生APIプログラミングの愛好家の集まる場所になっていた。むろん,筆者もそのような生APIプログラミングの愛好家の一人である。
ここからが重要なポイントである。もともと,生APIプログラミングではニーズを満たせないからMFCが生まれたはずだ。それにもかかわらず,なぜ生APIプログラミングに回帰することができるのだろうか。
筆者は,「絶対生APIプログラミングの時代」が終焉を迎えた理由は「プログラムの複雑さの爆発」だと述べた。ということは,裏を返せば,複雑ではないプログラムであれば,生APIプログラミングでも問題なく開発可能であることを示すのである。そして,世の中で必要とされるプログラムのすべてが複雑というわけではない。
では,複雑ではないプログラムに着目してみよう。そのようなプログラムを,生APIプログラミングで開発するケースと,MFCのような巨大ライブラリを使って開発するケースとを比較してみよう。どちらがより優れたプログラムを開発できるだろうか。
その答は一概には出せないが,生APIプログラムについて以下のような可能性は十分にあり得るだろう。
- MFCのぶんだけ生APIプログラムのほうが小さく軽い
- プログラムを構成するコード量がMFCのぶんだけ減るので,全体を把握しやすくなり,間違いが入り込みにくい(MFCはソースを読まねば使えない存在であり,ブラックボックスではない)
- コードのすべてがテキストエディタで容易に操作できる
- MFCのバージョンに依存することがなくなる
- 記述不足のMFCのマニュアルではなく,記述が充実したAPIリファレンスを使って開発できる
つまり,小さくシンプルなプログラムを作るのであれば,生APIプログラムも依然として有効であり,場合によってはより良い成果を出せるということだ。
しかし,このような流れは徐々にフェードアウトしていった感がある。その理由は,おそらく.NET Frameworkのリリースにあるだろうと思う。.NET Frameworkのクラスライブラリは,MFCの弱点をよく研究して作られていて,積極的に拒絶する理由もさほど見あたらない。もちろん,JITによる翻訳や,フレームワークを経由するオーバーヘッドなどもあるのだが,パソコンの性能が上がったので決定的な要因ではなくなってきた。
いよいよ生APIプログラム主義も本当に終焉を迎えるかのように見えたのだが……。
ここで,W-ZERO3(Windows Mobile)が状況を一変させた感がある。
リソースが制限されたW-ZERO3では,さほど大きなプログラムを実行することはできない。もちろん,搭載されているメモリーをすべて一つのプログラムで使い切れば,かなりのサイズのプログラムが実行でき,それはかなりの複雑さを示すだろう。そのような複雑なプログラムは,もしかしたら生APIプログラミングでは対処できないかもしれない。
しかし,たいていの場合一つのプログラムで資源のすべてを使い切るような使い方はあり得ないだろう。まして,メモリーの隙間に常時入れておく便利な小物ツールのようなものなら,リソースの制限によって,大きく複雑なプログラムにすることはできない。一方で,可能な限り小さく軽いプログラムへのニーズは大きい。リソースが制限されたデバイスである以上,誰も無駄遣いできるメモリーは持っていないのだ。
つまり,極限まで小さくシンプルなプログラムへのニーズがあり,それに最もよく対応しうる手段が生APIプログラミングではないかと考えられるのである。
メモリー消費量の比較
しかし,本当に生APIプログラミングはリソースの節約になるのだろうか?
生APIプログラム,MFCプログラム,.NET Compact Framework上のC#プログラムの実行ファイルのサイズと,起動時のメモリー使用量を比較してみよう。完全に同等の条件の比較にはならないが,あくまで大ざっぱな傾向を見るということで,Visual Studio 2005を用いて,以下の条件(開発言語とプロジェクトの種類)で三つのプログラムを作成した。
- API版
- Visual C++→スマートデバイス→Win32スマートデバイスプロジェクト
- MFC版
- Visual C++→スマートデバイス→MFCスマートデバイスアプリケーション
- C#版
- Visual C#→スマートデバイス→Windows Mobile 5.0 Pocket PC→デバイス アプリケーション
いずれもリリースビルドを行い,実行は「JPN Windows Mobile 5.0 Pocket PC VGA Emulator」を用いた。メモリー容量の表記は起動前と起動後の残り容量である。
- API版
- 実行ファイルサイズ 31,232B
- プログラム実行用メモリー 27.66MB→27.87MB(27.87-27.66=0.21)
- MFC版
- 実行ファイルサイズ 235,520B(共有MFCライブラリを使うと40,448B)
- プログラム実行用メモリー 27.66MB→28.06MB(28.06-27.66=0.4)
- C#版
- 実行ファイルサイズ 4,608B
- プログラム実行用メモリー 19.35MB→23.91MB(23.91-19.35=4.56)
まず,実行ファイルのサイズという点で,C#版が桁違いに小さいことと,MFC版が桁違いに大きいことに驚かされる。MFC版は,ライブラリを実行ファイルに取り込んでいるから巨大化するので,「共有DLLでMFCを使う」を指定してライブラリを実行ファイルに取り込まないようにすればさほど大きくはならない。
実行ファイルサイズだけを見ると,C#版が圧倒的に優れているように見えるが,メモリー消費量を見ると桁違いに大きな数値であることに驚かされる。おそらく,これはプログラム本体が消費したメモリーではなく,JITがプログラムをネイティブコードに翻訳する処理に使用されたのだろう。とはいえ,実行にあたって消費されてしまうことに変わりはない。本当は,事前にネイティブコードに翻訳するngenコマンドの.NET Compact Framework版があれば状況が変わるのだが,存在を確認できなかった。
これらと比較して,API版の数値はいずれも安定している。プログラムの内容が等価ではないので,これだけでAPI版が最善とまでは断言しきれないが,実行ファイルが膨らみすぎるMFC版やメモリーを食いすぎるC#版と比較して,リソースの節約効果が安定して出ていることは推測できる。
この結果を見ても,やはり生APIプログラミングは挑戦する価値があるのではないだろうか。
次回予告
筆者がしばしば繰り返し述べていることではあるが,プログラミングの世界には(他のどのような世界とも同様に),すべての問題を解決する万能解はない。そのような観点から批判されるべきものは,特定の技術や方法論を神格化し,絶対視してしまうことである。
例えば,オブジェクト指向プログラミングを神格化して絶対視する人をしばしば見かけるが,これも常に最善というわけではない。複雑さに対処する手段としてのオブジェクト指向プログラミングは,シンプルなプログラムでは有り難みを実感できないこともある。そして,W-ZERO3では大きく重い複雑なプログラムよりも,シンプルで軽いプログラムが価値を持つこともあり得るのである。
さて,そろそろ長々と続いた原則論も飽きてきたころだろう。次回は,具体的なコードの内容に踏み込んでいく。
内容は,UnicodeとシフトJISの折り合いをどう付けるかである。もともとシフトJISを前提として開発されたワンべぇだが,Windows MobileプログラミングはUnicodeで行うことが基本である。それだけなら,歴史あるどのプログラムでも突き当たるよくある問題と言える。しかし,W-ZERO3(Windows Mobile)では一筋縄では行かないこともある。そこで発生した問題と対象方法は,おそらくワンべぇ以外の開発作業にも適用できる情報ではないだろうか。
乞うご期待である。