在Ada中使用具有非统一字节跳过的数据缓冲区的惯用方法



我正在尝试从Ada中的字节缓冲区读取,例如文件或通过网络连接的缓冲区。消息的大小是可变的,有一个共同的头,在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;
}
}

我不确定做这件事的习惯方法。注意,在某些格式中,消息类型也可能不是报头中的前导数据成员。你应该写入和读取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;

可以使用具有Unchecked_Union方面的记录。

type Message (Msg_Type : Message_Type) is record
Header : Message_Header;
case Msg_Type is
when Foo =>
Tomatoes : Interfaces.Integer_16;
when Bar =>
Oranges : Interfaces.Integer_32;
Apples  : Interfaces.Integer_32;
end case;
end record
with Unchecked_Union;

请注意,当使用Unchecked_Union时,无法访问鉴别符。

注意:TomatoesC代码和您提供的Ada代码中的大小不相同。

最新更新