├── .circleci └── config.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples └── dotnet │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── cs │ ├── .gitignore │ ├── Program.cs │ └── cs.csproj │ └── src │ ├── api.rs │ └── lib.rs ├── src ├── error.rs ├── ignores.rs ├── lib.rs └── symbol_config.rs └── tests ├── rust └── main_example.rs ├── snapshots └── test_everything__main_example.snap └── test_everything.rs /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | build: 5 | docker: 6 | - image: rust:1 7 | steps: 8 | - checkout 9 | - run: 10 | name: Print version information 11 | command: rustc --version; cargo --version; rustup --version 12 | - run: 13 | name: Calculate dependencies 14 | command: cargo generate-lockfile 15 | - restore_cache: 16 | keys: 17 | - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} 18 | - run: 19 | name: Build all targets 20 | command: cargo build --all --all-targets 21 | - save_cache: 22 | paths: 23 | - /usr/local/cargo/registry 24 | - target/debug/.fingerprint 25 | - target/debug/build 26 | - target/debug/deps 27 | key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} 28 | - run: 29 | name: Run all tests 30 | command: cargo test --all 31 | - run: 32 | name: Ensure code is consistently formatted via rustfmt 33 | command: rustup component add rustfmt && cargo fmt --all -- --check 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | tests/snapshots/*.new 3 | 4 | # This is a library, so we should ignore Cargo.lock. 5 | Cargo.lock 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "csharpbindgen" 3 | version = "0.1.0" 4 | authors = ["Atul Varma "] 5 | description = "A library for generating C# bindings from Rust code." 6 | license = "MPL-2.0" 7 | keywords = ["bindings", "ffi", "code-generation", "csharp"] 8 | categories = ["external-ffi-bindings", "development-tools::ffi"] 9 | readme = "README.md" 10 | repository = "https://github.com/toolness/csharpbindgen/" 11 | edition = "2018" 12 | 13 | [dev-dependencies] 14 | insta = "0.8.1" 15 | 16 | [dependencies.syn] 17 | version = "0.15" 18 | features = ["full", "extra-traits"] 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `csharpbindgen`   [![CircleCI](https://circleci.com/gh/toolness/csharpbindgen.svg?style=svg)](https://circleci.com/gh/toolness/csharpbindgen) [![docs.rs](https://docs.rs/csharpbindgen/badge.svg)][docs] [![crates.io](https://img.shields.io/crates/v/csharpbindgen.svg)](https://crates.io/crates/csharpbindgen) 2 | 3 | csharpbindgen is a Rust library for generating low-level C# bindings from Rust code. 4 | 5 | For more details, consult the [online documentation][docs] or clone this repository and 6 | run `cargo doc --open`. 7 | 8 | [docs]: https://docs.rs/csharpbindgen 9 | -------------------------------------------------------------------------------- /examples/dotnet/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "testdotnet" 3 | version = "0.1.0" 4 | authors = ["Jihyun Yu "] 5 | edition = "2018" 6 | 7 | [lib] 8 | name = "testdotnet" 9 | crate-type = ["dylib"] 10 | 11 | [dependencies] 12 | 13 | [build-dependencies] 14 | csharpbindgen = { path = "../../" } 15 | -------------------------------------------------------------------------------- /examples/dotnet/README.md: -------------------------------------------------------------------------------- 1 | # `dotnet example` 2 | 3 | ``` 4 | cargo build 5 | (cd cs && LD_LIBRARY_PATH="../target/debug" dotnet run) 6 | ``` 7 | -------------------------------------------------------------------------------- /examples/dotnet/build.rs: -------------------------------------------------------------------------------- 1 | use csharpbindgen::CSAccess; 2 | use std::env; 3 | use std::fs; 4 | use std::path::PathBuf; 5 | 6 | const API_RS: [&'static str; 2] = ["src", "api.rs"]; 7 | type PathParts = [&'static str]; 8 | 9 | fn read_file(path_parts: &PathParts) -> String { 10 | let path = path_from_cwd(path_parts); 11 | 12 | if !path.exists() { 13 | panic!("Expected file to exist: {}", path.to_string_lossy()); 14 | } 15 | 16 | if let Ok(code) = fs::read_to_string(&path) { 17 | code 18 | } else { 19 | panic!("Unable to read {}!", path.to_string_lossy()) 20 | } 21 | } 22 | 23 | fn path_from_cwd(parts: &PathParts) -> PathBuf { 24 | let mut pathbuf = env::current_dir().unwrap(); 25 | for part in parts.iter() { 26 | pathbuf.push(part); 27 | } 28 | pathbuf 29 | } 30 | 31 | fn write_if_changed(path_parts: &PathParts, content: &String) { 32 | let path = path_from_cwd(&path_parts); 33 | 34 | if has_content_changed(&path, &content) { 35 | println!("Writing {}.", path_parts.join("/")); 36 | 37 | fs::write(path, content).unwrap(); 38 | } 39 | } 40 | 41 | fn has_content_changed(path: &PathBuf, new_content: &String) -> bool { 42 | if path.exists() { 43 | let curr_content = fs::read_to_string(path.clone()).unwrap(); 44 | if curr_content == *new_content { 45 | return false; 46 | } 47 | } 48 | true 49 | } 50 | 51 | fn build_csharp_code() { 52 | let srcfile = API_RS; 53 | let code = read_file(&srcfile); 54 | let bindings_result = csharpbindgen::Builder::new("testdotnet", code) 55 | .class_name("TestDotNet") 56 | .generate(); 57 | 58 | match bindings_result { 59 | Err(err) => { 60 | println!( 61 | "Unable to generate C# code from {}.\n{}.", 62 | srcfile.join("/"), 63 | err 64 | ); 65 | std::process::exit(1); 66 | } 67 | Ok(bindings_code) => { 68 | write_if_changed(&["cs", "TestDotNet.cs"], &bindings_code); 69 | } 70 | } 71 | } 72 | 73 | pub fn main() { 74 | build_csharp_code(); 75 | } 76 | -------------------------------------------------------------------------------- /examples/dotnet/cs/.gitignore: -------------------------------------------------------------------------------- 1 | obj 2 | bin 3 | *.cs 4 | !Program.cs 5 | -------------------------------------------------------------------------------- /examples/dotnet/cs/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | using static TestDotNet; 5 | 6 | class Program 7 | { 8 | static Byte op_add(Byte a, Byte b) { 9 | return (Byte)(a + b); 10 | } 11 | 12 | static Byte op_sub(Byte a, Byte b) { 13 | return (Byte)(a - b); 14 | } 15 | 16 | static void Main(string[] args) 17 | { 18 | Debug.Assert(3 == apply_bin_fn(1, 2, op_add)); 19 | Debug.Assert(2 == apply_bin_fn(4, 2, op_sub)); 20 | Console.WriteLine("all tests passed"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/dotnet/cs/cs.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | netcoreapp3.0 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/dotnet/src/api.rs: -------------------------------------------------------------------------------- 1 | pub type BinaryFn = extern "C" fn(a: u8, b: u8) -> u8; 2 | 3 | #[no_mangle] 4 | pub unsafe extern "C" fn apply_bin_fn(a: u8, b: u8, f: BinaryFn) -> u8 { 5 | f(a, b) 6 | } 7 | -------------------------------------------------------------------------------- /examples/dotnet/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod api; 2 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::fmt::{Display, Formatter}; 3 | 4 | /// This represents an error from the library. 5 | #[derive(Debug)] 6 | pub enum Error { 7 | /// A wrapped error from the Syn library, caused when 8 | /// parsing the source Rust code. 9 | SynError(syn::Error), 10 | /// Represents a feature of the source Rust code that cannot 11 | /// be converted to C#. The first item is the error message, 12 | /// and the second is the identifier in the source Rust 13 | /// code that caused the error. 14 | UnsupportedError(String, Option), 15 | } 16 | 17 | pub(crate) type Result = std::result::Result; 18 | 19 | impl Display for Error { 20 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 21 | match self { 22 | Error::SynError(err) => write!(f, "Couldn't parse Rust code: {}", err), 23 | Error::UnsupportedError(reason, maybe_ident) => { 24 | let loc = if let Some(ident) = maybe_ident { 25 | format!(" while processing symbol \"{}\"", ident.to_string()) 26 | } else { 27 | String::from("") 28 | }; 29 | write!(f, "Unable to export C# code{} because {}", loc, reason) 30 | } 31 | } 32 | } 33 | } 34 | 35 | impl std::error::Error for Error { 36 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 37 | match self { 38 | Error::SynError(ref err) => Some(err), 39 | Error::UnsupportedError(_, _) => None, 40 | } 41 | } 42 | } 43 | 44 | pub(crate) fn add_ident(result: Result, ident: &syn::Ident) -> Result { 45 | match result { 46 | Err(Error::UnsupportedError(reason, None)) => { 47 | Err(Error::UnsupportedError(reason, Some(ident.clone()))) 48 | } 49 | _ => result, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/ignores.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeSet; 2 | 3 | pub struct Ignores { 4 | exact: BTreeSet, 5 | prefixes: Vec, 6 | } 7 | 8 | impl Ignores { 9 | pub fn new() -> Self { 10 | Ignores { 11 | exact: BTreeSet::new(), 12 | prefixes: vec![], 13 | } 14 | } 15 | 16 | pub fn add_static_array(&mut self, ignore: &[&str]) { 17 | for name in ignore.iter() { 18 | if (*name).ends_with("*") { 19 | let substr = &(*name)[..(*name).len() - 1]; 20 | self.prefixes.push(String::from(substr)); 21 | } 22 | self.exact.insert(String::from(*name)); 23 | } 24 | } 25 | 26 | pub fn ignore>(&self, value: T) -> bool { 27 | let s = value.as_ref(); 28 | 29 | if self.exact.contains(s) { 30 | return true; 31 | } 32 | 33 | for prefix in self.prefixes.iter() { 34 | if s.starts_with(prefix) { 35 | return true; 36 | } 37 | } 38 | 39 | false 40 | } 41 | } 42 | 43 | #[cfg(test)] 44 | mod tests { 45 | use super::*; 46 | 47 | fn from_static_array(ignore: &[&str]) -> Ignores { 48 | let mut result = Ignores::new(); 49 | result.add_static_array(ignore); 50 | result 51 | } 52 | 53 | #[test] 54 | fn test_it_works_with_exact_matches() { 55 | let ig = from_static_array(&["boop"]); 56 | assert_eq!(ig.ignore("boop"), true); 57 | assert_eq!(ig.ignore("boopy"), false); 58 | } 59 | 60 | #[test] 61 | fn test_it_works_with_prefixes() { 62 | let ig = from_static_array(&["boop*"]); 63 | assert_eq!(ig.ignore("boop"), true); 64 | assert_eq!(ig.ignore("boopy"), true); 65 | assert_eq!(ig.ignore("funkyboop"), false); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! csharpbindgen is a library for generating low-level C# bindings 2 | //! from Rust code. 3 | //! 4 | //! It is currently in a very primitive state, largely designed for use by the 5 | //! [Unity Pathfinder plugin][plugin] and missing many features. 6 | //! 7 | //! ## Quick start 8 | //! 9 | //! The library is intended for use via a [Cargo build script][build]. 10 | //! 11 | //! Here's an example of a simple program that converts some simple Rust code 12 | //! into C#: 13 | //! 14 | //! ``` 15 | //! let rust = r#" 16 | //! pub unsafe extern "C" fn my_func(foo: i32) -> f32 { /* ... */ } 17 | //! "#; 18 | //! 19 | //! let code = csharpbindgen::Builder::new("MyDll", rust.to_string()) 20 | //! .class_name("MyStuff") 21 | //! .generate() 22 | //! .unwrap(); 23 | //! 24 | //! println!("{}", code); 25 | //! ``` 26 | //! 27 | //! This will print out something like the following C# code: 28 | //! 29 | //! ```csharp 30 | //! // This file has been auto-generated, please do not edit it. 31 | //! 32 | //! using System; 33 | //! using System.Runtime.InteropServices; 34 | //! 35 | //! internal class MyStuff { 36 | //! [DllImport("MyDll")] 37 | //! internal static extern Single my_func(Int32 foo); 38 | //! } 39 | //! ``` 40 | //! 41 | //! For a more complete example, see the Unity Pathfinder plugin's [`build.rs`][]. 42 | //! 43 | //! 44 | //! [plugin]: https://github.com/toolness/pathfinder-unity-fun 45 | //! [build]: https://doc.rust-lang.org/cargo/reference/build-scripts.html 46 | //! [`build.rs`]: https://github.com/toolness/pathfinder-unity-fun/blob/master/build.rs 47 | 48 | use std::borrow::Borrow; 49 | use std::collections::HashMap; 50 | use std::fmt; 51 | use std::fmt::{Display, Formatter}; 52 | use std::rc::Rc; 53 | use syn::Item; 54 | 55 | mod error; 56 | mod ignores; 57 | mod symbol_config; 58 | 59 | pub use error::Error; 60 | use error::Result; 61 | use symbol_config::{SymbolConfig, SymbolConfigManager}; 62 | 63 | const INDENT: &'static str = " "; 64 | 65 | /// Enumeration for C#'s access modifiers. 66 | #[derive(Clone, Copy, Debug)] 67 | pub enum CSAccess { 68 | Private, 69 | Protected, 70 | Internal, 71 | Public, 72 | } 73 | 74 | impl Display for CSAccess { 75 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 76 | write!( 77 | f, 78 | "{}", 79 | match self { 80 | CSAccess::Private => "private", 81 | CSAccess::Protected => "protected", 82 | CSAccess::Internal => "internal", 83 | CSAccess::Public => "public", 84 | } 85 | ) 86 | } 87 | } 88 | 89 | impl Default for CSAccess { 90 | fn default() -> Self { 91 | CSAccess::Internal 92 | } 93 | } 94 | 95 | struct CSTypeDef { 96 | name: String, 97 | ty: CSType, 98 | } 99 | 100 | impl CSTypeDef { 101 | pub fn from_rust_type_def(rust_type_def: &syn::ItemType) -> Result { 102 | Ok(CSTypeDef { 103 | name: rust_type_def.ident.to_string(), 104 | ty: CSType::from_rust_type(&rust_type_def.ty)?, 105 | }) 106 | } 107 | } 108 | 109 | #[derive(Clone, Debug)] 110 | struct CSType { 111 | name: String, 112 | is_ptr: bool, 113 | 114 | descr: CSTyDescr, 115 | } 116 | 117 | #[derive(Clone, Debug)] 118 | enum CSTyDescr { 119 | /// named type, including primitives and unknown types 120 | Named, 121 | /// struct type 122 | Struct(Rc), 123 | /// delegate type 124 | Delegate(Box), 125 | } 126 | 127 | impl CSType { 128 | fn void() -> Self { 129 | CSType { 130 | name: "void".to_owned(), 131 | is_ptr: false, 132 | descr: CSTyDescr::Named, 133 | } 134 | } 135 | 136 | pub fn from_rust_type(rust_type: &syn::Type) -> Result { 137 | match rust_type { 138 | syn::Type::Path(type_path) => { 139 | let last = type_path 140 | .path 141 | .segments 142 | .last() 143 | .expect("expected at least one path segment on type!"); 144 | Ok(CSType { 145 | name: last.value().ident.to_string(), 146 | is_ptr: false, 147 | descr: CSTyDescr::Named, 148 | }) 149 | } 150 | 151 | syn::Type::Ptr(type_ptr) => { 152 | let mut wrapped_type = CSType::from_rust_type(&type_ptr.elem)?; 153 | if wrapped_type.is_ptr { 154 | return unsupported(format!( 155 | "double pointers for {} are unsupported!", 156 | wrapped_type.name 157 | )); 158 | } 159 | wrapped_type.is_ptr = true; 160 | Ok(wrapped_type) 161 | } 162 | 163 | syn::Type::BareFn(bare_fn) => { 164 | let d = CSDelegate::from_rust_fn(bare_fn)?; 165 | Ok(CSType { 166 | name: "__bare_fn".into(), 167 | is_ptr: false, 168 | descr: CSTyDescr::Delegate(Box::new(d)), 169 | }) 170 | } 171 | 172 | _ => unsupported("the type is unsupported"), 173 | } 174 | } 175 | } 176 | 177 | impl Display for CSType { 178 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 179 | let name = to_cs_primitive(&self.name); 180 | if self.is_ptr { 181 | match &self.descr { 182 | CSTyDescr::Struct(_s) => write!(f, "ref {}", name), 183 | _ => write!(f, "IntPtr /* {} */", name), 184 | } 185 | } else { 186 | write!(f, "{}", name) 187 | } 188 | } 189 | } 190 | 191 | #[derive(Clone, Debug)] 192 | struct CSDelegate { 193 | output: Option, 194 | args: Vec<(String, CSType)>, 195 | cfg: SymbolConfig, 196 | } 197 | 198 | impl CSDelegate { 199 | pub fn from_rust_fn(bare_fn: &syn::TypeBareFn) -> Result { 200 | if bare_fn.lifetimes.is_some() { 201 | return unsupported("lifetimes are unsupported!"); 202 | } 203 | if bare_fn.variadic.is_some() { 204 | return unsupported("variadic is unsupported!"); 205 | } 206 | 207 | let output = match bare_fn.output { 208 | syn::ReturnType::Default => None, 209 | syn::ReturnType::Type(_, ref out_ty) => Some(CSType::from_rust_type(out_ty)?), 210 | }; 211 | 212 | let mut args = Vec::new(); 213 | for (i, fn_arg) in bare_fn.inputs.iter().enumerate() { 214 | let name = match fn_arg.name { 215 | Some((syn::BareFnArgName::Named(ref ident), _)) => ident.to_string(), 216 | _ => format!("_unnamed_{}", i), 217 | }; 218 | args.push((name, CSType::from_rust_type(&fn_arg.ty)?)); 219 | } 220 | 221 | Ok(CSDelegate { 222 | output, 223 | args, 224 | cfg: SymbolConfig::default(), 225 | }) 226 | } 227 | 228 | pub fn return_ty(&self) -> CSType { 229 | self.output.clone().unwrap_or(CSType::void()) 230 | } 231 | } 232 | 233 | struct CSConst { 234 | name: String, 235 | ty: CSType, 236 | value: String, 237 | cfg: SymbolConfig, 238 | } 239 | 240 | impl CSConst { 241 | pub fn from_rust_const(rust_const: &syn::ItemConst, cfg: SymbolConfig) -> Result { 242 | let value = if let syn::Expr::Lit(expr_lit) = &rust_const.expr.borrow() { 243 | if let syn::Lit::Int(lit_int) = &expr_lit.lit { 244 | lit_int.value().to_string() 245 | } else { 246 | return unsupported(format!( 247 | "Unsupported const expression literal value: {:?}", 248 | expr_lit 249 | )); 250 | } 251 | } else { 252 | return unsupported(format!( 253 | "Unsupported const expression value: {:?}", 254 | rust_const.expr 255 | )); 256 | }; 257 | Ok(CSConst { 258 | name: munge_cs_name(rust_const.ident.to_string()), 259 | ty: CSType::from_rust_type(&rust_const.ty)?, 260 | value, 261 | cfg, 262 | }) 263 | } 264 | } 265 | 266 | #[derive(Debug)] 267 | struct CSStructField { 268 | name: String, 269 | ty: CSType, 270 | } 271 | 272 | impl CSStructField { 273 | pub fn from_named_rust_field(rust_field: &syn::Field) -> Result { 274 | Ok(CSStructField { 275 | name: munge_cs_name(rust_field.ident.as_ref().unwrap().to_string()), 276 | ty: CSType::from_rust_type(&rust_field.ty)?, 277 | }) 278 | } 279 | 280 | pub fn to_string(&self) -> String { 281 | to_cs_var_decl(&self.ty, &self.name) 282 | } 283 | } 284 | 285 | #[derive(Debug)] 286 | struct CSStruct { 287 | name: String, 288 | fields: Vec, 289 | cfg: SymbolConfig, 290 | } 291 | 292 | impl CSStruct { 293 | pub fn from_rust_struct(rust_struct: &syn::ItemStruct, cfg: SymbolConfig) -> Result { 294 | let mut fields = vec![]; 295 | 296 | if let syn::Fields::Named(rust_fields) = &rust_struct.fields { 297 | for rust_field in rust_fields.named.iter() { 298 | fields.push(CSStructField::from_named_rust_field(rust_field)?); 299 | } 300 | } 301 | Ok(CSStruct { 302 | name: rust_struct.ident.to_string(), 303 | fields, 304 | cfg, 305 | }) 306 | } 307 | } 308 | 309 | impl Display for CSStruct { 310 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 311 | writeln!(f, "[Serializable]")?; 312 | writeln!(f, "[StructLayout(LayoutKind.Sequential)]")?; 313 | writeln!(f, "{} struct {} {{", self.cfg.access, self.name)?; 314 | for field in self.fields.iter() { 315 | writeln!(f, "{}{} {};", INDENT, self.cfg.access, field.to_string())?; 316 | } 317 | 318 | let constructor_args: Vec = 319 | self.fields.iter().map(|field| field.to_string()).collect(); 320 | writeln!( 321 | f, 322 | "\n{}{} {}({}) {{", 323 | INDENT, 324 | self.cfg.access, 325 | self.name, 326 | constructor_args.join(", ") 327 | )?; 328 | for field in self.fields.iter() { 329 | writeln!( 330 | f, 331 | "{}{}this.{} = {};", 332 | INDENT, INDENT, field.name, field.name 333 | )?; 334 | } 335 | writeln!(f, "{}}}", INDENT)?; 336 | 337 | writeln!(f, "}}") 338 | } 339 | } 340 | 341 | struct CSFuncArg { 342 | name: String, 343 | ty: CSType, 344 | } 345 | 346 | impl CSFuncArg { 347 | pub fn from_rust_arg_captured(rust_arg: &syn::ArgCaptured) -> Result { 348 | if let syn::Pat::Ident(pat_ident) = &rust_arg.pat { 349 | Ok(CSFuncArg { 350 | name: munge_cs_name(pat_ident.ident.to_string()), 351 | ty: CSType::from_rust_type(&rust_arg.ty)?, 352 | }) 353 | } else { 354 | unsupported(format!( 355 | "captured arg pattern is unsupported: {:?}", 356 | rust_arg.pat 357 | )) 358 | } 359 | } 360 | 361 | pub fn to_string(&self) -> String { 362 | to_cs_var_decl(&self.ty, &self.name) 363 | } 364 | } 365 | 366 | struct CSFunc { 367 | name: String, 368 | args: Vec, 369 | return_ty: Option, 370 | cfg: SymbolConfig, 371 | } 372 | 373 | impl CSFunc { 374 | pub fn from_rust_fn(rust_fn: &syn::ItemFn, cfg: SymbolConfig) -> Result { 375 | let mut args = vec![]; 376 | 377 | for input in rust_fn.decl.inputs.iter() { 378 | if let syn::FnArg::Captured(cap) = input { 379 | args.push(CSFuncArg::from_rust_arg_captured(&cap)?); 380 | } else { 381 | return unsupported(format!( 382 | "Input for function '{}' is unsupported: {:?}", 383 | rust_fn.ident.to_string(), 384 | input 385 | )); 386 | } 387 | } 388 | 389 | let return_ty = match &rust_fn.decl.output { 390 | syn::ReturnType::Default => None, 391 | syn::ReturnType::Type(_, ty) => Some(CSType::from_rust_type(&ty)?), 392 | }; 393 | 394 | Ok(CSFunc { 395 | name: rust_fn.ident.to_string(), 396 | args, 397 | return_ty, 398 | cfg, 399 | }) 400 | } 401 | } 402 | 403 | impl Display for CSFunc { 404 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 405 | let return_ty = match &self.return_ty { 406 | None => String::from("void"), 407 | Some(ty) => ty.to_string(), 408 | }; 409 | let args: Vec = self.args.iter().map(|arg| arg.to_string()).collect(); 410 | write!( 411 | f, 412 | "{} static extern {} {}({});", 413 | self.cfg.access, 414 | return_ty, 415 | self.name, 416 | args.join(", ") 417 | ) 418 | } 419 | } 420 | 421 | struct CSFile { 422 | class_name: String, 423 | dll_name: String, 424 | consts: Vec, 425 | structs: Vec>, 426 | funcs: Vec, 427 | type_defs: HashMap, 428 | delegate_defs: Vec<(String, CSDelegate)>, 429 | } 430 | 431 | impl CSFile { 432 | pub fn new(class_name: String, dll_name: String) -> Self { 433 | CSFile { 434 | class_name, 435 | dll_name, 436 | consts: vec![], 437 | structs: vec![], 438 | funcs: vec![], 439 | type_defs: HashMap::new(), 440 | delegate_defs: vec![], 441 | } 442 | } 443 | 444 | pub fn populate_from_rust_file( 445 | &mut self, 446 | rust_file: &syn::File, 447 | cfg_mgr: &SymbolConfigManager, 448 | ) -> Result<()> { 449 | for item in rust_file.items.iter() { 450 | match item { 451 | Item::Const(item_const) => { 452 | if let Some(cfg) = cfg_mgr.get(&item_const.ident) { 453 | let cs_const = error::add_ident( 454 | CSConst::from_rust_const(&item_const, cfg), 455 | &item_const.ident, 456 | )?; 457 | self.consts.push(cs_const); 458 | } 459 | } 460 | Item::Struct(item_struct) => { 461 | if let Some(cfg) = cfg_mgr.get(&item_struct.ident) { 462 | let cs_struct = error::add_ident( 463 | CSStruct::from_rust_struct(&item_struct, cfg), 464 | &item_struct.ident, 465 | )?; 466 | self.structs.push(Rc::new(cs_struct)); 467 | } 468 | } 469 | Item::Fn(item_fn) => { 470 | if item_fn.abi.is_some() { 471 | if let Some(cfg) = cfg_mgr.get(&item_fn.ident) { 472 | let cs_func = error::add_ident( 473 | CSFunc::from_rust_fn(&item_fn, cfg), 474 | &item_fn.ident, 475 | )?; 476 | self.funcs.push(cs_func); 477 | } 478 | } 479 | } 480 | Item::Type(item_type) => { 481 | if let Some(cfg) = cfg_mgr.get(&item_type.ident) { 482 | let type_def = error::add_ident( 483 | CSTypeDef::from_rust_type_def(&item_type), 484 | &item_type.ident, 485 | )?; 486 | 487 | match type_def.ty.descr { 488 | CSTyDescr::Delegate(mut d) => { 489 | d.cfg = cfg; 490 | self.delegate_defs.push((type_def.name.clone(), *d.clone())); 491 | } 492 | _ => { 493 | self.type_defs.insert(type_def.name.clone(), type_def); 494 | } 495 | } 496 | } 497 | } 498 | _ => {} 499 | } 500 | } 501 | 502 | Ok(()) 503 | } 504 | 505 | fn resolve_types(&mut self) -> Result<()> { 506 | let mut struct_map: HashMap<&str, &Rc> = HashMap::new(); 507 | 508 | for st in self.structs.iter() { 509 | struct_map.insert(&st.name, &st); 510 | } 511 | 512 | for func in self.funcs.iter_mut() { 513 | for arg in func.args.iter_mut() { 514 | resolve_type(&self.type_defs, &struct_map, &mut arg.ty)?; 515 | } 516 | if let Some(return_ty) = &func.return_ty { 517 | if let Some(ty) = resolve_type_def(return_ty, &self.type_defs)? { 518 | func.return_ty = Some(ty); 519 | } 520 | } 521 | } 522 | 523 | for (_name, d) in self.delegate_defs.iter_mut() { 524 | if let Some(ty) = resolve_type_def(&d.return_ty(), &self.type_defs)? { 525 | d.output = Some(ty); 526 | } 527 | 528 | for (_arg_name, arg_ty) in d.args.iter_mut() { 529 | resolve_type(&self.type_defs, &struct_map, arg_ty)?; 530 | } 531 | } 532 | 533 | Ok(()) 534 | } 535 | } 536 | 537 | impl Display for CSFile { 538 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 539 | writeln!( 540 | f, 541 | "// This file has been auto-generated, please do not edit it.\n" 542 | )?; 543 | writeln!(f, "using System;")?; 544 | writeln!(f, "using System.Runtime.InteropServices;\n")?; 545 | 546 | for st in self.structs.iter() { 547 | writeln!(f, "{}", st)?; 548 | } 549 | writeln!(f, "{} class {} {{", CSAccess::default(), self.class_name)?; 550 | 551 | for con in self.consts.iter() { 552 | writeln!( 553 | f, 554 | "{}{} const {} {} = {};\n", 555 | INDENT, con.cfg.access, con.ty, con.name, con.value 556 | )?; 557 | } 558 | 559 | for (name, d) in self.delegate_defs.iter() { 560 | write!( 561 | f, 562 | "{}{} delegate {} {}(", 563 | INDENT, 564 | d.cfg.access, 565 | d.return_ty(), 566 | name 567 | )?; 568 | for (i, (name, ty)) in d.args.iter().enumerate() { 569 | if i == 0 { 570 | write!(f, "{} {}", ty, name)?; 571 | } else { 572 | write!(f, ", {} {}", ty, name)?; 573 | } 574 | } 575 | writeln!(f, ");\n")?; 576 | } 577 | 578 | for func in self.funcs.iter() { 579 | writeln!(f, "{}[DllImport(\"{}\")]", INDENT, self.dll_name)?; 580 | writeln!(f, "{}{}\n", INDENT, func)?; 581 | } 582 | writeln!(f, "}}") 583 | } 584 | } 585 | 586 | /// A [builder pattern] for the Rust-to-C# conversion process. 587 | /// 588 | /// [builder pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html 589 | pub struct Builder { 590 | class_name: String, 591 | dll_name: String, 592 | rust_code: String, 593 | sconfig: SymbolConfigManager, 594 | } 595 | 596 | impl Builder { 597 | /// Creates a new instance with the following arguments: 598 | /// 599 | /// * `dll_name` is the name of the DLL that the C# `DllImport` attribute 600 | /// will be bound to for all exported functions. 601 | /// 602 | /// * `rust_code` is the source Rust code to convert into C#. 603 | pub fn new>(dll_name: T, rust_code: String) -> Self { 604 | Builder { 605 | class_name: String::from("RustExports"), 606 | dll_name: String::from(dll_name.as_ref()), 607 | rust_code, 608 | sconfig: SymbolConfigManager::new(), 609 | } 610 | } 611 | 612 | /// Sets the name of the C# class that will contain all exported functions. 613 | /// If never called, the C# class will be called `RustExports`. 614 | pub fn class_name>(mut self, class_name: T) -> Self { 615 | self.class_name = String::from(class_name.as_ref()); 616 | self 617 | } 618 | 619 | /// Specifies a list of Rust identifier patterns to be ignored (i.e., not 620 | /// exported to C#). 621 | /// 622 | /// The pattern syntax is currently very simple: if it ends with a `*`, it 623 | /// matches any Rust identifier that starts with the part of the pattern before 624 | /// the `*` (e.g., `Boop*` matches `BoopJones` and `BoopFoo`). Otherwise, it 625 | /// represents an exact match to a Rust identifier. 626 | pub fn ignore(mut self, ignores: &[&str]) -> Self { 627 | self.sconfig.ignores.add_static_array(ignores); 628 | self 629 | } 630 | 631 | /// Specifies that the given Rust identifier should be exported to C# with the 632 | /// given C# access modifier. By default, all exports are given the `internal` 633 | /// access modifier. 634 | pub fn access>(mut self, symbol_name: T, access: CSAccess) -> Self { 635 | self.sconfig 636 | .config_map 637 | .insert(String::from(symbol_name.as_ref()), SymbolConfig { access }); 638 | self 639 | } 640 | 641 | /// Performs the conversion of source Rust code to C#. 642 | pub fn generate(self) -> Result { 643 | let syntax = parse_file(&self.rust_code)?; 644 | let mut program = CSFile::new(self.class_name, self.dll_name); 645 | program.populate_from_rust_file(&syntax, &self.sconfig)?; 646 | program.resolve_types()?; 647 | Ok(format!("{}", program)) 648 | } 649 | } 650 | 651 | fn parse_file(rust_code: &String) -> Result { 652 | match syn::parse_file(rust_code) { 653 | Ok(result) => Ok(result), 654 | Err(err) => Err(Error::SynError(err)), 655 | } 656 | } 657 | 658 | fn resolve_type( 659 | type_defs: &HashMap, 660 | struct_map: &HashMap<&str, &Rc>, 661 | ty: &mut CSType, 662 | ) -> Result<()> { 663 | if let Some(ty1) = resolve_type_def(&ty, &type_defs)? { 664 | *ty = ty1; 665 | } 666 | if let Some(st) = struct_map.get(&ty.name.as_ref()) { 667 | ty.descr = CSTyDescr::Struct((*st).clone()); 668 | } 669 | Ok(()) 670 | } 671 | 672 | fn resolve_type_def(ty: &CSType, type_defs: &HashMap) -> Result> { 673 | if let Some(type_def) = type_defs.get(&ty.name) { 674 | if ty.is_ptr && type_def.ty.is_ptr { 675 | return unsupported(format!( 676 | "double pointer to {} via type {} is unsupported!", 677 | type_def.ty.name, type_def.name 678 | )); 679 | } 680 | if let CSTyDescr::Delegate(_d) = &type_def.ty.descr { 681 | // resolve delegate type to named type 682 | return Ok(Some(CSType { 683 | name: ty.name.to_owned(), 684 | is_ptr: false, 685 | descr: CSTyDescr::Named, 686 | })); 687 | } 688 | Ok(Some(type_def.ty.clone())) 689 | } else { 690 | Ok(None) 691 | } 692 | } 693 | 694 | fn munge_cs_name(name: String) -> String { 695 | match name.as_ref() { 696 | "string" => String::from("str"), 697 | _ => name, 698 | } 699 | } 700 | 701 | fn to_cs_primitive<'a>(type_name: &'a str) -> &'a str { 702 | match type_name { 703 | "i8" => "SByte", 704 | "u8" => "Byte", 705 | "i16" => "Int16", 706 | "u16" => "UInt16", 707 | "i32" => "Int32", 708 | "u32" => "UInt32", 709 | "u64" => "Int64", 710 | "i64" => "UInt64", 711 | "isize" => "IntPtr", 712 | "usize" => "UIntPtr", 713 | "f32" => "Single", 714 | "f64" => "Double", 715 | _ => type_name, 716 | } 717 | } 718 | 719 | fn to_cs_var_decl>(ty: &CSType, name: T) -> String { 720 | format!("{} {}", ty, name.as_ref()) 721 | } 722 | 723 | fn unsupported(msg: S) -> Result 724 | where 725 | S: Into, 726 | { 727 | Err(Error::UnsupportedError(msg.into(), None)) 728 | } 729 | 730 | #[cfg(test)] 731 | mod tests { 732 | use super::*; 733 | 734 | #[test] 735 | fn test_it_errors_on_invalid_rust_code() { 736 | let err = Builder::new("Blarg", String::from("HELLO THERE")) 737 | .generate() 738 | .unwrap_err(); 739 | let err_msg = format!("{}", err); 740 | assert_eq!(err_msg, "Couldn't parse Rust code: expected `!`"); 741 | } 742 | 743 | #[test] 744 | fn test_it_errors_on_unsupported_rust_code() { 745 | let err = Builder::new( 746 | "Blarg", 747 | String::from( 748 | r#" 749 | pub type MyFunkyThing<'a> = &'a u8; 750 | "#, 751 | ), 752 | ) 753 | .generate() 754 | .unwrap_err(); 755 | assert_eq!( 756 | format!("{}", err), 757 | "Unable to export C# code while processing symbol \"MyFunkyThing\" because the type is unsupported" 758 | ); 759 | } 760 | } 761 | -------------------------------------------------------------------------------- /src/symbol_config.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::ignores::Ignores; 4 | use crate::CSAccess; 5 | 6 | #[derive(Clone, Copy, Debug)] 7 | pub struct SymbolConfig { 8 | pub access: CSAccess, 9 | } 10 | 11 | impl Default for SymbolConfig { 12 | fn default() -> Self { 13 | SymbolConfig { 14 | access: CSAccess::default(), 15 | } 16 | } 17 | } 18 | 19 | pub struct SymbolConfigManager { 20 | pub ignores: Ignores, 21 | pub config_map: HashMap, 22 | } 23 | 24 | impl SymbolConfigManager { 25 | pub fn new() -> Self { 26 | SymbolConfigManager { 27 | ignores: Ignores::new(), 28 | config_map: HashMap::new(), 29 | } 30 | } 31 | 32 | pub fn get(&self, ident: &syn::Ident) -> Option { 33 | let string = ident.to_string(); 34 | if self.ignores.ignore(&string) { 35 | None 36 | } else { 37 | if let Some(cfg) = self.config_map.get(&string) { 38 | Some(*cfg) 39 | } else { 40 | Some(SymbolConfig::default()) 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/rust/main_example.rs: -------------------------------------------------------------------------------- 1 | pub const BOOP: u8 = 1; 2 | pub const IGNORE_THIS_CONST: u8 = 2; 3 | 4 | #[repr(C)] 5 | pub struct MyStruct { 6 | pub foo: f32, 7 | pub bar: f32, 8 | } 9 | 10 | #[repr(C)] 11 | pub struct IgnoreThisStruct { 12 | pub foo: f32, 13 | } 14 | 15 | pub type MyOpaqueRef = *mut MyOpaqueStruct; 16 | 17 | pub unsafe extern "C" fn public_func() {} 18 | 19 | #[repr(C)] 20 | pub struct PublicStruct { 21 | pub bop: i32, 22 | } 23 | 24 | fn unexported_func() {} 25 | 26 | pub unsafe extern "C" fn blarg(a: i32, b: *const MyStruct, c: MyOpaqueRef) -> u8 { 27 | 120 28 | } 29 | 30 | pub unsafe extern "C" fn ignore_this_func(a: i32) -> u8 { 31 | 120 32 | } 33 | 34 | // test primitive types 35 | pub extern "C" fn primitive_typeck( 36 | _v1: u8, 37 | _v2: i8, 38 | _v3: u16, 39 | _v4: i16, 40 | _v5: u32, 41 | _v6: i32, 42 | _v7: u64, 43 | _v8: i64, 44 | _v9: usize, 45 | _v10: isize, 46 | ) -> u8 { 47 | 0 48 | } 49 | 50 | pub type CallbackFn = extern "C" fn(v: u8) -> u8; 51 | pub type PublicCallbackFn = extern "C" fn(s: *const MyStruct); 52 | 53 | pub unsafe extern "C" fn fn_with_callback(v: u8, cb: CallbackFn) -> u8 { 54 | cb(v) 55 | } 56 | 57 | pub unsafe extern "C" fn fn_with_callback_ptr(v: u8, cb: *const CallbackFn) -> u8 { 58 | (*cb)(v) 59 | } 60 | -------------------------------------------------------------------------------- /tests/snapshots/test_everything__main_example.snap: -------------------------------------------------------------------------------- 1 | --- 2 | created: "2019-11-24T12:59:31.538548128Z" 3 | creator: insta@0.8.2 4 | source: tests/test_everything.rs 5 | expression: code 6 | --- 7 | // This file has been auto-generated, please do not edit it. 8 | 9 | using System; 10 | using System.Runtime.InteropServices; 11 | 12 | [Serializable] 13 | [StructLayout(LayoutKind.Sequential)] 14 | internal struct MyStruct { 15 | internal Single foo; 16 | internal Single bar; 17 | 18 | internal MyStruct(Single foo, Single bar) { 19 | this.foo = foo; 20 | this.bar = bar; 21 | } 22 | } 23 | 24 | [Serializable] 25 | [StructLayout(LayoutKind.Sequential)] 26 | public struct PublicStruct { 27 | public Int32 bop; 28 | 29 | public PublicStruct(Int32 bop) { 30 | this.bop = bop; 31 | } 32 | } 33 | 34 | internal class MyStuff { 35 | internal const Byte BOOP = 1; 36 | 37 | internal delegate Byte CallbackFn(Byte v); 38 | 39 | public delegate void PublicCallbackFn(ref MyStruct s); 40 | 41 | [DllImport("MyDll")] 42 | public static extern void public_func(); 43 | 44 | [DllImport("MyDll")] 45 | internal static extern Byte blarg(Int32 a, ref MyStruct b, IntPtr /* MyOpaqueStruct */ c); 46 | 47 | [DllImport("MyDll")] 48 | internal static extern Byte primitive_typeck(Byte _v1, SByte _v2, UInt16 _v3, Int16 _v4, UInt32 _v5, Int32 _v6, Int64 _v7, UInt64 _v8, UIntPtr _v9, IntPtr _v10); 49 | 50 | [DllImport("MyDll")] 51 | internal static extern Byte fn_with_callback(Byte v, CallbackFn cb); 52 | 53 | [DllImport("MyDll")] 54 | internal static extern Byte fn_with_callback_ptr(Byte v, IntPtr /* CallbackFn */ cb); 55 | 56 | } 57 | 58 | -------------------------------------------------------------------------------- /tests/test_everything.rs: -------------------------------------------------------------------------------- 1 | use insta::assert_snapshot_matches; 2 | use std::fs; 3 | use std::path::PathBuf; 4 | 5 | use csharpbindgen::{Builder, CSAccess}; 6 | 7 | fn load_example(name: &'static str) -> String { 8 | let mut path = PathBuf::new(); 9 | path.push("tests"); 10 | path.push("rust"); 11 | path.push(format!("{}.rs", name)); 12 | fs::read_to_string(path).unwrap() 13 | } 14 | 15 | #[test] 16 | fn test_main_example() { 17 | let example_name = "main_example"; 18 | let code = Builder::new("MyDll", load_example(example_name)) 19 | .class_name("MyStuff") 20 | .ignore(&["ignore_*", "IGNORE_*", "Ignore*"]) 21 | .access("public_func", CSAccess::Public) 22 | .access("PublicStruct", CSAccess::Public) 23 | .access("PublicCallbackFn", CSAccess::Public) 24 | .generate() 25 | .unwrap(); 26 | 27 | assert_snapshot_matches!(example_name, code); 28 | } 29 | --------------------------------------------------------------------------------