Open nihohit opened 5 months ago
A few thoughts:
the given example is for running this in tests, which IMO is a peculiar pattern,
I agree this is mildly abusing unit tests for brevity. That said, often in these unit tests we also want to check that currently generated bindings still match the previously generated bindings.
and it isn't the Rusty way to do this - this is a library, so the correct place for generating extra artifacts is in build.rs.
I don't quite agree for two reasons:
For one, build.rs
is a pre-build script, not a build script. Given the weird things foreign (proc) macros can do, there are instances where you have more information during / after compilation of your crate, not before. If you don't want to use the unit tests hack the best thing to do is creating a dependent bin crate, which runs the interoptopus build logic in its main.rs
.
Also, Interoptopus is both a library and a build tool. There is a trade-off where and when bindings are generated, and what that implies. We basically have a two-part process: 1) getting function and type info, and 2) writing bindings for a specific target language and info generated in 1).
In Interoptopus step 1) happens during compilation of the actual library, which has the advantage that when you work on and compile your library, you immediately know whether you bindings generation will work, or if you added some unsupported constructs (in all fairness, a backend might still not write bindings correctly, but that's a bug of that backend then).
On the downside, that also means we're rather tightly coupled (as a proc macro) to the Rust compiler, and running "out-of-band" is not supported.
With that said, changing how binding generation works can (and maybe should) be changed. In particular supporting 3rd-party types right now is painful, and changing the generation system would address that.
This, with the comment saying "In real projects you might want to add this code to another crate instead"
This is what I meant above. To clarify, your crate layout should be (as we're using it in our projects):
your_crate_core/ -- basic logic if your library in pure Rust
your_crate_ffi/ -- binding logic translating Rust to C-like surface API
your_crate_ffi_bindings/ -- a simple `main.rs` that depends on `your_crate_ffi_bindings` and runs Interoptopus
raises also the question of how/when is my library actually compiled, with added questions such as whether its compiled in release mode, etc..
This depends on your build config, but in essence you could just compile the entire workspace in release mode, then you'd get your_crate_ffi.dll
or so, together with matching bindings.
Thanks for the quick answer!
I think that writing this in the docs would be very helpful, as would having a sample project with the 3-tier structure that you describe. Specifically, clarifying how & what to build in release or debug mode. The reference project is awesome, but it is unclear whether it describes your_crate_core
or your_crate_ffi
in your schema. If I understand your description correctly, the FFI headers and inventory generation should be in your_crate_ffi
- is that correct? If so, can it re-export enums or other types from your_crate_core
without repeating their declaration?
Yes, sorry, the docs could be better and I'd love to have a book or so.
The reference project is awesome, but it is unclear whether it describes your_crate_core or your_crate_ffi in your schema. If I understand your description correctly, the FFI headers and inventory generation should be in your_crate_ffi - is that correct?
My preferred setup goes like so: Have a cargo workspace with at least 3 crates in it, see example above.
your_crate_core
you put regular "Rusty" functions and types, for example a fn f() { ... } -> Result<T, E>
your_crate_ffi
crate then contains a translation layer where you think about how to expose your nice Rust crate as a FFI / C compatible library. For example you might add a function fn f() { ... } -> i32
that in turn calls your_crate_core::f
, and translates the Result
type into some i32
value. You'd also add functions that would handle lifecycle functions such as xxx_init()
, xxx_destroy()
which would create and destroy "service-like" types of your core crate.your_crate_ffi_bindings
is a small "application" crate that mostly depends on your_crate_ffi
and Interoptopus. In its main() {}
function you would just add all the Interoptopus logic you'd find in the unit tests, such as writing C# bindings.Specifically, clarifying how & what to build in release or debug mode
I don't quite understand, but given the example above:
cargo build
should build your DLL in debug modecargo build --release
should build your DLL in release modecargo run -p your_crate_ffi_bindings
would produce your bindings, but the bindings don't care whether the DLLs have been built in in debug or release mode.Was that what you were asking?
Yes, that answered my question.
Thank you very much :)
your_crate_ffi_bindings
is a small "application" crate that mostly depends onyour_crate_ffi
and Interoptopus. In itsmain() {}
function you would just add all the Interoptopus logic you'd find in the unit tests, such as writing C# bindings.
Is this the way that is supposed to be when in the documentation is said "this in reality should be in a separate crate/application" ?
I was also confused if it meant what you specified above. Also, would it happen in its build.rs
(you mentioned main) or in a test inside your_crate_ffi_bindings
?
[edit:] The request is now for documentation / example for the full build process. see discussion.
[original post:] I'm looking through the documentation, and couldn't find an example on how to create bindings (specifically, C#) during the build process - the given example is for running this in tests, which IMO is a peculiar pattern, and it isn't the Rusty way to do this - this is a library, so the correct place for generating extra artifacts is in
build.rs
. This, with the comment saying "In real projects you might want to add this code to another crate instead" raises also the question of how/when is my library actually compiled, with added questions such as whether its compiled in release mode, etc.. Am I missing some more ergonomic way to do this?