Home > Net >  Problems Translating C 'extern "C" __declspec(dllexport)' struct to Rust
Problems Translating C 'extern "C" __declspec(dllexport)' struct to Rust

Time:11-29

I am currently attempting to rebuild and update a Project written in Rust (more specifically it's an SKSE64 plugin for Skyrim: https://github.com/lukasaldersley/sse-mod-skyrim-search-se forked from qbx2) The last problem I'm facing is the library now requires a struct to be exported from our library for version checking.

I have attempted many probably stupid ways to implement this, but I can't get it to work.

The c code is as follows:

struct SKSEPluginVersionData
{
    enum
    {
        kVersion = 1,
    };

    enum
    {
        // set this if you are using a (potential at this time of writing) post-AE version of the Address Library
        kVersionIndependent_AddressLibraryPostAE = 1 << 0,
        // set this if you exclusively use signature matching to find your addresses and have NO HARDCODED ADDRESSES
        kVersionIndependent_Signatures = 1 << 1,
    };

    UInt32  dataVersion;            // set to kVersion

    UInt32  pluginVersion;          // version number of your plugin
    char    name[256];              // null-terminated ASCII plugin name

    char    author[256];            // null-terminated ASCII plugin author name (can be empty)
    char    supportEmail[256];      // null-terminated ASCII support email address (can be empty)

    // version compatibility
    UInt32  versionIndependence;    // set to one of the kVersionIndependent_ enums or zero
    UInt32  compatibleVersions[16]; // zero-terminated list of RUNTIME_VERSION_ defines your plugin is compatible with

    UInt32  seVersionRequired;      // minimum version of the script extender required, compared against PACKED_SKSE_VERSION
                                    // you probably should just set this to 0 unless you know what you are doing
};

#define RUNTIME_VERSION_1_6_318 0x010613E0

extern "C" {
    __declspec(dllexport) SKSEPluginVersionData SKSEPlugin_Version =
    {
        SKSEPluginVersionData::kVersion,

        1,
        "Skyrim Search",

        "qbx2",
        "",

        0,  // not version independent
        { RUNTIME_VERSION_1_6_318, 0 }, // RUNTIME_VERSION_1_6_318 is 

        0,  // works with any version of the script extender. you probably do not need to put anything here
    };
};

What I've come up with so far in Rust is:

enum KVersionenum {
    KVersion=1,
}

#[repr(C)]
pub struct SKSEPluginVersionData {
    dataVersion: u32,

    pluginVersion: u32,
    name: [char;256],

    author: [char;256],
    supportEmail: [char;256],

    versionIndependence: u32,
    compatibleVersions: [u32;16],

    seVersionRequired: u32,
}

//0x010613E0 is RUNTIME_VERSION_1_6_318

//how can I do this OUTSIDE of a method and how can I make it public to the dll? is that even possible?
let SKSEPlugin_Version = SKSEPluginVersionData {
    dataVersion: KVersionenum::KVersion as u32,
    pluginVersion: 1,
    name: "Skyrim Search\0", //this doesn't work, how can I fill this char array?
    author: "qbx2 / lukasaldersley\0", //same here
    supportEmail: "[email protected]\0", //and here
    versionIndependence: 0,
    compatibleVersions: [0x010613E0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], //I'm sure this is a horrible way of doing this
    seVersionRequired: 0,
};

When I tried to use the let thingy outside of a function the compiler complained about expecting 'item' but my google-fu isn't good enough to find any useful information there because I just kept finding information about items in the videogame Rust.

For the car array/string problem I have come across that std:ffi stuff and am completely lost in it's documentation but as far as I can tell it will only ever deal with pointers, which is not what I need.

The two questions now are how to I fill these char arrays (I cannot just pass a pointer) and how do I create an instance of this struct as a global variable (or however Rust calls it) that I can export since the let name = something {...} doesn't work.

As far as I can tell exporting to the dll for a function would look like this, but I assume it wouldn't work the same way for that struct.

#[no_mangle]
pub extern "C" fn SKSEPlugin_Query(skse: *const SKSEInterface, info: *mut PluginInfo) -> bool {...}

Is it even possible to do this?

Could someone help me here or at least point me in the right direction? Please note I am an absolute beginner to Rust and apparently falsely assumed just adding a struct wouldn't be so complicated.

CodePudding user response:

First, a char in Rust is a 32-bit value while its 8-bit in C (that's not strictly true but Rust doesn't support architectures where it isn't). So the name, author, and supportEmail fields should be u8 arrays.


You can export a global value by using #[no_mangle] on a public static variable:

#[no_mangle]
pub static SKSEPlugin_Version: SKSEPluginVersionData = SKSEPluginVersionData {
    ...
};

See: Can a Rust constant/static be exposed to C?


You can initialize a u8 array from a literal using a byte string and dereferencing it: *b"...". Unfortunately, there's no shorthand like in C for zero-padding the undetermined part of the array, so you'd be left with:

    name: *b"Skyrim Search\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
    author: *b"qbx2 / lukasaldersley\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
    supportEmail: *b"[email protected]\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
    compatibleVersions: [0x010613E0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],

Which honestly, is not nice at all. You can clean this up with some functions that pad the array for you, however, initializing static variables requires const functions, which are still relatively immature in Rust so we can't use things like for loops or traits to help us out:

const fn zero_pad_u8<const N: usize, const M: usize>(arr: &[u8; N]) -> [u8; M] {
    let mut m = [0; M];
    let mut i = 0;
    while i < N {
        m[i] = arr[i];
        i  = 1;
    }
    m
}

const fn zero_pad_u32<const N: usize, const M: usize>(arr: &[u32; N]) -> [u32; M] {
    let mut m = [0; M];
    let mut i = 0;
    while i < N {
        m[i] = arr[i];
        i  = 1;
    }
    m
}

...

    name: zero_pad_u8(b"Skyrim Search"),
    author: zero_pad_u8(b"qbx2 / lukasaldersley"),
    supportEmail: zero_pad_u8(b"[email protected]"),
    compatibleVersions: zero_pad_u32(&[0x010613E0]),

Still not that nice, but at least its manageable. There may be a crate available that can do this for you.


Lastly, you don't have to use the same field naming convention as used in C since its just the order and type that matters, so I'd recommend using snake_case, but if you do want to keep the same names for consistency, you can put the #[allow(non_snake_case)] attribute on SKSEPluginVersionData to suppress the compiler warnings.

I would also recommend making a constant for that magic value instead of just a comment:

const RUNTIME_VERSION_1_6_318: u32 = 0x010613E0;

See the full thing on the playground.

  • Related