Optional external functions

Is there a way to declare an optional external function that can be linked at runtime?

If for example I am running code in a process that may or may not have a shared object attached that may or may not export a function, is there a way of accessing the exported function without panicking if it cannot be found (which is the default behavior of extern "C" functions)?

While I could use operating system apis such as dlsym I'd prefer not to as these functions are being declared as part of a library and I do not want to require such a setup process.

This is the original code, that will panic at runtime if the function exit cannot be found:

fn main() {
    unsafe {
        exit(0);
        panic!();
    }
}

extern "C" {
    pub fn exit(code: ::std::os::raw::c_int);
}

One idea I had was to declare it is a global variable like so:

fn main() {
    unsafe {
        exit.unwrap()(0);
        panic!();
    }
}

extern "C" {
    pub static exit: Option<extern "C" fn(code: ::std::os::raw::c_int)>;
}

This however results in a segmentation fault.

Am I implementing this wrong? Or are there any other ways I can achieve this.
Thanks

The technical term you need is "weak linkage". See

1 Like

Thank you so much for the guidance! Using the weak utility you provided I created the following class that should work on windows and posix:

use std::sync::atomic::{AtomicUsize, Ordering};
use std::{marker, mem};
use std::ffi::CString;
use std::os::raw::c_char;

pub struct Weak<F> {
	name: &'static str,
	addr: AtomicUsize,
	_marker: marker::PhantomData<F>,
}

impl<F> Weak<F> {
	pub const fn new(name: &'static str) -> Weak<F> {
		Weak {
			name,
			addr: AtomicUsize::new(1),
			_marker: marker::PhantomData,
		}
	}
	
	pub fn get(&self) -> Option<&F> {
		assert_eq!(mem::size_of::<F>(), mem::size_of::<usize>());
		unsafe {
			if self.addr.load(Ordering::SeqCst) == 1 {
				self.addr.store(fetch(self.name), Ordering::SeqCst);
			}
			if self.addr.load(Ordering::SeqCst) == 0 {
				None
			} else {
				mem::transmute::<&AtomicUsize, Option<&F>>(&self.addr)
			}
		}
	}
}

unsafe fn fetch(name: &str) -> usize {
	let name = match CString::new(name) {
		Ok(cstr) => cstr,
		Err(..) => return 0,
	};
	find_symbol(name.as_ptr())
}

#[cfg(unix)]
unsafe fn find_symbol(name: *const c_char) -> usize {
	use libc::{dlsym, RTLD_DEFAULT};
	dlsym(RTLD_DEFAULT, name) as usize
}

#[cfg(windows)]
unsafe fn find_symbol(name: *const c_char) -> usize {
	use std::ptr::null;
	use kernel32::{GetModuleHandle, GetProcAddress};
	GetProcAddress(GetModuleHandleA(null()), name) as usize
}
1 Like

You probably want #[cfg(unix)] there

1 Like