Libloading: unreliable with statics; reliable "dynamically"

I’ve tried converting one of my apps from sharedlib to libloading. Although I found libloading's docs unhelpful, I found a useful model here: windows_dpi. This showed how to create the library and its functions as statics which is exactly what I need because my app loads the DLL once and then uses it throughout. (I also tried using normal variables but libloading::Symbol's need for lifetimes defeated me.) Here’s an outline of how I use the library:

use dpl::{self, DPL};
fn main() {
    println!("DPL {}", DPL.version());
    /// etc.
    dpl::close();
}

And here’s an outline of how I use libloading:

use libloading as so;

lazy_static! {
    pub static ref DPL: Dpl = Dpl::new().unwrap();
}

pub struct Dpl {
    pub instance_id: i32,
}

impl Dpl {
    fn new() -> XResult<Dpl> {
        let instance_id = CREATE_LIBRARY();
        if instance_id == 0 {
            xerr!("failed to open PDF library");
        }
        /// etc
        Ok(Dpl { instance_id })
    }
    /// etc
}

lazy_static! {
    pub(crate) static ref LIB: so::Library =
        so::Library::new("DebenuPDFLibrary64DLL1612.dll").unwrap();
    pub(crate) static ref CREATE_LIBRARY: so::Symbol<'static, SigVrI> =
        { unsafe { LIB.get(b"DPLCreateLibrary\0").unwrap() } };
    pub(crate) static ref RELEASE_LIBRARY: so::Symbol<'static, SigIrI> =
        { unsafe { LIB.get(b"DPLReleaseLibrary\0").unwrap() } };
    /// etc
}

// MUST call just before program termination!
pub fn close() {
    RELEASE_LIBRARY(DPL.instance_id);
}

// Have also tried "stdcall" and "win64"; neither helps
pub(crate) type SigVrI = extern "C" fn() -> i32;
pub(crate) type SigIrI = extern "C" fn(i32) -> i32;

What I’ve found when I run my automated tests is that I get odd failures when I run them all. However, when I then run the failed tests individually, they almost always work — none always fail; in fact they normally pass.

So now I’m thinking of reverting back to sharedlib since that’s never given me any reliability problems. But OTOH I like creating the library and its functions statically and would rather that worked.

I’m using Windows 7-64, Rust 1.34.0.

I now have reliable use — but not using statics and only on Windows.
Here’s a cut-down example of use in a main.rs:

use dpl;

fn main() {
    match run() {
        Ok(()) => (),
        Err(err) => eprintln!("Error: {}", err),
    };
}

fn run() -> Result<(), std::io::Error> {
    let dpl = dpl::Dpl::new()?;
    let mut pdf = dpl.dapdf_open("test.pdf");
    let count = pdf.get_page_count();
    println!("pages={}", count);
}

And here’s skeleton of how I’m using libloading:

use libloading::os::windows::{Library, Symbol}; // If I don't use win-specific it requires explicit lifetimes
use std::io::{Error, ErrorKind};
use std::rc::Rc;
use widestring::WideCString;

type XResult<T> = Result<T, Error>;

pub struct DaPdf {
    instance_id: i32,
    fh: i32,
    fn_close_file: Symbol<SigIIrI>,
    fn_get_page_count: Symbol<SigIIrI>,
    fn_open_file: Symbol<SigIWWrI>,
}

impl DaPdf {
    fn new(instance_id: i32, lib: Rc<Library>) -> DaPdf {
        DaPdf {
            instance_id,
            fn_close_file: unsafe { lib.get(b"DPLDACloseFile\0").unwrap() },
            fn_get_page_count: unsafe { lib.get(b"DPLDAGetPageCount\0").unwrap() },
            fn_open_file: unsafe { lib.get(b"DPLDAOpenFile\0").unwrap() },
        }
    }

    fn open_file(&mut self, filename: &str) {
        let os_filename = WideCString::from_str(filename).unwrap();
        let self.fh = (self.fn_open_file)(self.instance_id, os_filename.as_ptr());
    }

    pub fn get_page_count(&self) -> i32 {
        (self.fn_get_page_count)(self.instance_id, self.fh)
    }
}

impl Drop for DaPdf {
    fn drop(&mut self) {
        if self.fh != 0 {
            (self.fn_close_file)(self.instance_id, self.fh);
        }
    }
}

pub struct Dpl {
    pub instance_id: i32,
    lib: Rc<Library>,
    fn_release_library: Symbol<SigIrI>,
}

impl Dpl {
    pub fn new() -> XResult<Dpl> {
        let lib = Rc::new(Library::new(DPL_DLL).unwrap());
        let fn_create_library: Symbol<SigVrI> = unsafe { lib.get(b"DPLCreateLibrary\0").unwrap() };
        let instance_id = fn_create_library();
        if instance_id == 0 {
            return Err(Error::new(ErrorKind::Other, "Failed to open DPL"));
        }
        Ok(Dpl {
            instance_id,
            lib: Rc::clone(&lib),
            fn_release_library: unsafe {
                lib.get(b"DPLReleaseLibrary\0").unwrap()
            },
        })
    }

    pub fn dapdf_open(&self, filename: &str) -> DaPdf {
        let mut pdf = DaPdf::new(self.instance_id, Rc::clone(&self.lib));
        pdf.open_file(&filename);
        pdf
    }
}

impl Drop for Dpl {
    fn drop(&mut self) {
        (self.fn_release_library)(self.instance_id);
    }
}

pub type Wchar = u16;

type SigIFrI = extern "C" fn(i32, f64) -> i32;
type SigIIIIrI = extern "C" fn(i32, i32, i32, i32) -> i32;
type SigIIIIrW = extern "C" fn(i32, i32, i32, i32) -> *const Wchar;
type SigIIIrI = extern "C" fn(i32, i32, i32) -> i32;
type SigIIrI = extern "C" fn(i32, i32) -> i32;
type SigIWWrI = extern "C" fn(i32, *const Wchar, *const Wchar) -> i32;
type SigIWrI = extern "C" fn(i32, *const Wchar) -> i32;
type SigIrI = extern "C" fn(i32) -> i32;
type SigVrI = extern "C" fn() -> i32;

I would really like to use libloading::{Library,Symbol} and use the right lifetimes but the libloading docs don’t help: does anyone know a (preferably small, understandable) example that uses them?