如何将 C 样式数组修改为 D 样式数组?

How can I modify a C-style array as D-style array?

问题

接受C风格数组作为参数,将其修改为D风格数组(包括更改长度),并return将其作为C风格数组的最佳方式是什么-样式数组?


在上下文中

我正在用 D 编写一个 库,它编译为具有 C 接口的 DLL(我将从 C++ 调用我的 D DLL,因此 C 接口是必要的)。 它接受 byte 个数组并修改它们的内容,有时会更改数组长度

因为我使用的是 C 接口,所以我的函数必须接受 C 风格的数组。理想情况下,如果给定的缓冲区太小,我希望能够分配更多内存(即扩展bufferMaxSize)。

这就是我的 D DLL 现在接受参数的方式:

// D library code; compiles to DLL with C interface.
// bufferSize is the data length, and is a pointer because I may modify the data length.
// bufferMaxSize is the total allocated buffer size.
export extern(C) void patchData(const size_t bufferMaxSize, size_t * bufferSize, byte * buffer) { ... }

在我的 D 库中,我有接受 D 样式数组的现有代码。 沿线的某个地方,必须将 C 样式数组转换为 D 样式数组

我目前正在进行这样的转换(简化示例):

// D library code; compiles to DLL with C interface.
export extern(C) void patchData(const size_t bufferMaxSize, size_t * bufferSize, byte * buffer) {
    // Convert from C-style array to D-style.
    byte[] dStyleArray = buffer[0 .. *bufferSize];

    // Modify data.
    dStyleArray[0] = cast(byte) 0xab;
    dStyleArray[1] = cast(byte) 0xbc;

    dStyleArray.length = dStyleArray.length + 16;

    // Return modified data as C-style array.
    buffer[0 .. dStyleArray.length] = dStyleArray[0 .. dStyleArray.length];
    *bufferSize = dStyleArray.length;
}

它有效,但我不确定这里到底发生了什么。我主要关心的是速度。如果我循环执行此函数,我不想不断分配新内存并来回复制其内容

当我执行 byte[] dStyleArray = buffer[0 .. *bufferSize] 时,D 是在分配新的内存块并将所有内容复制到 D 样式数组中,还是指向已分配的 C 样式数组?

当我执行 dStyleArray.length = dStyleArray.length + 16 时发生了什么?由于 dStyleArray 是从 buffer 切分出来的,我现在要分配新的 memory/copying 内存吗?或者我扩展到 buffer?

当我buffer[0 .. dStyleArray.length] = dStyleArray[0 .. dStyleArray.length];时,我复制内存,对吗?

是否可以只"bind"将 D 样式数组转换为 C 样式数组,并通过 D 样式数组的接口访问预分配的内存?

When I do byte[] dStyleArray = buffer[0 .. *bufferSize], is D allocating a new chunk of memory and copying everything into the D-style array, or is it pointing to the already-allocated C-style array?

它只是指向它。右侧的切片运算符(概念上)与 array.pointer = &first_element; array._length = length; 相同 - 非常快速和简单的操作。 (我称它为 _length 而不是 length 顺便说一句,因为设置长度 属性 实际上可能会调用一个函数,这是下一个。)

What's going on when I do dStyleArray.length = dStyleArray.length + 16?

那将分配新的内存。当长度被扩展时,除非运行时可以证明它是安全的(或者你告诉它假设它是安全的并且它知道它来自 GC),否则该数组将被复制到一个新位置。它基本上在指针上调用 realloc() - 虽然不是字面意思,但它与 C realloc 不兼容。

由于它来自 C,运行时只知道它不拥有内存,它以某种方式在别处管理,并且在尝试扩展时总是会分配一个新内存。如果你想通过其他方式扩展,你需要自己做。

When I do buffer[0 .. dStyleArray.length] = dStyleArray[0 .. dStyleArray.length];, I am copying memory, right?

对,确实复制了,因为您在左侧切片。

Is it possible to just "bind" a D-style array to a C-style array, and access pre-allocated memory through the D-style array's interface?

普通的右手切片:

auto d_array = c_array[0 .. c_array_length];

处理除长度扩展之外的所有内容。它保留指针,因此写入元素会立即影响原始内容。 (顺便说一句,因为它是共享的 C 内存,请确保在 D 仍在使用它时不要 free 它!只要你只在这个函数中使用它就可以了,但不要将切片存储在任何地方.)

如果确实需要加长,需要自己动手。我喜欢这样做的方式是对整个潜在数组进行切片,满容量,然后再次切片以获得有限的容量 window.

所以可能:

auto whole_array = buffer[0 .. bufferMaxSize]; // assuming buffer is already fully allocated on the C side
auto part_youre_using = whole_array[0 .. *bufferSize];

// to extend:
*bufferSize += 16; // extend the size
part_your_using = whole_array[0 .. *bufferSize]; // and reslice from the original

我做 whole_array 而不是重新切片 buffer 的原因是 D 可以为我捕获越界行为。它不会在裸指针上执行此操作,但会在切片指针上执行此操作,因为它知道最大大小作为其长度。

如果您需要扩展缓冲区,请使用正确的 C 函数来执行此操作,例如 realloc 或其他任何内容,然后再次切出 whole_array 和 part_youre_using。

When I do byte[] dStyleArray = buffer[0 .. *bufferSize], is D allocating a new chunk of memory and copying everything into the D-style array, or is it pointing to the already-allocated C-style array?

它在指着。 Phobos 使用相同的技巧将 C "string" 转换为 D: https://github.com/D-Programming-Language/phobos/blob/67c95e6de21d5d627e3c57128b4d6e332c82f785/std/string.d#L208-L211

What's going on when I do dStyleArray.length = dStyleArray.length + 16? Since dStyleArray was sliced from buffer, am I allocating new memory/copying memory now? Or did I extend into buffer?

这可能不是您想要/期望的。它将在 garbage collected 内存上分配一个新块,并将内容复制到其中。它无法扩展它,因为运行时没有关于内存块的任何信息(它不管理它)。您真的要扩展缓冲区,还是要移动指针(这将在 D 中切片)?

When I do buffer[0 .. dStyleArray.length] = dStyleArray[0 .. dStyleArray.length];, I am copying memory, right?

是的。这已降低为 memcpy。

Is it possible to just "bind" a D-style array to a C-style array, and access pre-allocated memory through the D-style array's interface?

是的,你一开始就是这么做的 ;)

如果您只想更改数组的前 2 个元素,只需进行绑定并更改它们,它就会 "just work"。

如果你想测试这个行为,我建议你在函数下面放一个 unittest 块,这样你就可以通过给它一个指针来测试会发生什么。此外,如果您想确保没有进行任何 GC 分配,您可能需要考虑将 @nogc 放在您的函数上以静态检查它(并且 nothrow 通常也是一个好主意C函数).

检查你的代码:

byte[] dStyleArray = buffer[0 .. *bufferSize];

这看起来不错。请注意,这不会分配内存。 D 数组本质上是一个指针和一个长度,所以这一行等同于 伪代码:

struct DByteArray { byte* ptr; size_t length; }
DByteArray dStyleArray;
dStyleArray.ptr = buffer;
dStyleArray.length = *bufferSize;

从这里开始,访问 dStyleArray 的元素将访问 buffer 指向的相同数据,这意味着:

dStyleArray[0] = cast(byte) 0xab;
dStyleArray[1] = cast(byte) 0xbc;

也会"modify buffer"。

更进一步:

dStyleArray.length = dStyleArray.length + 16;

增加 D 动态数组的 length 将导致重新分配。此处的 D 运行时会将 dStyleArray 切片的内存复制到新分配的内存块中。如果你想让 dStyleArray 指向第一个 byte 但有更长的 length,你必须再次切片指针:

dStyleArray = dStyleArray.ptr[0 .. dStyleArray.length + 16];

或:

dStyleArray = buffer[0 .. *bufferSize + 16];

然后,行:

buffer[0 .. dStyleArray.length] = dStyleArray[0 .. dStyleArray.length];

(正如你猜的那样复制内存)变得多余,因为两者指向同一个内存块。

Is it possible to just "bind" a D-style array to a C-style array, and access pre-allocated memory through the D-style array's interface?

是的,这正是指针切片的作用。