1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
//! Helper to ensure the bindings match the used DLL.
//!
//! Using an API guard is as simple as defining and exporting a function `my_api_guard` returning an
//! [`APIVersion`] as in the example below. Backends supporting API guards will automatically
//! generate guard code executed when the library is loaded.
//!
//! When developing we **highly recommend** adding API guards, as mismatching bindings are the #1
//! cause of "inexplicable" errors (undefined behavior) that often take hours to hunt down.
//!
//! # Example
//!
//! This will create a FFI function called `pattern_api_guard`, and backends might automatically
//! create guards calling this function when loading the DLL.
//!
//! ```
//! use interoptopus::{ffi_function, Inventory, InventoryBuilder, function};
//! use interoptopus::patterns::api_guard::APIVersion;
//!
//! // Guard function used by backends.
//! #[ffi_function]
//! #[no_mangle]
//! pub extern "C" fn my_api_guard() -> APIVersion {
//! my_inventory().into()
//! }
//!
//! // Inventory of our exports.
//! pub fn my_inventory() -> Inventory {
//! InventoryBuilder::new()
//! .register(function!(my_api_guard))
//! .inventory()
//! }
//! ```
//!
//! In backends that support API guards an error message like this might be emitted if you try load
//! a library with mismatching bindings:
//!
//! ```csharp
//! Exception: API reports hash X which differs from hash in bindings (Y). You probably forgot to update / copy either the bindings or the library.
//! ```
//!
//!
//! # Hash Value
//!
//! The hash value
//!
//! - is based on the signatures of the involved functions, types and constants,
//! - is expected to change when the API changes, e.g., functions, types, fields, ... are added
//! changed or removed,
//! - will even react to benign API changes (e.g., just adding functions),
//! - might even react to documentation changes (subject to change; feedback welcome).
//!
use crate::lang::c::CType;
use crate::lang::rust::CTypeInfo;
use crate::patterns::TypePattern;
use crate::Inventory;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
/// Holds the API version hash of the given library.
#[repr(transparent)]
#[allow(dead_code)]
#[derive(Debug, Default, PartialOrd, PartialEq, Copy, Clone)]
pub struct APIVersion {
version: u64,
}
impl APIVersion {
/// Create a new API version from the given hash.
pub fn new(version: u64) -> Self {
Self { version }
}
/// Create a new API version from the given library.
pub fn from_inventory(inventory: &Inventory) -> Self {
let version = inventory_hash(inventory);
Self { version }
}
}
unsafe impl CTypeInfo for APIVersion {
fn type_info() -> CType {
CType::Pattern(TypePattern::APIVersion)
}
}
impl From<Inventory> for APIVersion {
fn from(i: Inventory) -> Self {
Self::from_inventory(&i)
}
}
/// Returns a unique hash for an inventory; used by backends.
pub fn inventory_hash(inventory: &Inventory) -> u64 {
let mut hasher = DefaultHasher::new();
// TODO: Do we need to hash patterns as well? They should never impact the 'relevant' API surface?
let types = inventory.ctypes();
let functions = inventory.functions();
let constants = inventory.constants();
// TODO: Should probably exclude documentation & co.
for t in types {
t.hash(&mut hasher);
}
// TODO: Should probably exclude documentation & co.
for f in functions {
f.hash(&mut hasher);
}
for c in constants {
c.name().hash(&mut hasher);
c.value().fucking_hash_it_already(&mut hasher);
}
hasher.finish()
}