8 Stimmen

Das Erstellen einer statischen C-Struktur, die Zeichenfolgen enthält

Ich versuche, eine dynamische Bibliothek in Rust zu erstellen, die eine Struktur als Symbol exportiert, das in ein C-Programm geladen wird über dlopen().

Allerdings stieß ich beim Zugriff auf den zweiten String in der Struktur auf einige Segfaults, also habe ich ein kleines Testprogramm erstellt, um herauszufinden, was ich falsch mache.

Das ist der Rust-Code (test.rs), kompiliert mit "rustc --crate-type dylib test.rs":

#[repr(C)]
pub struct PluginDesc {
    name: &'static str,
    version: &'static str,
    description: &'static str
}

#[no_mangle]
pub static PLUGIN_DESC: PluginDesc = PluginDesc {
    name: "Test Plugin\0",
    version: "1.0\0",
    description: "Test Rust Plugin\0"
};

Und hier ist das C-Programm, das versucht, die Bibliothek zu laden (test.c), kompiliert mit "gcc test.c -ldl -o test":

#include 
#include 

typedef struct {
    const char *name;
    const char *version;
    const char *description;
} plugin_desc;

int main(int argc, char **argv) {
    void *handle;
    plugin_desc *desc;

    handle = dlopen("./libtest.so", RTLD_LOCAL | RTLD_LAZY);
    if (!handle) {
        printf("failed to dlopen: %s\n", dlerror());
        return 1;
    }

    desc = (plugin_desc *) dlsym(handle, "PLUGIN_DESC");
    if (!desc) {
        printf("failed to dlsym: %s\n", dlerror());
        return 1;
    }

    printf("name: %p\n", desc->name);
    printf("version: %p\n", desc->version);
    printf("description: %p\n", desc->description);

    return 0;
}

Das ist die Ausgabe:

name: 0x7fa59ef8d750
version: 0xc
description: 0x7fa59ef8d75c

Wie man sieht, ist die Adresse von desc->version tatsächlich 0xc (12), was die Länge des ersten Strings ist. Es scheint also, dass die Struktur, die in die Bibliothek verpackt wird, auch die Zeichenfolgenlänge nach der Speicheradresse enthält.

Verwende ich hier den falschen Zeichenfolgentyp? Wie man sehen kann, musste ich die Zeichenfolgen auch manuell mit NULL terminieren. Ich habe versucht, den CString Wrapper zu verwenden, aber das scheint in diesem Fall nicht zu funktionieren ("static items are not allowed to have destructors").

Ich verwende die neueste Rust-Nightly auf Linux:

$ rustc --version
rustc 0.12.0-pre-nightly (f8426e2e2 2014-09-16 02:26:01 +0000)

3voto

Francis Gagné Punkte 52719

Das Layout eines Slices (&[T] oder &str) besteht aus einem Zeiger gefolgt von einer Länge, wie im Slice-Strukt im std::raw-Modul dokumentiert. Deshalb zeigt das Lesen des version-Felds in Ihrem C-Code die Länge des Werts des name-Felds an. (Allerdings wird darauf hingewiesen, dass das genaue Speicherlayout von Slices als nicht stabil betrachtet wird und sich daher in einer späteren Version ändern könnte. In jedem Fall sollten Sie nicht Rust-spezifische Datentypen an C übergeben; geben Sie nur primitive Typen - einschließlich Rohzeiger - und Typen an, die mit #[repr(C)] annotiert sind.)

BEARBEITEN: Leider gibt es derzeit keinen Weg, dies in Rust zu tun. Es gibt Funktionen, um Rohzeiger aus Slices zu erhalten, aber Funktionsaufrufe sind in statischen Initialisierern nicht erlaubt. Wie von sellibitze in den Kommentaren vorgeschlagen, sollten Sie diese Variable in einer C-Quelldatei definieren.

3voto

cg909 Punkte 2007

Wie bereits in den anderen Antworten erwähnt, ist das Hauptproblem, dass &str eine Referenz auf einen dynamisch dimensionierten Typ ist. Rust stellt solche Referenzen oder Zeiger im Speicher mit einem "fat" Zeiger dar, der auch eine Länge enthält und nicht mit einem einfachen Zeiger wie const char * in C.

Da das Speicherlayout für diese Referenzen nicht (noch) stabil ist, kann man &str, &[T] oder dyn T nicht zuverlässig für FFI verwenden.

Seit Rust 1.32 (Januar 2019) ist str::as_ptr im konstanten Kontext verwendbar, was es ermöglicht, leicht einen Rohzeiger auf einen statischen String zu erstellen. Das einzige verbleibende Problem ist, dass Rohzeiger standardmäßig als nicht threadsicher betrachtet werden. Deshalb muss auch Sync auf PluginDesc implementiert werden, um sicherzustellen, dass Ihre Struktur threadsicher ist.

#[repr(C)]
pub struct PluginDesc {
    name: *const u8,
    version: *const u8,
    description: *const u8
}

unsafe impl Sync for PluginDesc {}

#[no_mangle]
pub static PLUGIN_DESC: PluginDesc = PluginDesc {
    name: "Test Plugin\0".as_ptr(),
    version: "1.0\0".as_ptr(),
    description: "Test Rust Plugin\0".as_ptr()
};

Seit 2017 gibt es auch das null_terminated crate, das null-terminierte Strings lesbarer und sicherer macht, aber derzeit instabile Sprachfunktionen erfordert, die nur mit einem Nightly-Compiler verwendet werden können:

use null_terminated::{str0_utf8, NulStr};

#[repr(C)]
pub struct PluginDesc {
    name: &'static NulStr,
    version: &'static NulStr,
    description: &'static NulStr
}

#[no_mangle]
pub static PLUGIN_DESC: PluginDesc = PluginDesc {
    name: str0_utf8!("Test Plugin"),
    version: str0_utf8!("1.0"),
    description: str0_utf8!("Test Rust Plugin")
};

2voto

oli_obk Punkte 29489

Die kurze Antwort ist, dass du eine Struktur statisch nicht zuweisen kannst. In Zukunft wird Rust diese Fähigkeit wahrscheinlich erlangen.

Was du tun kannst, ist eine Struktur statisch zuzuweisen, die Nullzeiger enthält, und diese Nullzeiger auf etwas Nützliches zu setzen, wenn du die Funktion aufrufst. Rust hat static mut. Es erfordert unsicheren Code, ist nicht thread-sicher und gilt meines Wissens nach als Code-Geruch.

An dieser Stelle betrachte ich es als Workaround für die Tatsache, dass es keine Möglichkeit gibt, ein &[T] in ein *const T in einem statischen Kontext umzuwandeln.

static S: &'static [u8] = b"http://example.org/eg-amp_rust\n\0";
static mut desc: LV2Descriptor = LV2Descriptor {
    amp_uri: 0 as *const libc::c_char, // ptr::null() isn't const fn (yet)
};

#[no_mangle]
pub extern fn lv2_descriptor(index: i32) -> *const LV2Descriptor {
     let ptr = S.as_ptr() as *const libc::c_char;
     unsafe {
        desc.amp_uri = ptr;
        &desc as *const LV2Descriptor
     }
}

Antwort aus der Duplikat-Frage kopiert

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X