91f6f67257
The first language stecker on the C ABI hub: a .NET binding exposing all 514 indicators as idiomatic `IDisposable` classes, generated from `wickra.h`. ## What's here - **`bindings/csharp/`** — the `Wickra` .NET 8 package. `[LibraryImport]` source-generated P/Invoke (`NativeMethods.g.cs`) plus idiomatic wrappers (`Indicators.g.cs`), both generated from the committed `bindings/c/include/wickra.h`. The binding owns no indicator maths — it only marshals types across the C ABI. - **Marshalling, verified end-to-end against the native library.** Opaque handles cross as `nint` kept alive per call via a `SafeHandle`; `bool` as `[MarshalAs(U1)]` (Rust `bool` is one byte); a self-correcting `DllImportResolver` validates the loaded library actually exports the Wickra ABI. Tests cover one representative per FFI archetype (scalar, candle, pairwise, multi-output, bars, profile, values-profile, order-book / array-input) plus exact Sma reference values. - **NuGet packaging** — `dotnet pack` produces `Wickra.<version>.nupkg`; the release pipeline stages prebuilt native libraries under `runtimes/<rid>/native/` for six target triples (win/linux/osx × x64/arm64). - **`examples/csharp/`** — nine examples mirroring `examples/c/`: streaming, backtest, multi_timeframe, parallel_assets, three strategies, and fetch_btcusdt + live_binance. - **CI** — a `csharp` job on the three OSes builds the C ABI, tests the binding, and runs the offline examples. **Release** — a gated `csharp-publish` job packs and pushes to NuGet (gated on `NUGET_API_KEY`, independent of the GitHub-release job so a C# hiccup never blocks the C/C++ asset release). - **Docs consistency wave** — README, CONTRIBUTING, CHANGELOG, examples/README, the issue / PR templates, `sync-about.yml`, and `.gitattributes`. The native Python / Node / WASM bindings and the C ABI are untouched; this is additive. Publishing to NuGet stays gated behind the release tag and the secret.
88 lines
3.0 KiB
C#
88 lines
3.0 KiB
C#
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace Wickra;
|
|
|
|
/// <summary>
|
|
/// Native library resolution for the Wickra C ABI.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// When consumed as a NuGet package the native library ships under
|
|
/// <c>runtimes/<rid>/native/</c> and the default runtime resolver finds it
|
|
/// automatically. For local development (project reference against a cargo build)
|
|
/// the resolver additionally walks up the directory tree to locate
|
|
/// <c>target/release</c> or <c>target/debug</c>. Every candidate is validated to
|
|
/// actually export the Wickra ABI before it is accepted, so an unrelated library
|
|
/// of the same name cannot shadow the real one.
|
|
/// </remarks>
|
|
internal static class WickraNative
|
|
{
|
|
/// <summary>The library name passed to <c>[LibraryImport]</c>.</summary>
|
|
internal const string LibraryName = "wickra";
|
|
|
|
// Any exported symbol works as a fingerprint; sma_new exists in every build.
|
|
private const string SentinelSymbol = "wickra_sma_new";
|
|
|
|
[ModuleInitializer]
|
|
internal static void Register()
|
|
{
|
|
NativeLibrary.SetDllImportResolver(typeof(WickraNative).Assembly, Resolve);
|
|
}
|
|
|
|
private static nint Resolve(string libraryName, System.Reflection.Assembly assembly, DllImportSearchPath? searchPath)
|
|
{
|
|
if (libraryName != LibraryName)
|
|
{
|
|
return nint.Zero;
|
|
}
|
|
|
|
// 1. Default resolution (NuGet runtimes/ layout, app-local copies). Accept
|
|
// only if it is genuinely the Wickra ABI; otherwise discard and fall through.
|
|
if (NativeLibrary.TryLoad(libraryName, assembly, searchPath, out var handle))
|
|
{
|
|
if (Exports(handle))
|
|
{
|
|
return handle;
|
|
}
|
|
|
|
NativeLibrary.Free(handle);
|
|
}
|
|
|
|
// 2. Development fallback: locate the cargo build output.
|
|
var fileName = NativeFileName();
|
|
var dir = AppContext.BaseDirectory;
|
|
for (var i = 0; i < 16 && dir is not null; i++)
|
|
{
|
|
foreach (var profile in new[] { "release", "debug" })
|
|
{
|
|
var candidate = Path.Combine(dir, "target", profile, fileName);
|
|
if (File.Exists(candidate) && NativeLibrary.TryLoad(candidate, out var devHandle))
|
|
{
|
|
if (Exports(devHandle))
|
|
{
|
|
return devHandle;
|
|
}
|
|
|
|
NativeLibrary.Free(devHandle);
|
|
}
|
|
}
|
|
|
|
dir = Path.GetDirectoryName(dir.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
|
|
}
|
|
|
|
return nint.Zero;
|
|
}
|
|
|
|
private static bool Exports(nint handle) => NativeLibrary.TryGetExport(handle, SentinelSymbol, out _);
|
|
|
|
private static string NativeFileName()
|
|
{
|
|
if (OperatingSystem.IsWindows())
|
|
{
|
|
return "wickra.dll";
|
|
}
|
|
|
|
return OperatingSystem.IsMacOS() ? "libwickra.dylib" : "libwickra.so";
|
|
}
|
|
}
|