buffer - 在 Ada 中使用具有非均匀字节跳跃的数据缓冲区的惯用方法

标签 buffer ada memory-address

我正在尝试从 Ada 中的字节缓冲区读取数据,例如文件或通过网络连接的缓冲区。消息的大小是可变的,具有公共(public) header ,在 C++ 中,它看起来像这样:

enum class MessageType : uint16_t {
    Foo = 0, Bar = 1
};

// Force the layout.
#pragma pack(push,1)

// 4 byte message header
struct MessageHeader {
    uint16_t checksum;
    uint16_t type;
};

// 4 byte header + 4 byte message
struct FooMessage {
    MessageHeader header;
    uint32_t tomatoes;
};

// 4 byte header + 8 byte message
struct BarMessage {
    MessageHeader header;
    uint32_t oranges;
    uint32_t apples;
};
#pragma pack(pop)

// For simplicity, assume the buffer is complete and only holds full messages.
void read(char* buffer, uint32_t bytesLeft) {
    while (bytesLeft > 0) {
        MessageHeader* header = reinterpret_cast<MessageHeader*>(buffer);
        switch (header->type) {
            case FooType: {
                FooMessage* foo = reinterpret_case<FooMessage*>(buffer);
                // process as const FooMessage&
                processFoo(*foo);
            }
            case BarType: {
                BarMessage* bar = reinterpret_cast<BarMessage*>(buffer);
                // process as const BarMessage&
                processBar(*bar);
            }
        }
        const auto size = (header->type == Foo ? sizeof(FooMessage) : sizeof(BarMessage));
        buffer += size;
        bytesLeft -= size;
    }
}

我不确定这样做的惯用方式。请注意,在某些格式中,消息类型也可能不是 header 中的前导数据成员。您是否应该写入和读取 Character 数组或来自 Interfaces.C.char_array 的内容,或者来自 System.Address 的内存地址> 还是其他什么?或者这应该是这里其他地方的数组的地址,或者只是一个带有“Convention => C”的数组以防止包含前导大小?

这是我迄今为止在 Ada 中所拥有的:

type Message_Type is (Foo, Bar) with Size => 16;
for Message_Type use (Foo => 0, Bar => 1);

-- Assume these work correctly and I don't need to do bit layout directly.
type Message_Header is record
    Checksum : Interfaces.Integer_16;
    Msg_Type : Message_Type;
end record
    with Convention => C, Size => 32;

type Foo_Message is record
    Header   : Message_Header;
    Tomatoes : Interfaces.Integer_32;
end record
    with Convention => C, Size => 64;

type Bar_Message is record
    Header  : Message_Header;
    Oranges : Interfaces.Integer_32;
    Apples  : Interfaces.Integer_32;
end record
    with Convention => C, Size => 96;

procedure Read(
    -- System.Address seems really weird here
    Buffer     : in out System.Address;
    Bytes_Left : in out Interfaces.Integer_64)
is
    use type Interfaces.Integer_64;
    use type System.Address;
    function To_Address is new Ada.Unchecked_Conversion (Interfaces.Integer_64, System.Address);
    function To_Integer is new Ada.Unchecked_Conversion (System.Address, Interfaces.Integer_64);

    procedure Process_Bar (B : aliased Bar_Message) is null;
    procedure Process_Foo (F : aliased Foo_Message) is null;
begin
    while Bytes_Left > 0 loop
        declare
            -- I'm really lost here.
            --
            -- Do you use access types to access the buffer or
            -- setting the address with "for Foo'Address use Buffer"??
            --
            Header : Message_Header;
            for Header'Address use Buffer;
    enter code here
            -- I'm assuming this doesn't initialize Foo and Bar here?    
            Foo_Msg : aliased Foo_Message;
            Bar_Msg : aliased Bar_Message;
            for Foo_Msg'Address use Buffer;
            for Bar_Msg'Address use Buffer;
            -- I'm assuming this doesn't initialize Foo and Bar here?    
            Size : System.Address := To_Address(0);
        begin
            case Header.Msg_Type is
                when Foo => Process_Foo (Foo_Msg);
                when Bar => Process_Bar (Bar_Msg);
            end case;
            Size := To_Address (if Header.Msg_Type = Foo then Foo'Size else Bar'Size);

            -- There's probably a better way to do this.
            Buffer := To_Address(To_Integer (Buffer) + To_Integer (Size));
            Bytes_Left := Bytes_Left - To_Integer (Size);
        end;
    end loop;
end Read;

以可变方式跨缓冲区中的字节并读取数据的惯用方法是什么?

最佳答案

我会保持简单:只需在给定地址定义一个缓冲区数组:

Buf : System.Storage_Elements.Storage_Array (0 .. Bytes_Left - 1)
        with Address => Buffer;

然后逐条消息解析缓冲区。下面的示例提供了我如何解决这个问题的草图(免责声明:没有测试它)。

message_reader.ads

with System;
with System.Storage_Elements;
with Interfaces;

package Message_Reader is

   package SSE renames System.Storage_Elements;
   
   --  NOTE: Not using an enum type eases the implementation of the parser (I think).
   --        In particular for detecting unknown message types.

   type Message_Type is new Interfaces.Unsigned_16;   
   Message_Type_Foo : constant Message_Type := 0;
   Message_Type_Bar : constant Message_Type := 1;

   -- Assume these work correctly and I don't need to do bit layout directly.
   type Message_Header is record
      Checksum : Interfaces.Integer_16;
      Msg_Type : Message_Type;
   end record
     with Convention => C, Size => 32;

   type Foo_Message is record
      Header   : Message_Header;
      Tomatoes : Interfaces.Integer_32;
   end record
     with Convention => C, Size => 64;

   type Bar_Message is record
      Header  : Message_Header;
      Oranges : Interfaces.Integer_32;
      Apples  : Interfaces.Integer_32;
   end record
     with Convention => C, Size => 96;
   
   Unknown_Message_Type : exception;
   
   procedure Read
     (Buffer     : in     System.Address;
      Bytes_Left : in out SSE.Storage_Count);
   
private
   use type SSE.Storage_Count;

   pragma Compile_Time_Error 
     (System.Storage_Unit /= 8, "implementation expects a storage unit size of 8");
   
   Foo_Msg_Size_Bytes : constant SSE.Storage_Count := 
                          Foo_Message'Size / System.Storage_Unit;
   Bar_Msg_Size_Bytes : constant SSE.Storage_Count := 
                          Bar_Message'Size / System.Storage_Unit;
   
   procedure Process_Bar (B : Bar_Message) is null;
   procedure Process_Foo (F : Foo_Message) is null;
   
end Message_Reader;

message_reader.adb

with Ada.Unchecked_Conversion;

package body Message_Reader is   
   
   generic
      type Chunk_Type is private;      
   procedure Read_Chunk 
     (Buffer  : in     SSE.Storage_Array;
      Offset  : in     SSE.Storage_Offset;
      Chunk   :    out Chunk_Type;
      Success :    out Boolean);
   
   ----------
   -- Read --
   ----------

   procedure Read
     (Buffer     : in     System.Address;
      Bytes_Left : in out SSE.Storage_Count)
   is      
      Buf : SSE.Storage_Array (0 .. Bytes_Left - 1)
        with Address => Buffer;
      
      procedure Read_Header  is new Read_Chunk (Message_Header);
      procedure Read_Foo_Msg is new Read_Chunk (Foo_Message);
      procedure Read_Bar_Msg is new Read_Chunk (Bar_Message);
      
      Header  : Message_Header;
      Success : Boolean;
      
   begin
      loop
         Read_Header (Buf, Buf'Last - Bytes_Left - 1, Header, Success);         
         if not Success then
            exit;  --  Not enough data left in buffer.
         end if;
         
         case Header.Msg_Type is
                  
            when Message_Type_Foo =>
               declare
                  Foo : Foo_Message;
               begin
                  Read_Foo_Msg (Buf, Buf'Last - Bytes_Left - 1, Foo, Success);
                  if not Success then
                     exit;  --  Not enough data left in buffer.
                  end if;
                  
                  Bytes_Left := Bytes_Left - Foo_Msg_Size_Bytes; 
                  Process_Foo (Foo);
                  
               end;
                     
            when Message_Type_Bar =>
               declare
                  Bar : Bar_Message;
               begin
                  Read_Bar_Msg (Buf, Buf'Last - Bytes_Left - 1, Bar, Success);
                  if not Success then
                     exit;  --  Not enough data left in buffer.
                  end if;                  
                  
                  Bytes_Left := Bytes_Left - Bar_Msg_Size_Bytes;   
                  Process_Bar (Bar);
                  
               end;
                     
            when others =>
               raise Unknown_Message_Type;
                     
         end case;
      end loop;      
      
   end Read;
      
   ----------------
   -- Read_Chunk --
   ----------------

   procedure Read_Chunk 
     (Buffer  : in     SSE.Storage_Array;
      Offset  : in     SSE.Storage_Offset;
      Chunk   :    out Chunk_Type;
      Success :    out Boolean)
   is
      Chunk_Type_Bytes : constant SSE.Storage_Count := 
                            Chunk_Type'Size / System.Storage_Unit;
      
      subtype Chunk_Raw is SSE.Storage_Array (0 .. Chunk_Type_Bytes - 1);  
      
      function To_Chunk is new Ada.Unchecked_Conversion 
        (Source => Chunk_Raw, Target => Chunk_Type);
      
      Slice_First : constant SSE.Storage_Offset := Offset;
      Slice_Last  : constant SSE.Storage_Offset := Offset + Chunk_Type_Bytes - 1;
      
   begin      
      if Slice_Last <= Buffer'Last then
         Chunk := To_Chunk (Buffer (Slice_First .. Slice_Last));
         Success := True;
      else
         Success := False;
      end if;
      
   end Read_Chunk;   

end Message_Reader;

关于buffer - 在 Ada 中使用具有非均匀字节跳跃的数据缓冲区的惯用方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71976705/

相关文章:

c - 未在服务器端客户端-服务器 TCP 上接收单独的值

c++ - 复制指针值c++

ada - Adacore 提供的 LWIP 堆栈

c++ - 如何找到字符串的内存地址?

java - 是否可以创建我自己的 Graphics2D 并与 Applet 中的另一个组合?

Ada - 如何从单行输入中读取数组?

linux - 降级 gcc 版本后,Ada 编译器崩溃并显示 "Ada compiler not installed on this system."

c++ - 通过限定名访问成员数据的地址会产生错误

c - 目标文件如何在 C 中链接?解释这种特殊情况

c++ - gpsd 客户端数据缓冲区