Cześć,
Uczę się interfejsować Rusta z C, bo nie chcę więcej pisać w C, a jednak chciałbym rozwijać niektóre projekty i być może później przepisywać resztę kodu. Napisałem sobie prosty przykład testowy:
use std::str;
use std::slice;
#[repr(C)]
struct Window {}
extern "C" {
fn strlen(cstr: *const u8) -> usize;
}
struct Stream {
handle: Option<*const Window>,
name: String
}
fn prefix_eq(prefix: &String, s: &String) -> bool {
s.len() >= prefix.len() && s[0..prefix.len()] == *prefix
}
impl Stream {
fn new(name: String) -> Self {
Stream { handle: None, name: name }
}
fn try_window(&mut self, handle: *const Window, name: String) -> bool {
if self.handle.is_none() && prefix_eq(&self.name, &name) {
self.handle = Some(handle);
true
} else {
false
}
}
fn try_key(&mut self, handle: *const Window, key: u8) {
self.handle.map(|my_handle| {
if handle == my_handle {
println!("hit {} for {:?}", str::from_utf8(&[key]).unwrap(), handle);
}
});
}
}
fn ptr2str(ptr: *const u8) -> String {
unsafe {
String::from(str::from_utf8(slice::from_raw_parts(ptr, strlen(ptr))).unwrap())
}
}
#[no_mangle]
extern "C"
fn init_stream(name: *const u8) -> Box<Stream> {
Box::new(Stream::new(String::from(ptr2str(name))))
}
#[no_mangle]
extern "C"
fn win2stream(s: &mut Stream, handle: *const Window, name: *const u8) -> bool {
s.try_window(handle, ptr2str(name))
}
#[no_mangle]
fn key2stream(s: &mut Stream, handle: *const Window, key: u8) {
s.try_key(handle, key);
}
#[no_mangle]
fn free_streams(_s: Box<Stream>) {}
I po stronie C:
#include <stdio.h>
typedef struct stream *Stream;
typedef struct win *Window;
typedef unsigned char u8;
Stream init_stream(const char* win_name);
int win2stream(Stream s, Window win, const char *name);
void key2stream(Stream s, Window win, u8 key);
void free_streams(Stream s);
int main() {
Stream s = init_stream("foowin");
Window w = (Window) 0xafa;
printf("%d\n", win2stream(s, w, "caca"));
key2stream(s, w, 's');
printf("%d\n", win2stream(s, w, "foowind"));
key2stream(s, w, 'd');
free_streams(s);
}
Kod działa poprawnie, co bardzo mnie cieszy. Chciałem się upewnić, że dobrze zwalniam pamięć i valgrind zgłosił mi jeden wyciek pamięci. Co ciekawe w miejscu dość niespodziewanym (w try_key
):
==53920== Memcheck, a memory error detector
==53920== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==53920== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==53920== Command: ./test_streams
==53920==
hit d for 0xafa
0
1
==53920==
==53920== HEAP SUMMARY:
==53920== in use at exit: 1,024 bytes in 1 blocks
==53920== total heap usage: 6 allocs, 5 frees, 5,177 bytes allocated
==53920==
==53920== 1,024 bytes in 1 blocks are still reachable in loss record 1 of 1
==53920== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==53920== by 0x169172: _ZN3std4sync4once4Once15call_once_force28_$u7b$$u7b$closure$u7d$$u7d$17h78adb5c909ad3e88E.llvm.718437650808914235 (alloc.rs:89)
==53920== by 0x11B014: std::sync::once::Once::call_inner (once.rs:434)
==53920== by 0x12187D: std::io::stdio::_print (once.rs:334)
==53920== by 0x11C2CC: stream::Stream::try_key::{{closure}} (in /home/lew/tools/dwm/test_streams)
==53920== by 0x1491E9: core::option::Option<T>::map (in /home/lew/tools/dwm/test_streams)
==53920== by 0x11C1CB: stream::Stream::try_key (in /home/lew/tools/dwm/test_streams)
==53920== by 0x11C46A: key2stream (in /home/lew/tools/dwm/test_streams)
==53920== by 0x11BF1F: main (in /home/lew/tools/dwm/test_streams)
==53920==
==53920== LEAK SUMMARY:
==53920== definitely lost: 0 bytes in 0 blocks
==53920== indirectly lost: 0 bytes in 0 blocks
==53920== possibly lost: 0 bytes in 0 blocks
==53920== still reachable: 1,024 bytes in 1 blocks
==53920== suppressed: 0 bytes in 0 blocks
==53920==
==53920== For lists of detected and suppressed errors, rerun with: -s
==53920== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Istotnie, gdy usunąłem printa, wyciek znikł, a gdy próbowałem zrobić podobnie tylko bez interfejsowania do C (czyli z mapem i closure), problem nie wystąpił. Ktoś ma pomysł skąd ten problem i jak mu zaradzić?
EDIT: udało mi się obejść problem używając printf
z libc, myśląc, że pewnie Rustowe Stdout ma jakiś bufor, który nie jest zwalniany. Zrobiłem tak:
unsafe { printf("Hit '%c' for %x\n\0".as_ptr(), key as usize, handle) };
I działa. Bajt zerowy okazał się konieczny, bo as_ptr() nie zwraca bajtu zerowego na końcu. To rozwiązalo problem, ale wciąż ciekawi mnie co mógłbym zrobić, żeby jednak użyć rusowego println!