Multiple objects with interdependent lifetimes in the same struct?


#1

I seem to be struggling with a chicken & egg problem.

I’m importing a DLL with libloading and I want to “get” all the imports and store them so that I don’t have to “get” a function every time I want to use it.

The problem I’m having is I can’t seem to just store a reference to the import and I can’t seem to get it to recognize that “dll” will live as long as “AdsGetVersion” below.

I can’t just take a reference to the function import because it’s a struct itself which has to live somewhere.

It lets me apply a lifetime to libloading::Symbol, but I can’t figure out how to apply a lifetime to a libloading::Library.

I’m pretty sure I can solve the problem with Option and/or Box, but then I have to unwrap the function pointer every time I want to use it. It would be really nice for the compiler to be able to assure me that all the function pointers actually exist and storing them in Option would lose that guarantee.

I feel like there must be a simple solution and I’m still just too green in rust to see it.

Here are the relevant bits of my code:

extern crate libloading; // 0.5.0
type AdsGetVersionFunc = unsafe extern "system" fn ( *mut u32, *mut u32, *mut u8, *mut u8, *mut u16 ) -> u32;
pub struct Library<'a> {
	dll : libloading::Library,
	AdsGetVersion : libloading::Symbol<'a, AdsGetVersionFunc>,
}
impl<'a> Library<'a> {
	pub fn new() -> Library<'a> {
		let dll = libloading::Library::new ( "ace32.dll" ).expect ( "ERROR loading ace32.dll" );
		unsafe {
			let AdsGetVersion = dll.get(b"AdsGetVersion").unwrap();
			return Library {
				dll,
				AdsGetVersion,
			};
		}
	}

#2

You’re attempting to create what’s called a self referential struct - in short, this isn’t allowed by Rust.

Your Library type owns the libloading::Library type. In turn, you obtain a symbol from libloading::Library that has a reference back to the Library instance it came from. Then you try to store both as values in your Library type, and you end up with Symbol holding a reference to libloading::Library, which is part of self (your Library) == self reference.

Try to create the libloading::Library earlier in your app, and then give your Library a reference to it - you can then obtain symbols from it and keep them in your Library as well.


#3

I’ve had to deal with this exact problem before. You can’t store both the library and the Symbol<F> libloading gives you directly because you’ll get a self-referential struct.

What I did was take advantage of the fact that a function pointer is Copy and move out of the Symbol (let func: fn(u32, u32)->u32 = *lib.get("add")). This is obviously unsafe because we’re sidestepping Symbol's safety guarantees, so I made sure to manually tie the function pointer’s lifetime to that of the DLL.

struct Vtable {
  lib: Library,
  add: fn (u32, u32) -> u32,
  ...
}

#4

Your solution worked perfectly! It’s exactly what I was looking for, thank you!


#5

For posterity, here’s my updated working snippet. The only other weirdness that I ran into is that you have to wrap the function pointer in parenthesis in order to call it. I added an example below showing that, too.

extern crate libloading; // 0.5.0
type AdsGetVersionFunc = unsafe extern "system" fn ( *mut u32, *mut u32, *mut u8, *mut u8, *mut u16 ) -> u32;
pub struct Library {
	dll : libloading::Library,
	AdsGetVersion : AdsGetVersionFunc,
}
impl<'a> Library<'a> {
	pub fn new() -> Library<'a> {
		let dll = libloading::Library::new ( "ace32.dll" ).expect ( "ERROR loading ace32.dll" );
		unsafe {
			let AdsGetVersion = *dll.get(b"AdsGetVersion").unwrap();
			return Library {
				dll,
				AdsGetVersion,
			};
		}
	}
	pub fn GetVersion ( &self ) -> Result<(u32, u32, char, String),AceError>
	{
		const MAX_DESC_LEN : u16 = 64;
		let mut ulMajor : u32 = 0;
		let mut ulMinor : u32 = 0;
		let mut ucLetter : u8 = 0;
		let mut ucDescLen : u16 = MAX_DESC_LEN;
		let mut ucDesc : Vec<u8> = Vec::with_capacity ( ucDescLen as usize );
		unsafe
		{
			self.Assert (
				(self.AdsGetVersion) ( // <<< must wrap func ptr in paren to call it
					&mut ulMajor, &mut ulMinor, &mut ucLetter, ucDesc.as_mut_ptr(), &mut ucDescLen
				),
				"AdsGetVersion"
			)?;
		}
		assert! ( ucDescLen < MAX_DESC_LEN );
		unsafe { ucDesc.set_len ( ucDescLen as usize ); }
		let desc = String::from_utf8_lossy ( &ucDesc ).into_owned();
		return Result::Ok ( ( ulMajor, ulMinor, ucLetter as char, desc ) );
	}

}