Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

function pointers #347

Closed
Tracked by #297
ufcpp opened this issue Jul 25, 2021 · 9 comments
Closed
Tracked by #297

function pointers #347

ufcpp opened this issue Jul 25, 2021 · 9 comments
Labels

Comments

@ufcpp
Copy link
Owner

ufcpp commented Jul 25, 2021

https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/function-pointers.md

元々 static delegate (静的メソッドならアロケーションなしで間接参照できないか?という話)だったけど、どうも unsafe 必須になったみたい。
「アロケーションなしで assembly unload を安全にトラッキングする手段がないから」とのこと。

unsafe 必須となると、ちょっとしたパフォーマンス改善のために頑張って使うというほどじゃなくなる。
なので、interop シナリオ以外での用途があんまりなく、実例込みで説明した方がよさそう。

ufcpp-live/UfcppLiveAgenda#33 (comment)

@ufcpp ufcpp mentioned this issue Jul 25, 2021
19 tasks
@ufcpp
Copy link
Owner Author

ufcpp commented Jul 25, 2021

#297 (comment)

@ufcpp ufcpp added the C# 9.0 label Aug 13, 2021
@ufcpp
Copy link
Owner Author

ufcpp commented Sep 29, 2021

@ufcpp
Copy link
Owner Author

ufcpp commented Mar 11, 2022

@ufcpp
Copy link
Owner Author

ufcpp commented May 2, 2022

@ufcpp
Copy link
Owner Author

ufcpp commented Mar 29, 2023

using System.Runtime.InteropServices;

var libraryHandle = NativeLibrary.Load("user32.dll");
var functionPtr = NativeLibrary.GetExport(libraryHandle, "MessageBoxA");

unsafe
{
    // Define a delegate for the MessageBoxW function
    var messageBox = (delegate* unmanaged[Cdecl]<nint, byte*, byte*, uint, int>)functionPtr;

    fixed (byte* p1 = "Hello World!"u8)
    fixed (byte* p2 = "My Caption"u8)
    {
        // Call the MessageBoxW function
        var result = messageBox(0, p1, p2, 1);

        Console.WriteLine(result == 1 ? "OK" : "Cancel");
    }
}

static partial class Lib
{
    [LibraryImport("user32.dll")]
    public static partial int MessageBoxA(nint hWnd, ReadOnlySpan<byte> lpText, ReadOnlySpan<byte> lpCaption, uint uType);
}

@ufcpp
Copy link
Owner Author

ufcpp commented Mar 29, 2023

win32 API を bind して、primitive な型しか使わない限りは別に DllImport, LibraryImport よりも速いとかもなさげ。

速度を理由には使わなさそう。
動的なロードとか、Free したい要件で使うもの?

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.InteropServices;

BenchmarkRunner.Run<PInvokeBenchmark>();

public class PInvokeBenchmark
{
    [Benchmark]
    public DateOnly UseNativeLibrary()
    {
        ByNativeLibrary.GetSystemTime(out var t);
        return new(t.Year, t.Month, t.Day);
    }

    [Benchmark]
    public unsafe DateOnly UseLibraryImport()
    {
        ByLibraryImport.GetSystemTime(out var t);
        return new(t.Year, t.Month, t.Day);
    }

    [Benchmark]
    public unsafe DateOnly UseDllImport()
    {
        ByDllImport.GetSystemTime(out var t);
        return new(t.Year, t.Month, t.Day);
    }
}

unsafe static class ByNativeLibrary
{
    private static nint _ptr = NativeLibrary.GetExport(NativeLibrary.Load("kernel32.dll"), "GetSystemTime");

    public static void GetSystemTime(out SystemTime systemTime)
    {
        var getSystemTime = (delegate* unmanaged[Cdecl]<out SystemTime, void>)_ptr;
        getSystemTime(out systemTime);
    }
}

static partial class ByLibraryImport
{
    [LibraryImport("kernel32.dll")]
    public static partial void GetSystemTime(out SystemTime systemTime);
}

static partial class ByDllImport
{
    [DllImport("kernel32.dll")]
    public static extern void GetSystemTime(out SystemTime systemTime);
}

struct SystemTime
{
    public ushort Year;
    public ushort Month;
    public ushort DayOfWeek;
    public ushort Day;
    public ushort Hour;
    public ushort Minute;
    public ushort Second;
    public ushort Milliseconds;
}

@ufcpp
Copy link
Owner Author

ufcpp commented Mar 29, 2023

dotnet/runtime 内、ちらほら

NativeLibrary.TryGetExport(kernel32, "GetTempPath2W", out IntPtr func)
IntPtr pfnGetSystemTime = NativeLibrary.GetExport(kernel32Lib, "GetSystemTimeAsFileTime")

        if (!NativeLibrary.TryLoad($"{Interop.Libraries.MsQuic}.{s_minMsQuicVersion.Major}", typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out IntPtr msQuicHandle) &&
            !NativeLibrary.TryLoad(Interop.Libraries.MsQuic, typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out msQuicHandle))

@ufcpp
Copy link
Owner Author

ufcpp commented Mar 29, 2023

.NET IL の仕様的には .NET Framework 1.0 の頃から関数ポインターの仕様持ってる。

関数ポインターのアドレスを IntPtr で取る手段も同じく 1.0 の頃から、
https://learn.microsoft.com/ja-jp/dotnet/api/system.runtimemethodhandle.getfunctionpointer?view=net-7.0
で取れた。

問題はそれを C# から効率よく invoke する手段がなかったこと。
これも、IL 的には ldftn, ldvirtftn, ldtoken, calli 命令使ってできたけども、C# コンパイラーがこれらの命令を直接生成することはなかった。
(ILEmit すればできた。)

「卵が先か」問題になるけども、効率よく invoke できないから関数ポインターの使い道限られてた。

delegate* 記法を使った関数ポインターが C# に導入されたことで、関数ポインターに対して f(x, y, z) みたいなコードが書けるようになって、これが calli 生成するようになった。
.NET Core 3.0 で、 NativeLibrary クラスが追加されて、DLL 中の export 関数を関数ポインターで取る手段が入った。
これで、ネイティブDLLを動的にロードして、その中の関数を calli で呼べるように。

@ufcpp
Copy link
Owner Author

ufcpp commented Apr 1, 2023

@ufcpp ufcpp closed this as completed Apr 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant