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?