Open2

.NETのP/Invokeやマーシャリングのメモ

インターフェースのマーシャリングにコケるので、
実際にC++のコードをdllへコンパイルして、C#から動かしてみる。

export.cpp
#define DllExport __declspec(dllexport)

struct ITestA
{
    virtual int FunctionA() = 0;
};

struct ITestB : public ITestA
{
    virtual int FunctionB() = 0;
};

using f_type = int();
struct C : public ITestB
{
    int FunctionA() override {
        return 100;
    }

    int FunctionB() override {
        return 456;
    }

    f_type * functionC;
};

auto a = C{};

int FunctionC() {
    return 789;
}

extern "C" {
    DllExport int _stdcall HelloFromDll(){
        return 100;
    }
    DllExport ITestB * _stdcall ReturnStruct(){
        a.functionC = &FunctionC;
        return &a;
    }
}

これをコンパイルしてDLLを出力する。

https://docs.microsoft.com/ja-jp/cpp/build/reference/md-mt-ld-use-run-time-library?view=msvc-160
cl .\exports.cpp /LD

clang-clでも与えるパラメータは一緒。

Developer Command Prompt for VS 2019などはbatファイルをcmd起動時に/kと一緒に与えている。

Program.cs
using System;
using System.Runtime.InteropServices;

namespace SandBox
{
    class Program
    {
        const string LibName = "exports.dll";

        [DllImport(LibName)]
        static extern int HelloFromDll();
        [DllImport(LibName)]
        static extern nint ReturnStruct();

        static unsafe void Main(string[] _)
        {
            Console.WriteLine(HelloFromDll());
            // aのアドレス
            var st = ReturnStruct();
            var p = (nint*)st;

            // aの参照先を取得(配列でもイイがカッコつけてSpanを使う)
            var pArray = new Span<nint>((void*)(*p),2);

            // 関数ポインタへキャスト
            var functionA = (delegate*<nint, int>)pArray[0];
            var functionB = (delegate*<nint, int>)pArray[1];
            
            // functionCを指すアドレスへ
            p++;

            // functionCの参照先を関数ポインタへキャスト
            var funcC = (delegate*<int>)(*p);

            Console.WriteLine($"Func A:{functionA(st)}");
            Console.WriteLine($"Func B:{functionB(st)}");
            // ただの関数なのでクラス自身のポインタは要らない?
            Console.WriteLine($"Func C:{funcC()}");
        }
    }
}
<PropertyGroup>
  <!-- OutputTypeなどは省略 -->
  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

csprojファイルにAllowUnsafeBlocksの追記を忘れずに。

> dotnet run
100
Func A:100
Func B:456
Func C:789
作成者以外のコメントは許可されていません