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

C#でDLLを使う時の引数の型

環境/言語:[Win XP, C#]
分類:[.NET]

C#でDLLを使おうと思っています。**の引数の型が違うらしく、
うまくデータを取り込めません。

http://www.atmarkit.co.jp/fdotnet/dotnettips/024w32api/w32api.html
というリンクから有益な知識を得まして、使えるようになった
のですが、うまく行かない関数が出まして、皆様のお知恵を拝
借いたしたく、お願いします。

そのDLLにはソースも付いていまして、問題の関数は以下のよう
になっています。
BOOL __stdcall mp3infp_GetValue(const char *szValueName, char **buf);

C#では以下のようにしました。
[DllImport("mp3infp.dll")]
static extern bool mp3infp_GetValue(string Tag, [Parame2]);

第一引数は、別の関数で確認しているので、これで問題ありません。
問題は第二引数で、「バッファのポインタを受け取るポインタ」と
いうのですから、

(1) bool mp3infp_GetValue(string Tag, ref string Buf);
とやったのですが、「stringはrefに変換できない」のでダメ。

string型ではなく、StringBuilder かな、と思ったので
(2) bool mp3infp_GetValue(string Tag, System.Text.StringBuilder Buf);
と宣言し、呼び出しは
buf = new System.Text.StringBuilder(256);
res = mp3infp_GetValue("SIZK",buf);
のようにしました。エラーは出ませんが、予想もできない文字列
が入っています。(数字のはずですが、キャラクタ画素でした)

Cの文字列はCharの配列だったと思い出し、
(3) bool mp3infp_GetValue(string Tag, ref char[] Buf);
とやったのですが、「変換できない」のでダメ。

もう手詰まりで、他にやる方法が思い浮かびません。

VB6では
Declare Function mp3infp_GetValue Lib "mp3infp.dll"
   (ByVal Tag As String, ByRef buf As String) As Boolean
できちんと動いています。

ちなみに、mp3infp.dll は以下のURLで配布されています。
C++のソースもあります。
http://www.win32lab.com/fsw/mp3infp/index.html
> string型ではなく、StringBuilder かな、と思ったので
> (2) bool mp3infp_GetValue(string Tag, System.Text.StringBuilder Buf);
> と宣言し、呼び出しは
> buf = new System.Text.StringBuilder(256);
> res = mp3infp_GetValue("SIZK",buf);
> のようにしました。エラーは出ませんが、予想もできない文字列
> が入っています。(数字のはずですが、キャラクタ画素でした)

こんにちは。

StringBuilderで行うのがいいと思いますが、以下のように
してみてはどうでしょうか。

bool mp3infp_GetValue(string Tag, ref System.Text.StringBuilder Buf);
と宣言して、呼び出しは、

res = mp3infp_GetValue("SIZK", ref buf);

です。又、stringのときも呼び出しの際にrefが無かったのでは
ないでしょうか。
■No10335に返信(antさんの記事)
> StringBuilderで行うのがいいと思いますが、以下のように
> してみてはどうでしょうか。
> bool mp3infp_GetValue(string Tag, ref System.Text.StringBuilder Buf);

関数のシグネチャからすると、この関数は自身で用意したアンマネージメモリ
のアドレスを第二引数に返すのですよね。

アンマネージメモリの内容から StringBuilder クラスのインスタンスを作成
した後、相互運用マーシャラはアンマネージメモリを解放しますが、解放には
CoTaskMemFree 関数が使われることになっています。mp3infp_GetValue 関数
のソースを読んでいないのですが、この関数は CoTaskMemAlloc 関数を使って
メモリを割り当てるのでしょうか? 仮に割り当てるとして、呼び出し元がそれ
を解放する仕様なんでしょうか?

CoTaskMemFree 関数での解放が不適切なアンマネージメモリのアドレスは、一
旦 IntPtr 型の出力パラメータで受け取った後、明示的にマーシャリングする
ことになります。

それから、bool 型の戻り値のアンマネージ型は既定で 16 bit 幅じゃないで
す? 明示的に属性を指定する必要がある気がするのですが、どうでしょうか?
ant 付き人さん、おおたさんありがとうございました。
結果は、refをつけてOKでした。
呼び出し側にもrefを付けるんですね。これがキモでした。

実は、この質問出す前に、ref stringでもやってみたのです。でも、「変換で
きない」というコンパイルエラーが出てアッサリ、諦めました。
今回、string にも、呼び出し側にもrefを付けたら、できました。「そういや、
Cでも呼び出すときは&を付けたな」と思い出し、ヘルプを見たら、C#には
&もあるんですね。ほとんでCですね。まだ試していませんが、感心しました。

おおたさんの以下の指摘ですが、
> CoTaskMemFree 関数での解放が不適切なアンマネージメモリのアドレスは、一
> 旦 IntPtr 型の出力パラメータで受け取った後、明示的にマーシャリングする
> ことになります。
ソースには CoTaskMemAlloc を使っていないようですが、これは、いわゆる
「メモリリーク」ってやつですか。良く分からないので、それらしい現象が
出たら考えることにします。

戻り値をshortにしてもOKでしたが、この際、C#の顔を立てて、boolで行く
ことにします。(蛇足ですが、このように自動的に型変換してくれるのが
「マーシャリング」というヤツなんでしょうか)

どうも、自分のスキルの程度を暴露したようで、すいませんでした。また
疑問が出ましたら、よろしくお願いします。
解決済み!
こんにちは、じゃんぬ です。

■No10346に返信(show_landさんの記事)
> 「マーシャリング」というヤツ

相互運用マーシャリングの概要
http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/cpguide/html/cpconinteropmarshalingoverview.asp
解決済み!
■No10346に返信(show_landさんの記事)
> ソースには CoTaskMemAlloc を使っていないようですが、これは、いわゆる
> 「メモリリーク」ってやつですか。良く分からないので、それらしい現象が
> 出たら考えることにします。

ソースを読みましたが、返ってくるアドレスは static な CString オブジェ
クトを LPCSTR 型に型変換して得たものですよね。CString クラスが管理する
メモリですから、メモリリークは発生しません。反対に、勝手に解放してはい
けないメモリです。

ただ、CoTaskMemFree 関数による解放は失敗するでしょうから、実害はないは
ずです。あとはプログラマの良識や美的センスの問題でしょうね。:-)

> 戻り値をshortにしてもOKでしたが、この際、C#の顔を立てて、boolで行く
> ことにします。

これはこちらの書き方が下手すぎました。BOOL 型は 32 bit 幅だが、bool 型
へ変換する際、既定では相互運用マーシャラは BOOL 型の値を 16 bit 分しか
見てくれないのではないか? という疑問だったんです。

指摘ではなく疑問なのは、bool 型の戻り値に属性を指定している例を見た記
憶がないからなのですが、

http://www.microsoft.com/japan/msdn/library/ja/cpguide/html/cpcondefaultmarshalingforbooleans.asp

には、

| UnmanagedType.VariantBool : 2 バイトの整数値。値 -1 は TRUE を表し、
| 0 は FALSE を表します。これは Boolean パラメータの既定の形式です。

とあります。よくあるドキュメントの不備なのか、これまたよくある意味の取
り違えなのか、レジスタで返される戻り値ではそれほど問題にならないだけな
のか、「戻り値はパラメータじゃないよ」「じゃあ、何なのさ?」ということ
なのか。まぁ、余談でした。
解決済み!
> ただ、CoTaskMemFree 関数による解放は失敗するでしょうから、実害はないは
> ずです。

そんなの実装次第ですから、「実害が無い」かどうかは分からないですよ。

実際、でたらめなポインタを与えて free() すると管理領域がぶっ壊れるような malloc(), free() の実装は山のようにあります。
じゃんぬさん、おおたさん、渋木さんいろいろありがとうございます。

じゃんぬさんには結構お世話になってます。GoogleでC#関係を探すと
ありますので、申し遅れましたが、いつもありがとうございます。
おおたさんは、ソースまで追っていただきまして感謝、感謝。

C#はいじり始めたばかりですが、VBのいやな面がなくなってますので、
気に入ってます。たわむれに Ruby に触れてから、VBのいやな面ばか
り目に付いてプログラムも休んでいましたが、C#で再開しました。
でも、その間の進歩が早くて、当分追いつくのが大変です。
いわく、XML, ADO, DOM などなど。

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