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

Please support for Marshal.GetDelegateForFunctionPointer<T>() in Dotnet 7.0 / 8.0 #85699

Closed
DeafMan1983 opened this issue May 3, 2023 · 5 comments
Labels
area-System.Runtime.InteropServices question Answer questions and provide assistance, not an issue with source code or documentation.

Comments

@DeafMan1983
Copy link

DeafMan1983 commented May 3, 2023

          > Please support for Marshal.GetDelegateForFunctionPointer<>()

Function pointers are more performant and AOT friendly replacement for Marshal.GetDelegateForFunctionPointer. Can you use them instead? If you need help with how to switch to function pointers, please open a new issue or discussion on it.

Originally posted by @jkotas in #69919 (comment)

Ok how do I know replace from Marshal.GetDelegateForFunctionPointer<T> to delegate unmanaged<...>. Tshould get alsodelegate unmanaged` or not?

namespace valve;

using static DeafMan1983.ConvFunctions;
using DeafMan1983.Interop.SDL3;
using static DeafMan1983.Interop.SDL3.SDL3;

using System.IO;
using System.Runtime.InteropServices;

unsafe class Game
{
    // For Linux:
    private const string dl = "c";    
    
    [DllImport(dl)]
    private static extern nint dlopen(sbyte *so_file, uint flags);

    [DllImport(dl)]
    private static extern nint dlsym(nint so_handle, sbyte *func_name);

    [DllImport(dl)]
    private static extern void dlclose(nint so_handle);

    [DllImport(dl)]
    private static extern sbyte *dlerror();

    public delegate void pfnChangeGame_t(sbyte *progname);
    public delegate int pfnMain_t(string[] args, string progname, int bchange_name, ref pfnChangeGame_t changegame_func); // delegate* unmanaged<sbyte *, void> 
    public delegate void pfnShutdown_t();
    private pfnMain_t pfnMain_p;
    private pfnShutdown_t pfnShutdown_p;

    private string szGameDir = string.Empty;
    private string[] szArgs = Environment.GetCommandLineArgs();

    private const string GAMEDIR = "valve";

    private string GetError()
    {
        return CharPtrToString(dlerror());
    }

    private nint handle, pfn_func;
    private T GetProcAddress<T>(string function, bool throwIfNotFound = false)
    {
        handle = nint.Zero;
        pfn_func = nint.Zero;
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
        {
            handle = dlopen(StringFromHeap(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Engine.so")), 0x00002);
            if (handle == nint.Zero)
            {
                Console.WriteLine("Unable to load {0}", GetError());
            }

            pfn_func = dlsym(handle, StringFromHeap(function));
            if (pfn_func == nint.Zero)
            {
                Console.WriteLine("Unable to pass function name {0}", GetError());
            }
        }

        if (pfn_func == nint.Zero)
        {
            if (throwIfNotFound)
            {
                throw new EntryPointNotFoundException(function);
            }
            return default(T);
        }

        return Marshal.GetDelegateForFunctionPointer<T>(pfn_func);
    }

    private void FreeLibrary()
    {
        if (handle != nint.Zero)
        {
            dlclose(handle);
        }
    }

    private string strncpy(ref string dst, string src, int num)
    {
        return dst=src.Substring(0,num);
    }


    private void Sys_LoadEngine()
    {
        pfnMain_p = GetProcAddress<pfnMain_t>("Host_Main");
        if (pfnMain_p == null)
        {
            Console.WriteLine("Error pass method is failed: {0}", GetError());
        }

        pfnShutdown_p = GetProcAddress<pfnShutdown_t>("Host_Shutdown");
    }

    private void Sys_UnloadEngine()
    {
        if (pfnShutdown_p != null)
        {
            pfnShutdown_p();
        }

        if (Convert.ToBoolean(handle))
        {
            FreeLibrary();
        }

        pfnMain_p = null;
	    pfnShutdown_p = null;
    }

    private void Sys_ChangeGame(sbyte *progname)
    {
        if (progname == null)
        {
            Console.Error.WriteLine("Error: Sys_ChangeGame is null.");
        }

        if (pfnShutdown_p == null)
        {
            Console.Error.WriteLine("Error: Sys_ChangeGame: missed 'Host_Shutdown'.");
        }

        strncpy(ref szGameDir, CharPtrToString(progname), szGameDir.Length - 1);
        Sys_UnloadEngine();
        Sys_LoadEngine();
        pfnChangeGame_t pfnChangeGame = Sys_ChangeGame;
        pfnMain_p(szArgs, szGameDir, 1, ref pfnChangeGame);
    }

    public int Sys_Start()
    {
        Sys_LoadEngine();
        pfnChangeGame_t pfnChangeGame = Sys_ChangeGame;
        int returnCode = pfnMain_p(szArgs, GAMEDIR, 0, ref pfnChangeGame);
        Sys_UnloadEngine();
        return returnCode;
    }

    static int Main(string[] args)
    {
        return new Game().Sys_Start();
    }
}

as into GameLauncher but used PublishSingleFile and PublishTrimmed

And I would like to pass Host_Main and Host_Shutdown from native library Engine

namespace Engine;

using System;
using System.IO;
using System.Runtime.InteropServices;

using static DeafMan1983.ConvFunctions;
using DeafMan1983.Interop.SDL3;
using static DeafMan1983.Interop.SDL3.SDL3;

public unsafe class Host
{
    private static delegate* unmanaged<sbyte *, void> pChangeGame = null;







    // ------------------------------------------------
    // Host_Main()
    // ------------------------------------------------
    [UnmanagedCallersOnly(EntryPoint = "Host_Main")]
    public static int Host_Main(sbyte** args, sbyte* progname, int bchange_name, delegate* unmanaged<sbyte *, void> func)
    {
        Console.WriteLine("Hello Half-Life Engine, lol!");

        pChangeGame = func;

        string progname_str = CharPtrToString(progname);
        if (progname_str == "valve");
        {
            if (!Directory.Exists(progname_str))
            {
                Console.WriteLine("Not found, Please re-update Steam!");
            }
            else
            {
                Console.WriteLine("Woohoo, Half-Life Sharp (x64)");
                Console.WriteLine("Engine.so, Client.so and Server.so are written in C#\n" +
                "and are compiled in NativeAot with Shared. :-D");
            }
        }



        return 0;
    }








    // ------------------------------------------------
    // Host_Shutdown()
    // ------------------------------------------------
    [UnmanagedCallersOnly(EntryPoint = "Host_Shutdown")]
    public static void Host_Shutdown()
    {
        Environment.Exit(0);
    }

}








    // ------------------------------------------------
    // Host_Shutdown()
    // ------------------------------------------------
    [UnmanagedCallersOnly(EntryPoint = "Host_Shutdown")]
    public static void Host_Shutdown()
    {
        Environment.Exit(0);
    }
}

Engine uses PublishAot, StripSymbols and InvariantGlobalization
Result:
image
How do I replace Marshal.GetDelegateForFunctionPointer<T> to delegate *unamaged[cdel]<>?

@ghost ghost added the untriaged New issue has not been triaged by the area owner label May 3, 2023
@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label May 3, 2023
@ghost
Copy link

ghost commented May 3, 2023

Tagging subscribers to this area: @dotnet/interop-contrib
See info in area-owners.md if you want to be subscribed.

Issue Details
          > Please support for Marshal.GetDelegateFunctionPointer<>()

Function pointers are more performant and AOT friendly replacement for Marshal.GetDelegateFunctionPointer. Can you use them instead? If you need help with how to switch to function pointers, please open a new issue or discussion on it.

Originally posted by @jkotas in #69919 (comment)

Ok how do I know replace from Marshal.GetDelegateFunctionPointer<T> to delegate unmanaged<...>. Tshould get alsodelegate unmanaged` or not?

namespace valve;

using static DeafMan1983.ConvFunctions;
using DeafMan1983.Interop.SDL3;
using static DeafMan1983.Interop.SDL3.SDL3;

using System.IO;
using System.Runtime.InteropServices;

unsafe class Game
{
    // For Linux:
    private const string dl = "c";    
    
    [DllImport(dl)]
    private static extern nint dlopen(sbyte *so_file, uint flags);

    [DllImport(dl)]
    private static extern nint dlsym(nint so_handle, sbyte *func_name);

    [DllImport(dl)]
    private static extern void dlclose(nint so_handle);

    [DllImport(dl)]
    private static extern sbyte *dlerror();

    public delegate void pfnChangeGame_t(sbyte *progname);
    public delegate int pfnMain_t(string[] args, string progname, int bchange_name, ref pfnChangeGame_t changegame_func); // delegate* unmanaged<sbyte *, void> 
    public delegate void pfnShutdown_t();
    private pfnMain_t pfnMain_p;
    private pfnShutdown_t pfnShutdown_p;

    private string szGameDir = string.Empty;
    private string[] szArgs = Environment.GetCommandLineArgs();

    private const string GAMEDIR = "valve";

    private string GetError()
    {
        return CharPtrToString(dlerror());
    }

    private nint handle, pfn_func;
    private T GetProcAddress<T>(string function, bool throwIfNotFound = false)
    {
        handle = nint.Zero;
        pfn_func = nint.Zero;
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
        {
            handle = dlopen(StringFromHeap(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Engine.so")), 0x00002);
            if (handle == nint.Zero)
            {
                Console.WriteLine("Unable to load {0}", GetError());
            }

            pfn_func = dlsym(handle, StringFromHeap(function));
            if (pfn_func == nint.Zero)
            {
                Console.WriteLine("Unable to pass function name {0}", GetError());
            }
        }

        if (pfn_func == nint.Zero)
        {
            if (throwIfNotFound)
            {
                throw new EntryPointNotFoundException(function);
            }
            return default(T);
        }

        return Marshal.GetDelegateForFunctionPointer<T>(pfn_func);
    }

    private void FreeLibrary()
    {
        if (handle != nint.Zero)
        {
            dlclose(handle);
        }
    }

    private string strncpy(ref string dst, string src, int num)
    {
        return dst=src.Substring(0,num);
    }


    private void Sys_LoadEngine()
    {
        pfnMain_p = GetProcAddress<pfnMain_t>("Host_Main");
        if (pfnMain_p == null)
        {
            Console.WriteLine("Error pass method is failed: {0}", GetError());
        }

        pfnShutdown_p = GetProcAddress<pfnShutdown_t>("Host_Shutdown");
    }

    private void Sys_UnloadEngine()
    {
        if (pfnShutdown_p != null)
        {
            pfnShutdown_p();
        }

        if (Convert.ToBoolean(handle))
        {
            FreeLibrary();
        }

        pfnMain_p = null;
	    pfnShutdown_p = null;
    }

    private void Sys_ChangeGame(sbyte *progname)
    {
        if (progname == null)
        {
            Console.Error.WriteLine("Error: Sys_ChangeGame is null.");
        }

        if (pfnShutdown_p == null)
        {
            Console.Error.WriteLine("Error: Sys_ChangeGame: missed 'Host_Shutdown'.");
        }

        strncpy(ref szGameDir, CharPtrToString(progname), szGameDir.Length - 1);
        Sys_UnloadEngine();
        Sys_LoadEngine();
        pfnChangeGame_t pfnChangeGame = Sys_ChangeGame;
        pfnMain_p(szArgs, szGameDir, 1, ref pfnChangeGame);
    }

    public int Sys_Start()
    {
        Sys_LoadEngine();
        pfnChangeGame_t pfnChangeGame = Sys_ChangeGame;
        int returnCode = pfnMain_p(szArgs, GAMEDIR, 0, ref pfnChangeGame);
        Sys_UnloadEngine();
        return returnCode;
    }

    static int Main(string[] args)
    {
        return new Game().Sys_Start();
    }
}

as into GameLauncher but used PublishSingleFile and PublishTrimmed

And I would like to pass Host_Main and Host_Shutdown from native library Engine

namespace Engine;

using System;
using System.IO;
using System.Runtime.InteropServices;

using static DeafMan1983.ConvFunctions;
using DeafMan1983.Interop.SDL3;
using static DeafMan1983.Interop.SDL3.SDL3;

public unsafe class Host
{
    private static delegate* unmanaged<sbyte *, void> pChangeGame = null;







    // ------------------------------------------------
    // Host_Main()
    // ------------------------------------------------
    [UnmanagedCallersOnly(EntryPoint = "Host_Main")]
    public static int Host_Main(sbyte** args, sbyte* progname, int bchange_name, delegate* unmanaged<sbyte *, void> func)
    {
        Console.WriteLine("Hello Half-Life Engine, lol!");

        pChangeGame = func;

        string progname_str = CharPtrToString(progname);
        if (progname_str == "valve");
        {
            if (!Directory.Exists(progname_str))
            {
                Console.WriteLine("Not found, Please re-update Steam!");
            }
            else
            {
                Console.WriteLine("Woohoo, Half-Life Sharp (x64)");
                Console.WriteLine("Engine.so, Client.so and Server.so are written in C#\n" +
                "and are compiled in NativeAot with Shared. :-D");
            }
        }



        return 0;
    }








    // ------------------------------------------------
    // Host_Shutdown()
    // ------------------------------------------------
    [UnmanagedCallersOnly(EntryPoint = "Host_Shutdown")]
    public static void Host_Shutdown()
    {
        Environment.Exit(0);
    }

}








    // ------------------------------------------------
    // Host_Shutdown()
    // ------------------------------------------------
    [UnmanagedCallersOnly(EntryPoint = "Host_Shutdown")]
    public static void Host_Shutdown()
    {
        Environment.Exit(0);
    }
}

Engine uses PublishAot, StripSymbols and InvariantGlobalization
Result:
image
How do I replace Marshal.GetDelegateFunctionPointer<T> to delegate *unamaged[cdel]<>?

Author: DeafMan1983
Assignees: -
Labels:

area-System.Runtime.InteropServices, untriaged, needs-area-label

Milestone: -

@jkotas jkotas added question Answer questions and provide assistance, not an issue with source code or documentation. and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels May 3, 2023
@DeafMan1983
Copy link
Author

Wait please! @jkotas - I think you understand wrong It is not FunctionPointer just Delegate as GetDelegateForFunctionPointer<T>()

See issue #13197

@DeafMan1983 DeafMan1983 changed the title Please support for Marshal.GetDelegateFunctionPointer<T>() in Dotnet 7.0 / 8.0 Please support for Marshal.GetDelegateForFunctionPointer<T>() in Dotnet 7.0 / 8.0 May 3, 2023
@DeafMan1983
Copy link
Author

DeafMan1983 commented May 3, 2023

Hey thanks I have resolved it. But I don't know if it works unmanaged or without unmanaged from GameLauncher if I use for pfnChangeGame(string) -> delegate unmanaged<sbyte *, void> pfnChangeGame

And It looks like my Game.cs without Marshal.GetDelegateForFunctionPointer<T>()

namespace valve;

using static DeafMan1983.ConvFunctions;
using DeafMan1983.Interop.SDL3;
using static DeafMan1983.Interop.SDL3.SDL3;

using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

unsafe class Game
{
    // For Linux:
    private const string dl = "c";    
    
    [DllImport(dl)]
    private static extern void *dlopen(sbyte *so_file, uint flags);

    [DllImport(dl)]
    private static extern void *dlsym(void *so_handle, sbyte *func_name);

    [DllImport(dl)]
    private static extern void dlclose(void *so_handle);

    [DllImport(dl)]
    private static extern sbyte *dlerror();

    public static delegate *<string[], sbyte *, int, delegate* unmanaged[Cdecl]<sbyte *, void>, int> pfnMain_p_new;
    public static delegate* <void> pfnShutdown_p_new;

    private static string szGameDir = string.Empty;
    private static string[] szArgs = Environment.GetCommandLineArgs();

    private static string GAMEDIR = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "valve");

    private static string GetError()
    {
        return CharPtrToString(dlerror());
    }
    private static void *hEngine;

    private static void FreeLibrary()
    {
        if (hEngine != null)
        {
            dlclose(hEngine);
        }
    }

    private static string strncpy(ref string dst, string src, int num)
    {
        return dst=src.Substring(0,num);
    }


    private static void Sys_LoadEngine()
    {
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
        {
            hEngine = dlopen(StringFromHeap(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Engine.so")), 0x00002);
            if (hEngine == null)
            {
                Console.WriteLine("Unable to load {0}", GetError());
            }

            pfnMain_p_new = (delegate *<string[], sbyte *, int, delegate* unmanaged[Cdecl]<sbyte *, void>, int> )dlsym(hEngine, StringFromHeap("Host_Main"));
            if (pfnMain_p_new == null)
            {
                Console.WriteLine("Unable to pass function name {0}", GetError());
            }
        }

        pfnShutdown_p_new = (delegate *<void>)dlsym(hEngine, StringFromHeap("Host_Shutdown"));
    }

    private static void Sys_UnloadEngine()
    {
        if (pfnShutdown_p_new != null)
        {
            pfnShutdown_p_new();
        }

        if (hEngine != null)
        {
            FreeLibrary();
        }

        pfnMain_p_new = null;
	    pfnShutdown_p_new = null;
    }

    [UnmanagedCallersOnly(CallConvs = new[] {typeof(CallConvCdecl)})]
    private static void Sys_ChangeGame(sbyte *progname)
    {
        if (progname == null)
        {
            Console.Error.WriteLine("Error: Sys_ChangeGame is null.");
        }

        if (pfnShutdown_p_new == null)
        {
            Console.Error.WriteLine("Error: Sys_ChangeGame: missed 'Host_Shutdown'.");
        }

        strncpy(ref szGameDir, CharPtrToString(progname), szGameDir.Length - 1);
        Sys_UnloadEngine();
        Sys_LoadEngine();
        delegate* unmanaged[Cdecl]<sbyte *, void> pfnChangeGame = &Sys_ChangeGame;
        pfnMain_p_new(szArgs, StringFromHeap(szGameDir), 1, pfnChangeGame);
    }

    public static int Sys_Start()
    {
        Sys_LoadEngine();
        delegate* unmanaged[Cdecl]<sbyte *, void> pfnChangeGame = &Sys_ChangeGame;
        int returnCode = pfnMain_p_new(szArgs, StringFromHeap(GAMEDIR), 0, pfnChangeGame);
        Sys_UnloadEngine();
        return returnCode;
    }

    static int Main(string[] args)
    {
        return Sys_Start();
    }
}

And Engine as native library looks like normal or wrong if I know communicate with pfnChangeGame from Host_Main()
and passes current base directory like Quake3/baseq3 or Half-Life/valve for example:

namespace Engine;

using System;
using System.IO;
using System.Runtime.InteropServices;

using static DeafMan1983.ConvFunctions;
using DeafMan1983.Interop.SDL3;
using static DeafMan1983.Interop.SDL3.SDL3;

public unsafe class Host
{
    private static delegate* <string, void> pChangeGame = null;

    // ...

    // ------------------------------------------------
    // Host_Main()
    // ------------------------------------------------
    [UnmanagedCallersOnly(EntryPoint = "Host_Main")]
    public static int Host_Main(sbyte** args, sbyte* progname, int bchange_name, delegate* <string, void> func)
    {
        Console.WriteLine("Hello Half-Life Engine, lol!");

        pChangeGame = func;

        if (Directory.Exists(CharPtrToString(progname)))
        {
            Console.WriteLine("Not found, Please re-update Steam!");
        }
        else
        {
            Console.WriteLine("Woohoo, Half-Life Sharp (x64)");
            Console.WriteLine("All executables and libraries are written in C#\n" +
            "and are compiled in NativeAot with Shared. :-D");
        }



        return 0;
    }

    // ------------------------------------------------
    // Host_Shutdown()
    // ------------------------------------------------
    [UnmanagedCallersOnly(EntryPoint = "Host_Shutdown")]
    public static void Host_Shutdown()
    {
        Environment.Exit(0);
    }
}

I expect that like SDL_GL_GetProcAddress is void pointer ( void* )
If you case with (delegate *<float, float, float, float, void>) it becomes glViewport()

It looks modern delegate with unmanagable way. It seems works better then old dead GetDelegateForFunctionPointer

Bye bye GetDelegateForFunctionPointer!

Welcome delegate with unmanagable way.

Result: if you find wrong or correct with pfnChangeGame as unmanaged or not unmamanged?
image

Thanks for supporting and saving me! I am very excited to release game written in C#. :D

@jkotas
Copy link
Member

jkotas commented May 3, 2023

I don't know if it works unmanaged or without unmanaged

You do want to have unmanaged everywhere. The function pointers without unmanaged are for the calling methods within the same C# app without interop boundary, it is not the case here.

public static delegate* pfnShutdown_p_new;

This should be delegate* unmanaged<void> or delegate* unmanaged[CDecl]<void>

delegate* <string, void> pChangeGame = null;

This should be delegate* [CDecl]<sbyte*, void>.

@DeafMan1983
Copy link
Author

Thanks I will try later. Can I close it?

@ghost ghost removed the untriaged New issue has not been triaged by the area owner label May 3, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Jun 3, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Runtime.InteropServices question Answer questions and provide assistance, not an issue with source code or documentation.
Projects
None yet
Development

No branches or pull requests

2 participants