Error in passing a Structure with fields function,function pointer from CPP to Rust

Hello everyone, I am trying to initialize a structure object of CPP inside the rust by passing struct object from Cpp to Rust by pointer .
This structure contains various kinds of fields including a function , function pointer etc.I am getting segmentation fault and the other values initialised in Rust are also garbage values at Cpp end.
Please check the following code and output.Let me know the correct approach.

//file: ffi.h
#pragma once

typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef signed short int16_t;
typedef signed int int32_t;
typedef unsigned int uint32_t;

struct inner1{
    int32_t a;
};

enum class dummyEnum {
    variant1 = 0,
    variant2  = variant1,
    variant3 = 1,
    variant4 = 2,
};

enum class AvailableSensorCapabilityType : uint64_t {
    NONE = 0,
    READ_SENSOR_SETTINGS = 1,
    DEPTH_OUTPUT = 1 << 1,
    CONSTRAINED_HIGH_SPEED_VIDEO = 1 << 2,
    MONOCHROME = 1 << 3,
};

typedef void (*OnDestroy)(const dummyEnum enum1, void *cookie);

struct Info{
    std::string name1;
    std::string name2;
    int32_t var1_i32 = 0;
    int32_t arr1d[2] = {
        0,
    };
    int32_t (*arr2d)[4]{};
    bool bool_var1;
    float f_var1[4];
    uint8_t *us_char1{};
    inner1 *struct1{};
    void *cookie = nullptr;

    void (*fn1)(Info &info1){};
    OnDestroy onDestroy{};

    const bool isSupport(const AvailableSensorCapabilityType type) const {
        //some operation
        return true;
    };
    int32_t var2_i32 = 0;

};

extern "C"
{
    void call_to_rust(Info *Infovar);
}

//file: main.rs
use std::ffi::c_void;
use std::os::raw::c_uint;
use std::os::raw::c_float;
use std::os::raw::c_uchar;

#[repr(C)]
#[allow(non_camel_case_types)]
pub struct inner1{
     pub a:i32,
}

#[repr(C)]
#[allow(non_camel_case_types)]
pub enum dummyEnum {
    variant1 ,
    variant2 ,
    variant3 ,
    variant4 ,
}
#[repr(C)]
#[allow(non_camel_case_types)]
pub enum AvailableSensorCapabilityType {
    NONE = 0,
    READ_SENSOR_SETTINGS = 1,
    DEPTH_OUTPUT = 1 << 1,
    CONSTRAINED_HIGH_SPEED_VIDEO = 1 << 2,
    MONOCHROME = 1 << 3,
}

//typedef void (*OnDestroy)(const dummyEnum ennum1, void *cookie);
type fn1ptr=fn(&Info)->c_void;
type OnDestroyptr=fn(dummyEnum,*const c_void)->c_void;
type isSupportptr=fn(AvailableSensorCapabilityType)->c_void;

#[repr(C)]
#[allow(non_snake_case)]
pub struct Info{
    pub name1:String,
    pub name2:String,
    pub var1_i32 :i32,
    pub arr1d:[i32;2], // or can i use *const i32?
    pub arr2d:*const [i32;4],
    pub bool_var1:bool,
    pub f_var1:[c_float;4],// or f32
    pub us_char1:*const c_uchar,
    pub istruct1:*const inner1,
    pub cookie:*const c_void,

    pub fn1:fn1ptr,
    pub OnDestroy:OnDestroyptr,
    pub isSupport:isSupportptr,

    pub var2_i32:i32,

}

#[allow(dead_code)]
pub static ARRAY1:[[i32;4];4]=[
    [1,2,3,4],
    [5,6,7,8],
    [9,10,11,12],
    [13,14,15,16]
];



#[no_mangle]
pub extern "C" fn  call_to_rust(info_var: &mut Info){
    info_var.var1_i32=10;
    info_var.var2_i32=20;
    info_var.arr1d=[99,88];
    info_var.arr2d=ARRAY1.as_ptr();
    info_var.f_var1=[0.5,0.5,0.5,0.5];
    println!("rust code execute successfully");
}

//file pass.cpp
#include<stdio.h>
#include<iostream>

#include "ffi.h"
using namespace std;

int main(){
    Info Infovar;

    cout<<"calling rust create"<<endl;

    call_to_rust(&Infovar);

    cout<<"returned to cpp"<<endl;

    cout<<"value of var1_i32 is:"<<Infovar.var1_i32<<endl;
    cout<<"value of var2_i32 is:"<<Infovar.var2_i32<<endl;
    cout<<"value of arr1d[1] is:"<<Infovar.arr1d[1]<<endl;
    cout<<"value of arr2d[1][3] is:"<<Infovar.arr2d[1][3]<<endl;
    cout<<"value of f_var1[2] is:"<<Infovar.f_var1[2]<<endl;
    return 0;
}


Output:

calling rust create
rust code execute successfully
returned to cpp
value of var1_i32 is:-595791868
value of var2_i32 is:0
value of arr1d[1] is:0
Segmentation fault (core dumped)

Desired Output:

calling rust create
rust code execute successfully
returned to cpp
value of var1_i32 is:10
value of var2_i32 is:20
value of arr1d[1] is:88
value of arr2d[1][3] is:8
value of f_var1[2] is:0.5

You can't send complex, language-specific types through FFI. In particular, std::string in C++ and String in Rust have potentially completely different memory layouts and inner workings. You can't just pretend you have a std::string on one side and automagically get a String on the other side.

The only things you can safely send through an FFI boundary are self-contained types: basically, the primitives (integers, floating-point numbers, individual characters), pointers (which are usually just represented as an integer memory address), and by-value, trivially copiable ("plain old data") structs containing these types.

Wherever indirection, ownership, or some other kind of non-trivial memory manipulation is involved, you have to manually translate between the semantically "equivalent" types; practically, in the case of strings, this means passing the length and the raw character buffer (as a pointer) separately, instead of expecting layouts of String objects to match.

So, basically, you can't use your Info across FFI boundaries. The most likely direct cause of the segmentation fault is probably that the field offsets differ across the Rust struct and the C++ one.

You will have to invent and manually implement a new type that lets you safely pass this information through.


You might be confused about the C++ code too, in fact. This comment:

// or can i use *const i32?

suggests to me that you think arrays are pointers in C++ (or in Rust). They aren't, in either language.

1 Like

I understood the issue with strings . I commented out both the string fields and then executed. The Info fields are alligned properly until isSupport after this value of var2_i32 is corrupted. since isSupport is an function can we remove it from the rust end,I tried and it is working. Is this advisable? I know we can use impl blocks for functions in Rust.
Also the way I handled function pointers fn1 is it correct? the memory layout is it being matched?

I got confused and mentioned this // or can i use *const i32? I understand that arrays,pointers, pointer to arrays are different.

If your struct fields don't match exactly across the two languages, then your code is incorrect and causes UB. You can't just randomly remove fields from one side until some other fields happen to match up. The issue with strings was so severe that I didn't look further, but it's quite possible that there are other layout mismatches.

Did you consider using bindgen instead of manually translating between the two type definitions?

I have to do manual translation can't use bindgen.
can you check if there are any other layout mismatches?

As a first step, you could check the offset_of! each field (on the Rust side) and offsetof each field (on the C++ side) and check if there are any mismatches. If yes, that's a definite bug. If no, that's maybe good.

2 Likes

Thanks, I will check with that.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.