【问题标题】:How do I handle an int** in Ada?如何在 Ada 中处理 int**?
【发布时间】:2020-02-22 20:49:33
【问题描述】:

我正在尝试使用与它所来自的 C 库的预制绑定来调用 SDL_LoadWAV。 SDL_LoadWAV 只是 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;

这是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)

现在您可以看到,它通过引用以 Uint8** 的形式传递了一个 Uint8(无符号 8 位整数)数组。这让我非常恼火。这是适当的绑定:

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");

如您所见,绑定将 Uint8** 映射到 System.Address。我已经尝试了一些技巧来获取我想要的数据,但似乎没有任何效果。现在,我的代码看起来像这样(它有一些自定义类型和异常):

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;

但是,或多或少与我尝试过的所有其他方法一样,在执行 Load 函数时,我会收到 PROGRAM_ERROR/EXCEPTION_ACCESS_VIOLATION。

谁能弄清楚我需要如何处理这个 System.Address?谢谢!

【问题讨论】:

    标签: c pass-by-reference ada memory-address ffi


    【解决方案1】:

    definition of SDL_LoadWAV_RW

    如果调用成功,该函数会返回一个指向 SDL_AudioSpec 结构的指针,该结构填充了波源数据的音频数据格式。 audio_buf 填充了指向包含音频数据的已分配缓冲区的指针,而 audio_len 填充了该音频缓冲区的长度(以字节为单位)。

    这意味着被调用的函数分配所需的内存并填充它,然后返回指向已分配内存的指针及其长度。

    所以你得到的绑定不是很好,Ada。

    audio_buf 应该是字节数组的 out 参数,audio_len 应该是 Uint32out 参数。

    作为演示,使用这个 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;
      }
    }
    

    这个阿达

    type Raw is array (Interfaces.Unsigned_32) of Interfaces.Unsigned_8
    with Convention => C;
    

    定义了一个数组类型(如果我们实际声明了一个,它将占用 2^32-1 个字节!),而这个

    type Raw_P is access all Raw
    with Convention => C, Storage_Size => 0;
    

    定义了一个指向这样一个数组的指针。将存储大小限制为 0 意味着我们不能说new Raw_P

    把这些放在一起,

    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;
    

    给出一个程序,运行时会导致

    $ ./demo
     0 1 2 3 4 5 6 7 8 9
    $
    

    ----

    意识到你可能会被困住

    audio_buf : System.Address;
    

    你可以定义(或使用,如果已经定义)像我的RawRaw_P 并说

    procedure Get_Data (In_Buffer : System.Address;
                        Length    : out Interfaces.Unsigned_32)
    with
      Import,
      Convention    => C,
      External_Name => "get_data";
    

    然后使用

    Get_Data (In_Buffer => Allocated'Address,
              Length    => Length);
    

    【讨论】:

    • 你应该得到一枚奖牌,西蒙。令人困惑的问题,超级简单的解决方案。
    【解决方案2】:

    SDL_LoadWAV 将在您指定的位置写入一个指针(指向由 SDL 分配的新缓冲区),即未初始化变量 Double_Pointer 的值,它是一个随机地址 -> kaboom!

    在调用 SDL_LoadWAV 之前,您需要有类似的东西:

    Double_Pointer := SDL_Buffer'Address;
    

    SDL_Buffer 之前的定义如下:

    type Buffer_Access_Type is access all Buffer_Type;
    SDL_Buffer: Buffer_Access_Type;
    

    根据 API,您需要稍后使用 SDL_FreeWAV 释放 SDL_Buffer

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-15
      • 1970-01-01
      相关资源
      最近更新 更多