Он просит место, куда складывать данные из сишной памяти. Просит потому, потому что не знает как узнать, сколько элементов вытащить из Си. В Си с такими метаданными, как длина куска памяти, все довольно сложно - ну типа, их нет.
В данном случае, т.к. это просто возвращаемое значение, требуемое место ему поставить нельзя, так как неоткуда.
Но, само по себе возвращение указателя на непонятно-куда из Си - довольно странное явление, это, мягко говоря, очень плохой стиль.
В таком случае ничего бы не осталось, кроме как возвращать указатель и работать с ним где-то в другом месте.
В случае, когда длина примерно известна, есть следующие варианты.
Если сишная функция возвращает массив статического размера.
Типа так:
static int buffer
[4] =
{1, 2, 3, 4};
int* return_array_size_4
()
{
return buffer;
}то, спецификация типа массива в Virgil тоже должна содержать этот размер:
(define-external-function
("return_array_size_4" (:snake-case))
()
((& (simple-array int (4)) :out)))Это раскрывается в определение лисповой функции с именем "return-array-size-4", у которой нет аргументов, и которая возвращает одномерный массив из 4 интов
(точный лисповый тип массива будет (simple-array (signed-byte 32) (4))) каждый раз как вызывается.
Имя rv-переменной, кстати, и rv-форма - опциональные аргументы, их можно не писать, тогда возвращаться будет значение, возвращаемое сишной функцией.
И, кстати, я таки рекомендую использовать simple-array вместо просто array, т.к. они работают на порядок быстрее.
Если длина массива неизвестна, НО, мы знаем что он нуль-терминирован
(как сишные строки, например. Но, Virgil умеет в таком стиле работать с данными разных размеров - хоть со структурами, например, главное чтобы размер внутреннего типа последовательности был фиксирован.), то нужно использовать тип sequence, со значением длины в NIL, что будет указывать на нуль-терминированность.
Пример:
static char str
[] =
{72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 0};
char* return_array_null_terminated
()
{
return str;
}в лиспе сигнатура такая:
(define-external-function
("return_array_null_terminated" (:snake-case))
()
((& (sequence char nil string) :out)))Четвертый параметр спецификации типа(лисповый type specifier) обозначает тип последовательности, в которую сишная последовательность будет отображаться. В данном случае - в лисповую строку. Функция каждый раз будет возвращать "Hello, world!"
Но, чем плохи эти варианты, так это тем, что каждый раз при возвращении значения, они будут создавать новые лисповые массивы соответствующего размера, а лишнее выделение памяти - худший враг производительности. Но, если сигнатуру сишной функции изменить, и перейти к более распространенной и более принятой форме, в которой в функцию передается указатель на выделенную заранее память, а функция ее заполняет:
void fill_array
(int element, int* array, int length)
{
while(length--)
array[length] = element;
}то в лиспе можно будет сделать так:
(define-external-function
("fill_array" (:snake-case))
()
(void rv array)
(element int)
(array (& (simple-array int) :out))
(length int :aux (length array)))Это создает лисповую функцию с двумя аргументами - элемент и массив. Длина вычисляется внутри функции - :aux параметры в лисповую сигнатуру не попадают.
Функция возвращает массив, который ей передали. Здесь спецификатор типа массива без указания размерностей разрешен, т.к. размерности вычисляются из лиспового объекта массива.
Чем, кроме прочего, хорош этот вариант, так это еще тем, что указатель на данные лиспового массива в большинстве современных реализаций будет браться прямо из самого массива(например через sb-sys:vector-sap). Если же это невозможно, то, если размер массива в спецификации типа фиксированный, на сишном стеке выделиться память, указатель на нее передастся в Си, и после возврата сишной функции данные перекачаются в лисповый массив. Если размер нефиксирован, память, на время вызова в Си, выделиться динамическая.
Пример применения:
(let ((array (make-array 10 :element-type 'int)))
(fill-array 123 array));; => #
(123 123 123 123 123)Если все-таки сигнатуру поменять нельзя, то для реюза одного лиспового массива можно сделать так:
(defun return-array (array)
(declare (type (simple-array int (*)) array))
(let ((pointer (external-function-call
"return_array"
(()
(pointer)))))
(deref pointer '(simple-array int) 0 array)
array))Надо бы доки к Virgil давно написать, но все руки не доходят...
Кстати, если работать с файлами, я бы рекомендовал mmap'ить их(ну или на винде, соответственно, CreateFileMapping и т.п.), создавать окно(ну т.е. MapViewOfFile например) размером кратным "allocation granularity", создавать лисповый массив такого же размера, перекачивать в него данные, как указано в последнем примере, и двигать окно по мере обработки.