Skip to content

Add support for shared library import/export symbol tracking and thunk generation#1703

Open
chrislimpach wants to merge 7 commits intodtolnay:masterfrom
chrislimpach:main-shared-lib
Open

Add support for shared library import/export symbol tracking and thunk generation#1703
chrislimpach wants to merge 7 commits intodtolnay:masterfrom
chrislimpach:main-shared-lib

Conversation

@chrislimpach
Copy link
Copy Markdown

@chrislimpach chrislimpach commented Mar 2, 2026

In this PR, I've added functionality to cxx-gen for building better shared libraries (DLLs/SOs) which can bidirectionally call functions between the library and the executable loading it.

I've focused on building the bridge code as part of the C++ code, since in my project the C++ code doesn't compile using the system C++ standard library. This also avoids having to deal with C++ symbol name mangling across the executable/shared-library boundary, since the import/export symbols are all generated by cxx using its own deterministic naming scheme.

All platforms (Windows, macOS, Linux) use the concept of import libraries to declare symbols imported from shared libraries into executables.

Windows has more specific requirements for importing/exporting symbols:

  • the shared library needs to explicitly declare the imported and exported symbols
  • the executable needs to explicitly declare the exported symbols
  • the shared library needs to run-time link the imported symbols

On other platforms (macOS, Linux), these requirements are less strict since it's possible and common to export/import all symbols, and the dynamic linker supports bidirectional symbol resolution.

With these changes, it's easier to better constrain import/export symbol handling:

  • macOS: -U _symbol instead of -export_dynamic
  • Linux: --dynamic-list instead of --export-dynamic
  • Linux: generate a version-script

Implementation

Main commit (021bee3) adds three new public APIs to cxx-gen:

  1. format_import_symbols_for_linker(symbols, target_os) - Formats symbols the library imports from the executable:

    • Windows: .def file format (EXPORTS section)
    • macOS: -U _symbol linker arguments for dynamic lookup
    • Linux: Dynamic list format ({ symbol; }) for --dynamic-list
  2. format_export_symbols_for_linker(symbols, target_os) - Formats symbols the library exports (Windows .def only)

  3. GeneratedCode methods:

    • export_symbols() - List of mangled symbols the library exports
    • import_symbols() - List of mangled symbols imported from executable
    • generate_import_thunks(target_os) - Generates C++ thunk implementations for Windows that use GetProcAddress to resolve symbols at runtime

Supporting Refactorings

The following preparatory commits enabled this functionality:

  • 0148179: Expose cxx_gen::IMPLEMENTATION constant and adjust include paths
  • 7e75870: Fix Content::flush_blocks to properly clear blocks_pending
  • 51cb192: Add OutFile::with_buffer method for capturing generated output into strings
  • 1147faf: Refactor symbol writing to use string formatting for reusability

Testing

65f27b8 adds a test in shared_library demonstrating the complete workflow on Windows, Linux, and macOS, including:

  • Building a shared library that exports Rust functions and imports executable functions
  • Generating platform-specific linker files
  • Building and running a test executable that validates bidirectional calling

24ff6d4 adds a work-around for an incompatibility in the Linux test code with rustc versions <1.90.0. I have not investigated the root cause of this issue since the version-script use in the test is not testing essential functionality of this PR and the incompatibility is resolved in newer rustc versions.
This cset can be reverted once the msrv for cxx moves past 1.89.0

The new `IMPLEMENTATION` constant provides the complete content of the
"src/cxx.cc" implementation file.

This `#include "../include/cxx.h"` is changed to avoid imposing a
directory structure on users of `cxx_gen`.
otherwise calling Content::flush leaves blocks_pending set, resulting
in incorrect behaviour when Content::flush_blocks is called again
OutFile::with_buffer allows using write!(out, ...) and the various
write_*(out, ...)  functions to generate output into a String, which
can then be used when generating more complex output.
Rafactor with the aim to make imported and exported symbols more
accessible, as well as function signatures.

Extracted `get_extern_function_signature_parts`, which returns
`return_type`, `signature_args` and `noexcept` used to output the
function signature.

Extracted symbol name generation from writeln! calls, i.e. store the
symbol name in a variable which is then used in the writeln! call.
Introduced `format_symbols_for_linker` function to generate
OS-specific linker file content.

Added public `format_import_symbols_for_linker` and
`format_export_symbols_for_linker` functions for handling
import/export symbols using `format_symbols_for_linker`.

Enhanced `GeneratedCode` struct with public methods to retrieve
import/export symbols and generate import thunks (Windows-specific,
no-op on other platforms).

Updated `OutFile` to manage import/export symbols, including storing
data to generate import thunks.

Extended `write.rs` to record import/export symbols for function shims
and all type support shims.
@chrislimpach chrislimpach marked this pull request as draft March 2, 2026 02:42
+ Introduced a new integration test for validating shared library functionality.
+ Added a new workspace member `tests/shared_library` to the `Cargo.toml`.
+ Implemented a shared library test suite to verify `cxx-gen`'s ability to handle DLL scenarios with bidirectional function calls.
+ Created `tests/shared_library/library` with `lib.rs`, `build.rs`, and `Cargo.toml` for the shared library.
+ Added `tests/main.cc` and `tests/test_shared_library.rs` for testing the shared library's interaction with a host executable.
+ Updated `.gitignore` to exclude generated files from the shared library tests.
rustc <1.90.0 does not handle the version_script used in the Linux
test code -- skip using a version script with rustc <1.90.0 since this
is not testing essential functionality.

this cset can be reverted once the msrv for cxx moves past 1.89.0
@chrislimpach chrislimpach marked this pull request as ready for review March 2, 2026 03:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant