Skip to content

Commit e8e1d85

Browse files
authored
Merge pull request #119 from RivenSkaye/main
Add calling convention options for output
2 parents 9acd15b + 9d3edce commit e8e1d85

File tree

3 files changed

+48
-1
lines changed

3 files changed

+48
-1
lines changed

csbindgen/src/emitter.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ pub fn emit_csharp(
146146
"" => format!("{method_prefix}{entry_point}"),
147147
x => format!("{x}{entry_point}"),
148148
};
149+
let call_conv = &item.call_conv;
149150
let return_type = match &item.return_type {
150151
Some(x) => x.to_csharp_string(
151152
options,
@@ -184,7 +185,7 @@ pub fn emit_csharp(
184185
}
185186

186187
method_list_string.push_str_ln(
187-
format!(" [DllImport(__DllName, EntryPoint = \"{entry_point}\", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]").as_str(),
188+
format!(" [DllImport(__DllName, EntryPoint = \"{entry_point}\", CallingConvention = CallingConvention.{call_conv}, ExactSpelling = true)]").as_str(),
188189
);
189190
if return_type == "bool" {
190191
method_list_string.push_str_ln(" [return: MarshalAs(UnmanagedType.U1)]");

csbindgen/src/parser.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,50 @@ fn parse_method(item: FnItem, options: &BindgenOptions) -> Option<ExternMethod>
7777

7878
let method_name = sig.ident.to_string();
7979

80+
let is_x86_windows = std::env::var("CARGO_CFG_TARGET_ARCH").is_ok_and(|v| v == "x86")
81+
&& std::env::var("CARGO_CFG_TARGET_OS").is_ok_and(|v| v == "windows");
82+
let call_conv = if let Some(abi) = sig.abi.map(|abi| abi.name).flatten() {
83+
let abi_str = &abi.value();
84+
if abi_str.contains("system") {
85+
// For i686-pc-windows-* (32-bit binaries) the default calling convention is stdcall, unlike everywhere else.
86+
// See https://doc.rust-lang.org/reference/items/external-blocks.html#abi for a list of possible ABIs and what they translate to.
87+
if is_x86_windows {"StdCall"}
88+
else {"Cdecl"}
89+
}
90+
else if abi_str.contains("stdcall") {"StdCall"}
91+
else if abi_str.contains("thiscall") {
92+
if !is_x86_windows {
93+
eprintln!("ThisCall is only allowed on 32-bit MSVC as it's the 32-bit member function calling convention. Consider using \"system\" instead.");
94+
panic!("Cannot emit `thiscall` code in Rust for a non-x86 target.");
95+
}
96+
else {"ThisCall"}
97+
}
98+
else if abi_str.contains("win64") && (std::env::var("CARGO_CFG_TARGET_OS").is_ok_and(|v| v != "windows") || std::env::var("CARGO_CFG_TARGET_ARCH").is_ok_and(|v| v != "x86_64")) {
99+
eprintln!("win64 is an AMD64-only calling convention. Consider using \"system\" instead.");
100+
panic!("Cannot emit `win64` code in Rust for a non-AMD64-windows target.");
101+
}
102+
else if abi_str.contains("sysv64") && (std::env::var("CARGO_CFG_TARGET_OS").is_ok_and(|v| v == "windows") || std::env::var("CARGO_CFG_TARGET_ARCH").is_ok_and(|v| v != "x86_64")) {
103+
eprintln!("sysv64 is the calling convention for AMD64 non-windows, consider using \"system\" instead.");
104+
panic!("Cannot emit `sysv64` on non-AMD64 and/or windows target.");
105+
}
106+
else if abi_str.contains("aapcs") {
107+
eprintln!("ARM is an extremely complex landscape and not all possible combinations can be checked. Emitting at the user's risk.");
108+
"WinApi"
109+
}
110+
else if abi_str.contains("C") || abi_str.contains("cdecl") || abi_str.contains("win64") || abi_str.contains("sysv64") || abi_str.contains("aapcs") {"Cdecl"}
111+
else {
112+
// This is only ever hit for the `Rust`, `fastcall`, and `efiapi` calling conventions, none of which are supported by C# (as of .NET 9)
113+
// https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.callconvfastcall?view=net-9.0
114+
// https://learn.microsoft.com/en-us/dotnet/standard/native-interop/calling-conventions#platform-default-calling-convention
115+
// https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.callingconvention?view=net-9.0
116+
eprintln!("C# support for calling conventions is limited. Please stick to any of the supported options for interop:");
117+
eprintln!("`cdecl` for `Cdecl`, `stdcall` for `StdCall`, `thiscall` for `ThisCall`, `C` for the compiler's default (most likely `Cdecl`), `system` for automatic selection, or your platform-specific target.");
118+
panic!("Unsupported calling convention requested! .NET interop does not support {abi_str}, please consider using \"system\" for the extern ABI.");
119+
}
120+
} else {
121+
"Cdecl"
122+
}.to_string();
123+
80124
let mut parameters: Vec<Parameter> = Vec::new();
81125
let mut return_type: Option<RustType> = None;
82126

@@ -143,6 +187,7 @@ fn parse_method(item: FnItem, options: &BindgenOptions) -> Option<ExternMethod>
143187
parameters,
144188
return_type,
145189
doc_comment: gather_docs(&attrs),
190+
call_conv,
146191
});
147192
}
148193

csbindgen/src/type_meta.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ pub struct ExternMethod {
4444
pub parameters: Vec<Parameter>,
4545
pub return_type: Option<RustType>,
4646
pub export_naming: ExportSymbolNaming,
47+
pub call_conv: String,
4748
}
4849

4950
#[derive(Clone, Debug)]

0 commit comments

Comments
 (0)