74721


C++で作成された以下のような関数(API)をC#から呼び出したいと考えています。

**C++側の例:**

[code]

    extern "C" LONG WINAPI hoge(short Type, void *Param1, void *Param2, void *Param3)
    {
        switch(Type){
            //Typeに応じて、param1~3をshortやLong、BOOLにキャストして与えられたポインタに値を格納
            case 0:     //shortにキャストして値を
                *(short *)Param1 = hoge[index].hoge;
                break;
                case…
        }
    
[/code]

躓いておりますのは、C#側で変数の型を不定にするという処理でございます。

上記関数は、Typeという変数に応じて、各パラメータの内容が処理内で変わりまして、変数の型も異なります。

以下のようにC#側で変数を決め打ちで呼び出す形は成功するのですが、不定な型の宣言で苦慮しております。  
※ネットで調べておりますと、IntPtr型で何とかなりそうな気もして、下部のコードも試しましたが上手く行っておりません。

このような関数の呼び出しで良い知見ございましたら、アドバイスを頂けますと幸いでございます。

### 成功例

実際に使用する変数で定義して、ref で渡すと上手く行きます。  
但し、変数が固定化されてしまい、関数の使い方が制限されてしまいます。

**C#の例:**

[code]

    [DllImport("hogehoge.dll")] static extern int hoge(short Type, ref short Param1, ref short Param2, ref bool Param3);
    
    private void button1_Click(object sender, EventArgs e)
    {
        short sParam1 = 0, sParam2 = 0
        bool Flag = false;
    
        hoge(0, ref sParam1, ref sParam2, ref Flag);
        textBox1.Text += sParam1.ToString() + "\r\n";
    }
    
[/code]

### 失敗例

IntPtrを用いて、C#側で実際に用いる変数のポインタを取得してそれをDLLに渡す処理を検討しました。  
出来れば、このような形でC#側を実装したいのですが、そもそもこのようなアプローチが間違っているのかからご意見をいただけますと幸いです。
※GCHANDLEでpinnedとしているのは、ガーベージコレクタでの収集対策が必要と考えている為です。

失敗の内容としては、C#からC++にIntPtrで渡している変数に、C++側の処理の結果が反映されないという点と、  
GCHandle Pter1 = GCHandle.Alloc(sParam1,
GCHandleType.Pinned);とした後には、C#側での操作も変数に反映されないという点、  
bool型の変数に、GCHANDLE.ALLOCをすると例外エラーで落ちてしまうという点です。(shortにするとビルドは通ります。)

[code]

    DllImport("hogehoge.dll")] static extern int hoge(short Type, IntPtr Param1, IntPtr Param2, IntPtr Param3);
    
    private void button1_Click(object sender, EventArgs e)
    {
        short sParam1 = 0, sParam2 = 0
        bool Flag = false;
    
        IntPtr PsParam1;
        IntPtr PsParam2;
        IntPtr PFlag; 
    
        GCHandle Pter1 = GCHandle.Alloc(sParam1, GCHandleType.Pinned);
        GCHandle Pter2 = GCHandle.Alloc(sParam2, GCHandleType.Pinned);
        GCHandle Pter3 = GCHandle.Alloc(Flag, GCHandleType.Pinned); // 実際にはここで例外エラーがでます。bool型からshort型に変数を変えるとビルドが通るのですが、bool型の変数で、GCHANDLE.ALLOCは使えないのでしょうか。
    
        PsParam1 = Pter1.AddrOfPinnedObject();
        PsParam2 = Pter2.AddrOfPinnedObject();
        PFlag = Pter3.AddrOfPinnedObject();
    
        Ret = hoge(0, PsParam1, PsParam2, PFlag);
        textBox1.Text += sParam1.ToString() + "\r\n";
    
        Pter1.Free();
        Pter2.Free();
        Pter3.Free();
    }
    
[/code]


CC BY-SA 4.0
2021-03-17T09:59:22.663
74718
2021-03-18T07:11:10.403
2021-03-18T00:15:31.610
3060
44379
question
c#
void * を引数に取るDLL(C++)の呼び出し方


引数の異なるメソッドはオーバーロード定義可能なので

[code]

    [DllImport("hogehoge.dll")]
    static extern int hoge(short Type, ref short Param1, ref short Param2, ref bool Param3);
    [DllImport("hogehoge.dll")]
    static extern int hoge(short Type, ref int Param1, ref int Param2, ref int Param3);
    
[/code]

のように必要な引数パターンを定義しておくことをお勧めします。なお、C++ → C#に値が送られ、C# →
C++に値が送られない引数に関しては`ref`ではなく`out`とすることができます。というのも`out`引数は事前に初期化する必要がないため、呼び出時に変数宣言できます。

[code]

    [DllImport("hogehoge.dll")]
    static extern int hoge(short Type, out short Param1, out short Param2, out bool Param3);
    
    void button1_Click(object sender, EventArgs e) {
        hoge(0, out short sParam1, out short sParam2, out bool Flag);
        textBox1.Text += $"{sParam1}\r\n";
    }
    
[/code]

もちろん、失敗例のようなアプローチで`IntPtr`を渡す方法でも可能ですが、その場合も結局は

[code]

    short sParam1 = 0, sParam2 = 0
    bool Flag = false;
    
[/code]

のように呼び出し側は渡すべきデータ型を把握している必要がありますから`IntPtr`で統一する意義があまりありません。

* * *

どうしても`IntPtr`を使う場合、[コンパイルオプション`/unsafe`](https://docs.microsoft.com/en-
us/dotnet/csharp/language-reference/compiler-
options/language#allowunsafeblocks)を指定した上で[`unsafe`](https://docs.microsoft.com/ja-
jp/dotnet/csharp/programming-guide/unsafe-code-
pointers/)を使うのが楽です。変数のアドレスが取得できるので、[`void*`を引数に取る`IntPtr`コンストラクタ](https://docs.microsoft.com/ja-
jp/dotnet/api/system.intptr.-ctor?view=net-5.0#System_IntPtr__ctor_System_Void__)に渡せば済みます。

[code]

    DllImport("hogehoge.dll")]
    static extern int hoge(short Type, IntPtr Param1, IntPtr Param2, IntPtr Param3);
    
    void button1_Click(object sender, EventArgs e) {
        unsafe {
            short sParam1 = 0, sParam2 = 0
            bool Flag = false;
            Ret = hoge(0, new IntPtr(&sParam1), new IntPtr(&sParam2), new IntPtr(&Flag));
            textBox1.Text += $"{sParam1}\r\n";
        }
    }
    
[/code]


CC BY-SA 4.0
2021-03-17T13:20:46.460
74721
2021-03-18T07:11:10.403
2021-03-18T07:11:10.403
4236
4236
74718
answer


「 DLL の呼び方」は @sayuri さんの回答で言い尽くされているので、実用に供する際には更に１枚 wrapper をかませるといいですね。

[code]

    class Hoge
    {
        enum HogeArgType : short
        {
            SHORT = 0,
            DOUBLE = 1,
            LONG = 2,
        }
        [DllImport("hoge.dll", CallingConvention = CallingConvention.StdCall)] extern static int hoge(HogeArgType tshort, ref short p1, ref short p2, ref short p3);
        [DllImport("hoge.dll", CallingConvention = CallingConvention.StdCall)] extern static int hoge(HogeArgType tdouble, ref double p1, ref double p2, ref double p3);
        [DllImport("hoge.dll", CallingConvention = CallingConvention.StdCall)] extern static int hoge(HogeArgType tlong, ref long p1, ref long p2, ref long p3);
    
        private static int hoge(ref short p1, ref short p2, ref short p3)
        {
            return hoge(HogeArgType.SHORT, ref p1, ref p2, ref p3);
        }
        public void TestShort()
        {
            short s1 = 1, s2 = 2, s3 = 3;
            System.Diagnostics.Debug.WriteLine(hoge(ref s1, ref s2, ref s3));
        }
        // double / long についても同様なので略
    }
    
[/code]

元 DLL をソースレベルで直すのが良いに決まっていますが。


CC BY-SA 4.0
2021-03-18T01:47:22.773
74734
2021-03-18T01:47:22.773
8589
74718
answer