Имеется:
- массив байт (полученный по сети, например)
- структура без динамических массивов, указателей, делегатов и тд
struct Foo
{
float[3] coord;
ulong time;
ulong idx;
}
Foo getFoo1(ubyte[] data) { return (cast(Foo[])cast(void[])data)[0]; }
Foo getFoo2(void[] data) { return (cast(Foo[])data)[0]; }
В этом случае нужно быть уверенным в том, что data.length == Foo.sizeof
,
иначе эта функция выбросит исключение о несовпадении размеров
массивов при cast.
В случае, если нужно принять несколько структур должно выполняться
условие data.length % Foo.sizeof == 0
.
// аккуратно: результирующий массив будет указывать на ту же область
// памяти что и data
Foo[] getFoo(ubyte[] data) { return cast(Foo[])cast(void[])data; }
Так же это работает в обратном порядке:
// аккуратно: выделяется память
ubyte[] getData(Foo foo) { return cast(ubyte[])cast(void[])[foo]; }
union TBytes(T)
{
void[T.sizeof] bytes;
T value;
this(const(void)[] data)
{
assert(data.length == bytes.length);
bytes[] = data[];
}
this()(auto ref const(T) v) { value = v; }
ubyte[] dupUbyte() { return cast(ubyte[])(bytes.dup); }
}
Переменная типа union хранит все свои поля на общей области памяти, т.е.
поля bytes
и value
будут физически лежать в одной области памяти.
Foo getFoo2(void[] data) { return TBytes!Foo(data).value; }
ubyte[] getData2(Foo foo) { return TBytes!Foo(foo).dupUbyte; }
По сути и то и то массивы байт.
К ubyte[]
можно обращаться поэлементно, к void[]
нельзя:
ubyte[] val1 = getSomeData1();
if (val1[42] == 15) { ... }
void[] val2 = getSomeData2();
// if (val2[42] == 15) { ... } -- ошибка
К void[]
приводится любой массив неявно:
void foo(void[] arr) { writeln(arr.length); }
void bar(ubyte[] arr) { writeln(arr.length); }
struct Baz { float[4] v; }
auto val = [Baz([1,2,3,4])];
foo(val); // выведет количество байт, а не количество элементов
// bar(val); -- ошибка
bar(cast(ubyte[])val);