- PR -

C#でVC++作成DLLの参照渡し

投稿者投稿内容
真宮寺
会議室デビュー日: 2005/11/01
投稿数: 2
投稿日時: 2005-11-01 21:41
いくつか記事を検索してC#から構造体を参照渡しでDLLへ渡し実行結果をその構造体に
セットしてもらい呼出元で実行結果を受け取るという希望する動作をさせることができました。

ここで1つわからないことが出てきましたのでみなさんをお知恵を拝借できればと思い
投稿させていただきました。

問題はDLLの関数内での動作でして、
呼出をしてその関数内でパラメータで渡された構造体のメンバーに値をセットして
関数を抜けると問題ないのですが、パラメータによっては関数内でスレッドを作成し
コピーしたパラメータバッファにスレッド内で書き込みます。
その結果を呼出元にはメッセージで通知する処理があります。
この場合にはパラメータとして渡した構造体にDLL側では値をセットしていても
メッセージを受け取った呼出元では変更されないという状況になり困っております。

そもそもこういうコードを書くこと自体、問題があるのでしょうか?
渋木宏明(ひどり)
ぬし
会議室デビュー日: 2004/01/14
投稿数: 1155
お住まい・勤務地: 東京
投稿日時: 2005-11-01 23:28
引用:

そもそもこういうコードを書くこと自体、問題があるのでしょうか?



C/C++ 的に問題があるかどうかは分かりませんが、マーシャリングとの相性は最悪です。

マネージコードと組み合わせて使うことも視野に入れるなら、避けた方がいいと思います。

_________________
// 渋木宏明 (Hiroaki SHIBUKI)
// http://hidori.jp/
// Microsoft MVP for Visual C#
//
// @IT会議室 RSS 配信中: http://hidori.jp/rss/atmarkIT/
真宮寺
会議室デビュー日: 2005/11/01
投稿数: 2
投稿日時: 2005-11-02 00:25
引用:

C/C++ 的に問題があるかどうかは分かりませんが、マーシャリングとの相性は最悪です。

マネージコードと組み合わせて使うことも視野に入れるなら、避けた方がいいと思います。




ご回答ありがとうございます。
ということは解決方法としてはやはり無いと考えるべきでしょうか( p_q)エ-ン
ちなみに、この「相性は最悪」という部分にかかる処理はスレッド内で行うということでしょうか?
Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2005-11-02 00:50
一応、不可能ではありません。
GCHandleを使ったりMarshal.AllocCoTaskMemなどを使ったりすればオブジェクトのアドレスを固定できますから、アンマネージドからの書き込みを受けることもできます。
//ちなみにこの場合は値型の参照渡しではなくアドレスの値渡しになります。
一番の難点は、それらの固定したアドレスをいつ誰がどこでどうやって解放するかと言う点でしょう。
この辺はアンマネージド側の仕様によりますけど。

やはり、できるならアンマネージドの方をどうにかしたいものですね。
渋木宏明(ひどり)
ぬし
会議室デビュー日: 2004/01/14
投稿数: 1155
お住まい・勤務地: 東京
投稿日時: 2005-11-02 00:54
引用:

ということは解決方法としてはやはり無いと考えるべきでしょうか( p_q)エ-ン



今のままの設計では、期待通りの動作を得られないと思います。

引用:

ちなみに、この「相性は最悪」という部分にかかる処理はスレッド内で行うということでしょうか?



そうです。

マーシャリングは DLL 関数の呼び出し直前と直後に行われるので、それ以外のタイミングで、引数で渡した構造体の内容を変更しても反映されません。

それに、DLL 関数に渡した構造体のメモリアドレスが、スレッド動作中も常に「そこにある」保証も無いので、無効なメモリ領域をアクセスしてしまう危惧もあります。
Tdnr_Sym
ぬし
会議室デビュー日: 2005/09/13
投稿数: 464
お住まい・勤務地: 明石・神戸
投稿日時: 2005-11-02 00:54
こんばんは。

.NET素人なので勘違いしていたらごめんなさい。

引用:

真宮寺さんの書き込み (2005-11-01 21:41) より:

問題はDLLの関数内での動作でして、
呼出をしてその関数内でパラメータで渡された構造体のメンバーに値をセットして
関数を抜けると問題ないのですが、パラメータによっては関数内でスレッドを作成し
コピーしたパラメータバッファにスレッド内で書き込みます。
その結果を呼出元にはメッセージで通知する処理があります。
この場合にはパラメータとして渡した構造体にDLL側では値をセットしていても
メッセージを受け取った呼出元では変更されないという状況になり困っております。

そもそもこういうコードを書くこと自体、問題があるのでしょうか?



DLLとはどんな種類のものなんでしょうねぇ?
ノーマルDLL(Windows APIのような)だと仮定して…

構造体の参照(ポインタ)を関数の引数として渡しているんですよね!?
スレッドを起こさず構造体に値をセットした場合はちゃんと書き込まれていて、
スレッドを起こしてそのスレッドでセットした場合は書き込まれていないんですね!?

スレッドから構造体に値をセットする場合は、メインスレッド側(呼び出し元)と同期をとる必要がありますね。
「構造体への値のセットが完了しましたよ」という。
それが「メッセージで通知」で行われているということですが、Windowメッセージのことなんですかね?
それとも別の同期機構を使っているんでしょうか?

あと一番気になることは、構造体のポインタって固定されていますよね?
C#のマネージ変数はガベージコレクタによってアドレスが動いてしまうので、
ちゃんと"fixed"キーワードで、防いでおかないとマズイですよね。
渋木宏明(ひどり)
ぬし
会議室デビュー日: 2004/01/14
投稿数: 1155
お住まい・勤務地: 東京
投稿日時: 2005-11-02 08:10
引用:

GCHandleを使ったりMarshal.AllocCoTaskMemなどを使ったりすればオブジェクトのアドレスを固定できますから、アンマネージドからの書き込みを受けることもできます。



そか、アンマネージなメモリを用意してやればOKですね。
Tdnr_Sym
ぬし
会議室デビュー日: 2005/09/13
投稿数: 464
お住まい・勤務地: 明石・神戸
投稿日時: 2005-11-02 11:31
こんにちは。

暇だったので.NETのお勉強がてらに、真宮寺さんの問題にチャレンジすべく
テストコードを書いてみました…

テストコードを実行してみると予想に反して、
スレッドから、ちゃんと構造体メンバーに値をセットすることができてしまいました!(なぜでしょう?)

#ガベージコレクションや、DLL関数呼び出し時のマーシャリングコードについては全然意識していません。
#なので、”危険なコード”であるという自覚はあります(~_~;)


【呼び出し側 C#コード】
コード:
// DLL関数のインポート
[DllImport("Foo.dll")]
private extern static void Foo([In] ref  FOO_PARAM paramIN, out FOO_PARAM paramOut);

// 構造体の定義
struct FOO_PARAM
{
	public int x;
	public int y;
}

// 同期用にAPIをインポート
[DllImport("kernel32.dll")]
static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName);

[DllImport("kernel32", SetLastError=true, ExactSpelling=true)]
static extern Int32 WaitForSingleObject(IntPtr handle, Int32 milliseconds);

private void button1_Click(object sender, System.EventArgs e)
{

	// イベントオブジェクト作成
	IntPtr hEvent = IntPtr.Zero;
	hEvent = CreateEvent(IntPtr.Zero, false, false, "Foo");

	FOO_PARAM paramIn;
	FOO_PARAM paramOut;

	// paramInに入力用の値を設定
	paramIn.x = 3;
	paramIn.y = 7;

	// DLL関数の呼び出し
	Foo(ref paramIn, out paramOut);

	// paramOutに値が設定されるまで待機
	WaitForSingleObject(hEvent, 10000);

	// paramOutの値を確認
	System.Diagnostics.Debug.Assert(paramOut.x == 7);
	System.Diagnostics.Debug.Assert(paramOut.y == 3);
}



【DLL側 C++コード】
コード:
// 構造体定義
typedef struct {
	int x;
	int y;
} FOO_PARAM;

// グローバル変数
HANDLE g_hEvent = NULL;		// イベントオブジェクトのハンドル
FOO_PARAM* g_pParamIn = NULL;	// Foo関数の第1引数の構造体ポインタ
FOO_PARAM* g_pParamOut = NULL;	// Foo関数の第2引数の構造体ポインタ

// スレッド処理
DWORD WINAPI FooThread(LPVOID lpParameter)
{
	// わざとタイムラグを作る
	::Sleep(5000);

	if (g_pParamIn && g_pParamOut) {
		// Foo関数の第2引数の構造体に第1引数の値をセット
		g_pParamOut->x = g_pParamIn->y;
		g_pParamOut->y = g_pParamIn->x;		
	}

	// 同期を取る
	::SetEvent(g_hEvent);

	return 0;
}

// エクスポート関数
void WINAPI Foo(FOO_PARAM* pParamIn, FOO_PARAM* pParamOut)
{
	// 構造体のポインタをグローバル変数に退避させておく
	g_pParamIn = pParamIn;
	g_pParamOut = pParamOut;

	// 通知用イベントオブジェクトを作成
	g_hEvent = ::CreateEvent(NULL, FALSE, FALSE, _T("Foo"));

	// スレッドを起動
	DWORD dwThreadID;
	::CreateThread(NULL, 0, FooThread, NULL, 0, &dwThreadID);
}



#汚いコードでスイマセン(~_~;)

独り言ですが…
System.Threading.ManualResetEventクラスやSystem.Threading.AutoResetEventクラスって
なぜ”名前付き”で作成できないんでしょう?
System.Threading.Mutexクラスは”名前付き”で作成できるのに。
おかげで.NETでWinAPIのCreateEvent関数を使わないといけないことになってしまいました。

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