Chapter 8.
IStreamとOleLoadPicture



前回のサンプルに修正を加えて行くことにしましょう。 前回に引き続きOLEにおけるCOMインターフェースの使われ方を見ていきます。

今回実装対象となるインターフェースはIStreamです。このインターフェースを引数に取る関数は膨大な量になりますが、その中でも比較的簡単に使用でき、かつ、使用頻度の高いと思われるOleLoadPicture関数を取り上げます。

OleLoadPicture関数は、<olectl.h>の中で次のように定義されています。
WINOLECTLAPI OleLoadPicture(LPSTREAM lpstream, LONG lSize, BOOL fRunmode,
    REFIID riid, LPVOID FAR* lplpvObj);
また、LPSTREAMは下記のように、<objidl.h>で定義されています。
typedef /* [unique] */ IStream __RPC_FAR *LPSTREAM;
このことより、OleLoadPicture関数は第1引数として、IStreamインターフェースのポインタを取ることが分かります。
また、この関数は第4引数にREFIID、第5引数にLPVOID *を取っています。これは、QueryInterfaceの引数に良く似ています。そして、実際にQueryInterfaceのように振舞います。COMインターフェースを出力引数に取る関数には、この形式が多く見られます。今後もこの形式に遭遇する機会が多いので、今のうちに慣れておいてください。

この関数は、イメージを含んだストリームに対して、IPictureインターフェースを返却します。このインターフェースは、OLEピクチャオブジェクトをカプセル化するインターフェースです。今回のサンプルでは、このインターフェースを使用して、さまざまなファイル形式のイメージを描画してみたいと思います。ファイルを選択する部分は前回のOLEドラッグ&ドロップの機能を修正して使用することにします。

上記を踏まえたサンプルが次のソースです。今回のサンプルは前回のサンプルを修正する形になっているので、前回のサンプルを見てない方には、かなりのボリュームになると思われます。例によって別ページに掲載します。
simpledroptarget.h simpledroptarget.cpp chap8.cpp
chap8.rc
resource.h
simplestream.h simplestream.cpp



simpledroptarget.h
このファイルは、IDropTargetを実装するCSimpleDropTargetクラスを定義しています。
基本部分の解説は前回の章を参照してください。

今回、このクラスは、ダイアログ(メインウィンドウ)にファイルのドロップを通知するという役目を担っています。そのため、次の2点の修正しています。
    1. ドラッグ&ドロップを処理すること
    2. ドロップをメインウィンドウに通知すること
上記の2項目の内、1番はIDropTarget::Drop関数を実装するだけなので、ヘッダファイルには影響しません。
2番の要求を満たすためには、通知先ウィンドウハンドルをクラスのメンバとして保管しておく必要があります。そのため、メンバ変数にウィンドウハンドルを追加しました(37行目)
また、通知先ウィンドウハンドルを受け取るために、ウィンドウハンドルをCreateInstance関数とコンストラクタの引数に追加しました(それぞれ、7行目31行目)



simpledroptarget.cpp
このファイルは、CSimpleDropTargetクラスの実装部分です。
基本部分の解説は前回の章を参照してください。

CreateInstance関数に通知先ウィンドウハンドルが渡されるようになり(7行目)、コンストラクタにそれを渡すように修正されています(13行目)
コンストラクタは、渡されたウィンドウハンドルをメンバ変数に代入して保存しています(133行目)

そして、最も大きな修正は、IDropTarget::Dropを実装するようになったことです。(88-121行目)
ドロップされたデータを取得するには、IDataObject::GetData関数を呼び出すことで可能です。この関数は、引数として2つの構造体を取ります。最初の引数に取得するデータに関する情報を設定します(104-108行目)。2つめの引数には、ドロップされたデータが返されます。今回取得するデータはCF_HDROP型で(104行目)、これはWindowsエクスプローラのファイルドラッグ&ドロップ処理を示すデータ型です。その他の設定項目に関しては、今はこのように設定する程度の認識で問題ないです。
この設定によって取得できるデータは、WM_DROPFILESメッセージでお馴染みのHDROPハンドルです。今回のサンプルでは、これをメインウィンドウに通知しています(112行目)。通常、WM_DROPFILESメッセージのHDROPハンドルは、DragFinish関数で開放します。今回の場合でもその方法で開放することが可能ですが、今回はOLEのサンプルなので、OLEの仕組みに従って開放することにします。それは単純にReleaseStgMedium関数を呼び出すだけです(117行目)。この方法を取る場合、メインウィンドウ側でDragFinish関数を呼び出してはなりません。



simplestream.h
このファイルは、IStreamを実装するCSimpleStreamクラスを定義しています。

毎回お馴染みのインターフェースを実装するクラス宣言です。
このクラスでは、IStreamのデータ元としてファイルを使用するので、ファイルハンドルがメンバ変数にあります(38行目)
また、インスタンス作成時にファイルをオープンするので、CreateInstance関数の第1引数にファイル名を取ることも特徴です(7行目)



simplestream.cpp
このファイルは、CSimpleStreamクラスの実装部分です。

CreateInstance関数は、ストリームの元データとなるファイルをオープンし、そのハンドルをコンストラクタに渡すようになっています。このサンプルの場合、ファイルにはデータが存在することが前提となるので、読み取りモードのみでオープンしています。

QueryInterface関数では、3種類のIIDに対してIStreamインターフェースを返却しています(58-59行目)
IStreamインターフェースはISequentialStreamインタフェースから派生されたインターフェースであり、ISequentialStreamインタフェースはIStreamインターフェースに含まれていると考えることが出来ます。そのためIStreamインターフェースを実装した場合、ISequentialStreamインターフェースも実装したことになるのです。インターフェースが含まれているならば、それを返却するようにしても大した手間ではありません。そういう理由で、QueryInterface関数はISequentialStreamインターフェースの要求に応えるようにしてあるのです。ただし、現在のOleLoadPicture関数は、QueryInterface関数を呼び出さないので、この実装は必要ではありません。

ISequentialStream::Readは、ストリームからバイトデータを読み取る関数です。今回の実装では、単純にクラスに関連付けられたファイルハンドルに対して、読み取り操作を行っているだけです。この関数の動作は、COMの仕様によって厳格に定められており、実装する場合はそれに従わねばなりません。その仕様では、ストリーム上(この場合はファイル)から読み込めるデータが無くなった場合、S_FALSEを返却することが定義されています。このサンプルでも、その通りに実装しています。また、第3引数pcbReadにはNULL値が許可されているため、pcbReadに有効なポインタが設定されている場合のみ、実際に読み込まれたバイト数を返却するように実装しています。

ISequentialStream::Writeは、ストリームにバイトデータを書き込む関数です。今回の実装では、E_NOTIMPLを返却しているだけです。これは、この関数が実際には実装されていないことを示します。これもCOMの仕様によって定められた動作です。ただし、これはこの関数がOleLoadPicture関数で使用されないという前提において、このように実装しています。将来、OleLoadPicture関数の仕様が変更された場合、この限りではありません。

IStream::Seekは、ストリームの操作位置を設定or取得する関数です。この関数は、2^64-1までのバイト位置操作が可能ですが、今回2GB以上のファイルを扱う予定は無いので、その部分に関しては省略しています。処理としては、クラスに関連付けられたファイルハンドルの位置を更新しています。また、この関数も第3引数plibNewPositionにNULL値が許可されていますので、有効なポインタが渡されていない場合は、移動後の位置を設定しないように実装しています。

その他のIStreamインターフェースの関数は、OleLoadPicture関数からは使用されないので全て未実装です。



chap8.cpp
chap8.rc
resource.h

このファイルは、アプリケーションのエントリーポイントを実装しています。
基本部分の解説は前回の章を参照してください。
また、同様にchap8.rcとresource.hに関して特に解説を行いません。

今回のサンプルでは、OleLoadPictureとRegisterDragDropという2つのOLEシステムの関数を使用しています(厳密にはRevokeDragDropも含まれていますが)。そのため、OleInitializeを呼び出す必要があります。これは前回のサンプルを流用しているので、特に修正していません。

DialogFunc関数
ダイアログボックスプロシージャでは、IPictureインターフェースのポインタを格納するために、staticな変数を定義しています。大きな修正としては、WM_DROPFILESとWM_PAINTの両メッセージハンドラが追加されたことです。それら2つは下で説明します。それ以外の修正としては、WM_CLOSEメッセージハンドラ内で、IPictureインターフェースへの参照を解除する処理が追加されていることです(96-100行目)

WM_DROPFILESメッセージハンドラ
このハンドラは、DragAcceptFiles関数を呼び出すことによって起動されるものではなく、CSimpleDropTargetクラスのIDropTarget::Drop関数からの通知によって起動されます。割り当てられたデータの開放は、CSimpleDropTargetクラス側で行われているため、こちらではDragFinish関数を呼び出してはいけません。
ドロップされたHDROPハンドルは、OnDropFiles関数に渡されています(49行目)。OnDropFiles関数では、ドロップされたファイル名を取得し(116行目)、そのファイル名を元にストリームオブジェクトを作成し(117行目)OleLoadPicture関数に渡しています(122行目)OleLoadPicture関数からは、IPictureインターフェースへのポインタが取得できます。OleLoadPicture関数の仕様では、内部でエラーが発生した場合、第5引数のインターフェースポインタには、NULL値が設定されることが明記されています。そのため、OleLoadPicture関数から発生するエラーに関しては、特別な処理を行ってはいません。
最後に、全ての処理が完了した時点で、IStreamインターフェースへの参照を開放しています(125行目)

WM_PAINTメッセージハンドラ
ここでは、クライアント領域全体に、ピクチャーオブジェクトの内容を描画しています。
まず、クライアント領域全体のサイズを取得しています(67行目)
次に、ピクチャーオブジェクトからイメージのサイズを取得しています(72-73行目)。IPictureインターフェースは、格納されているイメージのサイズをHIMETRIC単位で返却します。HIMETRIC単位とは、1論理単位(= 1 High Metric Point)が0.01mmに変換される単位系です。今回は、これ以上詳細には述べませんが、後の章では詳しく解説します
最後に、IPicture::Render関数を呼び出してイメージを描画します(78-83行目)。この関数のイメージの出力に関する引数は、論理単位を指定します(マッピングモードなしの場合は、1 point = 1 pixel)。ソースイメージに対する引数は、HIMETRIC単位を使用します。これにはIPictureインターフェースから取得した、イメージのサイズをそのまま使用できます。そのため、HIMETRICとピクセルの単位変換は、今回不要であり、特にその説明はしないことにします。



以上で今回のソースの解説を終わります。
IPicture自体の説明は微小ですが、その他を説明するのに行数を取られた感じがします。ここまで大きくなると、さすがに基礎として学ぶべき範囲を明らかに超えている気がしてきます。今回の内容は読み飛ばしても、全体の学習には影響しないでしょう。
また、IDropTargetの実装を通じて、DragAcceptFiles、DragFinish関数が内部で何をしているかが、少し分かったのではないかと思います。

なお、今回行ったIStreamの実装ですが、これと全く同じことをSHCreateStreamOnFileという関数が実装しています。これが使用できる環境ならば、その動作を確認してみるのも面白いかと思います。

最後に上記7ファイルを固めたソースです。Win32アプリケーションとして、ビルド&実行してください。
C++用ソース:chap8.lzh(7KB未満)


前へ 次へ
COM研究室へ
総合トップへ