Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support non-C calling conventions #55

Open
Michael-F-Bryan opened this issue Sep 27, 2017 · 7 comments
Open

Support non-C calling conventions #55

Michael-F-Bryan opened this issue Sep 27, 2017 · 7 comments

Comments

@Michael-F-Bryan
Copy link
Contributor

I'm trying to export some Rust functions as a DLL to be used in a larger application, but instead of the usual extern "C" calling convention, I'm using stdcall. With stdcall, cbindgen doesn't emit declarations.

Would it be possible to tweak the rules so cbindgen will make bindings for anything marked extern and #[no_mangle], no matter the calling convention? I don't think there's any time you wouldn't want bindings for a function marked extern.

This looks like it's related to #49, but affects all forms of extern, not just where the "C" is omitted.

@eqrion
Copy link
Collaborator

eqrion commented Sep 27, 2017

This seems reasonable to me.

What needs to be output for fn extern "stdcall"? Just __stdcall after the return type?

@Michael-F-Bryan
Copy link
Contributor Author

According to msdn the syntax looks like this: return-type __stdcall function-name[(argument-list)]. I just tried this out on my Arch Linux and while clang compiles a dummy program just fine, gcc spits out a syntax error. Apparently gcc wants you to write __attribute__((cdecl)) instead.

I haven't used the C pre-processor for much more than conditional compilation and #include, is there a non-hacky way to make this work across different compilers and platforms?

Alternatively, you could add an extra flag to the config struct which takes a Option<CallingConvention> (where CallingConvention is an enum of GCC or MSVC) with the default being None. This would mean you need to explicitly choose how the calling convention should be rendered in a header file. Then you can add a pass before rendering to emit warnings if a non-C calling convention is detected and the user hasn't specified a CallingConvention.


Awesome project by the way. I originally made something similar because rusty-binder looked inactive, it's nowhere near as complete as cbindgen though!

@eqrion
Copy link
Collaborator

eqrion commented Sep 27, 2017

Hmm, so I think we can generate the following to handle this:

#ifndef _MSC_VER
// might need some fudging depending on where the attribute needs to go in the function declaration.
#define __stdcall __attribute__((stdcall))
#endif

Seeing as stdcall is a supported function attribute for gcc (and possibly clang). (1)

@Michael-F-Bryan
Copy link
Contributor Author

That's quite a nice solution. I'm assuming those #defines will only be inserted if the corresponding calling convention is used? You may need some sort of lookup table which resolves a Rust extern "C" and friends to their clang/msvc/gcc counterparts (__cdecl or __attribute__((cdecl))).

I'll see if I can start working on a PR for this tomorrow.

@Michael-F-Bryan
Copy link
Contributor Author

@eqrion, do you have anything in mind for how to add support for calling conventions into cbindgen? I've worked with syn in the past, so wrapping my head around how you extract a Function from its AST node was easy enough.

I think this is all I need to do to add support for calling conventions:

  • tweak Function::load() to accept a syn::Abi and store it internally.
  • update Function's Source::write() impl to actually write out the calling convention
  • Make sure the ABI is detected and passed in
  • Add some sort of flag to the configuration
  • make sure macros are declared at the top if needed

... Is that about it?

@eqrion
Copy link
Collaborator

eqrion commented Sep 28, 2017

@Michael-F-Bryan Yes, that sounds reasonable to me.

You'll need to have a pass to detect what macros should be declared at the top, and it'd be best if this worked after we eliminated unused items (the dependency pass). That can most likely go in Library (1) where I've put other passes like that. Maybe accumulate them into a set of flags or a struct of bool fields that gets passed onto Bindings, where we write it out at the beginning?

Thanks for looking into this! The code isn't the best organized, it's evolved to support a bunch of random features people have wanted quickly.

@otavio
Copy link

otavio commented Feb 22, 2020

We ended needing this feature as well but in our case we had to use extern "system" as it allow dynamic support for Win32 as well as Win64 and others.

From FFI nomicon:

Most of the abis in this list are self-explanatory, but the system abi may seem a little odd. This constraint selects whatever the appropriate ABI is for interoperating with the target's libraries. For example, on win32 with a x86 architecture, this means that the abi used would be stdcall. On x86_64, however, windows uses the C calling convention, so C would be used. This means that in our previous example, we could have used extern "system" { ... } to define a block for all windows systems, not only x86 ones.

It'd be great if we could handle this gracefully.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants