Repository: rust-lang/rust-bindgen
Pull request #2812 (2024-04-23 07:35:45)

feat: add dynamic loading of variable
It's just a rebase of #2114 , since the original pull requests are closed, I thought might just rebase and create a new one for reviewing, @emilio.

Supporting for dynamic loading can be a useful feature when one need to found library in a dynamic way(i.e. choose between libraries with compatible ABI), and supporting loading static variable is a important feature in this feature.

discord9: > It's just a rebase of #2114 ,since the original pull requests are closed, I thought might just rebase and create a new one for reviewing, @emilio.
> 
> Supporting for dynamic loading can be a useful feature when one need to found library in a dynamic way(i.e. choose between libraries with compatible ABI), and supporting loading static variable is a important feature in this since many ABI might want to export a static variable


emilio: I removed the `get_var` prefix because afaict it's not necessary, and I made it so that @pradt2 and you are both attributed authorship.

Patch:
diff --git a/bindgen/codegen/dyngen.rs b/bindgen/codegen/dyngen.rs
index b26c0b18..4b2749ec 100644
--- a/bindgen/codegen/dyngen.rs
+++ b/bindgen/codegen/dyngen.rs
@@ -1,7 +1,7 @@
 use crate::codegen;
 use crate::ir::context::BindgenContext;
 use crate::ir::function::ClangAbi;
-use proc_macro2::Ident;
+use proc_macro2::{Ident, TokenStream};
 
 /// Used to build the output tokens for dynamic bindings.
 #[derive(Default)]
@@ -122,7 +122,7 @@ impl DynamicItems {
     }
 
     #[allow(clippy::too_many_arguments)]
-    pub(crate) fn push(
+    pub(crate) fn push_func(
         &mut self,
         ident: Ident,
         abi: ClangAbi,
@@ -196,4 +196,47 @@ impl DynamicItems {
             #ident
         });
     }
+
+    pub fn push_var(
+        &mut self,
+        ident: Ident,
+        ty: TokenStream,
+        is_required: bool,
+    ) {
+        let member = if is_required {
+            quote! { *mut #ty }
+        } else {
+            quote! { Result<*mut #ty, ::libloading::Error> }
+        };
+
+        self.struct_members.push(quote! {
+            pub #ident: #member,
+        });
+
+        let deref = if is_required {
+            quote! { self.#ident }
+        } else {
+            quote! { *self.#ident.as_ref().expect("Expected variable, got error.") }
+        };
+        self.struct_implementation.push(quote! {
+            pub unsafe fn #ident (&self) -> *mut #ty {
+                #deref
+            }
+        });
+
+        let ident_str = codegen::helpers::ast_ty::cstr_expr(ident.to_string());
+        self.constructor_inits.push(if is_required {
+            quote! {
+                let #ident = __library.get::<*mut #ty>(#ident_str).map(|sym| *sym)?;
+            }
+        } else {
+            quote! {
+                let #ident = __library.get::<*mut #ty>(#ident_str).map(|sym| *sym);
+            }
+        });
+
+        self.init_fields.push(quote! {
+            #ident
+        });
+    }
 }
diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs
index 070d9dec..0770275d 100644
--- a/bindgen/codegen/mod.rs
+++ b/bindgen/codegen/mod.rs
@@ -53,7 +53,7 @@ use crate::ir::ty::{Type, TypeKind};
 use crate::ir::var::Var;
 
 use proc_macro2::{Ident, Span};
-use quote::TokenStreamExt;
+use quote::{ToTokens, TokenStreamExt};
 
 use crate::{Entry, HashMap, HashSet};
 use std::borrow::Cow;
@@ -799,7 +799,17 @@ impl CodeGenerator for Var {
                 }
             );
 
-            result.push(tokens);
+            if ctx.options().dynamic_library_name.is_some() {
+                result.dynamic_items().push_var(
+                    canonical_ident,
+                    self.ty()
+                        .to_rust_ty_or_opaque(ctx, &())
+                        .into_token_stream(),
+                    ctx.options().dynamic_link_require_all,
+                );
+            } else {
+                result.push(tokens);
+            }
         }
     }
 }
@@ -4576,7 +4586,7 @@ impl CodeGenerator for Function {
             let args_identifiers =
                 utils::fnsig_argument_identifiers(ctx, signature);
             let ret_ty = utils::fnsig_return_ty(ctx, signature);
-            result.dynamic_items().push(
+            result.dynamic_items().push_func(
                 ident,
                 abi,
                 signature.is_variadic(),


Test patch:
diff --git a/bindgen-tests/tests/expectations/tests/dynamic_loading_variable_required.rs b/bindgen-tests/tests/expectations/tests/dynamic_loading_variable_required.rs
new file mode 100644
index 00000000..a96efbe5
--- /dev/null
+++ b/bindgen-tests/tests/expectations/tests/dynamic_loading_variable_required.rs
@@ -0,0 +1,32 @@
+#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]
+pub struct TestLib {
+    __library: ::libloading::Library,
+    pub foo: *mut ::std::os::raw::c_int,
+    pub baz: *mut *mut ::std::os::raw::c_int,
+}
+impl TestLib {
+    pub unsafe fn new<P>(path: P) -> Result<Self, ::libloading::Error>
+    where
+        P: AsRef<::std::ffi::OsStr>,
+    {
+        let library = ::libloading::Library::new(path)?;
+        Self::from_library(library)
+    }
+    pub unsafe fn from_library<L>(library: L) -> Result<Self, ::libloading::Error>
+    where
+        L: Into<::libloading::Library>,
+    {
+        let __library = library.into();
+        let foo = __library.get::<*mut ::std::os::raw::c_int>(b"foo\0").map(|sym| *sym)?;
+        let baz = __library
+            .get::<*mut *mut ::std::os::raw::c_int>(b"baz\0")
+            .map(|sym| *sym)?;
+        Ok(TestLib { __library, foo, baz })
+    }
+    pub unsafe fn foo(&self) -> *mut ::std::os::raw::c_int {
+        self.foo
+    }
+    pub unsafe fn baz(&self) -> *mut *mut ::std::os::raw::c_int {
+        self.baz
+    }
+}
diff --git a/bindgen-tests/tests/expectations/tests/dynamic_loading_variable_simple.rs b/bindgen-tests/tests/expectations/tests/dynamic_loading_variable_simple.rs
new file mode 100644
index 00000000..ced70dbd
--- /dev/null
+++ b/bindgen-tests/tests/expectations/tests/dynamic_loading_variable_simple.rs
@@ -0,0 +1,32 @@
+#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]
+pub struct TestLib {
+    __library: ::libloading::Library,
+    pub foo: Result<*mut ::std::os::raw::c_int, ::libloading::Error>,
+    pub baz: Result<*mut *mut ::std::os::raw::c_int, ::libloading::Error>,
+}
+impl TestLib {
+    pub unsafe fn new<P>(path: P) -> Result<Self, ::libloading::Error>
+    where
+        P: AsRef<::std::ffi::OsStr>,
+    {
+        let library = ::libloading::Library::new(path)?;
+        Self::from_library(library)
+    }
+    pub unsafe fn from_library<L>(library: L) -> Result<Self, ::libloading::Error>
+    where
+        L: Into<::libloading::Library>,
+    {
+        let __library = library.into();
+        let foo = __library.get::<*mut ::std::os::raw::c_int>(b"foo\0").map(|sym| *sym);
+        let baz = __library
+            .get::<*mut *mut ::std::os::raw::c_int>(b"baz\0")
+            .map(|sym| *sym);
+        Ok(TestLib { __library, foo, baz })
+    }
+    pub unsafe fn foo(&self) -> *mut ::std::os::raw::c_int {
+        *self.foo.as_ref().expect("Expected variable, got error.")
+    }
+    pub unsafe fn baz(&self) -> *mut *mut ::std::os::raw::c_int {
+        *self.baz.as_ref().expect("Expected variable, got error.")
+    }
+}
diff --git a/bindgen-tests/tests/expectations/tests/dynamic_loading_variable_with_allowlist.rs b/bindgen-tests/tests/expectations/tests/dynamic_loading_variable_with_allowlist.rs
new file mode 100644
index 00000000..3f29e738
--- /dev/null
+++ b/bindgen-tests/tests/expectations/tests/dynamic_loading_variable_with_allowlist.rs
@@ -0,0 +1,30 @@
+#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]
+pub struct TestLib {
+    __library: ::libloading::Library,
+    pub foo: Result<*mut ::std::os::raw::c_int, ::libloading::Error>,
+    pub bar: Result<*mut ::std::os::raw::c_int, ::libloading::Error>,
+}
+impl TestLib {
+    pub unsafe fn new<P>(path: P) -> Result<Self, ::libloading::Error>
+    where
+        P: AsRef<::std::ffi::OsStr>,
+    {
+        let library = ::libloading::Library::new(path)?;
+        Self::from_library(library)
+    }
+    pub unsafe fn from_library<L>(library: L) -> Result<Self, ::libloading::Error>
+    where
+        L: Into<::libloading::Library>,
+    {
+        let __library = library.into();
+        let foo = __library.get::<*mut ::std::os::raw::c_int>(b"foo\0").map(|sym| *sym);
+        let bar = __library.get::<*mut ::std::os::raw::c_int>(b"bar\0").map(|sym| *sym);
+        Ok(TestLib { __library, foo, bar })
+    }
+    pub unsafe fn foo(&self) -> *mut ::std::os::raw::c_int {
+        *self.foo.as_ref().expect("Expected variable, got error.")
+    }
+    pub unsafe fn bar(&self) -> *mut ::std::os::raw::c_int {
+        *self.bar.as_ref().expect("Expected variable, got error.")
+    }
+}
diff --git a/bindgen-tests/tests/headers/dynamic_loading_variable_required.h b/bindgen-tests/tests/headers/dynamic_loading_variable_required.h
new file mode 100644
index 00000000..8e811bb3
--- /dev/null
+++ b/bindgen-tests/tests/headers/dynamic_loading_variable_required.h
@@ -0,0 +1,4 @@
+// bindgen-flags: --dynamic-loading TestLib --dynamic-link-require-all
+
+int foo;
+int *baz;
\ No newline at end of file
diff --git a/bindgen-tests/tests/headers/dynamic_loading_variable_simple.h b/bindgen-tests/tests/headers/dynamic_loading_variable_simple.h
new file mode 100644
index 00000000..5ecad752
--- /dev/null
+++ b/bindgen-tests/tests/headers/dynamic_loading_variable_simple.h
@@ -0,0 +1,4 @@
+// bindgen-flags: --dynamic-loading TestLib
+
+int foo;
+int *baz;
\ No newline at end of file
diff --git a/bindgen-tests/tests/headers/dynamic_loading_variable_with_allowlist.hpp b/bindgen-tests/tests/headers/dynamic_loading_variable_with_allowlist.hpp
new file mode 100644
index 00000000..5e273030
--- /dev/null
+++ b/bindgen-tests/tests/headers/dynamic_loading_variable_with_allowlist.hpp
@@ -0,0 +1,5 @@
+// bindgen-flags: --dynamic-loading TestLib --allowlist-var foo --allowlist-var bar
+
+int foo;
+int bar;
+int baz; // should not be allowed
\ No newline at end of file