Как использовать CStruct

Я пытаюсь вернуть структуру из общей библиотеки, написанной на C. Это простой код для тестирования возвращаемой структуры и простого int32, libstruct.c, скомпилированного gcc -shared -Wl,-soname,libstruct.so.1 -o libstruct.so.1 libstruct.c:

#include <stdint.h>

int32_t newint(int32_t arg) {
    return arg;
}

struct MyStruct {
    int32_t member;
};
struct MyStruct newstruct(int32_t arg) {
    struct MyStruct myStruct;
    myStruct.member = arg;
    return(myStruct);
}

Я могу использовать эту библиотеку с простой программой C, usestruct.c, скомпилированной gcc -o usestruct usestruct.c ./libstruct.so.1:

#include <stdio.h>
#include <stdint.h>

struct MyStruct {
    int32_t member;
};
extern struct MyStruct newstruct(int32_t);
extern int32_t newint(int32_t);

int main() {
    printf("%d\n", newint(42));
    struct MyStruct myStruct;
    myStruct = newstruct(42);
    printf("%d\n", myStruct.member);
    return 0;
}

Я могу запустить его с помощью LD_LIBRARY_PATH=./ ./usestruct, и он работает правильно, печатает два значения. Теперь напишем аналогичную программу на раку, usestruct.raku:

#!/bin/env raku
use NativeCall;

sub newint(int32) returns int32 is native('./libstruct.so.1') { * }
say newint(42);

class MyStruct is repr('CStruct') {
    has int32 $.member;
}
sub newstruct(int32) returns MyStruct is native('./libstruct.so.1') { * }
say newstruct(42).member;

Это сначала печатает 42, но затем завершается с ошибкой сегментации.

На C этот пример работает, но я не спец в C, может что-то забыл, какие-то параметры компиляции? Или это баг ракудо?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
7
0
155
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Интерфейс NativeCall требует, чтобы транзакции C-структур выполнялись с указателями:

Объекты CStruct передаются собственным функциям по ссылке, и собственные функции также должны возвращать объекты CStruct по ссылке.

Однако ваша функция C возвращает новую структуру по значению. Затем, я думаю, это пытаются интерпретировать как адрес памяти, так как он ожидает указатель и пытается читать/записывать из диких областей памяти, отсюда и segfault.

Вы можете указать свою функцию как:

struct MyStruct* newstruct(int32_t val) {
    /* dynamically allocating now */
    struct MyStruct *stru = malloc(sizeof *stru);
    stru->member = val;
    return stru;
}

с #include <stdlib.h> в самом верху для malloc. Программа Raku, по сути, такая же по модулю некоторой эстетики:

# prog.raku
use NativeCall;

my constant LIB = "./libstruct.so";

class MyStruct is repr("CStruct") {
    has int32 $.member;
}

# C bridge
sub newint(int32) returns int32 is native(LIB) { * }
sub newstruct(int32) returns MyStruct is native(LIB) { * }

say newint(42);

my $s := newstruct(84);
say $s;
say $s.member;

Мы собираем библиотеку и запускаем программу Raku, чтобы получить

$ gcc -Wall -Wextra -pedantic -shared -o libstruct.so -fPIC mod_struct.c
$ raku prog.raku
42
MyStruct.new(member => 84)
84

(взял на себя смелость переименовать файл C в «mod_struct.c»)

Выглядит неплохо. Но есть проблема: теперь, когда динамическое распределение было сделано, возникает ответственность за его возврат. И нам нужно сделать это самостоятельно с C-мостовым фризером:

Когда тип на основе CStruct используется в качестве возвращаемого типа собственной функции, сборщик мусора не управляет памятью.

Так

/* addendum to mod_struct.c */
void free_struct(struct MyStruct* s) {
    free(s);
}

Отметив, что, поскольку сама структура не имеет динамических распределений для своих членов (поскольку она имеет только целое число), мы не делали дальнейшего освобождения.

Теперь программа Raku должна знать об этом и использовать это:

# prog.raku
use NativeCall;

my constant LIB = "./libstruct.so";

class MyStruct is repr("CStruct") {
    has int32 $.member;
}

# C bridge
sub newint(int32) returns int32 is native(LIB) { * }
sub newstruct(int32) returns MyStruct is native(LIB) { * }
sub free_struct(MyStruct) is native(LIB) { * };   # <-- new!

say newint(42);

my $s := newstruct(84);
say $s;
say $s.member;

# ... after some time
free_struct($s);
say "successfully freed struct";

и вывод следует как

42
MyStruct.new(member => 84)
84
successfully freed struct

Вручную отслеживать объекты MyStruct, чтобы помнить об их освобождении через некоторое время, может быть обременительно; это будет писать C! На уровне Раку у нас уже есть класс, представляющий структуру; то мы можем добавить к нему подметод DESTROY, который освобождает себя всякий раз, когда сборщик мусора сочтет это необходимым:

class MyStruct is repr("CStruct") {
    has int32 $.member;

    submethod DESTROY {
        free_struct(self);
    }
}

С этим дополнением не требуются ручные вызовы free_struct (на самом деле, лучше этого не делать, потому что это может привести к двойному освобождению, что является неопределенным поведением на уровне C).


P.S. ваш основной файл C может быть пересмотрен, например, файл заголовка кажется в порядке, но это выходит за рамки или это был только демонстративный пример, кто знает. В любом случае, спасибо за предоставление MRE и добро пожаловать на сайт.

"Интерфейс NativeCall требует...". Я едва знаю C и определенно недостаточно, чтобы интуитивно понять, почему любое заданное требование NativeCall является таким, какое оно есть, в частности, можно ли однажды улучшить NativeCall, чтобы уменьшить или устранить любое текущее ограничение. Достаточно ли вы знаете, чтобы прокомментировать, может ли это требование когда-нибудь быть устранено (и если да, то знаете ли вы какие-либо ссылки на то, где это обсуждалось?) Если да, подумайте о том, чтобы добавить это в свой ответ или, по крайней мере, прокомментировать . Ваш ответ кажется мне уже выдающимся, но от этого мне хочется большего!

raiph 06.11.2022 17:02

привет @raiph, спасибо за добрые слова. Я точно не знаю, почему это так. Я могу предположить, хотя :) В C лучше передавать структуры по ссылке, особенно когда они большие, поскольку передача по значению копирует все это целиком, и не только теряется скорость, но и стек функции может переполниться. С указателем оба эти вопроса больше не являются проблемой. Тем не менее, это делает структуру is rw на уровне C :) [продолжение]

Mustafa Aydın 06.11.2022 17:21

[продолжение] (это может быть желательным или нежелательным; при желании, еще одно преимущество указателей!). Учитывая, что вы, вероятно, также являетесь автором части C и/или уже легко ошибиться в C, я думаю, что выбор указателя - это круто. Но опять же, разработчики интерфейса NativeCall и те, у кого больше знаний C, знают лучше меня.

Mustafa Aydın 06.11.2022 17:22

@MustafaAydın, я заметил, что вы используете привязку: my $s := newstruct(84);. Избегает ли это ненужного копирования возвращаемой структуры?

fingolfin 06.11.2022 20:22

@fingolfin Извините, надо было это уточнить! Ответ: не совсем. С := вместо = я избегаю обертывания контейнера Scalar вокруг значения RHS (в данном случае экземпляра MyStruct). Это (:=) заставит $s напрямую "смотреть" на значение RHS, так сказать, и предотвратит переназначение $s (например, $s = -7 завершится ошибкой после этого момента; так что своего рода навязанная неизменность, завещание). Вы можете посмотреть о скалярах и контейнерах здесь и здесь. (Также: ваш ответ превосходен, спасибо за это!)

Mustafa Aydın 06.11.2022 21:03

В дополнение к отличному ответу @Mustafa.

Я нашел другой способ решить свою проблему: мы можем выделить структуру в raku и передать ее функции C. Вот пример, файл mod_struct.c:

#include <stdint.h>

struct MyStruct {
    int32_t member;
};
void writestruct(struct MyStruct *outputStruct, int32_t arg) {
    outputStruct->member = arg;
}

Файл usestruct.raku:

#!/bin/env raku
use NativeCall;

class MyStruct is repr('CStruct') {
    has int32 $.member;
}
sub writestruct(MyStruct is rw, int32) is native('./libstruct.so') { * }

my $myStruct = MyStruct.new;
writestruct($myStruct, 42);
say $myStruct.member;

Скомпилируйте и запустите его:

$ gcc -Wall -Wextra -pedantic -shared -o libstruct.so -fPIC mod_struct.c
$ ./usestruct.raku
42

еще раз, спасибо, что поделились этим. Здесь хотелось бы указать на одно предостережение: этот подход отлично работает со структурами для членов, не использующих указатели (например, int и float); однако, если он имеет строку (char *) или массив, созданные Raku значения передаются нормально, но их нельзя безопасно использовать на уровне C без клонирования, потому что ссылка может быть уничтожена на уровне Raku (возможно, даже немедленно!) и тогда C-уровень будет искать запрещенную область в памяти. Для них необходимо динамическое размещение на уровне C.

Mustafa Aydın 07.11.2022 19:08

Например, если вы запустите пример здесь без strdup, но все остальное то же самое, я иногда получаю "foo is str and 123"... но! Я также получаю «foo is &sayM and 123», т. е. странное чтение памяти и даже иногда ошибку «Неверное завершение строки UTF-8», подразумевая, что переданная строка иногда уже ушла (освобождена), и мы сталкиваемся с undefined поведение, как видно.

Mustafa Aydın 07.11.2022 19:09

Еще раз спасибо за ваши объяснения, @MustafaAydın. Я много раз читал эту страницу документации, но благодаря вам начал в ней разбираться!

fingolfin 08.11.2022 18:33

Другие вопросы по теме