On the making of Raylib bindings

Hi there,

I am working on some bindings for raylib 6.0. I know there exist already some bindings, but these looked a bit abandoned so I decided to kickstart my own and hopefully get them to a usable state in the near future.

I am using some generative AI (gemini) to generate the bulk. Some functions however have/will need manual intervention.

I have some questions which hopefully if answered will allow me to complete these bindings. Here I go:

1

I am taking the approach of having in one side the plain c bindings (raylib_c.ads) and then the “thick” bindings in (raylib.ads, raylib.adb). I expose some “thick” types that then get converted into the C counterparts in the bindings.

The structures are simple when taking this approach, so that the conversion is fast. For more complex cases I intend to have other approaches as show in my following questions.

Should I consider other strategies?

2

What is the cleanest way to bind a function that takes a pointer (and usually a count) to several elements corresponding to a C type? This is because it would be rather nicer to have the binding take an Ada array of Ada types (note that the type inside the array is not of the C type) and then pass it somehow to the C function.

e.g. in the following, have Check_Collision_Point_Poly take an array of Vector2 for Points

-- be advised that this may not compile
   function Check_Collision_Point_Poly (Point : Vector2; Points : System.Address; Point_Count : Integer) return Boolean is
   begin
      return To_Ada (Raylib_C.CheckCollisionPointPoly (To_C (Point), Points, To_C (Point_Count)));
   end Check_Collision_Point_Poly;

3

How can I make the build system configurable for different platforms (basically linux, openbsd and other UNIX-like OSes for the time being) and adding options such as an additional linking location or the location of libraylib.a for example.


I share here the project which has the Unlicense license. If someome wants to collaborate on finishing this, he/she is happily welcome. I am also new to Ada, so more senior advice is really appreciated.

Cheers
–zen

Yes, that is the way. In thick bindings you replace char * to String, procedure to functions returning objects, wrap resources into controlled types, replace int results with exceptions.

You wrap the C vector type into an opaque Ada type with operations to access elements.

Add Target_OS scenario to the gpr file. Select linker options according to the scenario.

You can also make gpr file for an external library. For that you use for Externally_Built use "true";Then you simply with it in your project. Here is a complicated case of the ODBC library which has different names under different systems:

project ODBC is

   type ODBC_Driver_Type is ("ODBC32", "unixODBC", "auto");
   ODBC_Driver : ODBC_Driver_Type := external ("odbc", "auto");
   
   type OS_Type is
        (  "Windows", "Windows_NT",
           "Linux",
           "UNIX",
           "OSX",
           "FreeBSD",
           "auto"
        );
   Target_OS : OS_Type := external ("Target_OS", "auto");
 
   Target_Triplet := Project'Target;

   case Target_OS is
      when "auto" =>
         case Target_Triplet is
            when "aarch64-linux-gnu"   |
                 "arm-linux"           |
                 "arm-linux-gnueabi"   |
                 "arm-linux-gnueabihf" |
                 "x86_64-linux"        |
                 "x86_64-linux-gnu"    |
                 "x86_64-redhat-linux" |
                 "x86_64-suse-linux"   |
                 "x86-linux"           |
                 "x86-suse-linux"      |
                 "i686-linux-gnu"      |
                 "i686-redhat-linux"   |
                 "i686-suse-linux"     =>
               Target_OS := "Linux";
            when "x86_64-apple-darwin" |
                 "x86_64-darwin"       |
                 "x86-darwin"          =>
               Target_OS := "OSX";
            when "x86_64-freebsd"      |
                 "i686-freebsd"        =>
               Target_OS := "FreeBSD";
            when "x86_64-pc-cygwin"    |
                 "x86_64-w64-mingw32"  |
                 "x86_64-windows"      |
                 "x86-windows"         |
                 "i686-pc-cygwin"      |
                 "i686-pc-mingw32"     |
                 "i686-w64-mingw32"    =>
               Target_OS := "Windows";
            when "" =>
               for Source_Files use
                   (  "target is undefined, "
                   &  "scenario auto cannot be used"
                   );
            when others =>
               for Source_Files use
                   (  "target "
                   &  Target_Triplet
                   &  " is unknown, scenario auto cannot be used"
                   );
         end case;
      when others =>
         null;
   end case;

   case ODBC_Driver is
      when "auto" =>
         case Target_OS is
            when "Linux" | "OSX" | "FreeBSD" | "UNIX" =>
               ODBC_Driver := "unixODBC";
            when "Windows" | "Windows_NT" =>
               ODBC_Driver := "ODBC32";
            when "auto" =>
               for Source_Files use
                   (  "target is undefined, "
                   &  "scenario auto cannot be used"
                   );
         end case;
      when others =>
         null;
   end case;
 
   case ODBC_Driver is
      when "unixODBC" | "auto" =>
         for Externally_Built use "true";
         for Source_Files use ();
         for Library_Dir use ".";
         for Library_Name use "odbc";
         for Library_Kind use "dynamic";
      when "ODBC32" =>
         for Externally_Built use "true";
         for Source_Files use ();
         for Library_Dir use ".";
         for Library_Name use "odbc32";
         for Library_Kind use "dynamic";
   end case;
end ODBC;

It tries to figure out the target OS (which gprbuild should rather provide out of the box, but for misterious reasons does not).

Thanks for the response :hugs:

I am concerned that it may be big overhead doing the whole transformation if the arrays are large. Is there some reasonable approach so that copying each time can be avoided?

That is why you wrap it in an Ada type. There is no copying:

   type Vector is tagged private;
   ... opeations
private
   type Vector is new Ada.Finalization.Controlled with record
      Ptr : C_Vector_Ptr;
   end record;

FYI, I have updated my low-level bindings to Raylib 6.0 see Raylib 6.0 bindings - #5 by Fabien.C

That is nice! My objective with the new bindings is to have some more abstraction using the Ada typing capabilities (similar to sdlada), so I think I will continue on the journey.

Cheers
– zen