C#
.NET

ClangSharpPInvokeGeneratorを使ってみた

ClangSharpPInvokeGeneratorって?

Microsoftの.Net用ClangバインディングMicrosoft/ClangSharp
サンプル 兼 libclang.dllのP/Invokeのコードの自動生成用のプログラムです。

ClangSharpPInvokeGeneratorはlibclang.dllだけでなく、
一般のネイティブDLL呼び出しのためのP/Invokeのコードの生成もできそうなのですが、
ググっても日本語の情報が出てこなかったので試しに使ってみました。

準備

LLVMのセットアップ

LLVM Download Pageから
Pre-Built Binaries: Clang for Windows (64-bit)
ダウンロード & 展開 & パスを通す

.Net Coreのセットアップ

※ 現時点での最新版は2.1
.NET Core 2.1 downloadsから
.NET Core Binaries: x64
ダウンロード & 展開 & パスを通す

ClangSharpPInvokeGeneratorのビルド

git clone https://github.com/Microsoft/ClangSharp
cd ClangSharp/ClangSharpPInvokeGenerator
donet publish -c Release -r win-x64

実行

ネイティブDLLサンプル

hello.h
#ifndef __HELLO__
#define __HELLO__

#ifdef DLL_EXPORT
#define DECLSPEC __declspec(dllexport)
#else
#define DECLSPEC __declspec(dllimport)
#endif

#ifdef __cplusplus
extern "C" {
#endif
  DECLSPEC void CallMe(char* name, char* resp);
#ifdef __cplusplus
}
#endif

#endif
hello.cpp
#define DLL_EXPORT
#include "hello.h"

#include <stdio.h>

void CallMe(char* name, char* resp) {
    snprintf(resp, 260, "Hello %s!", name);
    return;
}

ラッパーの生成

ClangSharpPInvokeGenerator --n Native --m Hello --o Generated.cs --l hello.dll --f hello.h

生成物

Generated.cs
namespace Native
{
    using System;
    using System.Runtime.InteropServices;

    public static partial class Hello
    {
        private const string libraryPath = "hello.dll";

        [DllImport(libraryPath, EntryPoint = "CallMe", CallingConvention = CallingConvention.Cdecl)]
        public static extern void CallMe(IntPtr @name, IntPtr @resp);

    }
}

呼び出しサンプル

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

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var name = "World";
            var namePtr = Marshal.StringToHGlobalAnsi(name);

            string resp = "";
            var respPtr = Marshal.StringToHGlobalAnsi(resp);

            // 呼び出し
            Hello.CallMe(namePtr, respPtr);

            resp = Marshal.PtrToStringAnsi(respPtr);

            Console.WriteLine(resp);
            Console.Read();

            Marshal.FreeHGlobal(namePtr);
            Marshal.FreeHGlobal(respPtr);
        }
    }
}

とりあえず、文字列の受け渡しができることは確認できました。
構造体の受け渡し等は試してませんが、libclang.dllで使ってるくらいなので大丈夫でしょう、きっと。