CppSharp icon indicating copy to clipboard operation
CppSharp copied to clipboard

Create a C# cross platform binding file

Open droyer57 opened this issue 4 years ago • 6 comments

Is there a way to generate a C# cross platform binding file ? I want to create a GLFW C# binding, so it use libglfw.so.3.3 on linux and glfw3.dll on windows. MonoGame check the system at runtime and take the right library. Can I do something similarly with CppSharp ?

droyer57 avatar Apr 17 '21 18:04 droyer57

Hmm I am not sure if this is supported out of the box right now.

Can you send an example of how it should work? How does the code for the selection at runtime look like?

tritao avatar Apr 19 '21 10:04 tritao

It could be possible for C-library, but not for C++ library.

            [SuppressUnmanagedCodeSecurity, DllImport("MOAB", EntryPoint = "imoab_initialize", CallingConvention = __CallingConvention.Cdecl)]
            internal static extern int Initialize(int argc, sbyte** argv);

The key is DllImport(), dotnet core 5 support sofile without suffix like *.dll or *.so, just "MOAB" will match "libMOAB.so" and "MOAB.dll". For C-library, the exported name is standardized, so should be portable.

but DllImport()'s EntryPoint parameter is a C++ decorated/mangled name, it is not possible to be cross-platform. so the generated cs file will not portable between Windows and Linux.

So I have 2 projects for Windows and Linux respectively.

generated binding for Windows generated binding for Linux https://bitbucket.org/qingfengxia/moabsharp/src/dev/MOABSharp.Linux/MOABSharp.Linux.cs

qingfengxia avatar Apr 19 '21 11:04 qingfengxia

The problem is that DllImport cannot be set at runtime. It only accept const value. MonoGame does something like this :

private class Windows
{
    [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

    [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern IntPtr LoadLibraryW(string lpszLib);
}

private class Linux
{
    [DllImport("libdl.so.2")]
    public static extern IntPtr dlopen(string path, int flags);

    [DllImport("libdl.so.2")]
    public static extern IntPtr dlsym(IntPtr handle, string symbol);
}
public static IntPtr LoadLibrary(string libName)
{
    string assemblyLocation = Path.GetDirectoryName(typeof(FuncLoader).Assembly.Location) ?? "./";
    string libPath = Path.Combine(assemblyLocation, "runtimes", libName);

    if (CurrentPlatform.OS == OS.Windows)
        return Windows.LoadLibraryW(libPath);

    return Linux.dlopen(libPath, RTLD_LAZY);
}
private static IntPtr NativeLibrary = GetNativeLibrary();

private static IntPtr GetNativeLibrary()
{
    if (CurrentPlatform.OS == OS.Windows)
        return FuncLoader.LoadLibrary("glfw3.dll");

    return FuncLoader.LoadLibrary("libglfw.so.3.3");
}

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int d_glfwInit();
private static d_glfwInit _glfwInit = FuncLoader.LoadFunction<d_glfwInit>(NativeLibrary, "glfwInit");
public static int glfwInit() => _glfwInit();

I don't know if CppSharp can do the same.

droyer57 avatar Apr 19 '21 12:04 droyer57

For the time being it can't. The approach we've supported so far is at the time of deployment, that is, referencing a library with the same name and then deploying the specific one for the platform. That is, you generate one library for win and one for Linux, name each, for example, GLFWSharp.dll, use one for development and when you finally deploy, you choose the correct one for your target system.

ddobrev avatar Apr 20 '21 21:04 ddobrev

I believe it is possible to target on dotnet core 5 for the C ABI library

qingfengxia avatar Apr 21 '21 07:04 qingfengxia

If anyone is interested, I created a C# binding for both OpenGL and GLFW a long time ago. It has been used in some projects already. You can locate it at https://github.com/thebonejarmer/arqan.git. The approach I use is using preprocessor directives to determine the correct dll file at compile time and simply serving a nuget package per operating system.

I don't think you should try to bypass the problem of having to compile for two operating systems and therefore having to provide a version per operating systems. This could introduce more issues than you are solving. The fact is and remains that operating systems are different and at some point you need to stop providing cross-platform solutions and create a bridge between the native solution and the cross-platform one.Shared libraries are quite delicate and if you call them wrongly you'd be dealing with lots of AccessViolationExceptions or worse, none and just a plain crash with nothing but an hex code to work with. It only makes it unnecessarily more complex and most folks don't mind having to compile the same version per os anyway.

TheBoneJarmer avatar Apr 21 '21 15:04 TheBoneJarmer