.NET Core 3から、標準(System.Runtime.InteropServices.NativeLibrary
クラス)でライブラリの動的ロード、およびエクスポートされた関数のアドレスを取得できるようになったらしいです。
このツイートで知りました。
unsafe だし実質 IntPtr だし、 https://t.co/hl3GxKI4Mh これとの組み合わせでよろしくやれないかな…
— ++C++; // 管理人: 岩永 (@ufcpp) June 29, 2020
つまり、WindowsでいうとLoadLibrary
とGetProcAddress
のP/Invokeを書く必要がなくなります。
(Linuxでのdlopen
とdlsym
は試してません)
動的ロード以外にも、DLLImport
したメソッドのライブラリの解決も制御できるようになっているようですが、この記事では動的ロードしたライブラリの関数を呼び出すだけです。
動的ロードと関数の呼び出し例
using System;
using System.Runtime.InteropServices;
class Program
{
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
delegate int MessageBoxW(IntPtr hWnd, string lpText, string lpCaption, uint uType);
static void Main(string[] args)
{
IntPtr user32 = NativeLibrary.Load("User32.dll");
var fpMessageBox = NativeLibrary.GetExport(user32, "MessageBoxW");
var messagebox = Marshal.GetDelegateForFunctionPointer<MessageBoxW>(fpMessageBox);
messagebox(default, "NativeLibrarySample", "Caption", 0);
NativeLibrary.Free(user32);
}
}
なんというか、LoadLibrary
とGetProcAddress
そのままですね。
P/Invokeの様に末尾のA
/W
は自動で検索してくれません。
ポインターが得られるので、GetDelegateForFunctionPointer
でデリゲート経由で呼び出す必要があります。
NativeLibrary.Load
の戻り値はせめてSafeLibraryHandle
とかにくるんで欲しかった。
NativeLibrary.GetExport
も型引数でデリゲートを指定させて欲しい。
C# 9.0 Preview
C# 9.0には関数ポインターの構文が入るので、同様のことをしてみました。
// <LangVersion>preview</LangVersion>
// <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
using System;
using System.Runtime.InteropServices;
unsafe class Program
{
static void Main(string[] args)
{
IntPtr user32 = NativeLibrary.Load("User32.dll");
var fpMessageBox = NativeLibrary.GetExport(user32, "MessageBoxW");
var messagebox = (delegate* stdcall<IntPtr, char*, char*, uint, int>)fpMessageBox;
fixed (char* text = "NativeLibrarySample")
fixed (char* caption = "Caption")
messagebox(default, text, caption, 0);
NativeLibrary.Free(user32);
}
}
マーシャリングされないので、文字列も固定しないと行けないのが面倒ですね。
(正式版では関数ポインターの構文は少し変わるようですが。)
文字列の渡し方
文字列の渡し方が煩わしいので、下記のようのな方法を考えたが、(「安全」ではなく)動作として保証できるのかちょっとわからない…
static class Extention
{
public static StringHolder Hold(this string s) => new StringHolder(s);
}
unsafe readonly ref struct StringHolder
{
readonly ReadOnlySpan<char> span;
public StringHolder(string s) => span = s;
public char* Pointer => (char*)Unsafe.AsPointer(ref Unsafe.AsRef(in MemoryMarshal.GetReference(span)));
}
...
messagebox(default, "NativeLibrarySample".Hold().Pointer, caption, 0);
...