Hi,
I made an account since I need help with the following 'funky' behavior. As this is commercial software, I don't want to expose function names, so bare with my generic names, please.
Situation
there is an external (fortran based) windows dll. I make use of the routines the following way:
#[link(name = "dllname", kind = "dylib")]
extern "C" {
fn external_function(
index1: *mut c_long,
index2: *mut c_long,
option: *const c_char,
strlen: c_long,
number_of_values: *mut c_long,
values: *mut c_double,
string_array: *const c_char,
strlen: c_long,
noerr: *mut c_long,
);
}
I am wrapping this function this way:
pub fn wrapped_func(option: EnumType, index1: i32, index2: i32) -> Result<ResultStruct, String> {
let mut ca_index1: c_long = index1 + 1; // from 0-based to 1-based indexing
let mut ca_index2: c_long = index2 + 1; // from 0-based to 1-based indexing
let mut ca_option = format!("{:?}", option); // Display trait to convert to String representation
let len = ca_option.len() as i32;
// these are going to be set from the fortran side, initialize all as 0
let mut ca_nvals = 0; // number of values
let mut vals = [0.; CONST1 as usize]; // CONST1 is a hardcoded parameter, as is CASTRLEN
let mut buf = [0 as c_char; CASTRLEN * CONST1 as usize];
let mut return_code: c_long = 10;
unsafe {
external_function(
&mut ca_index1 as *mut _,
&mut ca_index2 as *mut _,
ca_option.as_ptr() as *mut _,
len as c_long,
&mut ca_nvals as *mut _,
vals.as_mut_ptr() as *mut _,
buf.as_mut_ptr() as *mut c_char,
CASTRLEN as c_long,
&mut return_code as *mut _,
);
}
// this piece is irrelevant
if return_code != 0 {
return Err(error_fn());
}
// in my desperation, I copy the values 1-by-1. I don't think I should need a copy at all!
let mut buf_copy = [0; CASTRLEN * CONST1 ];
let mut vals_copy = [0.; CONST1 ];
for cnt in 0..TQFNLEN * ca_nvals as usize {
buf_copy[cnt] = buf[cnt];
}
for cnt in 0..ca_nvals as usize {
vals_copy[cnt] = vals[cnt];
}
// Truncate the length to the actual number of parameters
let return_vals = vals_copy[..ca_nvals as usize].to_vec();
// Convert the C strings to Rust strings
let mut reply = Vec::with_capacity(ca_nvals as usize);
let mut offset = 0;
for _ in 0..ca_nvals as usize {
let c_str = unsafe { CStr::from_ptr(buf_copy[offset..offset + TQFNLEN].as_ptr()) };
let str_slice = c_str.to_str().unwrap();
reply.push(str_slice[..TQFNLEN].trim().to_string());
offset += TQFNLEN;
}
Ok(ResultStruct{
values: return_vals,
expressions: reply,
})
}
As you can see, i tried a few more or less convoluted ways to convert.
I had many iterations on this, but it never got robustly running - running my tests on this code sometimes (!) gives me an access violation / invalid read error, and sometimes runs.
Interestingly enough, it seems that adding dbg!(&buf_copy)
somewhere after the unsafe block stabilizes things - but not to an extent that it never happens (specifically as obviously if doing a non-development build, the dbg statements get dropped.
As I know that this is not a running example of the problem, let me confirm that the Fortran code actually writes 'good' data. The f64 values can't be bad, and the returned char-arrays are all simple ASCII bytecodes. I can confirm that these are correct. The Fortran program would crash if any non-sane characters were in there much earlier.
The issue really seems to be that the either the pointers/buffers are loosing validity. I have a suspicion that they may get optimized away?
LLMs suggested to use std::ptr::read_volatile(buf.as_ptr())
, but that fundamentally did not change the issue.
So I guess I am either looking for a way to stop any (premature?) optimization.
I guess there are many ways to swap a tire, but the conversion of the char-array to String can be done in multitudes of way. I know I do not need to go through the unsafe CStr if I know the length yadayada. This is merely still in place from my attempts at debugging. But coming back to my question, I don't think I should need any copy of the buffers before returning?