質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.10%

C#実行プログラムからのC++ライブラリ(dll)呼び出し時の構造体の参照渡しについて

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 660

yukibeatles

score 4

今回2度目の質問をさせていただきます。
開発環境はVisual Studio 2013です。

実現したいこと

C#で書かれた実行プログラム(A.exe)からC++で書かれたライブラリ(B.dll)内の関数(Func_C)を呼び出す際、引数として渡す、
構造体の配列(struct_D)をA.exe側で受け取りたい。

発生している問題

A.exeからFunc_Cをコールした後、struct_Dがnullである。
(Func_Cの実行結果が格納されない)

試したこと

・下記コードのようなプログラムを実装してみました。その結果、以下のことは可能でした。

・A.exeからB.dll側に値(int型、int型・char型を含む構造体)を引数として渡すことはできる。
(下記コード内の関数1の入力引数はB.dll側に問題なく渡せる)
・B.dll側からA.exe側に値(int型)を引数として渡すこともできる。
(下記コード内の関数2の出力引数はA.exeに問題なく返せる)

以上の結果より、問題は呼び出し元にあると考え、試行錯誤していますが、うまく実装できずにいます。

下記コード内で誤り等ありましたらご教示頂けませんでしょうか。
お手数ですが宜しくお願い致します。

<A.exe(呼び出し元)>

    class Program
    {
        // C++受渡し用構造体
        [StructLayout(LayoutKind.Sequential)]
        internal struct struct_D
        {
            // 変数宣言(マーシャリング)
            [MarshalAs(UnmanagedType.I4)]
            internal int val1;                             // 値1
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
            internal string val2;                         // 値2(値が省略("")の可能性あり)
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 60)]
            internal string val3;                                 // 値3

            // カプセル化
            public int Val1
            {
                get { return val1; }
                set { val1 = value; }
            }
            public string Val2
            {
                get { return val2; }
                set { val2 = value; }
            }
            public string Val3
            {
                get { return val3; }
                set { val3 = value; }
            }
        }

        // < C++のDLLファイルより関数を取得 >
        // 関数1(配列サイズ取得関数)←こちらは問題なく動作する
        [DllImport("B.dll")]        
        static extern int GetTotal(               
            IntPtr ptrTotal                              // (出力引数)←出力引数もint型変数のみであれば問題なく受け取れる  
            );

     // 関数2(構造体配列の取得関数)
        [DllImport("B.dll")]      
        static extern int Func_C(              
            IntPtr[] dataOut,                           // (出力引数)ポインタの配列(これが受け取れない)
            int num,                                     // (入力引数)←入力引数はB.dllに問題なく渡せる
            struct_D dataIn                             // (入力引数)←構造体も問題なく渡せる
            );

        static internal int Func_E()
        {
            // 出力構造体の配列サイズを取得(これは問題なく動作する)
            IntPtr ptrTotal = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(int)));                 // レコード数ポインタ(メモリ確保)
            Marshal.StructureToPtr(Total, ptrTotal, false);                                      // レコード数とポインタを結び付け
            GetTotal(ptrTortal);                                                                 // DLLの実行(レコード数取得)
            Total = (int)Marshal.PtrToStructure(ptrTotal, typeof(int));                          // ポインタから値(int型)を取得
            Marshal.FreeHGlobal(ptrTotal);                                                       // メモリ解放

            // 出力構造体分のバッファ確保(これが問題)
            int ii;
            struct_D[] dataOut = new struct_D[Total];                              // 出力構造体の宣言
            IntPtr[] ptrdataOut = new IntPtr[Total];                                             // 出力構造体のポインタの宣言 
            for (ii = 0; ii < Total; ii++)                          // 出力構造体のサイズ分のメモリを確保
            {
                ptrdataOut [ii] = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(struct_D)));   
                Marshal.StructureToPtr(dataOut[ii], ptrdataOut[ii], false);
            }

            // DLLの関数実行
            Func_C(ptrdataOut, num, dataIn);

            // ポインタから値(構造体)を取得(受け取れない…)
            for (ii = 0; ii < Total; ii++)
            {
                dataOut[ii] = (struct_D)Marshal.PtrToStructure(ptrdataOut[ii], typeof(struct_D));
                Marshal.FreeHGlobal(ptrdataOut[ii]);
            }
        }
    }


<B.dll(呼び出し先)>

// C#受渡し用構造体
 struct gyddwsproduct {
     long        val1;                // 値1
     char        val2[16];            // 値2(値が省略("")の可能性あり)
     char        val3[60];            // 値3
}

// 関数1(配列サイズ取得関数)←こちらは問題なく動作する
DLLEXPORT int __stdcall GetTotal(int* Total)
{
    …
}

// 関数2(構造体配列の取得関数)
DLLEXPORT int __stdcall Func_C(struct_D* dataOut, int num, struct_D dataIn)
{
    …  // 入力引数(num, dataInは問題なく取得できている)
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

0

    // アンマネージなメモリサイズ1個分
    var size = Marshal.SizeOf(typeof(struct_D));
    IntPtr ptr = Marshal.AllocHGlobal( size * Total);

    //略 (Marshal.Copyはいらない)

    //受け取り
    struct_D[] dataOut = new struct_D[Total]; 
    for ( int i = 0; i < Total; ++i)
    {
        //メモリずらしながら1個ずつ取り出す
        struct_D pp = (struct_D)Marshal.PtrToStructure(ptr + (size*i), typeof(struct_D));
        dataOut[i] = pp;
    }
    Marshal.FreeHGlobal(ptr);


実際に動作させていませんが、こんな受け取り方でいけるはず

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/06/15 08:26

    ご回答ありがとうございます。
    これは動きそうだな、と思って、//略 と記載頂いている部分にDLLの関数を追加し、
    実装してみましたが、受け取れませんでした。。

    コンパイル時エラーはしませんが、dataOutの各要素の構造体の各値がDLLの出力すべき値と一致しません。
    (値が全く異なるので恐らく参照しているアドレスが正しくないのだと思います)

    キャンセル

  • 2019/06/16 20:27

    1.修正したコードを質問に「追記」してみて

    2.どの値がおかしいか確認
    #途中でズレが起きているのか最初の最初からおかしいのか?

    3.使用する構造体は数値だけにしてみて

    キャンセル

  • 2019/07/08 18:54

    大変回答が遅くなり申し訳ございません。
    質問させて頂いた件ですが、なんとか解決いたしました。。
    原因は、呼び出し先でもメモリを確保してしまっていたためでした。
    (callocでメモリを確保していた)
    冷静に考えれば、当たり前だったのですが、お恥ずかしい限りです。
    ありがとうございました。

    キャンセル

0

C#側からC++(DLL)に渡すIntPtr配列はアンマネージ配列のメモリをC#で確保してDLLに渡す必要があるはずです。
詳しくは以下を参考にしてください。

C#からC++のDLLを使う

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/06/13 19:29

    ptrdataOutがIntPtrの配列になってるのでサイズ違いでエラーになってます。ptrdataOutをsrtruct_Dの配列にしないと。

    キャンセル

  • 2019/06/14 11:46

    申し訳ありません、根本的に私が勘違いしているようです。
    私の認識では、
    ptrdataOutはポインタ(IntPtr)の配列であり、ptrdataOut[ii]のポインタがさすアドレス先にsrtruct_D分のメモリを確保する必要があるという認識だったのですが、誤っているでしょうか…?

    ptrdataOutをsrtruct_Dの配列にしてみると、マネージ配列へコピーする際、ポインタから値(構造体)を取得する際、型がポインタでないためにエラーしてしまいます。

    お手数ですが、具体的にコードをご教示頂けませんでしょうか…?
    ご迷惑おかけしまして申し訳ございませんが、よろしくお願い致します。

    キャンセル

  • 2019/06/15 05:46

    ポインターの認識はあってますけど、そこにコピーできるのはポインターです。
    そもそも、マネージ配列にマーシャルコピーする必要はなくて、PtrToStructureで構造体のサイズ分をずらしながら構造体の配列に入れて行けばいいはず。
    今はスマホでしかアクセスできないのでコードは示せません。あしからず。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.10%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる
  • トップ
  • C#に関する質問
  • C#実行プログラムからのC++ライブラリ(dll)呼び出し時の構造体の参照渡しについて