DOBON.NET DOBON.NETプログラミング掲示板過去ログ

C#のWin32DLL呼び出しが、Vistaでのみ例外

環境/言語:[Vista Pro、C#2005EE]
分類:[.NET]


Win32 DLLであるmp3infp.dllを使ってmp3のタグを読み取るC#のプログラムを作っています。
WindowsXPの頃は、以下のプログラムで正常に動いていました。
ところがVistaに変えたとたん、mp3infp_GetValue関数をコールすると例外が発生するようになりました。
プログラムのコンパイルにはエラーはなく、実行時にVistaでのみ例外が出る状態です。



[DLLのCのヘッダの宣言]

BOOL __stdcall mp3infp_GetValue(const char *szValueName,char **buf);



[C#のプログラム]

[System.Runtime.InteropServices.DllImport("mp3infp.dll")]
extern static private bool mp3infp_GetValue( string szValueName, ref StringBuilder buf );

StringBuilder buf = new StringBuilder(256);
mp3infp_GetValue("INAM_v2", ref buf);



よく調べてみると、refをつけた引数を使う関数で例外がでるようです。StringBuilderではなくbyte[]を使った呼び出しなど試してみましたが、やはりrefを付けると例外がでます。char**なのでrefは必要ですし・・
ちなみに上記の呼び出し方はXP時代に一般的に使われていた方法のようで、私もネットを参考にして知りました。


ちなみにmp3infp.dllを使用した本家のネイティブアプリはVistaでもきちんと動作しました。最新ベータバージョンのDLLも同様で、本家は動きますがC#からの呼び出しで例外がでます。

もしかしてVistaでは.NETからの外部Win32 DLLの呼び出し方で文字列ポインタのポインタを与える時に、何か仕様変更というか注意点があるのでしょうか?それとも、mp3infp.dllの問題?私の環境特有の問題?
まったくわからずにお手上げです。もし同様の現象の方、あるいは解決法をご存じの方がいたらご教示ください。


Windows Vista Professional
Visual C# 2005 ExpressEdition
mp3infp v2.54a(http://www.win32lab.com/fsw/mp3infp/)
mp3infp 2.55beta1(http://www.win32lab.com/bbs2/index.cgi?no=9290&mode=allread)
こっちでメモリ確保しないアドレスを ref 参照型でやり取りするのはそもそも危険です。
ref IntPtr を使うべきです。
その後 Marshal.PtrToStringAnsi なり何なりで String に変換します。
C#にIntPtr型ってあるの?Marshalって何?という状態だったので、ちょっと調べてから、以下のようにすると、期待どおり動作しました。Hongliang 軍団さん、ありがとうございました。今までずっとうまくいかなかったのでちょっと感動。


[System.Runtime.InteropServices.DllImport("mp3infp.dll")]
extern static private bool mp3infp_GetValue( string szValueName, ref IntPtr buf );

IntPtr buf = Marshal.AllocHGlobal(256); //GetValueの戻り値が256バイト以内という仕様なので
mp3infp_GetValue("INAM_v2", ref buf);
string s = Marshal.PtrToStringAnsi(buf);


ただ、まだいくつかあやふやなところがあって、

メモリを解放せないかんかな〜と、最後に
Marshal.FreeHGlobal(buf);
をつけてみたのですが、やると例外になります。いらないのでしょうか?

またメモリ確保の方法が
Marshal.AllocHGlobal
Marshal.AllocCoTaskMem
の二種類あったのですが、今回の例でどちらが適当なのかも全くわかりませんでした。

あわせて教えていただければ幸いです。よろしくおねがいします。
// 「軍団」とかは投稿回数による肩書きなのです……。ていうかなぜ軍団長じゃなくて軍団なんだ?

今回の場合、相手が渡してくるアドレスは相手が独自に確保しているものです。ですのでこちらが確保する必要はありません。というか確保しちゃいけません。
先ほどのレスで ref を挙げましたが、こちらが初期化する必要がないので out を使ったほうがいいですね。

> IntPtr buf = Marshal.AllocHGlobal(256); //GetValueの戻り値が256バイト以内という仕様なので
> mp3infp_GetValue("INAM_v2", ref buf);

ref や out を使う(参照渡しする)場合、値そのものが書き換わる点にご注意ください。
256 バイト確保後、呼び出し先が buf の値を書き換えます。そうすると事前に確保した 256 バイトは宙に浮いてしまっていますよね。もうアドレスもわからないので解放しようもなく、リークしていくわけです。
前述のとおり、呼び出し元はメモリを確保してはいけません。単に IntPtr の変数を宣言してそれを渡すだけです。ref なら IntPtr.Zero とかで初期化する必要がありますが out ならそれも不要。

> メモリを解放せないかんかな〜と、最後に
> Marshal.FreeHGlobal(buf);

この buf の値は DLL 側が管理しているアドレスを指しています。勝手に解放しちゃいけません。
解放する必要がある場合、DLL が解放手段を用意しているはずです。あるいは解放しなくていいメモリなのかもしれません。詳しくは DLL のドキュメントを参照してください。ドキュメントがあるかどうか知りませんが。
  • 題名: Re[4]: うごきました!
  • 著者: とおりすがり
  • 日時: 2007/12/05 22:37:18
  • ID: 21099
  • この記事の返信元:
  • この記事への返信:
    • (なし)
  • ツリーを表示
軍団は肩書でしたか・・・・・失礼しました。なんか質問に答えてくれる集団みたいのがあるのかなと思ってました。(^^;

プログラムは教えていただいたようにoutで無事動作しました。DLLの方が領域を確保していたのですね。納得です。DLLのヘッダを見る限りは、とくに終了・解放する専用命令はないんですが、きっとうまいことやってくれているんでしょう。これで無事解決とさせていただきます。

Hongliangさん本当にありがとうございました。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板