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

C#でのアンマネージDLL呼び出し時のint型ポインタについて

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

いつも参考にさせて頂いております。

C#より呼び出すアンマネージDLLにおいて、DLL側の引数に構造体がある場合、以下の参考文献をもとに実現出来るのですが、構造体のメンバ変数にint型のポインタがある場合、C#側の宣言と呼び出し元はどのように指定したら宜しいのでしょうか?
byte型のポインタとchar型のポインタについては分かったのですが、まだ理解不足な点が多々ありまして・・・
すみませんが、宜しくお願いいたします。
<以下、参考文献>
http://www.atmarkit.co.jp/fdotnet/dotnettips/026w32struct/w32struct.html
すみません、記述し損ねた内容がありました。
int型ポインタの変数は、DLL側でエラーコードが格納される作りになっております。宜しくお願いいたします。
C# で書く構造体としては IntPtr 型ということじゃないかなぁ。
その IntPtr から実際の int 型にアクセスするには、Marshal.ReadInt32 とか、unsafe コンテキスト int* ptr = (int*)hogehoge.PointerField.ToPointer(); とか。

http://msdn.microsoft.com/ja-jp/library/eawzfdz5(v=VS.80).aspx
エラーコードを格納させるためのパラメータなら、out int でいいんじゃないかな。
Azuleanさん、ありがとうございます。
IntPtrの存在は知っていたのですが、実際のint型にアクセスする方法が分かりませんでした。今回はunsafeは使用しない方向ですので、Marshal.ReadInt32で
試してみたいと思います。結果については別途ご報告させて頂きます。
ありがとうございました。
Hongliangさん、ありがとうございます。
ref、outを使って参照渡しにするのかな?とかも考えたのですがイマイチ自信がありませんでした。out,intでのやり方についても勉強させて頂きます。
ありがとうございました。
■No28353に返信(Hongliangさんの記事)
>> 構造体のメンバ変数にint型のポインタがある場合、
> エラーコードを格納させるためのパラメータなら、out int でいいんじゃないかな。

out や ref は、パラメータにしか付与できないのではありませんか?

Out 属性の事だとしても、こちらも Field フラグは付与されていなかったはず…。
http://msdn.microsoft.com/ja-jp/library/system.runtime.interopservices.outattribute%28VS.80%29.aspx
魔界の仮面弁士さん、ありがとうございます。
おっしゃるとおりoutやrefはパラメータにしか付与できないようでしたので、
DLLを呼び出すときに引数で渡す構造体のポインタとDllImportの宣言にrefをつけてみました。
DLL内で構造体のメンバ変数にあるint型のポインタに値を格納しようとするところでエラーとなってしまいます。
(エラーとなっている箇所詳細は後述します。)
Marshal.AllocHGlobalメソッドを使用しないといけないのでしょうか?
その場合、どのタイミングで記述すれば宜しいのでしょうか?
また、AllocHGlobalを使用する場合、FreeHGlobal()を行わないといけないと
思いますが、今回のようにポインタを渡してDLL側で処理するメンバ変数と呼び出し元であるC#側で設定して渡すメンバ変数が混在している構造体の
場合はどのようにすれば宜しいのでしょうか?
宜しくお願いいたします。
===============以下コード内容===========================
呼び出し先DLL
typedef struct tag_TESTSTRUCT{
int* pStatus;
int SetVal1;
}TESTSTRUCT;

void StartStructure(TESTSTRUCT* TestStruct){
ptSTRCT = *TestStruct;
*ptSTRCT.pCAFStatus = 300; =============>ここでエラー
}

呼び出し元C#

[StructLayout(LayoutKind.Sequential)]
public struct STTEST
{
public IntPtr ErrorNo;
public int SetVal1;
}

[DllImport("Test.dll", EntryPoint = "#12", CallingConvention = CallingConvention.Cdecl)]
public static extern void StartStructure(ref STTEST sttest);

STTEST stest = new STTEST();

private void menuStructTest_Click(object sender, EventArgs e)
{

AFDStartStructure(ref stest);

}
お世話になっております。
自己レスです。
以下を参考にして解決致しました。
http://msdn.microsoft.com/ja-jp/library/eadtsekz(v=vs.80).aspx

Marshalクラスを駆使しないといけなかったようですが、その辺りが
全く理解不足でした。
皆さん、ありがとうございました。
解決済み!
■No28359に返信(pooさんの記事)
> 以下を参考にして解決致しました。

解決されてしまったようですが、一応疑問を提示しておきます。

なぜ、その構造体は int 型のポインタじゃないとダメなんでしょうか?
構造体をポインタで渡している以上、関数の中で書き換えた結果を関数の外からも見れるわけですから、構造体の中であえて int 型のポインタを使う理由が見えません。
(例のコードだからかもしれませんが)


> Marshalクラスを駆使しないといけなかったようですが、その辺りが
> 全く理解不足でした。

例となっていたコードでは、駆使するというほどのことでもないと思っていますが、元の例のコードに対してどういった解決をされたのでしょうか。
差し支えなければ開示してください。
(必要最小限のコードになっていない可能性を危惧しての質問ですが、余計なお節介になっているかもしれません。ご容赦ください)
Azuleanさん、ご指摘ありがとうございます。

> なぜ、その構造体は int 型のポインタじゃないとダメなんでしょうか?
> 構造体をポインタで渡している以上、関数の中で書き換えた結果を関数の外からも見れるわけですから、構造体の中であえて int 型のポインタを使う理由が見えません。
> (例のコードだからかもしれませんが)
実は先方が作成されたDLLがありまして、そのDLLを使用する事となっています。
その為Azuleanさんがおっしゃるとおり、int型のポインタを使う理由が
分からなかったのですが、DLLの仕様に基づいて作成していました。

> 例となっていたコードでは、駆使するというほどのことでもないと思っていますが、元の例のコードに対してどういった解決をされたのでしょうか。
> 差し支えなければ開示してください。
例では構造体のポインタの引数は2つとなっていますが、実際には17あります。
構造体の中でポインタとなっている箇所の扱いが分からなかったので、とりあえず
1つでも分かれば、後は応用がきくかと・・・。
何か間違っているかもしれませんが解決したコードを以下に記述します。

===============以下コード内容===========================
呼び出し先DLL
typedef struct tag_TESTSTRUCT
{
int* pStatus;
int SetVal1;
}TESTSTRUCT;

void StartStructure(TESTSTRUCT* TestStruct)
{
ptSTRCT = *TestStruct;
*ptSTRCT.pStatus = 300;
}

呼び出し元C#

private void menuStructTest_Click(object sender, EventArgs e)
{

TESTSTRUCT structAll;
structAll.pStatus = IntPtr.Zero;
structAll.SetVal1 = 7;

IntPtr ptErrorNo = Marshal.AllocCoTaskMem(Marshal.SizeOf(structAll.pStatus));

structAll.pStatus = ptErrorNo;

Console.WriteLine("\nBefore Call:");
Console.WriteLine("ErrorNo = {0}", Marshal.ReadInt32(structAll.pStatus));

StartStructure(ref structAll);

Console.WriteLine("\nAfter Call:");
Console.WriteLine("ErrorNo = {0}", Marshal.ReadInt32(structAll.pStatus));

Marshal.FreeCoTaskMem(ptErrorNo);

}

仕様では構造体をポインタで渡すことになっていますから、Azuleanさんの
おっしゃるとおり、構造体の中でポインタを使用しなくても呼び出し側C#でも呼び出し先アンマネージDLLでも、読み書きは可能ということですよね?

また、別の問題が浮上したのですが、構造体の変数の中にバイトのポインタがあり
DLL側で画像データが設定される仕様になっています。
構造体ではなく、単にバイトのポインタが引数の場合ですと、C#側ではDLLの宣言をbyte[] としてDLL関数を呼び出す前に指定されたバッファでNewする
事により、画像データを取得する事が出来るのですが、構造体だとどういった
指定にすればよいのでしょうか?
解決済みにしましたが、再度質問する事になってしまい申し訳ありません。
宜しくお願いいたします。
■No28382に返信(pooさんの記事)
> 分からなかったのですが、DLLの仕様に基づいて作成していました。

了解です。
理由があってのアクションであれば問題ないと考えます。

> 仕様では構造体をポインタで渡すことになっていますから、Azuleanさんの
> おっしゃるとおり、構造体の中でポインタを使用しなくても呼び出し側C#でも
> 呼び出し先アンマネージDLLでも、読み書きは可能ということですよね?

はい。
お聞きしている限りでは、C/C++ の DLL のインターフェースがそもそも良くないと考えます。

> また、別の問題が浮上したのですが、構造体の変数の中にバイトのポインタがあり
> DLL側で画像データが設定される仕様になっています。
> 構造体ではなく、単にバイトのポインタが引数の場合ですと、
> C#側ではDLLの宣言をbyte[] としてDLL関数を呼び出す前に指定された
> バッファでNewする事により、画像データを取得する事が出来るのですが、
> 構造体だとどういった指定にすればよいのでしょうか?

具体的なコードで示して頂きたいのですが、構造体のメンバーに BYTE* pbyBuffer; みたいなメンバーがいるのであれば、それに相当する IntPtr buffer; を用意して、必要なサイズ分、AllocCoTaskMem で確保したポインタを入れてやれば良いのでは?
Azuleanさん、お世話になります。

Azuleanさんの意見を参考にしまして、C/C++のDLLのインターフェースを
見直してもらおうかと思っています。
現在の仕様では、C/C++ DLL側では以下の構造体と関数宣言になっていますが、
これを手直しする場合、構造体内にあるポインタは不要と考えて、型のみを
そのまま残すような形にした方が宜しいでしょうか?
(例:int* pAはint pAに変更)

C/C++側 構造体宣言
typedef struct tag_STRUCTURE{
int* pA;
int* pB;
int iA;
int iB;
int iC;
int iD;
int iE;
int iF;
int iG;
int iH;
int iI;
int iJ;
int iK;
double dA;
BYTE* pC;
delegate pD;
delegate pE;
}STRUCTURE;

C/C++側 関数宣言
int StartStructure(STRUCTURE* structure);
/////////////////////////////////////////////////

また構造体をポインタで渡すという事で、C#側ではDLL宣言部と
DLL関数コール箇所でrefを使用していますが、この場合、
GCHandleを利用してガベージコレクションでメモリのアドレスが変わらないように
する必要があるんですよね?
その際、GCHandle.Alloc(STRUCTURE,Pinned)として構造体を指定した形にすると
良いのでしょうか?
またその後DLL側で設定された値を読む場合、どうやって参照すれば宜しいのでしょうか?
(C#側構造体宣言は省略させて頂きます。)

C#側 関数宣言
int StartStructure(ref STRUCTURE structure);


STRUCTURE structure;
structure.pA = 0;
structure.iA = 7;
structure.dA = 0;

GCHandle gch = GCHandle.Alloc(structure, GCHandleType.Pinned);

StartStructure(ref structure);

================> ここで構造体内のメンバ変数をそれぞれ呼ぶ場合、
どうやって参照すれば良いでしょうか?

gch.Free();

その辺りがよく分かりませんでした。
すみませんが、宜しくお願い致します。
お世話になります。
すみません、先程の内容に抜けがありました。
C#側の実行コードの箇所です。

> (C#側構造体宣言は省略させて頂きます。)
>
> C#側 関数宣言
> int StartStructure(ref STRUCTURE structure);
>
>
> STRUCTURE structure;
> structure.pA = 0;
> structure.iA = 7;
> structure.dA = 0;
>
> GCHandle gch = GCHandle.Alloc(structure, GCHandleType.Pinned);

Console.WriteLine("\nBefore Call:");
Console.WriteLine("pA = {0} iA = {1}", structure.pA, structure.iA);
>
> StartStructure(ref structure);
>
> ================> ここで構造体内のメンバ変数をそれぞれ呼ぶ場合、
> どうやって参照すれば良いでしょうか?

以下のようにやってみたのですが、オブジェクトのアドレスを取得後にどうやって参照すれば良いのでしょうか?
IntPtr ptRtn = gch.AddrOfPinnedObject();

Console.WriteLine("\nAfter Call:");
Console.WriteLine("pA = {0} iA = {1}", structure.pA, structure.iA);

> gch.Free();

宜しくお願い致します。
何度もスミマセン。
refを使用してDLL側には渡しているので、GCHandleは使用しなくても
良いのでしたね。
勘違いしておりました。

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