+ An unhandled error has occurred.
+
Reload
+
🗙
+
+
+
+
+
diff --git a/dotnet/native/Cargo.toml b/dotnet/native/Cargo.toml
new file mode 100644
index 00000000..44f37969
--- /dev/null
+++ b/dotnet/native/Cargo.toml
@@ -0,0 +1,27 @@
+# The cdylib backing the Firecrawl.Anydoc NuGet package. csbindgen reads the
+# extern "C" surface in `src/lib.rs` (via `build.rs`) and emits the C# DllImport
+# wrapper into `../src/NativeMethods.g.cs`.
+[package]
+name = "anydoc-dotnet"
+version = "0.1.7"
+edition = "2024"
+description = ".NET bindings for anydoc"
+license = "MIT"
+repository = "https://github.com/firecrawl/anydoc"
+publish = false
+
+[lib]
+name = "anydoc_dotnet"
+crate-type = ["cdylib"]
+
+[dependencies]
+anydoc = { path = "../.." }
+base64 = "0.22"
+serde_json = "1"
+
+[build-dependencies]
+csbindgen = "1.9"
+
+# The workspace root already sets the release profile (LTO, symbol strip);
+# the cdylib carries no unit bodies, so there is nothing to test without a
+# managed host (the same constraint the Node/Python bindings note).
\ No newline at end of file
diff --git a/dotnet/native/build.rs b/dotnet/native/build.rs
new file mode 100644
index 00000000..bea27f28
--- /dev/null
+++ b/dotnet/native/build.rs
@@ -0,0 +1,18 @@
+//! Generate the C# DllImport surface with csbindgen and place it next to the
+//! managed wrapper it belongs to.
+use std::path::Path;
+
+fn main() {
+ let out = Path::new(env!("CARGO_MANIFEST_DIR")).join("../src/NativeMethods.g.cs");
+ csbindgen::Builder::default()
+ .input_extern_file("src/lib.rs")
+ .csharp_dll_name("anydoc_dotnet")
+ .csharp_namespace("Firecrawl.Anydoc.Native")
+ .csharp_class_name("AnydocNative")
+ .csharp_class_accessibility("internal")
+ .csharp_use_function_pointer(true)
+ .generate_csharp_file(out.to_str().expect("csharp output path is utf-8"))
+ .unwrap();
+ println!("cargo:rerun-if-changed=src/lib.rs");
+ println!("cargo:rerun-if-changed=src/serialize.rs");
+}
diff --git a/dotnet/native/src/lib.rs b/dotnet/native/src/lib.rs
new file mode 100644
index 00000000..d8d5781b
--- /dev/null
+++ b/dotnet/native/src/lib.rs
@@ -0,0 +1,229 @@
+//! C ABI exposed to .NET, consumed by csbindgen (`build.rs`) and marshalled by
+//! the C# wrapper in `../src`.
+//!
+//! Inputs are UTF-8 byte slices and results are a small by-value struct whose
+//! payload is an owned buffer the caller frees with [`anydoc_free_result`].
+//! This keeps every crossing a plain `{ptr,len}` pair, so the ABI is identical
+//! on all six supported targets and never relies on `repr(C)` layout sharing a
+//! struct with C#.
+
+use std::ffi::{c_char, CString};
+use std::panic;
+use std::path::Path;
+use std::slice;
+
+use anydoc::ConvertError;
+
+mod serialize;
+
+/// Format codes shared with the C# `Format` enum (ordinal match). `-1` means
+/// "unspecified": let detection name it.
+const DOC: i32 = 0;
+const DOCX: i32 = 1;
+const ODT: i32 = 2;
+const PDF: i32 = 3;
+const PPT: i32 = 4;
+const PPTX: i32 = 5;
+const RTF: i32 = 6;
+const EPUB: i32 = 7;
+const EXCEL: i32 = 8;
+const ODS: i32 = 9;
+const ODP: i32 = 10;
+const CSV: i32 = 11;
+
+fn format_code(format: anydoc::Format) -> i32 {
+ match format {
+ anydoc::Format::Doc => DOC,
+ anydoc::Format::Docx => DOCX,
+ anydoc::Format::Odt => ODT,
+ anydoc::Format::Pdf => PDF,
+ anydoc::Format::Ppt => PPT,
+ anydoc::Format::Pptx => PPTX,
+ anydoc::Format::Rtf => RTF,
+ anydoc::Format::Epub => EPUB,
+ anydoc::Format::Excel => EXCEL,
+ anydoc::Format::Ods => ODS,
+ anydoc::Format::Odp => ODP,
+ anydoc::Format::Csv => CSV,
+ }
+}
+
+fn format_from_code(code: i32) -> Option