Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@
/expand.rs
/target/
/Cargo.lock
/tests/shared_library/library/target/
/tests/shared_library/library/Cargo.lock
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ cxx-build = { version = "=1.0.194", path = "gen/build" }
cxxbridge-cmd = { version = "=1.0.194", path = "gen/cmd" }

[workspace]
members = ["demo", "flags", "gen/build", "gen/cmd", "gen/lib", "macro", "tests/ffi"]
members = ["demo", "flags", "gen/build", "gen/cmd", "gen/lib", "macro", "tests/ffi", "tests/shared_library"]

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
Expand Down
1 change: 1 addition & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ fn main() {

cc::Build::new()
.file(manifest_dir.join("src/cxx.cc"))
.include(manifest_dir.join("include"))
.cpp(true)
.cpp_link_stdlib(None) // linked via link-cplusplus crate
.std(cxxbridge_flags::STD)
Expand Down
91 changes: 90 additions & 1 deletion gen/lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ mod gen;
mod syntax;

pub use crate::error::Error;
pub use crate::gen::include::{Include, HEADER};
pub use crate::gen::include::{Include, HEADER, IMPLEMENTATION};
pub use crate::gen::{CfgEvaluator, CfgResult, GeneratedCode, Opt};
pub use crate::syntax::IncludeKind;
use proc_macro2::TokenStream;
Expand All @@ -60,3 +60,92 @@ pub fn generate_header_and_cc(rust_source: TokenStream, opt: &Opt) -> Result<Gen
.map_err(Error::from)?;
gen::generate(syntax, opt).map_err(Error::from)
}

/// Format import symbols into OS-appropriate linker file content.
fn format_symbols_for_linker(symbols: &[String], target_os: &str) -> String {
match target_os {
"windows" => {
let mut result = String::from("EXPORTS\n");
for sym in symbols {
result.push_str(" ");
result.push_str(sym);
result.push('\n');
}
result
}
"macos" => {
let mut result = String::new();
for sym in symbols {
result.push_str("-U _");
result.push_str(sym);
result.push('\n');
}
result
}
_ => {
// Linux and other Unix-like systems
let mut result = String::from("{\n");
for sym in symbols {
result.push_str(" ");
result.push_str(sym);
result.push_str(";\n");
}
result.push_str("};");
result
}
}
}

/// Format symbols that a shared library imports from an executable into the appropriate linker file format.
///
/// When a shared library calls functions defined in the executable that loads it, those symbols
/// must be declared as available to the linker. This function generates the platform-specific
/// files needed:
/// - **Windows**: `.def` file format (EXPORTS section) - used with `/DEF:` linker flag
/// - **macOS**: Linker arguments (`-U _symbol` for each) - marks symbols as dynamic_lookup
/// - **Linux**: Dynamic list format (`{ symbol; }`) - used with `--dynamic-list`
///
/// # Arguments
/// * `symbols` - The list of symbol names the library imports from the executable
/// * `target_os` - The target operating system ("windows", "macos", or "linux")
///
/// # Example
/// ```
/// let import_symbols = vec!["exe_callback".to_string(), "exe_get_constant".to_string()];
/// let content = cxx_gen::format_import_symbols_for_linker(&import_symbols, "linux");
/// // content will be "{\n exe_callback;\n exe_get_constant;\n};"
/// ```
pub fn format_import_symbols_for_linker(symbols: &[String], target_os: &str) -> String {
format_symbols_for_linker(symbols, target_os)
}

/// Format symbols that a shared library exports into a Windows `.def` file format.
///
/// On Windows, shared libraries (DLLs) use `.def` files to explicitly list exported symbols.
/// This function generates the EXPORTS section needed for the library's `.def` file.
///
/// Note: This is Windows-specific. On Unix systems, exports are typically controlled via
/// version scripts (Linux) or visibility attributes, not separate export files.
///
/// # Arguments
/// * `symbols` - The list of symbol names the library exports
/// * `target_os` - Must be "windows"
///
/// # Panics
/// Panics if `target_os` is not "windows"
///
/// # Example
/// ```
/// let export_symbols = vec!["lib_process".to_string(), "lib_get_data".to_string()];
/// let content = cxx_gen::format_export_symbols_for_linker(&export_symbols, "windows");
/// // content will be "EXPORTS\n lib_process\n lib_get_data\n"
/// ```
pub fn format_export_symbols_for_linker(symbols: &[String], target_os: &str) -> String {
if target_os != "windows" {
panic!(
"format_export_symbols_for_linker is only supported for Windows targets, got: {}",
target_os
);
}
format_symbols_for_linker(symbols, target_os)
}
4 changes: 4 additions & 0 deletions gen/src/include.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ use std::ops::{Deref, DerefMut};
/// The complete contents of the "rust/cxx.h" header.
pub static HEADER: &str = include_str!("include/cxx.h");

/// The complete contents of the "rust/cxx.cc" implementation.
#[allow(dead_code)]
pub static IMPLEMENTATION: &str = include_str!("src/cxx.cc");

/// A header to #include.
///
/// The cxxbridge tool does not parse or even require the given paths to exist;
Expand Down
88 changes: 86 additions & 2 deletions gen/src/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,23 @@ pub struct GeneratedCode {
pub header: Vec<u8>,
/// The bytes of a C++ implementation file (e.g. .cc, cpp etc.)
pub implementation: Vec<u8>,
/// Export and import data (unused in cxx_build).
#[allow(dead_code)]
ctx: GenContext,
}

/// Export and import data used by cxx_gen but not cxx_build.
#[derive(Default)]
pub struct GenContext {
/// Symbols exported by the library. Access via `export_symbols()`.
exports: Vec<String>,
/// Import information for symbols the library needs from the executable.
/// Access symbols via `import_symbols()` or generate thunks via `generate_import_thunks()`.
imports: Vec<out::ImportInfo>,
/// Prefix to prepend to generated thunks (includes, pragmas, builtins).
thunk_prefix: String,
/// Postfix to append to generated thunks (pragma end).
thunk_postfix: String,
}

impl Default for Opt {
Expand Down Expand Up @@ -188,14 +205,81 @@ pub(super) fn generate(syntax: File, opt: &Opt) -> Result<GeneratedCode> {
// same token stream to avoid parsing twice. Others only need to generate
// one or the other.
let (mut header, mut implementation) = Default::default();
let mut ctx = GenContext::default();
if opt.gen_header {
header = write::gen(apis, types, opt, true);
let mut out_file = write::gen(apis, types, opt, true);
header = out_file.content();
}
if opt.gen_implementation {
implementation = write::gen(apis, types, opt, false);
let mut out_file = write::gen(apis, types, opt, false);
implementation = out_file.content();
ctx.exports = out_file.exports();
ctx.imports = out_file.imports();
ctx.thunk_prefix = out_file.thunk_prefix();
ctx.thunk_postfix = out_file.thunk_postfix();
}
Ok(GeneratedCode {
header,
implementation,
ctx,
})
}

impl GeneratedCode {
/// Get the list of symbols exported by the library.
///
/// These are the functions and types that the library provides to other code.
#[allow(dead_code)]
pub fn export_symbols(&self) -> Vec<String> {
self.ctx.exports.clone()
}

/// Get the list of symbols imported by the library from the executable.
///
/// These are the functions that the library calls which are defined in the
/// executable that loads it.
#[allow(dead_code)]
pub fn import_symbols(&self) -> Vec<String> {
self.ctx.imports.iter().map(|import| import.symbol.clone()).collect()
}

/// Generate Windows-specific import thunks for runtime symbol resolution.
///
/// This is only needed when building shared libraries on Windows where the library
/// imports functions from the executable. The thunks use GetProcAddress to resolve
/// symbols at runtime.
///
/// Returns an empty string if there are no imports or if the target OS is not Windows.
#[allow(dead_code)]
pub fn generate_import_thunks(&self, target_os: &str) -> String {
if target_os != "windows" || self.ctx.imports.is_empty() {
return String::new();
}

let mut out = String::new();
out.push_str(&self.ctx.thunk_prefix);
if !self.ctx.thunk_prefix.is_empty() {
out.push('\n');
}
out.push_str("#include <cstdio>\n");
out.push_str("#include <exception>\n");
out.push_str("#include <windows.h>\n");

for import in &self.ctx.imports {
let out::ImportInfo { symbol, return_type, signature_args, noexcept, call_args } = import;
out.push_str(&format!(
r#"extern "C" {return_type}{symbol}({signature_args}){noexcept} {{
static auto fn = reinterpret_cast<{return_type}(*)({signature_args})>(
reinterpret_cast<void*>(GetProcAddress(GetModuleHandle(NULL), "{symbol}")));
if (fn) return fn({call_args});
fprintf(stderr, "FATAL: Host EXE missing required export: {symbol}\n");
std::terminate();
}}
"#,
));
}

out.push_str(&self.ctx.thunk_postfix);
out
}
}
84 changes: 82 additions & 2 deletions gen/src/out.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub(crate) struct OutFile<'a> {
pub pragma: Pragma<'a>,
pub builtin: Builtins<'a>,
content: RefCell<Content<'a>>,
write_override: RefCell<Option<String>>,
}

#[derive(Default)]
Expand All @@ -26,6 +27,8 @@ pub(crate) struct Content<'a> {
suppress_next_section: bool,
section_pending: bool,
blocks_pending: usize,
imports: Vec<ImportInfo>,
exports: Vec<String>,
}

#[derive(Copy, Clone, PartialEq, Debug)]
Expand All @@ -34,6 +37,16 @@ enum BlockBoundary<'a> {
End(Block<'a>),
}

#[derive(Clone)]
#[allow(dead_code)]
pub(crate) struct ImportInfo {
pub symbol: String,
pub return_type: String,
pub signature_args: String,
pub noexcept: String,
pub call_args: String,
}

impl<'a> OutFile<'a> {
pub(crate) fn new(header: bool, opt: &'a Opt, types: &'a Types) -> Self {
OutFile {
Expand All @@ -44,6 +57,7 @@ impl<'a> OutFile<'a> {
pragma: Pragma::new(),
builtin: Builtins::new(),
content: RefCell::new(Content::new()),
write_override: RefCell::new(None),
}
}

Expand Down Expand Up @@ -108,6 +122,68 @@ impl<'a> OutFile<'a> {
self.content.get_mut().flush();
self.pragma.end.flush();
}

pub(crate) fn add_export(&mut self, export: String) {
if self.header { return; }

self.content.get_mut().exports.push(export);
}

pub(crate) fn exports(&mut self) -> Vec<String> {
std::mem::take(&mut self.content.get_mut().exports)
}

pub(crate) fn add_import(&mut self, symbol: String, return_type: &str, signature_args: &str, noexcept: &str, call_args: &str) {
if self.header { return; }

self.content.get_mut().imports.push(ImportInfo {
symbol,
return_type: return_type.to_string(),
signature_args: signature_args.to_string(),
noexcept: noexcept.to_string(),
call_args: call_args.to_string(),
});
}

pub(crate) fn imports(&mut self) -> Vec<ImportInfo> {
std::mem::take(&mut self.content.get_mut().imports)
}

pub(crate) fn thunk_prefix(&mut self) -> String {
self.flush();

let include = &self.include.content.bytes;
let pragma_begin = &self.pragma.begin.bytes;
let builtin = &self.builtin.content.bytes;

let mut out = String::new();
out.push_str(include);
if !out.is_empty() && !pragma_begin.is_empty() {
out.push('\n');
}
out.push_str(pragma_begin);
if !out.is_empty() && !builtin.is_empty() {
out.push('\n');
}
out.push_str(builtin);
out
}

pub(crate) fn thunk_postfix(&mut self) -> String {
self.flush();
self.pragma.end.bytes.clone()
}

/// Temporarily redirect writes to a buffer, returning the result and captured output
pub(crate) fn with_buffer<F, R>(&mut self, f: F) -> (R, String)
where
F: FnOnce(&mut Self) -> R,
{
*self.write_override.borrow_mut() = Some(String::new());
let result = f(self);
let buffer = self.write_override.borrow_mut().take().unwrap();
(result, buffer)
}
}

impl<'a> Write for Content<'a> {
Expand Down Expand Up @@ -169,7 +245,6 @@ impl<'a> Content<'a> {
self.bytes.push_str(b);
self.suppress_next_section = false;
self.section_pending = false;
self.blocks_pending = 0;
}
}

Expand Down Expand Up @@ -218,6 +293,7 @@ impl<'a> Content<'a> {
}

self.blocks.truncate(write);
self.blocks_pending = 0;
}
}

Expand Down Expand Up @@ -248,6 +324,10 @@ impl<'a> InfallibleWrite for Content<'a> {

impl<'a> InfallibleWrite for OutFile<'a> {
fn write_fmt(&mut self, args: Arguments) {
InfallibleWrite::write_fmt(self.content.get_mut(), args);
if let Some(buf) = self.write_override.borrow_mut().as_mut() {
Write::write_fmt(buf, args).unwrap();
} else {
InfallibleWrite::write_fmt(self.content.get_mut(), args);
}
}
}
1 change: 1 addition & 0 deletions gen/src/src
Loading