いつもお世話になっております。
環境:Windows XP + VS2010
USBで接続されたデジタルカメラから画像を取得するプログラムを作成しているのです
が、謎な現象に遭遇してしまいましたので、皆様のお力をお貸し下さい。
謎な現象は2点あります。
現象1
まず、デジカメを検索する為に、マイコンピュータ配下をIShellFolder::EnumObjectsで
列挙したのですが、VC++で作成した場合、デジカメが列挙されません。
全く同じ事をC#で書き直してみたところ、なぜかデジカメが列挙されてきます。
やった処理としては下記のみです。
SHGetFolderLocation(NULL, CSIDL_DRIVES, NULL, 0, &pIDL);
SHGetDesktopFolder(&pshDesktop);
pshDesktop->BindToObject(pIDL, NULL, IID_IShellFolder, (void**)&shMyComputer);
shMyComputer->EnumObjects(NULL, SHCONTF_FOLDERS, &pIDList);
pIDList->Next() ← 列挙されてこない。
なぜVC++で列挙されないのか、ご存じの方は、おりませんでしょうか。
現象2
C#で調査を続行しましたので、C#で起きた現象となりますが、
プログラムで一度デジカメ内の画像を列挙した後、デジカメを外し、再接続するとプログ
ラムから画像を列挙できなくなってしまいます。
プログラムを再起動すれば、再度画像の列挙ができます。
画像を列挙する前に、毎回、現象1で書いた処理を行い、インスタンスを作成している
為、古いインスタンスを参照している事もありません。
キャッシュ等による問題かと思い、SHChangeNotify等、色々試してみましたが改善できま
せんでした。
こちらの現象についても、何かご存じの方は、おりませんでしょうか。
よろしくお願いします。
現象1
とりあえず、きちんとエラー処理を記述してデバッグ実行をしてみれば、どこで問題が
発生しているか分かると思います。
ありがちなのは、COMインターフェイスを使うおまじないの「CoInitialize()」をしてな
いというのがわりと良くあるパターンです。
現象2
.NET系の言語は{}で括られたスコープの範囲外に行っても変数やオブジェクトなどが
生きています。
関数化するなり、クラス化するなり、明示的にデストラクタを呼ぶなりして、きちんと
破棄されているか明確にすべきかもしれません。
あと、参考コードにはメモリを開放する記述が全くありませんが、それらが悪さしてい
る可能性もあるかもしれません。
1,2とも情報不足ぎみなので、もう少し情報を開示するともっと良いアドバイスがあるか
もしれませんね。
AR様、ご回答ありがとうございます。
現象1、2共に COM の初期化関連による問題のようでした。
「CoInitialize()」を呼ぶことでVC++でも正しく列挙されるようになりました。
ありがとうございます。
ですが、C#と同じで、現象2のデジカメを再接続するとデジカメの認識はするが、ファイ
ルの列挙が出来ません。
試しに、列挙毎に CoInitialize() と CoUninitialize() を呼び出して、毎回初期化する
と、
デジカメを再接続しても列挙されるようになりました。
これで問題解決かと思いましたが、別の問題が発生して悩んでおります。
デジカメを再認識させる為のCOMの初期化ですが、COMを完全に初期化しないとダメなよう
でして、
CoInitialize()が S_OK を返さない場合(既に初期化済み)、デジカメを再認識できず、
CoInitialize()が S_OK を返すまで CoUninitialize() を繰り返し呼び出す必要がありま
す。
デジカメを列挙する度に COM を初期化してしまうので、別の場所で COM を使用している
と、そちらのメモリが壊れてしまいます。
CoInitialize()、CoUninitialize() 以外でCOMを初期化する方法は無いものでしょうか?
よろしくお願いします。
メモリの解放が原因なのかどうかはわかりませんが、
COMインターフェース使用後に関連する全てのインターフェースで
Releaseメソッド(C#ではMarshal.ReleaseComObject)を呼んでいるでしょうか?
subaru様、ご回答ありがとうございます。
メモリの開放処理を全て見直してみましたが、抜けはありませんでした。
IUnknown::Release()、IMalloc::Free() 、共に呼び出しております。
他に呼ぶべき開放処理があるのでしょうか?
>試しに、列挙毎に CoInitialize() と CoUninitialize() を呼び出して、毎回初期化す
ると、
>デジカメを再接続しても列挙されるようになりました。
CoInitializeEx、OleInitializeなどがありますが、中身は大差ありません。
それ以前に使い方が間違っているものと思われますので、別のメソッドを探すのでは
なく、適切な使い方を学ぶべきだと思います。
一般的に、CoInitialize()とCoUninitialize()は、インスタンスの生成時と破棄時に
行うものです。
マルチスレッドアプリケーションで無ければ、通常はInitInstance()とExitInstance
()内で1回しか呼びません。
(スレッドを生成するなら、スレッドの先頭と最後に呼ぶ)
これらは1対1で存在しなければならないものですし、何度も呼ぶ必要があるという時
点で他の場所が原因になっている可能性が著しく高いと思われます。
COMアクセスしている部分を重点的に、アプリケーション全体を見直した方が良いと思
います。
>プログラムで一度デジカメ内の画像を列挙した後、デジカメを外し、再接続するとプロ
グ
>ラムから画像を列挙できなくなってしまいます。
HDDなどの他のパスで問題ないのであれば、古いIEnumIDList(切断前)を参照し続け
ているから、切断後の新しい情報が参照できないのではないかと予想してます。
接続の前後でドライブレターや名称が変わる場合があるものは、IEnumIDListを取得し
なおさないと列挙できなかった記憶があります。
AR 様
デジカメからの読み込みのみを検証する為の専用テストプログラムを作成しておりますの
で、Shell関連以外のCOMを使用しておりません。
また、コンソールプログラムとすることで、できるだけ最小構成になるようにしておりま
す。
その状況で、 CoInitialize() と CoUninitialize()の再呼び出しという、正攻法では無い
方法でないと、デジカメの再接続を正しく処理できない状況となっております。
IEnumIDListの再取得に関しても、全ての開放処理を行った後に、再度、全てのインスタン
スを再作成しております。
1からテストプログラムを作成し直したりして、何度も見直しております。
現状、お手上げという状況となっております。
> デジカメを再接続するとデジカメの認識はするが、ファイルの列挙が出来ません。試し
> に、列挙毎に CoInitialize() と CoUninitialize() を呼び出して...
手元にあったカメラとtestプログラムでこの現象を確認できた。環境は「PWC-30」という
古いカメラ+WindowsXPsp3。他OSはドライバ未対応なため試していない。
具体的には、「カメラ内ファイル列挙→再接続→カメラ内ファイル列挙」とすると
IEnumIDList::Next() が最初のcallで S_FALSE を返すようになる。「再接続→カメラ内
ファイル列挙」だと問題無い。が、続けて「→再接続→カメラ内ファイル列挙」とすると症
状が発生。
一度でもカメラ内部ファイルを列挙した後に再接続すると症状が起きる模様。その後
CoInitialize() で S_OK を返させた後は再列挙可能。これがもろざきさんの症状と同じ
なのかどうかは断言できないが、似た症状であると思われる。
Windowsのエクスプローラでは再接続後も列挙可能。だが、カメラに割り当てられたドラ
イブをクリックして選択すると「(通常の)ファイル一覧表示領域の上部にカメラ取得映
像を垂れ流し+下部に内部ファイル一覧表示」と通常と異なる表示になる。シェル拡張が
働いて特別な動作をしている可能性がある。
x-finder というファイラで今回の操作を試したところ再接続後は症状が起きた。
プログラムbugの可能性を全否定はしないが、今回の症状は特別な対策が必要若しくは環
境固有、という類の要因で起きている可能性があるかもしれない。
gak 様
テストプログラムまで作成してご確認いただき、ありがとうございます。
まさしく仰るとおりの現象です。
当方で調査した限り、WindowsXPで必ず発生しております。
調査環境
デジカメ:FinePixF50Fd、iPod touch、iPhone
WindowsXPsp3(VMware8)
WindowsXPsp2(Dell製)
WindowsXPsp3(Sony Vaio VGN-G2)
Windows7では、本現象は再現しません。
WindowsXP固有の問題だと思いますが、ベストな回避策が無く困っております…
手元に手ごろな検証環境がありましたので、引っ張り出してきて検証してみました。
検証環境は、iPhone4/WindowsXPsp3(VMware8)/WindowsXPsp3実機です。
テストプログラムは、複数のタイプの自作のファイラーです。
結果、アプリケーションの起動時にCoInitialize()、アプリ終了時にCoUninitialize
()を呼ぶタイプのものはNGでした。
スレッドを起動して、そのつどCoInitialize()、スレッド終了時にCoUninitialize()
を呼ぶタイプのものはOKでした。
gakさんの指摘どおり、Windows7でのライブラリのように特殊な取得方法をしなければ
ならないものもありますし、これも特殊事例の一種と考えた方が良いです。
というかこれ、開放関数を呼んでもリソース掴んで離さないのでXP付属のUSBドライバ
のバグっぽいです。
Atata!!です。
>スレッドを起動して、そのつどCoInitialize()、スレッド終了時にCoUninitialize()
を呼ぶタイプのものはOKでした。
環境を構築できないため検証できていませんが、上記の呼び出しの代わりに
CoFreeUnusedLibraries を使用すれば問題を回避できないでしょうか?
AR 様
検証いただき、ありがとうございます。
Atata!! 様
CoFreeUnusedLibraries()を呼ぶことで、正しく再取得できるようになりました!
ありがとうございます。
ただ、未使用ライブラリの開放のようですので、他の場所で同じライブラリを使用している
場合に上手く動作しないような気がしますが、今回作成中のプログラムでは問題ありません
ので、解決とさせて頂きます。
皆様、大変ありがとうございまいました。