Dr. GUI と COM オートメーション、第 2 部:COM の素晴らしきデータ型

1999 年 3 月 30 日

以下も参照:「Dr.GUIとCOMオートメーション、第1部」

目次

Dr. GUIのビットとバイト
これまでの復習、これからの予定
COMのデータ型
やってみよう
これまでの復習、これからの予定

Dr. GUI のビットとバイト

MSDN と Site Builder

すでにご存じとは思いますが、MSDN Online と Site Builder Network が 1 つに統合され、Web 開発者か「従来のタイプの」開発者かに関係なく、あらゆる開発者にサービスが提供されるようになりました。何年も前の Wendy's の広告じゃないですが、「開発者は開発者」ということです。

最近は、その度合いが数年前よりもさらに進んでいます。Microsoft Visual Basic®開発者は、ASP ページを作っています。C++ 開発者は、サーバー コンポーネントを作っています。HTML 開発者は、C++ を使っています。そして、だれもがデータベースを使っています(まあ、全員ではないかもしれません。今のところは)。ますます多くの人がいろいろなものに少しずつ手を染めており、この傾向は今後も続くように思われます。

Web 開発者、デスクトップ開発者、サーバー開発者の間にはもう大きな隔たりはありません。MSDN でも、この傾向には気付いていました。Site Builder と MSDN Online 両方の会員になっている人の割合は、60% を大きく越えています。そして読者からも、ほとんど共通の読者層に対して 2 つもサイトがあるのは紛らわしいというご意見もいただきました。

そこで、私たちは決断をくだしました。2 つのサイトと、その制作チームを統合することにしたのです。その結果、読者は 1 つのサイトですべての用が足りるというメリットが得られます。今後も当サイトをご愛顧ください。

なお、私、Dr. GUI は、ほかのコラムの執筆陣とともに、MSDN Online の「Voices」欄に毎月コラムを連載します。

Roger Sessions が COM と COM+ を語る

Dr. GUI は、つい最近開催された MSDN Professional Developers' Summit で、「COM と DCOM」(Wiley Computer Pub.、1998 年発行 ) の著者であり、ObjectWatch(http://www.objectwatch.com/)の社長も務める Roger Sessions による、COM、DCOM、MTS、COM+ についての講演を聞く機会を得ることができました。Roger やその他の講演者を招待したこのサミットは、Microsoft の Pacific Northwest 地区オフィスが主催した 1 日限りのセミナーです(実のところ、このイベントは、シアトルとポートランドでそれぞれ 1 回ずつ開催されました)。Dr. GUI はこのセミナーの開催者に聴診器を傾けたい(賛辞を送りたい)と思います。すばらしいセミナーでした!

講演者も第一級でしたし、入場料には Microsoft Windows® 2000 のベータ版と Roger Sessions の著書も含まれていました。

名医は、危うくこのイベントを見逃すところでした。彼が知ることができたのは、友人が教えてくれからです。みなさんもこうしたイベントを見逃さないようにしましょうね。今後のイベントへの参加予定は、http://msevents.microsoft.com/isapi/events/usa/enu/default.asphttp://msdn.microsoft.com/events/ を見て決めましょう。また、http://www.microsoft.com/insider/subscribe/default.htm?mscomtb=icp_free%20newsletter で、Microsoft からの電子メールのお知らせ(山ほどあります)を送ってもらうように登録もできます。大事なことを忘れていました。ほとんどのイベントが入場無料です。

いずれにせよ、Roger Sessions については後でもっと取り上げます。さしあたり、彼がこれまでは CORBA に深くかかわってきたけれど、現在は、エンタープライズ システムを手がける開発者に COM を勧めているとだけ言っておきましょう。理由ですか?彼の本を読んでみてください。理由がわかるはずです。わかりやすい、たいへんよい本です。プログラマだけでなく、一般の読者にも技術の入門書としてお勧めします。

Internet Explorer 5:熱いうちに手に入れよう

Dr. GUI は、Internet Explorer 5 を長い間使っています。あまりに長い間使っているので、その存在さえ忘れてしまいそうです。正直なところ、忘れそうになるのは、Internet Explorer 5 がほとんどクラッシュしないからです。Internet Explorer 5 の信頼性は、どの主要メーカーのバージョン 4 のブラウザよりもはるかに高いと言えます。それだけでもこの製品を手に入れる価値はあります。

Internet Explorer 5 には、生産性を高める多数の機能が搭載されています。Dr. GUI のお気に入りは、フォームのフィールドに以前入力した内容を、Internet Explorer 5 が記憶していて、そのフォームを使う次の機会にそれらを選択肢として提示するという機能です。最高!(もちろん、パスワード入力のときにはこの機能を無効にできます)。

開発者にとっては、3 つの利点があります。第一は、安定性が強化されたことで、Internet Explorer 5 を使用するアプリケーションを配備する意味が以前よりもさらに大きくなったという点です。第二は、DHTML ビヘイビアや XML サポート機能など、ぜひ使ってみたいと思うような優れた新機能がたくさんあるという点です。第三は、独自の Web アクセサリを開発できる点です(http://www.microsoft.com/windows/ie/WebAccess/)。

http://www.microsoft.com/windows/ie/ でぜひチェックしてください。

何でも Web アプリケーションにする?まだその段階ではありません

Wall Street Journal の Walt Mossberg は、Web で入手できるカレンダー アプリケーションをいくつか評価しましたが、いずれも気に入らなかったようです。なぜかって?理由は 2 つあります。理由その 1:「それらが書かれている言語、HTML が、アプリケーションを開発するのにはあまりに粗雑で、洗練されていない」。理由その 2:「全体の 95% を占めるダイアルアップ モデム ユーザーにとって、Web は、アプリケーション プログラムとして使うには遅すぎで、信頼性も低い。ユーザーが PC のハード ディスクから実行されるプログラムですでに慣れている応答速度と比べるとなおさら」。(記事の全文については、http://ptech.wsj.com/archive/ptech-19990304.html をご覧ください)。

全面的に Web に切り替える前に、必要なパフォーマンスが得られることを確認しましょう。Microsoft ActiveX®コントロールをうまく使えば、重要な領域でパフォーマンスが大幅に改善することがあります。逆に、Visual Basic でアプリケーションを開発した方がよいこともあります。多くの場合、Web が最良の選択ですが、いつもそうであるとは限りません。たとえば、Hotmail と Outlook Express を比較してみてください。新しい Outlook Express のすごい点は、Hotmail のメールを読み込むことができるという点です。つまり、PC アプリケーションの速さと、世界中のどこからでも Web ブラウザを使って電子メールにアクセスできるという両方の利点が得られるのです。

Windows CE 開発入門

Dr. GUI がこの 2 年に渡って Windows CE についてこれほどたくさん書いてきたというのに、まだ 1 つもアプリケーションを開発していないというのですか?。嘆かわしいことです。しかし、まだ希望はあります。PC Magazine Online に掲載された Douglas Boling の記事「Programming for Windows CE Devices」を読んでみてください。彼は、H/PC ターゲット プラットフォームのほかに、カスタムの埋め込みプラットフォームを含め、いくつかのプラットフォーム向けにアプリケーションを開発する手ほどきをしています。この記事については、http://www.zdnet.com/pcmag/pctech/content/18/06/tf1806.001.html をご覧ください。

コンピュータ稼業の楽しさ

コンピュータ稼業に携わっていて楽しいのは、常に新しい、楽しいものに出会えることです。以下は、Dr. GUI が最近出会ったものです。

Windows NT Embedded

埋め込み型 OS が必要なシステムに取り組んでいるけれど、Windows CE ではものたりないという経験はありませんか?Windows NT Embedded をチェックしてください。現在、ベータ段階です。

ClearType

Dr. GUI は、液晶画面が読みやすくなる Microsoft の新技術、ClearType を実装したマシンをこの目で見る機会を得ることができました。

びっくりしました。

コントラストはやや落ちますが、テキストの鮮明さには驚かされました。文字が鮮明に表示されます。小さなドットとはもうおさらばです。一度これを使うと、通常のテキストに戻るのは一苦労です。関連記事が http://www.microsoft.com/OpenType/cleartype/ に掲載されています。

無料コンピュータ

PC、そしてインターネット アクセスも無料で提供する会社の話を聞きましたか?画面に広告を表示するという条件付きですが(http://www.free-pc.com/ をご覧ください)。すごいこと考えますね(初回の 1 万台の無料コンピュータに 100 万件の応募があったそうです)。

正直言って、Dr. GUI が 3,000 ドルで元祖 IBM PC を購入したときには、まさかこんなに安いマシンが出現するなどとは夢にも思いませんでした。当時の装備は、48 KB(KB ですよ、MB ではなく)のメモリ、160 KB フロッピー ドライブ(ハード ディスクはおろか、両面フロッピー ドライブもありませんでした)、安っぽいカラー モニタだけでした。世の中、変われば変わるものです。何が飛び出すやら。

Office 2000 Developer

Office 2000 Developer は、これまでのエディションに比べ、はるかに強力な機能を提供します。これは主に Office 2000 の能力を最大限に活用するアプリケーションを開発したいユーザーを対象としていますが、独自の Office アシスタントを開発することさえできます。このコンセプトはお気に召しませんか?Scott Adams が Dilbert のキャラクタに基づいて一組の Office アシスタントを開発したとしたらどうでしょう。名医なら、けっこうな金額を支払うと思いますね。つまり、みなさんもお金をもうけるチャンスがあるということです。詳細は http://www.microsoft.com/office/developer/ をご覧ください。

今までの復習、これからの予定

今回は、COM オートメーションに関する 3 部構成の記事の第 2 部です。(名医としては、1 回のコラムで COM のデータ型をすべて語ろうと思っていたのですが、とても間に合いませんでした)。

3 部構成では、いかんせん分量が多すぎるというご意見もあるでしょう。しかし、Brockschmidt がこの主題に 100 ページ以上も費やしたことを考えてください(しかも、は、私が語ろうとしていることについてはあまり語っていません)。今回の主題は、バリアントです。次回は、BSTRDATECURRENCYDECIMALSAFEARRAY など、COM が提供するほかの特殊なデータ型を取り上げます。その後は、少しおもむきを変えて、COM のイベントについて説明します。

COM のデータ型

前回、名医はバリアントを含め、COM のデータ型についてはほとんど触れませんでした。実際、Dr. GUI が前回述べたのは以下のことでした。

「バリアントは 16 バイトで保存されます。最初の 2 バイトはタグで、バリアントの種別を表す数値です。次の 6 バイトはパディングです。最後の 8 バイトはバリアントの値です。値の形式はタグの値によります。 C/C++ では、バリアントの値を共用体で表します。バリアントはほとんどの C++ のデータ型に加えて、ポインタ、配列、文字列、データ、通貨型のオブジェクトを保持できます。バリアントを含む COM のデータ型の完全な処置は、次回行います。」

さて、前回の「次回」は、今回なので、バリアントについて詳しく説明しましょう。

COM のデータ型:Windows の標準

名医が医学生として COM のデータ型について学んでいたころ、面白いことに気付きました。つまり、それらがどこでも使われているということです。これらを、C と C++ で使うことができます。また、これらのほとんどを、Microsoft Visual J++®で使うこともできます。全部を Visual Basic で使うことができます。Microsoft SQL Server™、Access、Visual FoxPro®、Excel でも使われます。また、当然ながら、Visual Basic for Applications などのスクリプティング言語が現れるところならどこでも使われています。スクリプティング言語を使うアプリケーションは、Microsoft のもそうでないのもたくさんあります。

つまり、COM のデータ型を使えば、アプリケーションから、言語を問わず、ネットワーク上のどこかにあるデータベースに非常に簡単にデータを移動できるのです。

それだけではありません。COM のデータ型は実にパワフルです。たとえば、文字列型、2 つの異なる通貨型、日付型、多次元配列型などがあります。これらはどのようなプログラミングでも便利に使えますが、COM コンポーネントとのやり取りには特に便利です。

したがって、C や C++ で COM をプログラミングしない人も、Word で Visual Basic for Applications を使ってマクロを書いているか、SQL Server を使ってストアド プロシージャを書いているかにかかわりなく、ぜひこの記事を最後まで読んで、自分が使っているデータ型について学んでください。

COM 非対応プログラムでの COM データ型の使用

C や C++ プログラマは、ここで説明するデータ型が大いに気に入るはずです。特に、10 進数(CURRENCY や DECIMAL)や日付の正確な表現を必要とするユーザーはなおさらです(Visual Basic、Visual FoxPro、SQL Server でプログラミングをしている人は、いずれにしてもこれらの型を常時使っているでしょう。これらの型が組み込みで提供されているからです)。

これらのデータ型は、当然ながら COM コンポーネントを実装または使用しているプログラムから使用することができます。しかし、COM をサポートしている OS で実行されている COM 以外のプログラムから使用することもできるのです。

ほとんどのデータ型は、適切なヘッダーをインクルードするだけで使えます。ただし、データ型の中には、メモリの動的割り当てに依存するものもあります。それらを使う場合には、プログラムの始めに、必ず CoInitialize CoInitializeEx を呼び出す必要があります。COM のデータ型を使うのであれば、適切な呼び出しを行っておきましょう。そうすれば、トラブルを未然に防げます。

この記事に含まれているとても簡単な例は、本来なら COM や Windows をまったく使うことのない「Hello, world!」コンソール アプリケーションからこれを行う方法を示します。

なお、Visual J++ を使う場合、com.ms.com.* パッケージに、バリアントやセーフ配列をラップするクラスが含まれています。ほかのデータ型はラップしません。

便利なバリアント

バリアントに何を入れられるのかを知るもっとも手っ取り早い方法は、その定義を見ることです(Microsoft Visual C++ バージョン 6.0 の場合、現在、定義は oaidl.idl にあります。MIDL はこれを oaidl.h として出力し、それが oleauto.h によってインクルードされます)。完全な定義についてはここをクリックしてください。

それでは定義をセクションごとに見ていきましょう。これが最初の行です。

typedef [wire_marshal(wireVARIANT)] struct tagVARIANT VARIANT;

この前方宣言は VARIANT の型を定義しています。wire_marshal 属性は、VARIANT をマーシャリングする必要があるときには、wireVARIANT の IDL での型定義を使うことを表しています。wireVARIANT を調べると、共用体の正しいメンバだけを送るのに使われるケース属性など、バリアントをマーシャリングするために COM が使う形式を知ることができます。なお、バリアント型 VARIANT は構造体として定義されています(これは次に定義されています)。

始めに…

次は、バリアント自身の始まりです。

struct tagVARIANT {
union {
struct __tagVARIANT {
// いくらか省略 ...
union {
// 大幅に省略 ...
struct tagBRECORD {

COM の参考書を読んだことがあれば、これから見る宣言はやや奇妙に思われるかもしれません。struct union の中にネストされ、それが struct の中にネストされ、それが union の中にネストされ、それが struct の中にネストされていてるのなぜでしょう?ややこしいですね!

この質問の答えは、最後にとっておくことにします。1 つ余分な(外側)の共用体は、DECIMAL 型を処理するためのものです。

1 つのタグと 6 つの(通常は)未使用のバイト

さしあたり、外側の struct と共用体は無視して、struct __tagVARIANT で始まるコードに注目しましょう。これまでに COM オートメーションを学んだことがあれば、似たようなコードを見たことがあるかもしれません。ただし、最近新しい要素が(終わりに)追加されています。

struct __tagVARIANT {
VARTYPE vt;
WORD wReserved1;
WORD wReserved2;
WORD wReserved3;

まず、2 バイトの整数タグ vt があります。これにはバリアントの型を示すコードが格納されます。続くコメントからわかるように、コードは VT で始まる定数によって表されます。

タグのあとには、3 ワード、すなわち 6 バイトのパディングがあります。このパディングによって、続くデータが 8 バイトの境界で始まるようになっています。

基本の型

次に、バリアントのデータを保持する共用体があります。ここでは、これを複数のセクションに分けます。最初のセクションは基本の型を示します。

union {
LONG lVal; /* VT_I4 */
BYTE bVal; /* VT_UI1 */
SHORT iVal; /* VT_I2 */
FLOAT fltVal; /* VT_R4 */
DOUBLE dblVal; /* VT_R8 */
VARIANT_BOOL boolVal; /* VT_BOOL */
_VARIANT_BOOL bool; /* (obsolete) */

このセクションは 1、2、4 バイトの整数、4、8 バイトの 2 進浮動小数点(実)数、Visual Basic スタイルのブール値(真 / 偽)からなっています。I と R は変数の種類、すなわち整数または実数かを表します。U は符号なしを表し、数は型のサイズ(バイト)です。

将来のバージョンの COM には、8 バイト(64 ビット)の整数型(VT_I8VT_UI8)が存在することになりますが、現在はまだ実装されていません。

Visual Basic と VT_BOOL では、偽は 0 であり、真は -1 です。C 言語を使う場合は、この点に注意が必要です。C では、真を–1 ではなく 1 と定義するからです。2 番目のブールは廃止されているので、無視してください。

COM の特殊な型

次に、COM の特殊なデータ型が続きます。

SCODE scode; /* VT_ERROR */
CY cyVal; /* VT_CY */
DATE date; /* VT_DATE */
BSTR bstrVal; /* VT_BSTR */
IUnknown * punkVal; /* VT_UNKNOWN */
IDispatch * pdispVal; /* VT_DISPATCH */
SAFEARRAY * parray; /* VT_ARRAY */

SCODE は、HRESULT エラー コードを保持するエラー コードです。16 ビット版の Windows では、SCODEHRESULT は異なる型でした。Win32 では、両者は同じ型、すなわち 32 ビットの符号付き整数として扱われます。

DATE は、Visual Basic と Excel の日付の形式です。すなわち、1899 年 12 月 30 日からの経過日数を格納する 8 バイトの浮動小数点数です。これについては、次回詳しく説明します。

CY は CURRENCY の略です。これは Visual Basic の通貨型に対応しています。これについては、次回説明します。

BSTR は、Visual Basic の文字列の形式です。これも、次回説明します。

IUnknown IDispatch ポインタは、予想がつくと思います。COM のインタフェース ポインタです。これがバリアントをとても強力なものにします。これらは COM オブジェクトへのインタフェース ポインタを保持することができるのです。

両者のうち、IDispatch ポインタの方が強力です。というのは、オートメーション呼び出しを行うことができるプログラムなら、このポインタを使ってプロパティにアクセスしたり、オブジェクトのメソッドを呼び出したりできるからです。IUnknown ポインタ型は、オートメーション オブジェクトであるか否かを問わず、どんな COM オブジェクトもポイントできます。スクリプティング言語では、これを呼び出すことはできませんが、これを使うことのできる別のコンポーネントに渡すことができます。

最後に、SAFEARRAY は、Visual basic が配列を格納する方法に関係して、かなり複雑なデータ構造になっていますこれについては、次回詳しく説明します。

参照型

次のセクションは、たったいま説明したデータ型を含んでいますが、値渡しではなく、参照渡しによって渡されます。

BYTE * pbVal; /* VT_BYREF|VT_UI1 */
SHORT * piVal; /* VT_BYREF|VT_I2 */
LONG * plVal; /* VT_BYREF|VT_I4 */
FLOAT * pfltVal; /* VT_BYREF|VT_R4 */
DOUBLE * pdblVal; /* VT_BYREF|VT_R8 */
VARIANT_BOOL *pboolVal; /* VT_BYREF|VT_BOOL */
_VARIANT_BOOL *pbool; /* (obsolete) */
SCODE * pscode; /* VT_BYREF|VT_ERROR */
CY * pcyVal; /* VT_BYREF|VT_CY */
DATE * pdate; /* VT_BYREF|VT_DATE */
BSTR * pbstrVal; /* VT_BYREF|VT_BSTR */
IUnknown ** ppunkVal; /* VT_BYREF|VT_UNKNOWN */
IDispatch ** ppdispVal; /* VT_BYREF|VT_DISPATCH */
SAFEARRAY ** pparray; /* VT_BYREF|VT_ARRAY */
VARIANT * pvarVal; /* VT_BYREF|VT_VARIANT */
PVOID byref; /* Generic ByRef */

ご覧のように、これらはすべてデータ型と VT_BYREF 値との論理和となっていて、データ型が Foo から Foo * に変わっています。また、バリアント型へのポインタと、C++ プログラマが使用する汎用の void ポインタがあります。

その他の整数型

次のセットは、整数に関連するバリエーションです。INT UINT 型は、16 ビットと 32 ビットの OS ではサイズが違いますが、ほとんどのユーザーは、32 ビットのオペレーティング システムを使っているので、この違いはもうそれほど重要ではありません。

CHAR cVal; /* VT_I1 */
USHORT uiVal; /* VT_UI2 */
ULONG ulVal; /* VT_UI4 */
INT intVal; /* VT_INT */
UINT uintVal; /* VT_UINT */

次のセットには、上に示したセットの型を指すポインタと、1 つだけ特別なものがあります。特別な 10 進型へのポインタです。これについては次回取り上げます。これの参照渡しでないバージョンを目にしていないのは、DECIMAL 型が 8 バイトよりも大きいからです。これがバリアントにどのように適合するのか見てみましょう。

DECIMAL * pdecVal; /* VT_BYREF|VT_DECIMAL */
CHAR * pcVal; /* VT_BYREF|VT_I1 */
USHORT * puiVal; /* VT_BYREF|VT_UI2 */
ULONG * pulVal; /* VT_BYREF|VT_UI4 */
INT * pintVal; /* VT_BYREF|VT_INT */
UINT * puintVal; /* VT_BYREF|VT_UINT */

まもなくお目見え:レコード、またの名をユーザー定義データ型(UDT)

そろそろ終わりに近づいてきました。これまでに COM オートメーションを学んだことがある人でも、おそらく次の 2 つのデータ型は見たことがないでしょう。 これらは比較的最近、追加されたものです。

最初のものは、レコード、つまりユーザー定義データ型(UDT)を処理するためのものです。

struct __tagBRECORD {
PVOID pvRecord;
IRecordInfo * pRecInfo;
} __VARIANT_NAME_4; /* VT_RECORD */
} __VARIANT_NAME_3; // union
} __VARIANT_NAME_2; // struct __tagVARIANT—note underscores

なお、最後の 2 行は、共用体とバリアント構造全体を閉じる中括弧です。VARIANT_NAME_1 から VARIANT_NAME_4 までは無視してください。コンパイラが他の struct や共用体にネストされている名前の付いていない struct や共用体を処理できない場合、これらは何もないと定義されるか、またはダミー名と定義されます。

UDT(もしくはレコード)のデータには、データへのポインタと、UDT の IRecordInfo インタフェース、つまりレコードの要素に関するミニチュアのタイプ ライブラリへのポインタが含まれています。(IRecordInfo については、あまり気にすることはありません。これは主に、UDT を特定の言語でサポートするときに使われます)。C/C++ を使用している場合は、データ ポインタを適当な struct 型に型変換してデータにアクセスすることができます。MIDL が自動的に struct 宣言を生成してくれます。さもなければ、Visual Basic の UDT 型など、言語に実装されているメカニズムを使ってレコードにアクセスすることになります。

VT_RECORD は、Visual Basic UDT をほかの Visual Basic コンポーネントに渡したり、C++ と Visual Basic の間で構造体を受け渡したりするのに便利です。現在、これはスクリプティング言語では役に立ちません。またこの機能は、Visual Basic の最新バージョンにしか実装されていません。またこれは、COM のかなり新しいバージョン(Windows 98、DCOM95 1.2 を追加した Windows 95、Service Pack 4 を適用した Window NT 4.0)が必要です。ですから、みなさんが対象としているユーザーが適切な OS と適切な Visual Basic ランタイムの両方を持っていることを確認した上で、この機能を使う必要があります。

これについては、まだ文書化もされていないので、この辺で説明を終えたいと思います。数か月のうちには文書化されるでしょう。

未使用バイトのない、大きな 2 進数

struct tagVARIANT は、実際には共用体を内包する struct であることを思い出してください。共用体の最初の部分は、すでに説明した struct __tagVARIANT です。2 番目の部分は単純です。

DECIMAL decVal;

DECIMAL 型というのは、実は、VARIANT の予約されている 6 バイトを使用する唯一の型です。実際は、データに 14 バイトを使用します。すべてのバリアントの例にもれず、 最初の 2 バイトはタグとして使われます。この場合は、VT_DECIMAL という値を含んでいます。

まとめ

全体は次のようになります。

struct tagVARIANT {
union {
struct __tagVARIANT { // old variant, note underscores
// タグとパディング --8 バイト
union {
// 大量の VT_ データ型—8 バイト
struct __tagBRECORD {
// レコード ポインタ
            };
         };
      };
DECIMAL decVal; // new decimal type, with tag
   };
};

つまり、tagVARIANT _tagVARIANT(レコードの場合もある)か、または DECIMAL です。

おっと、typedef を忘れていました

最後に、ほかの関連する型の typedef がいくつかあります。

typedef VARIANT * LPVARIANT;
typedef VARIANT VARIANTARG;
typedef VARIANT * LPVARIANTARG;

VARIANTARG は、引数とした渡されたバリアントの型です。C++ では、VARIANT と同じです。

Visual Basic のバリアントを見慣れている人にとっては、ずいぶん複雑ですよね?

Visual Basic、Visual J++、スクリプティング言語でのバリアントの受け渡しと操作

Visual Basic とスクリプティング言語では、通常、バリアントを気にする必要はありません。オートメーション コンポーネントによってバリアントがこれらの言語に渡されさえすれば、バリアントが組み込みの型であるため、言語のランタイムが必要に応じてそれらを自動的に変換します。

ですから、これらの言語のいずれかでプログラミングを行う場合、バリアントの扱いをそれほど気にする必要はありません。

Visual J++ の場合も、一般にパラメータと戻り値の型は自動的に変換されます。ただし、戻り値などのために、バリアントを作成しなければならないこともあります。Visual J++ でバリアントを作成し、操作するには、必要なメソッドをすべて含んでいる com.ms.com.Variant クラスを使います。

ところで、Visual J++ でオブジェクトのプロパティを操作する場合、オブジェクトをラップするクラスを作成したときに提供された get set 関数を使う必要があります。Java 言語では、 Visual Basic や、Visual C++ でスマート COM ポインタを使う場合とは異なり、プロパティへのアクセスがサポートされていません。

 C++ コンポーネントとの間のバリアントの受け渡し

もちろん、C++ の場合にはもっと複雑になります。強力な機能が使えるようになる代わりに、その仕組みを知っている必要があります。ありがたいとことに、バリアントを扱う手法のいくつかは、Visual Basic の場合と同じくらい単純です。

デュアル インタフェースを実装する?簡単!

ほかのプログラムから呼び出される立場にあり、デュアル インタフェースを実装していれば、しめたものです。COM が、タイプ ライブラリの情報に基づいて、必要なデータの変換をすべて行い、標準 C++ データ型を使ってメソッドを呼び出します(デュアル インタフェースのカスタム部分を使ってクライアントから呼び出されたら、バリアントの変換はまったく行われません)。バリアントを処理する必要が生じるのは、スクリプティング クライアントに値を返すときだけです。その場合、返す値はバリアントでなければいけません。戻り値を返すためのバリアントを作成するには、以下に示す方法のいずれかを使います。

デュアル インタフェースを介してコンポーネントを呼び出す

同様に、デュアル インタフェースをサポートするコンポーネントを呼び出す場合、デュアル インタフェースのカスタム部分を使います。そうすれば、バリアントのデータ変換は必要なくなります。正しいヘッダーをインクルードし、お好きな手法を使ってオブジェクトを作成すればおしまいです(もちろん、VC++のスマート ポインタを使えばもっと簡単です)。

オートメーション専用インタフェースを呼び出す:簡単な方法

オートメーション専用インタフェースにタイプ ライブラリがある場合(あるいは、タイプ ライブラリを自分で書ける場合)、Visual C++ のスマート ポインタを使いましょう。これを使えば、オートメーション インタフェースのみを実装するコンポーネントを簡単に呼び出せます。#import が生成するラッパーには、オートメーション専用インタフェースに適切に対応するコードがあるのです。また、Visual Basic の場合と同様にプロパティにアクセスすることもできます。なかなかいいです。

Microsoft Foundation Class(MFC)を使っている場合は、ClassWizard を使って、オートメーション オブジェクトをクラスでラップすることもできます。ClassWizard を呼び出し(Ctrl+W を押す)、[オートメーションAutomation)]タブをクリックし、さらに[クラスの追加Add Class)]ボタンをクリックし、使用するタイプ ライブラリを指定します。なお、呼び出すオブジェクトがデュアル インタフェースを持っていても、この方法は IDispatch 呼び出しを生成するだけです。また、プロパティにアクセスするためには get set メソッドを使う必要があります。以上の 2 つの理由から、Dr. GUI は、どちらかといえばスマート ポインタの方が好きです。

オートメーション専用インタフェースの呼び出し:本当にむずかしい方法

タイプ ライブラリを持っていない?スマート ポインタが大の苦手?前回の記事を覚えていますか?生の IDispatch::Invoke を使ってメソッドを呼び出したり、プロパティにアクセスしたりするのは、かなり厄介でした。Visual Basic(または、Visual J++、あるいは上で説明した方法を使って Visual C++ で)なら 1 行ですむ簡単なメソッド呼び出しも、5、10、20、それ以上の C++ ステートメントになってしまいます。名医としては、これをやるくらいなら麻酔なしで歯を抜いたほうがましです。パラメータ用にバリアントの配列を用意し、それぞれのバリアントを適切に設定する(それぞれに 3 つのステートメントを使います)といったことをしなければなりません。ほかの言語や方式なら、これらがすべて自動的に処理されます(しかも、IDispatch を介して呼び出しを行う場合には、これらの処理を避けることはできないのです)。自分でやらなければなりません。

C++ でのバリアントの操作:バリアント用 COM API

バリアントの作成、コピー用の COM API

COM には、基本的なバリアント操作のための関数が一組用意されています。ほとんどの関数は、名前を見ただけでも用途がわかるでしょう。VariantInit(新規バリアント用)、VariantClear(既存のバリアントのクリア用)、VariantCopy(完全なコピー)、VariantCopyInd(必要なら逆参照できる)などがあります。

魔法のマクロ

現在文書化されていないマクロも一組あります。これらを使って、必要に応じてバリアントの様々なフィールドにアクセスできます。 たとえば、V_VT(pVariant) = VT_I4 のように、V_VT マクロを使って型タグにアクセスすることができます。また同様のマクロを使って、データ部分にもアクセスできます。前回、次のようなものを書きました。

VariantInit(&vararg[0]);
vararg[0].vt = VT_I4; // 32-bit integer
vararg[0].lVal = 5; // here's our 5!

これを次のように書くこともできます。

VariantInit(&vararg[0]);
V_VT(&vararg[0]) = VT_I4; // 32-bit integer
V_I4(&vararg[0]) = 5; // here's our 5!

このコードは読みにくいという意見もあるかもしれませんが、マクロを使えば、VT_I4 に対応する要素名が lVal であることを知っている必要はありません。おかげで、私が犯したことのある過ち犯さずに済みます(まだ飲み込めていない方のために付け加えると、V_XX マクロは、XX 部が同じ VT_XX マクロで指定されたデータにアクセスします。したがって、VT_R8 が必要な場合は、V_R8 を使ってアクセスします)。

ところで、これらのマクロを使って、いま述べたようにバリアントを変更したり、次に示すように、それにアクセスすることができます。

long count = V_I4(pVariant);

これらはまだ文書化されていませんが、簡単です。Visual C++ の include ディレクトリにある oleauto.h を参照してください。

データ型変換のための COM API

バリアントがあって、その型を変えたいだけならば、VariantChangeType VariantChangeTypeEx を使いましょう。VariantChangeTypeEx はロケール ID(LCID)を受け取ります。これによって、指定されたロケールで数値や日付を正しく解析できます。

変換元と変換先の正確な型がわかっている場合には、VarXxxFromYyy という形式の名前を持つ多数の関数が使えるので便利です。Xxx は変換先の型で、Yyy は変換元の型です。Xxx を括弧内の名前に置き換えて、次のいずれの型にも変換できます。unsigned char(UI1)short(I2)long(I4)float(R4)double(R8)DATE(Date)CURRENCY(Cy)BSTR(Bstr)BOOL(Bool)

これらの変換関数への入力は、前掲のいずれかの型のほか、OLECHAR *(<Xxx は Str。入力時に BSTR に置き換えられる)と IDISPATCH *(Xxx は Disp。オブジェクトの値(DISPID_VALUE)のプロパティを変換するということ)を指定できます。

たとえば、float から CURRENCY に変換する場合、関数名は VarCyFromR4 となります。オブジェクトから文字列(BSTR)への変換の場合は、VarBstrFromDisp となります。そして、文字列(BSTR または OLECHAR *)から long への場合は、VarI4FromStr となります。すべての候補を並べる必要はないでしょう。要領はわかりますよね?みなさんが全部数えたかどうかは知りませんが、関数は合計 81 個になります(9 の出力型に 10 の入力型を乗じ、そこから変換元と変換先が同じになってしまう 9 つ関数を差し引きます)。

いや、実はもっとたくさんあります。これらは文書化されていませんが、COM オートメーション用のヘッダー ファイル oleauto.h は、変換元と変換先にできるデータ型がほかにも 4 つあります。signed char(I1)unsigned short(UI2)unsigned long(UI4)、DECIMAL(Dec)の 4 つです。前記の公式を使えば、13 に 13 を乗じて合計 169 の関数になります。

しかし、これだけではありません。マクロ定義によって、Int(つまり、I4)と UInt(つまり、UI4)などの型を使うこともできます。もっとも、こうして生成される 56 個の新たな名前はすべて既存の 169 個の関数のいずれにかにマップされます。

つまり、これらの関数を呼び出す方法はたくさんあるということです。あるいは単にバリアントを作成して、VariantChangeType または VariantChangeTypeEx を使うこともできます(これらの API 関数は、バリアントに格納されている型を判別して、自動的に正しい関数を呼び出します。switch ステートメントの数を想像してみてください!)。

また、文字列を数値として解析する方法を細かくコントロールする便利な関数が 2 つあります。まず VarParseNumFromStr を呼び出して最初の解析を行い、続いて VarNumFromParseNum を呼び出して残りの処理をします。この 2 つの呼び出し間で、中間結果に変更を加えることができます。

隠された黄金!未公開の COM API バリアント演算子関数

Dr. GUI は、ヘッダーを掘り返して発見できるものにいつも驚きを禁じ得ません(アル・カポネの金庫を開けるよりもはるかに大きな収穫があります)。このたび、バリアントで演算(加算など)を行うための一組の関数があることを発見しました。説明の文書がないので、それらの働きを正確に知るのは困難です(2 つのオペランドの型が異なる場合はなおさらです)。ちなみに、COM チームは Platform SDK の将来のリリースに合わせてこれらの関数の文書を作成することを計画しています。

しかし、小さなテスト プログラムを書くのは簡単です。この記事にもサンプルを 1 つ用意しました。書き換えて使えば何でもテストできます。これらの関数はスクリプティング言語を(あるいは、Visual Basic さえ)実装するのに特に便利です。

バリアントを使った演算

次の 2 項演算子関数があります:VarAd、VarAndVarCatVarDivVarEqvVarIdivVarImpVarModVarMulVarOrVarPowVarSub、VarXor。これらの大半の働きは明らかですが、明らかでないものもあります。VarCat は、必要に応じてオペランドを変換して、文字列の連結を行います。VarEqv VarImp は、Visual Basic のビットごとの演算または論理演算を行う Eqv Imp 理論演算子に相当します。IDiv は、オペランドを切り捨てずに丸めて、整数の除算を行います。

また、次の単項演算子関数もあります:VarAbsVarFixVarIntVarNegVarNotVarNeg は数値否定(符号反転)です。VarFix VarInt は、Visual Basic の Fix Int 関数に相当します。VarRound は、指定された桁で数値を丸めます。VarCmp は、2 つのバリアントを比較して、VTCMP_LTVTCMP_EQVTCMP_GT または VTCMP_NULL を戻すようです。

バリアントの書式化

最後に、「withVarFormat...」で始まる一組のとても役に立つ関数があります。これらのいくつかは「Format....」で始まる Visual Basic の同等の関数に似ています。COM 書式化関数は、VarFormatCurrencyVarFormatDateTimeVarFormatNumberVarFormatPercent です。Visual Basic の対応する関数は、それぞれ FormatCurrencyFormatDateTime といった具合です(このほかにも複雑な下位レベル関数がありますが、Dr. GUI としては、それにはドキュメントができあがってから取り組むことにします)。

これらの関数は、Visual Basic の対応する関数と同じパラメータと DWORD を 1 つ受け取ります。DWORD の値は通常、ゼロです(現在、このほかに指定できる唯一の値は VAR_CALENDAR_HIJRI です。これは、日付の書式として西暦ではなく、ヒジュラ暦を使うことを指定します。いいえ、Dr. GUI は、「ヒジュラ」の発音が正しいかどうかは知りません。でも、誰かが教えてくれると確信しています。ぜひ知りたいものです)。

そして、書式化された文字列の最後に、最後のパラメータは関数の戻り値であるという COM の規約に従って、参照パラメータがあります(通常使われる戻り値は HRESULT 用に使われるからです)。

残りのパラメータは同じでなので、対応する Visual Basic 関数を調べて、それらの働きを確認してください(この領域では、Visual Basic と COM の間にはかなり重複する部分があります)。

バリアント処理用の C++ ヘルパー クラス

バリアントを初期化するために、バリアントごとに 3 つのステートメントを書き、それらのメモリ割り当てを管理するよりも、もっと簡単な方法があるはずだと考えているかもしれませんね。その考えは間違っていません。バリアントをもっと簡単に扱うために使えるヘルパー クラスがいくつかあります。

バリアントを C++ クラスにカプセル化するのは、比較的わかりやすく、簡単です。そんなわけで、Microsoft で書かれた C++ クラス ライブラリのほとんどすべてがそのようになっています。

MFC を使用しているのなら、COleVariant クラスを使うとよいでしょう。このクラスには、バリアントに格納できるそれぞれの型(CString を含む)に対応するコンストラクタのほかに、COleVariant オブジェクトの型を変換したり、コピー、比較するメンバーがあります。CArchive オブジェクトを使って、COleVariant オブジェクトを読んだり、書いたりすることさえできます。このクラスのデストクラタは、バリアントを破棄する前に、きちんとクリアされてかどうかを確認します。

ATL を使用する場合は、CComVariant クラスを使うとよいでしょう。このクラスは、MFC の COleVariant クラスに似ています。ただし、CString はサポートされていません。またオブジェクトの読み書きは、MFC の CArchive ではなく、COM の IStream ポインタを使って行います。未公開のバリアント関数のテスト用に作成したサンプル プログラム では、CComVariant クラスを使います。具体的には、不要になったら自動的に破棄される一時使用のバリアントを作るためにコンストラクタを使っています。

MFC または ATL を使用していなくても、望みを捨てないでください。Visual C++ 5.0 以降のバージョンは、_variant_t クラスをサポートしています。このクラスは、バリアントの読み書きをサポートしていない点がほかの 2 つと異なります。また次の点がほかの 2 つと異なります。つまり、エラーが発生すると、C++ 例外を発行する点と、値にアクセスするため一組のオーバーロードされた変換演算子をサポートする点です。

Visual C++ 5.0 以降のバージョンでコンパイルされた MFC プログラムで ATL を使用している場合は、3 つのクラスのいずれか(または全部)を使うことができます。アプリケーションにもっとも適したものを選びましょう。

やってみよう!

これまでお話ししてきたことを実際に試してみるまでは、説明をきちんと理解しているかどうかわからないことは Dr. GUI も十分わかっています。ならば、実際にやってみましょう!

  • サンプルに基づいて、バリアントを作成し、操作し、破棄するプログラムを自分で書いてみましょう。デバッガでシングル ステップをお楽しみください!

  • これらのデータ オブジェクトをオートメーション オブジェクトとの間でやり取りしてみましょう。

今までの復習、これからの予定

今回は、バリアントについて説明しました。ですが、COM のほかのデータ型については説明しませんでした。それが次回のコラムのテーマです。

その後は、イベントの謎と、コネクション ポイントなどのイベント用の COM モデルの探求を始めます。

付録 A

COM データのデモ プログラム

// Dr. GUI's demo program for some COM data
// Note that this is just a console app.
// Have fun modifying and single-stepping!

// Include of pre-compiled header file removed for clarity
//#include "stdafx.h"

// If you use precompiled headers, these should be in "stdafx.h"
#include <stdio.h>
#include <objbase.h>
#include <atlbase.h>
#include <comdef.h>

int main(int argc, char* argv[])
{
// must call CoInitialize or CoInitializeEx
HRESULT bOK = CoInitialize(NULL);
printf("CoInitialize returned %x\n\n", bOK);

// variant demo using ATL's CComVariant class (#include <atlbase.h>)
CComVariant result;

// Note that we're creating temporary variables of variant type!
VarAdd(&CComVariant(5), &CComVariant(4), &result);
result.ChangeType(VT_BSTR);
printf("a + b = %ls\n", V_BSTR(&result));

VarCat(&CComVariant("Hello, "), &CComVariant("world!"), &result);
result.ChangeType(VT_BSTR);
printf("a Cat b = %ls\n\n", V_BSTR(&result));


// BSTR demo
BSTR bstrMsg = SysAllocString(OLESTR("Hello"));
printf("SysAllocString returned %x\n", bstrMsg);

// call some method that expects a BSTR or LPOLESTR passing bstrMsg
//ptrInterface->Method(bstrMsg);

printf("Length is %u\n", SysStringLen(bstrMsg));
// print UINT stored four bytes before pointer
printf("Length word is is %u\n", *(UINT *)(((char *)bstrMsg) - 4) );

SysFreeString(bstrMsg);

return 0;
}

付録 B

バリアントの定義

typedef [wire_marshal(wireVARIANT)] struct tagVARIANT VARIANT;

struct tagVARIANT {
union {
struct __tagVARIANT {
VARTYPE vt;
WORD    wReserved1;
WORD    wReserved2;
WORD    wReserved3;
union {
LONG          lVal;         /* VT_I4                */
BYTE          bVal;         /* VT_UI1               */
SHORT         iVal;         /* VT_I2                */
FLOAT         fltVal;       /* VT_R4                */
DOUBLE        dblVal;       /* VT_R8                */
VARIANT_BOOL  boolVal;      /* VT_BOOL              */
_VARIANT_BOOL bool;         /* (obsolete)           */
SCODE         scode;        /* VT_ERROR             */
CY            cyVal;        /* VT_CY                */
DATE          date;         /* VT_DATE              */
BSTR          bstrVal;      /* VT_BSTR              */
IUnknown *    punkVal;      /* VT_UNKNOWN           */
IDispatch *   pdispVal;     /* VT_DISPATCH          */
SAFEARRAY *   parray;       /* VT_ARRAY             */
BYTE *        pbVal;        /* VT_BYREF|VT_UI1      */
SHORT *       piVal;        /* VT_BYREF|VT_I2       */
LONG *        plVal;        /* VT_BYREF|VT_I4       */
FLOAT *       pfltVal;      /* VT_BYREF|VT_R4       */
DOUBLE *      pdblVal;      /* VT_BYREF|VT_R8       */
VARIANT_BOOL *pboolVal;     /* VT_BYREF|VT_BOOL     */
_VARIANT_BOOL *pbool;       /* (obsolete)           */
SCODE *       pscode;       /* VT_BYREF|VT_ERROR    */
CY *          pcyVal;       /* VT_BYREF|VT_CY       */
DATE *        pdate;        /* VT_BYREF|VT_DATE     */
BSTR *        pbstrVal;     /* VT_BYREF|VT_BSTR     */
IUnknown **   ppunkVal;     /* VT_BYREF|VT_UNKNOWN  */
IDispatch **  ppdispVal;    /* VT_BYREF|VT_DISPATCH */
SAFEARRAY **  pparray;      /* VT_BYREF|VT_ARRAY    */
VARIANT *     pvarVal;      /* VT_BYREF|VT_VARIANT  */
PVOID         byref;        /* Generic ByRef        */
CHAR          cVal;         /* VT_I1                */
USHORT        uiVal;        /* VT_UI2               */
ULONG         ulVal;        /* VT_UI4               */
INT           intVal;       /* VT_INT               */
UINT          uintVal;      /* VT_UINT              */
DECIMAL *     pdecVal;      /* VT_BYREF|VT_DECIMAL  */
CHAR *        pcVal;        /* VT_BYREF|VT_I1       */
USHORT *      puiVal;       /* VT_BYREF|VT_UI2      */
ULONG *       pulVal;       /* VT_BYREF|VT_UI4      */
INT *         pintVal;      /* VT_BYREF|VT_INT      */
UINT *        puintVal;     /* VT_BYREF|VT_UINT     */
struct __tagBRECORD {
PVOID         pvRecord;
IRecordInfo * pRecInfo;
} __VARIANT_NAME_4;         /* VT_RECORD            */
} __VARIANT_NAME_3;
} __VARIANT_NAME_2;

DECIMAL decVal;
} __VARIANT_NAME_1;
};

typedef VARIANT * LPVARIANT;
typedef VARIANT VARIANTARG;
typedef VARIANT * LPVARIANTARG;
表示: