- PR -

DLLから文字列を取得する方法

投稿者投稿内容
いろは
常連さん
会議室デビュー日: 2005/05/11
投稿数: 31
投稿日時: 2006-07-13 11:24
いろはです。

C#で作っているEXEからVCで作っているDLLを呼び出して文字列を取得する方法を
教えてください。(VS2003)

DLL内でメモリを確保し、メッセージを作成してC#側で扱いたいと思っています。
MSDNのサンプルをチェックして、メモリ確保はCoTaskMemAllocを使用すると
言うことは確認しましたが、いまいち上手く行っておりません。

以下、DLL側のサンプルソースです。

int GetMessage( char** msg )
{
if( *msg ) CoTaskMemFree(*msg); // 解放が必要?

char message[] = "message!";
size_t len = strlen( message );
*msg = (char*)CoTaskMemAlloc( sizeof(char)*(len+1) );

return 0;
}

C#では以下の様に書いています。

[DllImport("XXXX.dll")]
public extern static int GetMessage( ref System.Text.StringBuilder msg );

System.Text.StringBuilder msg = new System.Text.StringBuilder();
msg.Append( (char)0 );
GetMessage( ref msg );

上記のソースだとCoTaskMemFreeで例外が発生します。
呼び出し側をStringBuilderからStringに変えてみたり、refをとってみたりしました
が、やっぱり上手く行きません。

MSDNのサンプルには戻り値として返す方法も出ていましたが、戻り値は
他の用途で使いたい為、引数として何とか文字列が取れないかと思っています。

以上、よろしくお願いします。
Blue
大ベテラン
会議室デビュー日: 2005/09/12
投稿数: 230
お住まい・勤務地: 知っている人は知っている
投稿日時: 2006-07-13 11:46
String変数を生成したいのであればSysAllocString関数を使ってみるのはどうでしょうか?

VC DLL側サンプル
コード:

void WINAPI SampleA( BSTR* b )
{
const char s[] = "Hello World";
::SysFreeString( *b );
*b = ::SysAllocStringByteLen( s, sizeof( s ) - 1 );
}

void WINAPI SampleW( BSTR* b )
{
const wchar_t s[] = L"Hello World";
::SysFreeString( *b );
*b = ::SysAllocString( s );
}



C#側で、きちんとMarshalAs属性を指定すればうまくいくと思います。
(Stringで受け取る)
※関数は2つ書きましたが(〜A、〜W)、MarshalAsを適切に設定すればどちらも同じ動きになります。
C#のデフォルト(MarshalAsを指定しない場合)がどちらか確かでないため2つ書いてみました。

[ メッセージ編集済み 編集者: Blue 編集日時 2006-07-13 11:51 ]
いろは
常連さん
会議室デビュー日: 2005/05/11
投稿数: 31
投稿日時: 2006-07-13 13:13
Blueさん 回答ありがとうございます。

あげていただいたサンプルを元にC#の呼び出し元を以下の様に書き換えることで、
欲しい処理が実現できました。

[DllImport("XXXX.dll")]
public extern static int GetMessage( [MarshalAs(UnmanagedType.BStr)] ref String msg );

ところで今回はBlueさんに教えて頂いた方法でまったく構わないのですが、
私が最初にチャレンジした方法では上手くいかないんでしょうか?
MarshalAsとかも試してみたんですが、やっぱりダメでしたし。。
やっぱりこの辺の話は難しいです。
Blue
大ベテラン
会議室デビュー日: 2005/09/12
投稿数: 230
お住まい・勤務地: 知っている人は知っている
投稿日時: 2006-07-13 14:09
一応

コード:


void WINAPI SampleA( BSTR* b )
{
const char s[] = "Hello World";
::CoTaskMemFree( *b );
*b = ( BSTR )::CoTaskMemAlloc( sizeof( s ) ); // strlen( s ) + 1
strcpy( ( char* )*b, s ); // VC2005 strcpy_s
}

void WINAPI SampleW( BSTR* b )
{
const wchar_t s[] = L"Hello World";
::CoTaskMemFree( *b );
*b = ( BSTR )::CoTaskMemAlloc( sizeof( s ) ); // ( wcslen( s ) + 1 ) * sizeof( wchar_t )
wcscpy( *b, s ); // VC2005 wcscpy_s
}


で動いているようには見える。
最初のやつでは、文字列を確保した領域に入れていないのでアウトですね。

[ メッセージ編集済み 編集者: Blue 編集日時 2006-07-13 14:33 ]
いろは
常連さん
会議室デビュー日: 2005/05/11
投稿数: 31
投稿日時: 2006-07-13 15:38
再び出していただいたCoTaskMemAllocを使用する方を試してみました。
仕様上CoTaskMemAllocを使えとなっているようですので、出来ればこちらでと思うので。
しかし、やはりCoTaskMemFreeの部分で例外が出てしまいます。

現状のソースをもう一度載せます。何処がBlueさんと違うのでしょうか?
ちなみに今回はDLL内はUnicodeを使用しています。
(毎回2パターン書いてもらって申し訳ありませんでした)

C# 呼び出し側

[DllImport("ConvertImage.dll")]
public extern static int GetMessage( [MarshalAs(UnmanagedType.BStr)] ref String msg );

string msg = "";
GetMessage( ref msg );

VC DLL側

int GetMessage( BSTR* msg )
{
::CoTaskMemFree( *msg ); // ここで早速例外が出てしまいます

-以下省略-
}

せっかく答えていただいているのに、長くなり申し訳ありません。
出来ればちゃんと理解しておきたいと思いますので。

よろしくお願いします。
Blue
大ベテラン
会議室デビュー日: 2005/09/12
投稿数: 230
お住まい・勤務地: 知っている人は知っている
投稿日時: 2006-07-13 15:47
引用:

いろはさんの書き込み (2006-07-13 15:38) より:
[DllImport("ConvertImage.dll")]
public extern static int GetMessage( [MarshalAs(UnmanagedType.BStr)] ref String msg );


渡すのは BSTR型ではなく、BSTR*型です。(NULL文字終端)
ですから
UnmanagedType.BStrではなくUnmanagedType.LPWStrでやってみてください。
(でも、SysAllocStringを使っているほうもダメだと思うんだけど何でだろ?)

[ メッセージ編集済み 編集者: Blue 編集日時 2006-07-13 16:03 ]
Blue
大ベテラン
会議室デビュー日: 2005/09/12
投稿数: 230
お住まい・勤務地: 知っている人は知っている
投稿日時: 2006-07-13 16:23
どうも、SysAllocString関数を間違えて認識していたようです。

UnmanagedType.BStr は
「長さを示すプリフィックスを付けた 2 バイトの Unicode 文字列。」

で、

UnmanagedType.LPWStr は
「終端が null の 2 バイトの Unicode 文字列。」

でした。

MSDN - UnmanagedType 列挙体


で、SysAllocStringの場合、UnmanagedType.BStrでもUnmanagedType.LPWStrでもうまくいくのは
どういう仕組みなんだろ。。。

↓↓↓
Dr. GUI と COM オートメーション、第 3 部:続 COM のすばらしきデータ型
に解説がありました。

ということは、SysAllocStringを使う場合「LPWStr」で受けるのは大丈夫なのかな?
これがまずいと、SysAllocStringByteLen+LPStrもまずくなるような。。。

# いろいろ編集してすいません。

[ メッセージ編集済み 編集者: Blue 編集日時 2006-07-13 16:42 ]
いろは
常連さん
会議室デビュー日: 2005/05/11
投稿数: 31
投稿日時: 2006-07-13 16:40
なんだかBlueさんまで悩ませてしまっているようです。。。
申し訳ありません。
現在の状況を報告します。

とりあえずUnmanagedTypeはLPWStrと修正しました。
がやはりCoTaskMemFreeで例外が発生します。

今まで進行中のプロジェクトの一部でテストをしていたので、今回の問題と切り分けるために、新規のプロジェクトを起こしてテストをしてみました。
すると、、、
なんか動きが変?

C#側をスタートプロジェクトとしてデバッガ起動するとCoTaskMemFreeで例外が発生します。
しかしVC側をスタートプロジェクトとして起動すると、何の問題も無くCoTaskMemFreeが成功します。。汗

とりえず、これから”Dr.GUIとCOM…”を読んでみます。

スキルアップ/キャリアアップ(JOB@IT)