I'm trying to call SDL_LoadWAV using a pre-made binding to the C library from which it comes. SDL_LoadWAV is just a wrapper for SDL_LoadWAV_RW:
function SDL_LoadWAV
(file : C.char_array;
spec : access SDL_AudioSpec;
audio_buf : System.Address;
audio_len : access Uint32) return access SDL_AudioSpec
is
begin
return SDL_LoadWAV_RW
(SDL_RWFromFile (file, C.To_C ("rb")),
1,
spec,
audio_buf,
audio_len);
end SDL_LoadWAV;
Here is the prototype of the function in C:
SDL_AudioSpec* SDL_LoadWAV_RW(SDL_RWops* src,
int freesrc,
SDL_AudioSpec* spec,
Uint8** audio_buf,
Uint32* audio_len)
(See here for more information)
Now as you can see, it passes a Uint8 (unsigned 8-bit integer) array by reference, in the form of a Uint8**. This is causing me a great bit of vexation. Here is the appropriate binding:
function SDL_LoadWAV_RW
(src : access SDL_RWops;
freesrc : C.int;
spec : access SDL_AudioSpec;
audio_buf : System.Address;
audio_len : access Uint32) return access SDL_AudioSpec;
pragma Import (C, SDL_LoadWAV_RW, "SDL_LoadWAV_RW");
As you can see, the binding maps the Uint8** to a System.Address. I've tried a couple of tricks to get that data where I want it to go, but nothing seems to work. Right now, my code looks like this (it has a few custom types and exceptions in it):
type Music is new Resource with
record
--Id : Integer; (Inherited from Resource)
--Filename : Unbounded_String; (Inherited from Resource)
--Archive_Name : Unbounded_String; (Inherited from Resource)
--Zzl_Size : Integer; (Inherited from Resource)
Audio : access SDL_AudioSpec_Access;
Length : aliased Uint32;
Buffer : System.Address;
Position : Integer := 1;
end record;
overriding procedure Load(Mus : in out Music) is
Double_Pointer : System.Address;
begin
Log("Loading music " & To_Ada(Get_Audio_Filepath(Mus)));
Audio_Load_Lock.Seize;
if null = SDL_LoadWAV(Get_Audio_Filepath(Mus), Mus.Audio.all, Double_Pointer, Mus.Length'access) then
raise Audio_Load_Failed with To_String(Mus.Filename) & "&Stack=" & Get_Call_Stack;
end if;
Log("Music length =" & Integer'Image(Integer(Mus.Length)));
declare
type Sample_Array is array(1..Mus.Length) of Uint8;
Single_Pointer : System.Address;
for Single_Pointer'address use Double_Pointer;
pragma Import(Ada, Single_Pointer);
Source : Sample_Array;
for Source'address use Single_Pointer;
pragma Import(Ada, Source);
Dest : Sample_Array;
for Dest'address use Mus.Buffer;
pragma Import(Ada, Dest);
begin
Dest := Source;
end;
Audio_Load_Lock.Release;
end Load;
But, like more or less everything else I've tried, I get a PROGRAM_ERROR/EXCEPTION_ACCESS_VIOLATION when the Load function is executed.
Can anyone figure out how I need to handle this System.Address? Thanks!
The definition of SDL_LoadWAV_RW
says
This function, if successfully called, returns a pointer to an SDL_AudioSpec structure filled with the audio data format of the wave source data. audio_buf is filled with a pointer to an allocated buffer containing the audio data, and audio_len is filled with the length of that audio buffer in bytes.
which means that the called function allocates the required memory and fills it, then returns pointers to the allocated memory and its length.
So the binding you’ve been supplied with isn’t very good Ada.
audio_buf
should be an out
parameter to an array of bytes, audio_len
an out
parameter to a Uint32
.
As a demo, using this C:
#include <stdlib.h>
void get_data (char **buf, int *len)
{
*len = 10;
*buf = malloc(*len);
for (int j = 0; j < *len; j++) {
(*buf)[j] = j;
}
}
this Ada
type Raw is array (Interfaces.Unsigned_32) of Interfaces.Unsigned_8
with Convention => C;
defines an array type (which would occupy 2^32-1 bytes if we actually declared one!), and this
type Raw_P is access all Raw
with Convention => C, Storage_Size => 0;
defines a pointer to such an array. Limiting the storage size to 0 means that we can’t say new Raw_P
.
Putting these together,
with Ada.Text_IO; use Ada.Text_IO;
with Interfaces;
procedure Demo is
type Raw is array (Interfaces.Unsigned_32) of Interfaces.Unsigned_8
with Convention => C;
type Raw_P is access all Raw
with Convention => C, Storage_Size => 0;
procedure Get_Data (In_Buffer : out Raw_P;
Length : out Interfaces.Unsigned_32)
with
Import,
Convention => C,
External_Name => "get_data";
Allocated : Raw_P;
Length : Interfaces.Unsigned_32;
use type Interfaces.Unsigned_32;
begin
Get_Data (In_Buffer => Allocated,
Length => Length);
for J in 0 .. Length - 1 loop
Put (Allocated (J)'Image);
end loop;
New_Line;
end Demo;
gives a program which when run results in
$ ./demo
0 1 2 3 4 5 6 7 8 9
$
Recognising that you’re probably stuck with
audio_buf : System.Address;
you could define (or use, if already defined) something like my Raw
, Raw_P
and say
procedure Get_Data (In_Buffer : System.Address;
Length : out Interfaces.Unsigned_32)
with
Import,
Convention => C,
External_Name => "get_data";
and then use
Get_Data (In_Buffer => Allocated'Address,
Length => Length);