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

DLL側で確保したメモリをC#側で使いたい

環境/言語:[環境(Windows7)、使用言語(C#)、.NET Framework(3.5)]
分類:[.NET]

こんにちは。初めて投稿させていただきます。
業務で使用するためC#を勉強しています。
C#歴は1ヶ月です。

やりたいこと
・DLL側で配列用のメモリを確保し、C#側で使用したい。

下のようなコードを書いてみたのですが、上手くいきません。
DLL側のfor文の中で落ちてしまいます。

状況は
・コンパイルは通る。
・デバッガで追った時に配列の先頭アドレスはnullで渡されており、
 newで確保したときに、アドレスが割り当てられている。
・for文でi = 2となったときに
 「'System.StackOverflowException' のハンドルされていない例外が mscorlib.dll で発生しました。」
 というエラー表示がでる。

ネットなどでいろいろ調べてみたのですが、原因がよくわかりません。
どこか勘違いしているのだと思うのですが。
わかる方教えていただけないでしょうか。
よろしくお願いします。

■DLL側
int TestAlloc_UnChar(
unsigned char** data,
int allocNum
)
{
if(*data != NULL)
{
*data = NULL;
}
if(*data == NULL)
{
*data = new unsigned char [allocNum];
}
for(int i = 0; i < allocNum; i++)
{
*data[i] = i;
}
return 0;
}

■C#側
namespace MSUI
{
public partial class Form1 : Form
{
[DllImport("C:test_function.dll")]
extern static unsafe int TestAlloc_UnChar(
out byte[] data,
int allocNum
);

byte[] testData0 = null;
int testAllocNum = 512 * 512;

private void Button1_Click(object sender, EventArgs e)
{
TestAlloc_UnChar(
out testData0,
testAllocNum,
);
}
}
}
■No27152に返信(ぴよさんの記事)
> やりたいこと
アンマネージ DLL との配列の渡し方について、下記が参考になるかもしれません。
http://msdn.microsoft.com/ja-jp/library/hk9wyw21.aspx


> ・DLL側で配列用のメモリを確保し、C#側で使用したい。
そのアンマネージメモリを解放する作業は、DLL/C# のどちらで行うのでしょうか?

C# 側で解放するのであれば、C# 側で確保した方が問題が少ない気がします。

たとえば、文字列なら StringBuilder に対して書き込ませるようにするとか、
あるいは、C# の fixed 指定で固定サイズバッファをこしらえておくとか、
MarshalAs属性で UnmanagedType.LPArry に対して SizeConst 指定するとか、
Marshal.AllocCoTaskMem や Marshal.AllocHGlobal を使うとか。


> 下のようなコードを書いてみたのですが、上手くいきません。
DLL 側の型を LPSAFEARRAY か BSTR/LPOLESTR あたりに変更するとか。
■No27153に返信(魔界の仮面弁士さんの記事)
早速の回答ありがとうございます。

> アンマネージ DLL との配列の渡し方について、下記が参考になるかもしれません。
> http://msdn.microsoft.com/ja-jp/library/hk9wyw21.aspx
参考になりました。配列のマーシャリングは問題ないように思えます。

> そのアンマネージメモリを解放する作業は、DLL/C# のどちらで行うのでしょうか?
DLL側で解放しようと考えています。今回のコードはテストサンプルで、
簡単な確認のために、個人的に使いなれたnew演算子を用いてメモリの確保の確認をしていますが、
背景としては、今後業務の都合上、高速化のためにSIMD演算をDLL側で
使用する必要があります。その為、アラインメントされたメモリの確保が
必要で、_mm_malloc関数を使用したいのです。
どちらかといえば、DLL側で確保せざるを得ないという状況です。
調べたところではC#側でアラインメントされたメモリの確保ができない様でしたので上記の方法をとっています。他に方法があればいいのですが。

ちなみに、業務は画像処理をやっており、C#側のbyte型配列、
DLL側のunsigned char型配列には、グレースケールの画像データが入ります。

> DLL 側の型を LPSAFEARRAY か BSTR/LPOLESTR あたりに変更するとか。
ありがとうございます。現在、帰宅しておりますので、
明日早速試してみようと思います。
// SIMD とかには詳しくないですが

C# 側でバイト配列をどうするんでしょうか。
C# の配列はマネージオブジェクトであり、マネージヒープ上にしか存在できません。
DllImport する関数の引数に out byte[] を使ったとしても、アンマネージメモリから丸ごとマネージヒープにコピーするのが精々です。またポインタは失われるので解放に使用することもできなくなります。
DLL と C# で同じメモリを使用したいのなら、out byte* や out IntPtr でポインタとしてそのまま受け取る必要があるでしょう。
// いっそもっと C/C++ に処理を委譲してやって、C# はメモリにノータッチとした方が楽かも。

>>DLL 側の型を LPSAFEARRAY か BSTR/LPOLESTR あたりに変更するとか。
これはもっと高いレイヤの配列なんで、今回の話では関係なさそう。
DLL側も自前のようですがなぜアンマネージドで作成されているのでしょう?
■No27155に返信(Hongliangさんの記事)

返信ありがとうございました。
> C# 側でバイト配列をどうするんでしょうか。
DLL側で処理した画像データをGUI(C#)側で表示したり、画像ファイルとして保存したりしたいのです。
> C# の配列はマネージオブジェクトであり、マネージヒープ上にしか存在できません。
今回の件はマネージドメモリとアンマネージドメモリを同等に扱っているのが問題なのですね。
> DllImport する関数の引数に out byte[] を使ったとしても、アンマネージメモリから丸ごとマネージヒープにコピーするのが精々です。
その方法も考えたのですが、今回のコードは高速化を目的としており、また扱う画像データ量が膨大な為、C#とDLL側でメモリを確保するのは避けたいです。
> // いっそもっと C/C++ に処理を委譲してやって、C# はメモリにノータッチとした方が楽かも。
ありがとうございます。勉強になります。この方法が一番目的を達成するために有効そうです。
アドバイスを元に調べてみたのですが、この方法だと関数群DLLを読みだすのでは
限界があり、CLRでラッパクラスを作成してみる必要がありそうです。

> >>DLL 側の型を LPSAFEARRAY か BSTR/LPOLESTR あたりに変更するとか。
> これはもっと高いレイヤの配列なんで、今回の話では関係なさそう。
本日、試してみましたが、状況は変わりありませんでした。

魔界の仮面弁士さん、Hongliangさんアドバイスありがとうございました。
とても為になり、勉強になりました。
とりあえず、ラッパクラスを作成してみようと思います。
CLR未経験なので勉強してからになりますので、解決には少々時間がかかりそうです。

解決した時にまた、コードを投稿しようと思います。
また行き詰った時はアドバイスよろしくお願いします。
ありがとうございました。
■No27156に返信(shuさんの記事)
返信ありがとうございます。
> DLL側も自前のようですがなぜアンマネージドで作成されているのでしょう?
今回、処理時間に制限があります。C#側で処理をしてしまうと目標処理時間が達成できないためです。詳細の例は下に示します。
・SIMD演算が高速化の為に必要です。(C#では使えません)
・DLL側でOpenMPなどの並列処理を使っています。
・高速化の為にVisualStudioのコンパイラを使っていません。(C#は対応してません)
など、理由は多々あります。
■No27158に返信(ぴよさんの記事)
> ■No27156に返信(shuさんの記事)
> 返信ありがとうございます。
>>DLL側も自前のようですがなぜアンマネージドで作成されているのでしょう?
> 今回、処理時間に制限があります。C#側で処理をしてしまうと目標処理時間が達成できないためです。詳細の例は下に示します。
> ・SIMD演算が高速化の為に必要です。(C#では使えません)
> ・DLL側でOpenMPなどの並列処理を使っています。
> ・高速化の為にVisualStudioのコンパイラを使っていません。(C#は対応してません)
> など、理由は多々あります。
失礼しました。見落としていました。
そうすると渡す時にマネージド配列でなく、IntPtrを使うようにした方が良いのではないでしょうか?

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