C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[4]: C#からC++のDLLの呼び出し(構造体とポインタのポインタ


(過去ログ 64 を表示中)

[トピック内 11 記事 (1 - 11 表示)]  << 0 >>

■37225 / inTopicNo.1)  C#からC++のDLLの呼び出し(構造体とポインタのポインタ
  
□投稿者/ Bakutiku (1回)-(2009/06/15(Mon) 21:26:12)

分類:[C#] 

はじめまして。Bakutikuと申します。

C#をはじめて間もない身ですが、引数が複雑なC++のDllの呼び出しが出来ず困っています。
C++で作成されたネイティブのDLL関数をC#で呼び出そうとしています。
DLL側(C++)の引数は以下のように定義されています。

// メソッド
char* piyo(char* fuga,TS_Hoge pS_Hoge)

// 構造体 TS_Hoge
typedef struct TS_Hoge {
	int      count;
	char**   path;
	pS_Data  Data;
} S_Hoge, *pS_Hoge;

// 構造体 TS_Data
typedef struct TS_Data {
	char id[11];
} S_Data, *pS_Data;

●この処理を、C#からは以下のように宣言して呼び出しています。

◆ 宣言

[DllImport("test.dll")]
private static extern string piyo(StringBuilder fuga, ref TS_Hoge pS_Hoge);

// TS_Hogeの定義
[StructLayout(LayoutKind.Sequential)]
public struct TS_Hoge
{
    public int count;
    public unsafe char** path;
    public TS_Data Data;
}

// TS_Dataの定義
[StructLayout(LayoutKind.Sequential)]
public struct TS_Data
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]
    public string id;          
}

◆ 処理

TS_Hoge tS_Hoge;
TS_Data tS_Data;

tS_Data.id = "test";

tS_Hoge.count = 1;
tS_Hoge.Data = tS_Data;

// ポインタのポインタ作成
string s = @"C:\Hoge\piyo.txt";

char[] a = s.ToCharArray();  // ポインタのポインタ作成用のChar配列

char*[] b = new char*[256];

//ポインタのポインタ作成
fixed (char** fp = b)  // 処理中のマネージ変数を固定する
{
    for(int i = 0 ; i<256; i++)
    {
        fixed (char* pa = &a[i])
        {
            fp[i] = pa;
        }
    }
    tS_Hoge.path = fp; // ポインタのポインタを設定
}

string ret;
StringBuilder fuga = new StringBuilder(11);
fuga.Append("12345");

ret =  piyo(fuga,ref tS_Hoge);

◆事象
上記のように記述したうえで呼び出しを行うと、C++のDLL内部の以下のような入力チェック処理で引っかかり、エラーのリターンコードが返されてしまいます。特に例外は発生しません。
/*--------------------------*/
/* 入力パラメータのチェック */
/*--------------------------*/
if ( fuga == NULL || pS_Hoge == NULL ){
	memcpy( rtn, "inputError", sizeof(rtn) );
	return &rtn[0] ;
} else if( pS_Hoge->count == 0 
	|| pS_Hoge->path == NULL 
	|| pS_Hoge->tS_Data == NULL ) {
	memcpy( rtn, "inputError", sizeof(rtn) );
	return( &rtn[0] );
}

自分としてはポインタのポインタの受け渡しが悪いのかなと思っていますが、
構造体の中に構造体を設定して呼び出しを行うところも自信がなく、
どれが正しいのか判断が付かない状態になってしまいました。
非常に長くなってしまいましたが、少しでもヒントをいただけるとありがたいです。

以上、よろしくお願いします。

引用返信 編集キー/
■37228 / inTopicNo.2)  Re[1]: C#からC++のDLLの呼び出し(構造体とポインタのポインタ
□投稿者/ 倉田 有大 (644回)-(2009/06/15(Mon) 22:47:31)
2009/06/15(Mon) 22:47:47 編集(投稿者)
C++忘却中ですが。

typedef struct TS_Hoge {
	int      count;
	char**   path;
	pS_Data  Data;
} S_Hoge, *pS_Hoge;

// 構造体 TS_Data
typedef struct TS_Data {
	char id[11];
} S_Data, *pS_Data;

pS_Dataがポインター宣言になっているのはよいのでしょうか?
pS_Data  Data;→S_Data  Data;
こうか
public struct TS_Data
こっちをclass?

すいません、全然自信なし!

引用返信 編集キー/
■37229 / inTopicNo.3)  Re[2]: C#からC++のDLLの呼び出し(構造体とポインタのポインタ
□投稿者/ 倉田 有大 (645回)-(2009/06/15(Mon) 22:49:42)
2009/06/15(Mon) 22:50:03 編集(投稿者)
後、役立たずですいませんが、

typedef struct TS_Hoge {
	int      count;
	char**   path;
	//pS_Data  Data;
} S_Hoge, *pS_Hoge;

pS_Data抜きの実装から私なら実験します。

引用返信 編集キー/
■37230 / inTopicNo.4)  Re[3]: C#からC++のDLLの呼び出し(構造体とポインタのポインタ
□投稿者/ Azulean (405回)-(2009/06/15(Mon) 22:56:20)
問題が絡み合って複雑になっているので、すぱっと解決は難しいかもしれません。
とりあえず、気づいた点のみフィードバックしておきます。

・C/C++とC#のcharは違う。
・fixedはそのブロック中のみ固定が保障されるので、スコープを抜けた時点でそのポインタを使ってはならないはず。

# あとは、ポインタの配列とか、構造体のポインタとか気になるところはあるけれども自信がない & 検証していないので保留します
引用返信 編集キー/
■37231 / inTopicNo.5)  Re[4]: C#からC++のDLLの呼び出し(構造体とポインタのポインタ
□投稿者/ Hongliang (419回)-(2009/06/15(Mon) 23:23:32)
まず、以下のツリーを熟読してください。それなりのボリュームですがちゃんと理解していないと火傷の元です。
相互運用マーシャリング
http://msdn.microsoft.com/ja-jp/library/04fy9ya1.aspx

さて問題になりそうな点を挙げていきましょう。
// 書いてる間にかぶった分は省略。

・返値
返値が char* のものを string で受けるのは危険です。返値を string で受け取った場合、返値への MarshalAs 属性の修飾に従って、マーシャラは自動的に CoTaskMemFree または SysStringFree のいずれかを呼び出します。
http://msdn.microsoft.com/ja-jp/library/f1cf4kkz.aspx
DLL の仕様として「関数の呼び出し元が CoTaskMemFree / SysStringFree によって領域を解放すること」とされていない限り、不正なメモリ解放が発生します。もちろん呼び出し元にはポインタが残らないので正しい解放方法も失われるかも知れません。
基本的に IntPtr として受け取り、必要に応じて Marshal.PtrToStringAnsi を使用して文字列に変換するようにしましょう。
unsafe でいいなら、sbyte* で受け取って new String(sbyte*) でも構いませんが。

・tS_Hoge.path への代入
ええと……何をしたいのか……。
そもそもこのコードのままだと OutOfRangeException が発生しますよね?
// i が 0-255 で、a.Length == 16 だから、a[i] はアクセスしきれない。
char** ってことは文字列配列を期待してるんでしょうか。count が要素数? コードだけじゃ読み切れないので何とも言えません。
文字列配列にするのなら、各文字列は StringToGlobalAnsi 辺りで確保して、Marshal.AllocGlobal でそれらの配列分を確保ってことになるかなぁ。

取り敢えずこれぐらい。
引用返信 編集キー/
■37244 / inTopicNo.6)  Re[2]: C#からC++のDLLの呼び出し(構造体とポインタのポインタ
□投稿者/ Bakutiku (2回)-(2009/06/16(Tue) 08:51:51)
倉田 有大 様

お世話になります。

> pS_Dataがポインター宣言になっているのはよいのでしょうか?

C++のヘッダに定義された構造体は、上記の通り定義されています。

> pS_Data抜きの実装から私なら実験します。

なるほど。まとめて解決しようとせず、一つ一つ解決していく方が結果的に早く解決しそうですね。
引用返信 編集キー/
■37245 / inTopicNo.7)  Re[4]: C#からC++のDLLの呼び出し(構造体とポインタのポインタ
□投稿者/ Bakutiku (3回)-(2009/06/16(Tue) 09:08:26)
Azulean 様

お世話になります。

> ・C/C++とC#のcharは違う。
C/C++のchar形配列はstringまたはStringBuilderという認識でしたが、char型のポインタのポインタは中々やり方がわからず、このような記述方法になってしまっています。

> ・fixedはそのブロック中のみ固定が保障されるので、スコープを抜けた時点でそのポインタを使ってはならないはず。
fixedステートメントの理解が足りませんでした。
少なくともこの使い方の場合、

ret = piyo(fuga,ref tS_Hoge);

はfixedステートメントのスコープ内に記述する必要があるということですね。

もっと勉強しつつ検証とコーディングを繰り返してみます。
引用返信 編集キー/
■37304 / inTopicNo.8)  Re[5]: C#からC++のDLLの呼び出し(構造体とポインタのポインタ
□投稿者/ Bakutiku (4回)-(2009/06/16(Tue) 20:58:48)
Hongliang 様

お世話になります。作業に没頭してしまい返信が遅れてしまいました。

> まず、以下のツリーを熟読してください。それなりのボリュームですがちゃんと理解していないと火傷の元です。
了解しました。確認します。
本日はどうしても確認ができなかったので、自宅に戻ってから読ませていただきます。

返値については了解しました。ご指摘の通り、IntPtrで受取って文字列に変換して使用します。

> ・tS_Hoge.path への代入
> ええと……何をしたいのか……。
> そもそもこのコードのままだと OutOfRangeException が発生しますよね?
> // i が 0-255 で、a.Length == 16 だから、a[i] はアクセスしきれない。
すみません、提示したコードが誤っていました。
for(int i = 0 ; i<a.Length; i++)
としていました。

> char** ってことは文字列配列を期待してるんでしょうか。count が要素数? コードだけじゃ読み切れないので何とも言えません。

char**にはイメージのファイルパスの文字列配列を持たせたいと思っています。
なお、C++では以下のようにしてデータを取り出しています。

char *path;
SQLCHAR FileName[50];

for(int i = 0; i < pS_Hoge->count; i++)
{
path = (pS_Hoge->path)[i];
memcpy(FileName, strrchr(path, '\\')+1, sizeof(FileName));

// 以降、イメージファイルのDB登録
}

> 文字列配列にするのなら、各文字列は StringToGlobalAnsi 辺りで確保して、Marshal.AllocGlobal でそれらの配列分を確保ってことになるかなぁ。

すみません、ここがどうしてもイメージができません。
StringTohGlobalAnsiを使うところまでは理解できましたが、
以下のようにMarshal.AllochGlobalを使うと
ポインタのポインタのポインタになってしまい(?)、C++のDll側では動作しません。

// TS_Hogeの定義 path部分を更新
[StructLayout(LayoutKind.Sequential)]
public struct TS_Hoge
{
public int count;
public IntPtr[] path; // 更新
public IntPtr Data; // 更新
}

string[] str1 = new string[pS_Hoge.count];
str1[0] = @"C:\Hoge\piyo.txt";
str1[1] = null;

IntPtr[] files = new IntPtr[2];
IntPtr[] files2 = new IntPtr[2];

for (int i = 0; i < imageDataNew.iFileCount; i++)
{
files[i] = Marshal.StringToHGlobalAnsi(str1[i]);
files2[i] = Marshal.AllocHGlobal(files[i]);
}

この記述だと、上記C++のコードの「memcpy」処理で
「AccessViolationException」が発生してしまいます。

ここについてもう少しヒントをいただきたいです。
お手数をおかけしますがお願い致します。

// 皆様のヒントをもとに構造体の呼び出し部分については解決しました。(C++内部でポインタ宣言されているため、IntPtrで受け渡し。)
// 詳細についてはすみませんが明日記述します。
引用返信 編集キー/
■37310 / inTopicNo.9)  Re[6]: C#からC++のDLLの呼び出し(構造体とポインタのポインタ
□投稿者/ Hongliang (420回)-(2009/06/16(Tue) 23:40:10)
配列に対する既定のマーシャリング
http://msdn.microsoft.com/ja-jp/library/z6cfh6e6.aspx
C# で構造体に配列を記述した場合、マーシャラは SAFEARRAY または固定長配列としてアンマネージに渡します。ポインタとしては渡せません。ポインタのポインタだろうが、あくまで IntPtr として渡す必要があります。
つまり、path メンバは IntPtr[] とは記述できず、IntPtr と定義しなければなりません。そしてこの path に設定する IntPtr を確保するために使用するのが Marshal.AllocHGlobal です。
この IntPtr に格納するのがそれぞれの Marshal.StringToHGlobalAnsi で取得した IntPtr ですね。Marshal.WriteIntPtr とか Marshal.Copy とかで。
イメージは大丈夫でしょうか?
char** list = (char**)malloc(count * sizeof(char*));
と同じであるってことですが。この malloc に該当するのが Marshal.AllocHGlobal です。

StringToHGlobalAnsi や AllocHGlobal で確保した IntPtr は解放を忘れずに。

> memcpy(FileName, strrchr(path, '\\')+1, sizeof(FileName));
バッファオーバーフローとか NULL 終端とか大変そうですけど。
引用返信 編集キー/
■37355 / inTopicNo.10)  Re[3]: C#からC++のDLLの呼び出し(構造体とポインタのポインタ
□投稿者/ Bakutiku (5回)-(2009/06/17(Wed) 20:43:29)
Hongliang 様

お世話になります。
まずは報告を。
ファイルが一つの場合は動きました。

ヒントをもとに以下のように記述しました。

// TS_Hogeの定義
[StructLayout(LayoutKind.Sequential)]
public struct TS_Hoge
{
    public int count;
    public IntPtr path; // 更新
    public IntPtr Data;
}

// TS_Dataの定義
[StructLayout(LayoutKind.Sequential)]
public struct TS_Data
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]
    public string id;          
}

[DllImport("test.dll")]
private static extern IntPtr piyo(IntPtr fuga, ref TS_Hoge pS_Hoge);

// コード
TS_Hoge tS_Hoge;
TS_Data tS_Data;

tS_Data.id = "test";

pS_Hoge.count=1; // ファイル数カウント
string filePath = @"C:\Hoge\piyo.txt";

IntPtr pathArrayPtr = Marshal.AllocHGlobal(pS_Hoge.count);     // count分のアンマネージメモリを確保
IntPtr pathStringPtr = Marshal.StringToHGlobalAnsi(filePath);  // ファイルパス文字列のIntPtrを取得
Marshal.WriteIntPtr(filePathArrayPtr, filePathStringPtr);      // 確保したアンマネージメモリに、ファイルパス文字列のIntPtrを設定

TS_Hoge.path = filePathArrayPtr;

IntPtr fuga = Marshal.AllocHGlobal(1); // fugastring取得用IntPtr

IntPtr ret =  piyo(fuga,ref tS_Hoge);

string retString = Marshal.PtrToStringAnsi(ret); // リターンコードの取得
string retFuga = Marshal.PtrToStringAnsi(fuga); // fugastringの取得

// アンマネージメモリの開放
Marshal.FreeHGlobal(pathArrayPtr);
Marshal.FreeHGlobal(pathStringPtr);
Marshal.FreeHGlobal(fuga);

後は、ファイル数だけMarshal.StringToHGlobalAnsiでIntPtrを取得してMarshal.WriteIntPtrでpathArrayPtrに書いていけば良さそうです。

>>memcpy(FileName, strrchr(path, '\\')+1, sizeof(FileName));
> バッファオーバーフローとか NULL 終端とか大変そうですけど。

仰るとおり、中々苦労していますが、ここからは提示いただいた資料をもとに自力で解決したいと思います。
迅速でかつ明快な回答をいただきありがとうございました。

ご協力いただいた皆様、ありがとうございました。

解決済み
引用返信 編集キー/
■37356 / inTopicNo.11)  Re[4]: C#からC++のDLLの呼び出し(構造体とポインタのポインタ
□投稿者/ Hongliang (423回)-(2009/06/17(Wed) 21:08:18)
> IntPtr pathArrayPtr = Marshal.AllocHGlobal(pS_Hoge.count); // count分のアンマネージメモリを確保
AllocHGlobal はバイト単位でメモリを確保するわけですが、ポインタ一つで何バイト使うかご存じですか?

> IntPtr fuga = Marshal.AllocHGlobal(1); // fugastring取得用IntPtr
> IntPtr ret = piyo(fuga,ref tS_Hoge);
アンマネージ関数の方で fuga に (どうやって|どんな) 値を格納しているのか知りませんが、おそらくバッファオーバーフローしています。1 バイトじゃ終端文字入れて終了です。
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -