- PR -

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

投稿者投稿内容
yayadon
常連さん
会議室デビュー日: 2003/07/23
投稿数: 41
投稿日時: 2006-07-18 16:47
DLL側の引数が wchar_t** とかになっていて,
かつ,そこを変更できない場合は,

コード:

[DllImport("xxx.dll")]
public static extern int GetMsg(out IntPtr msg);


と IntPtr 型で宣言して,

コード:

IntPtr msg = IntPtr.Zero;
int rc = GetMsg(out msg);
string s = Marshal.PtrToStringUni(msg);
MessageBox.Show(s.ToString());
// ここに IntPtr を開放するコード


とするんだろうけど,
どうやって確保したのかがわからないと,ギブアップかな。

最初に出てきた MS推奨?の ::CoTaskMemAlloc で確保していてくれれば,

例えば,

コード:
int GetMsgCoTask(LPWSTR *msg)
{
    const wchar_t *tmp = L"ほげほげちげちげち 略 げちげちげちち";
    *msg = (LPWSTR) ::CoTaskMemAlloc((wcslen(tmp) + 1) * sizeof(WCHAR));
    if(*msg == NULL)
    {
        return -1;
    }
    errno_t err = wmemcpy_s(*msg, wcslen(tmp) + 1, tmp, wcslen(tmp) + 1);
    if(err){
        return -1;
    }

    return 0;
}


だったとしたら

コード:

[DllImport("xxx.dll")]
public static extern int GetMsgCoTask(out IntPtr msg);

-----------------------------------------------
IntPtr msg = IntPtr.Zero;
int rc = GetMsgCoTask(out msg);
if(rc == 0)
{
    string s = Marshal.PtrToStringUni(msg);
    MessageBox.Show(s.ToString());
    Marshal.FreeCoTaskMem(msg);
}


でいいようです。
実際にちょっと試した感じではうまく行ってます。
いろは
常連さん
会議室デビュー日: 2005/05/11
投稿数: 31
投稿日時: 2006-07-18 19:06
質問者です。

皆さま いろいろとありがとうございました。また反応出来ずにすみません。。。
いろいろと例を出していただきましたが、メモリ管理を簡単にしようと考えると、BSTR*でやり取りするのが一番すっきりしそうですね。
と言う事で、今回はBSTR*を使った方法で実装したいと思います。

非常に勉強になりました。
ありがとうございました。
yayadon
常連さん
会議室デビュー日: 2003/07/23
投稿数: 41
投稿日時: 2006-07-19 07:54
発言がブレてしまってアレなんですが...
すみません,
いろいろと調べて,実験したところ
DLL側が,CoTaskMemAlloc で確保していれば,
LPWSTR* のようなポインタのポインタでも

 [MarshalAs(UnmanagedType.LPWStr)]out string msg

out で宣言すればOKのようです。
マーシャラは,ちゃんと開放しているようです。

いろいろと工夫してみましたが,
[MarshalAs(UnmanagedType.LPWStr)]ref string msg のように ref だと,
やっぱりマーシャラは開放する気がないようですね...

DLL側
コード:

int GetMsgCoTask(LPWSTR *msg)
{
    // out の時は,*msg == NULL なので関係ないんですが...
    // ref 時のテストのため
    if(*msg != NULL){
        ::CoTaskMemFree(*msg);
        *msg = NULL;
    }

    const wchar_t *tmp = L"ほげほげちげちげちちほげ 略 ちげちげちち";

    *msg = (LPWSTR) ::CoTaskMemAlloc((wcslen(tmp) + 1) * sizeof(WCHAR));
    if(*msg == NULL)
    {
        return (-1);
    }

    errno_t err = wmemcpy_s(*msg, wcslen(tmp) + 1, tmp, wcslen(tmp) + 1);
    if(err){
        return (-1);
    }

   return 0;
}



C#側
コード:

[DllImport("xxx.dll", EntryPoint = "GetMsgCoTask")]
public static extern int GetMsgCoTaskOut(
    [MarshalAs(UnmanagedType.LPWStr)]out string msg);
-------------------------------------------------
string msg = null;
int rc = GetMsgCoTaskOut(out msg);
if(rc == 0)
{
    MessageBox.Show(msg.ToString());
}

yayadon
常連さん
会議室デビュー日: 2003/07/23
投稿数: 41
投稿日時: 2006-07-19 10:49
引用:

いろはさんの書き込み (2006-07-18 19:06) より:
メモリ管理を簡単にしようと考えると、
BSTR*でやり取りするのが一番すっきりしそうですね。
と言う事で、今回はBSTR*を使った方法で実装したいと思います。


ヘルプに載っているやり方([MarshalAs(UnmanagedType.LPWStr)]StringBuilder)は,
受け渡しする文字列長の最大値を前もって決めれる時で,
文字列長を前もって決めれない場合は,それが無難なようですね。

で,DLLの引数の型に BSTR* を使う場合は,
C#側は,[MarshalAs(UnmanagedType.BStr)]ref string にして,
DLL側の BSTR型の文字列の作成は,SysAllocString系のものを使用する。


引用:

いろはさんの書き込み (2006-07-13 17:22) より:
MarshalAsでBStrを指定することで、DLL側ではBSTR*なのでCoTaskMemAllocを使うより、SysStringAllocを使って文字列を設定すべきという結論を導き出しました。


マーシャラは,UnmanagedType.BStr ヒントから,最後に,
プロセスヒープ(デフォルトヒープ)上のBSTRの文字列を開放しようと決めて
SysFreeString を呼ぶだろう想定されるから,
DLL側での文字列確保は,
SysStringAlloc 等で行うという理屈でいいんじゃないかと思います。


なので,
DLL側
コード:

int GetMsgBSTR(BSTR *msg)
{
    ::SysFreeString(*msg);

    LPCWSTR wcs = L"ほげほげ 略 ほげほげ";
    *msg = ::SysAllocString(wcs);
    if(*msg == NULL){
        return (-1);
    }
    return 0;
}



失敗した時のことを考えて,後から開放するようにするなら,
DLL側
コード:

int GetMsgBSTR(BSTR *msg)
{
    LPCWSTR wcs = L"ほげほげ 略 ほげほげ";
    BSTR tmp = ::SysAllocString(wcs);
    if(tmp == NULL){
        return (-1);
    }
    ::SysFreeString(*msg);
    *msg = tmp;
    return 0;
}



C#側
コード:

[DllImport("xxx.dll")]
public static extern int GetMsgBSTR(
    [MarshalAs(UnmanagedType.BStr)]ref string msg);
-------------------------------------------------
string msg = "";
int rc = GetMsgBSTR(ref msg);
if(rc == 0)
{
    MessageBox.Show(msg.ToString());
}




で,
文字列を扱いたいんだけど,
マーシャラのデフォルトの動作を回避したい場合のような時には,
データ型を IntPtr型にして,自力でメモリ管理すると。
渋木宏明(ひどり)
ぬし
会議室デビュー日: 2004/01/14
投稿数: 1155
お住まい・勤務地: 東京
投稿日時: 2006-07-19 12:00
引用:

DLL側が,CoTaskMemAlloc で確保していれば,
LPWSTR* のようなポインタのポインタでも

 [MarshalAs(UnmanagedType.LPWStr)]out string msg

out で宣言すればOKのようです。
マーシャラは,ちゃんと開放しているようです。



なるほど、out の場合は受け取り側にしか領域解放の権利が無いので、そういう動きになるんですね。

ヘルプにも

引用:

相互運用マーシャラは、アンマネージ コードによって割り当てられたメモリを常に解放しようとします。この動作は COM のメモリ管理の規則に準拠していますが、ネイティブ C++ の規則とは異なります。



という記載が見られるので、この動作(=out string で受け取った文字列は、マーシャラによって CoTaskMemFree() で解放される)は期待してもよさそうです。
渋木宏明(ひどり)
ぬし
会議室デビュー日: 2004/01/14
投稿数: 1155
お住まい・勤務地: 東京
投稿日時: 2006-07-19 12:10
引用:

引用:

いろはさんの書き込み (2006-07-13 17:22) より:
MarshalAsでBStrを指定することで、DLL側ではBSTR*なのでCoTaskMemAllocを使うより、SysStringAllocを使って文字列を設定すべきという結論を導き出しました。


マーシャラは,UnmanagedType.BStr ヒントから,最後に,
プロセスヒープ(デフォルトヒープ)上のBSTRの文字列を開放しようと決めて
SysFreeString を呼ぶだろう想定されるから,
DLL側での文字列確保は,
SysStringAlloc 等で行うという理屈でいいんじゃないかと思います。



えっとですね、何度か間接的に書いてるんですが、それ(=BSTR の領域は SysAllocString(), SysFreeString() で確保・解放する)が大原則です。

関数内部でプライベートに使用する場合ならともかく(それでも推奨はしませんが)、API 呼び出しや関数からの戻り値として使用するなら、BSTR は SysAllocString() によって領域確保し、SysFreeString() によって領域解放しなければなりません。

コード:
int GetMsgBSTR(BSTR *msg)
{
    ::SysFreeString(*msg);

(略)



IDL 的には、out 属性の場合、受け取った *msg を解放しては駄目です。
それが ref (=IDL 的には in, out)と out の違いです。

また、msg が NULL である場合はエラーとしなければなりません。

引用:

文字列を扱いたいんだけど,
マーシャラのデフォルトの動作を回避したい場合のような時には,
データ型を IntPtr型にして,自力でメモリ管理すると。



ですね。
ヘルプにもそのように記載されています。
Blue
大ベテラン
会議室デビュー日: 2005/09/12
投稿数: 230
お住まい・勤務地: 知っている人は知っている
投稿日時: 2006-07-19 12:11
質問です。
戻り値として文字列を返す場合、マーシャラの開放はどうなるんでしょうか?
コード:

//BSTR・・・これは問題なさそう。
BSTR WINAPI GetMsg1()
{
    const wchar_t* s = L"Hello World";
    return ::SysAllocString( s );
}

//LPWSTR・・・outを指定したときと同じ?
LPWSTR WINAPI GetMsg2()
{
    const wchar_t* s = L"Hello World";
    const size_t len = wcslen( s );
    wchar_t* p;
    
    p = static_cast< BSTR >( ::CoTaskMemAlloc( ( len + 1 ) * sizeof( wchar_t ) ) );
    if ( p )
    {
        if ( !wmemcpy_s( p, len + 1, s, len + 1 ) )
        {
            return p;
        }
        ::CoTaskMemFree( p );
    }
    return NULL;
}

いろは
常連さん
会議室デビュー日: 2005/05/11
投稿数: 31
投稿日時: 2006-07-19 13:09
答えてもらうばかりでは申し訳ないので。。。

引用:

Blueさんの書き込み (2006-07-19 12:11) より:
質問です。
戻り値として文字列を返す場合、マーシャラの開放はどうなるんでしょうか?



MSのサンプルをみる限り解放してくれているのではないかと思います。
こんな風に書いてあります。

コード:

extern "C" PINVOKELIB_API char* TestStringAsResult()
{
   char* result = (char*)CoTaskMemAlloc( 64 );
   strcpy( result, "This is return value" );
   return result;
}

[ DllImport( "..\\\\LIB\\\\PinvokeLib.dll" )]
public static extern String TestStringAsResult();	

// ************** string as result ********************************
String str = LibWrap.TestStringAsResult();
Console.WriteLine( "\\nString returned: {0}", str );




でも詳しいところは私には分かりません。。

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