chrisdill / raylib-cs

C# bindings for raylib, a simple and easy-to-use library to learn videogames programming
http://www.raylib.com/
zlib License
851 stars 74 forks source link

Generate binding and misc #272

Open Odex64 opened 1 month ago

Odex64 commented 1 month ago

Consider using raylib's definitions from the official repo to auto generate source files. This will make the latest APIs available in raylib-cs and drastically reduce the manual work of porting raylib's structs, enums and functions - make sure that all the structs are blittable.

Also consider these other changes

chrisdill commented 1 month ago

Looks like a nice way to generate bindings. Could probably handle most of the bindings. Would help with maintenance since the library is now passively maintained.

Open to updating .net and c# versions but only if we need to for specific features etc. As for LibraryImport, can you explain how the bindings would be better for AOT compared to the current approach?

Odex64 commented 1 month ago

Upgrading to .NET 8 should be fine for most users since it is LTS and defaults to C# 12. It it also required for the new p/invoke source generator (LibraryImport) to work.

As of LibraryImport there are the following benefits

If you want I can work on a generator in order to generate all the source files from raylib's definitions.

chrisdill commented 1 month ago

Still undecided but examples comparing/showing the pros and cons for both ideas would be useful either way.

Odex64 commented 1 month ago

Here's more details of pros and cons of both.

LibraryImport

Pros

  1. Source Generation: LibraryImport leverages source generators to auto-generate P/Invoke calls. This means that some code is generated at compile time, minimizing errors that can occur with manual marshalling and having a better debugging experience.

  2. Improved Performance: Generated code via LibraryImport can be more optimized for performance because the marshalling logic is generated at compile-time. This can lead to more efficient interop calls compared to runtime-based in DllImport.

  3. More Flexible Type Marshaling: LibraryImport provides better support for complex types and marshaling scenarios, making it easier to handle custom marshalling, though we don't really need this since all structs should be blittable.

  4. NativeAOT Support: LibraryImport is compatible with NativeAOT, allowing you to use source-generated P/Invoke with ahead-of-time compilation, which can significantly reduce application startup time and improve performance in some scenarios.

  5. Automatic String Marshalling: LibraryImport can automatically handle string marshalling for you, reducing the boilerplate for helper methods.

Cons

  1. Newer Feature: LibraryImport is relatively new (introduced in .NET 7), and not all legacy systems or projects may fully adopt or support it. It might not be as battle-tested as DllImport in every edge case.

  2. Potential Compatibility Issues: LibraryImport may not work with older versions of .NET (prior to .NET 7), limiting its use in projects that need to maintain compatibility with older .NET frameworks.

  3. Less Documentation and Examples: Since it's newer, there are fewer examples and documentation available compared to DllImport, which has been in use for a much longer time.


DllImport

Pros

  1. Widespread Adoption: DllImport is widely used, well-documented, and supported in all versions of .NET, making it a safe and reliable choice for P/Invoke across different projects and .NET versions.

  2. Simpler to Use: DllImport might be simpler to use compared to LibraryImport, which can be beneficial for certain scenarios.

  3. Broad Compatibility: Since it has been around for a long time, DllImport is supported by all versions of .NET, including .NET Framework, ensuring better backward compatibility.

Cons

  1. Manual Code: Developers need to handle marshaling manually, which can lead to errors in complex scenarios, such as dealing with complex data types or memory management issues.

  2. Performance Overhead: Runtime marshaling in DllImport may introduce performance overhead, particularly in scenarios where complex types need to be marshaled frequently.

  3. Lack of Compile-Time Checks: DllImport does not offer the same level of compile-time diagnostics as LibraryImport, meaning issues with interop signatures may only be detected at runtime.


In Practice

Let's take for example the InitWindow function in this project:

[DllImport("raylib", CallingConvention = CallingConvention.Cdecl)]
public static extern void InitWindow(int width, int height, sbyte* title);

However this is unsuitable for managed code, therefore there's an helper method for that:

public static void InitWindow(int width, int height, string title)
{
    using var str1 = title.ToUtf8Buffer();
    InitWindow(width, height, str1.AsPointer());
}

Whereas we can achieve the same thing with only two lines of code:

[LibraryImport(Name, StringMarshalling = StringMarshalling.Utf8)]
public static partial void InitWindow(int width, int height, string title);

Notice the StringMarshalling = StringMarshalling.Utf8, that's used to handle the string marshalling automatically in the generated code.

Now if you want to expose the unsafe version as well, you can do the following:

[LibraryImport("raylib", StringMarshalling = StringMarshalling.Utf8)]
public static partial void InitWindow(int width, int height, string title);

[LibraryImport("raylib")]
public static partial void InitWindow(int width, int height, sbyte* title);

Here we get the following benefits:

MrScautHD commented 1 month ago

if you generate the binding you need a github action to build it for every platform or having a Windows, Linux and Mac pc.

deathbeam commented 1 month ago

I was working on generator + new native bindings builder based on raylib zig build that I would want to eventually contribute to raylib-cs after i even out some remaining small issues, but thanks to zig being so easy to work with the natives support linux/osx/windows/wasm already together with support for cross-compilation so huge step up from what raylib-cs has now.

And for generator, the biggest issue is how raylib-cs is structured, e.g a lot of extra helper methods are spread out on the raylib structs, so im not sure how to deal with that. Ideally they should be just partial structs and the helper methods elsewhere, but moving all of them is about as big task as the effort it took to write the generator.

Anyway for anyone interested here is the code now for both bindings + generator:

https://github.com/deathbeam/Raylib.NET

And as i said the end goal is to PR it here ideally as im not interested in further fragmentation of CS raylib bindings (even though its mostly fault of the maintainers, raylib-cs included with its project structure, makes bigger contributions like this very hard).

EDIT:

So got the native generator to good state but the build.zig in 5.0 release is kinda unusable so raylib-cs would first need to get updated to 5.5, rip