diff --git a/csharp/Platform.Data.Doublets.NativeLibrary/DoubletsNativeLibrary.cs b/csharp/Platform.Data.Doublets.NativeLibrary/DoubletsNativeLibrary.cs new file mode 100644 index 000000000..94b959854 --- /dev/null +++ b/csharp/Platform.Data.Doublets.NativeLibrary/DoubletsNativeLibrary.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using Platform.Data.Doublets.Memory.United.Generic; + +namespace Platform.Data.Doublets.NativeLibrary +{ + public static unsafe class DoubletsNativeLibrary + { + private static readonly Dictionary> _instances = new(); + private static readonly object _lock = new object(); + private static IntPtr _nextHandle = new IntPtr(1); + + [UnmanagedCallersOnly(EntryPoint = "UInt64UnitedMemoryLinks_New")] + public static IntPtr CreateLinks(IntPtr pathPtr) + { + try + { + var path = Marshal.PtrToStringAnsi(pathPtr); + if (string.IsNullOrEmpty(path)) + return IntPtr.Zero; + + var links = new UnitedMemoryLinks(path); + + lock (_lock) + { + var handle = _nextHandle; + _nextHandle = new IntPtr(_nextHandle.ToInt64() + 1); + _instances[handle] = links; + return handle; + } + } + catch + { + return IntPtr.Zero; + } + } + + [UnmanagedCallersOnly(EntryPoint = "UInt64UnitedMemoryLinks_Drop")] + public static void DisposeLinks(IntPtr handle) + { + try + { + lock (_lock) + { + if (_instances.TryGetValue(handle, out var links)) + { + links.Dispose(); + _instances.Remove(handle); + } + } + } + catch + { + // Ignore disposal errors + } + } + + [UnmanagedCallersOnly(EntryPoint = "UInt64UnitedMemoryLinks_Create")] + public static ulong CreateLink(IntPtr handle, ulong* query, nuint queryLen) + { + try + { + lock (_lock) + { + if (!_instances.TryGetValue(handle, out var links)) + return 0; + + var queryArray = new ulong[queryLen]; + for (int i = 0; i < (int)queryLen; i++) + { + queryArray[i] = query[i]; + } + + return links.Create(queryArray); + } + } + catch + { + return 0; + } + } + + [UnmanagedCallersOnly(EntryPoint = "UInt64UnitedMemoryLinks_Count")] + public static ulong CountLinks(IntPtr handle, ulong* query, nuint queryLen) + { + try + { + lock (_lock) + { + if (!_instances.TryGetValue(handle, out var links)) + return 0; + + var queryArray = new ulong[queryLen]; + for (int i = 0; i < (int)queryLen; i++) + { + queryArray[i] = query[i]; + } + + return links.Count(queryArray); + } + } + catch + { + return 0; + } + } + + [UnmanagedCallersOnly(EntryPoint = "UInt64UnitedMemoryLinks_Update")] + public static ulong UpdateLink(IntPtr handle, ulong* query, nuint queryLen, ulong* replacement, nuint replacementLen) + { + try + { + lock (_lock) + { + if (!_instances.TryGetValue(handle, out var links)) + return 0; + + var queryArray = new ulong[queryLen]; + for (int i = 0; i < (int)queryLen; i++) + { + queryArray[i] = query[i]; + } + + var replacementArray = new ulong[replacementLen]; + for (int i = 0; i < (int)replacementLen; i++) + { + replacementArray[i] = replacement[i]; + } + + return links.Update(queryArray, replacementArray); + } + } + catch + { + return 0; + } + } + + [UnmanagedCallersOnly(EntryPoint = "UInt64UnitedMemoryLinks_Delete")] + public static ulong DeleteLink(IntPtr handle, ulong* query, nuint queryLen) + { + try + { + lock (_lock) + { + if (!_instances.TryGetValue(handle, out var links)) + return 0; + + var queryArray = new ulong[queryLen]; + for (int i = 0; i < (int)queryLen; i++) + { + queryArray[i] = query[i]; + } + + return links.Delete(queryArray); + } + } + catch + { + return 0; + } + } + + // Basic version info export + [UnmanagedCallersOnly(EntryPoint = "GetLibraryVersion")] + public static IntPtr GetVersion() + { + var version = "0.1.0-nativeaot"; + var ptr = Marshal.StringToHGlobalAnsi(version); + return ptr; + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets.NativeLibrary/Platform.Data.Doublets.NativeLibrary.csproj b/csharp/Platform.Data.Doublets.NativeLibrary/Platform.Data.Doublets.NativeLibrary.csproj new file mode 100644 index 000000000..3d13b1669 --- /dev/null +++ b/csharp/Platform.Data.Doublets.NativeLibrary/Platform.Data.Doublets.NativeLibrary.csproj @@ -0,0 +1,31 @@ + + + + LinksPlatform's Platform.Data.Doublets Native Library + konard, FreePhoenix888 + Platform.Data.Doublets.NativeLibrary + 0.1.0 + konard, FreePhoenix888 + net8 + true + Platform.Data.Doublets.NativeLibrary + Platform.Data.Doublets.NativeLibrary + LinksPlatform;Data.Doublets;NativeLibrary;CoreRT;NativeAOT + https://linksplatform.github.io/Data.Doublets + Unlicense + git + git://github.com/linksplatform/Data.Doublets + false + false + latest + enable + true + Library + true + + + + + + + \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets.NativeLibrary/README.md b/csharp/Platform.Data.Doublets.NativeLibrary/README.md new file mode 100644 index 000000000..4ca1cbed1 --- /dev/null +++ b/csharp/Platform.Data.Doublets.NativeLibrary/README.md @@ -0,0 +1,111 @@ +# Platform.Data.Doublets Native Library + +This project implements a native library version of Platform.Data.Doublets using .NET NativeAOT (formerly CoreRT). The native library exposes C-style APIs that can be called from any language that supports C FFI. + +## Overview + +The native library provides native bindings for the core Platform.Data.Doublets functionality, specifically the `UnitedMemoryLinks` implementation. All major operations (Create, Read, Update, Delete, Count) are exposed as C-style function exports. + +## Building + +### Prerequisites +- .NET 8 SDK or later +- NativeAOT support (included in .NET 8) + +### Build Commands + +```bash +# Build the native library for Linux x64 +dotnet publish -r linux-x64 -c Release --self-contained + +# Build for other platforms +dotnet publish -r win-x64 -c Release --self-contained # Windows +dotnet publish -r osx-x64 -c Release --self-contained # macOS +``` + +Or use the provided build script: +```bash +cd csharp/ +./build-native-library.sh +``` + +## Generated Files + +After building, you'll find the native libraries in: +- `bin/Release/net8/[runtime]/publish/Platform.Data.Doublets.NativeLibrary.so` (Linux) +- `bin/Release/net8/[runtime]/publish/Platform.Data.Doublets.NativeLibrary.dll` (Windows) +- `bin/Release/net8/[runtime]/publish/Platform.Data.Doublets.NativeLibrary.dylib` (macOS) + +## Exported Functions + +The library exports the following C-style functions: + +### Instance Management +- `IntPtr UInt64UnitedMemoryLinks_New(IntPtr pathPtr)` - Create a new links instance +- `void UInt64UnitedMemoryLinks_Drop(IntPtr handle)` - Dispose of a links instance + +### Link Operations +- `ulong UInt64UnitedMemoryLinks_Create(IntPtr handle, ulong* query, nuint queryLen)` - Create a link +- `ulong UInt64UnitedMemoryLinks_Count(IntPtr handle, ulong* query, nuint queryLen)` - Count matching links +- `ulong UInt64UnitedMemoryLinks_Update(IntPtr handle, ulong* query, nuint queryLen, ulong* replacement, nuint replacementLen)` - Update a link +- `ulong UInt64UnitedMemoryLinks_Delete(IntPtr handle, ulong* query, nuint queryLen)` - Delete a link + +### Utility +- `IntPtr GetLibraryVersion()` - Get library version string + +## Usage Example (C) + +```c +#include +#include + +// Function declarations +IntPtr UInt64UnitedMemoryLinks_New(const char* path); +void UInt64UnitedMemoryLinks_Drop(IntPtr handle); +ulong UInt64UnitedMemoryLinks_Create(IntPtr handle, ulong* query, size_t queryLen); +ulong UInt64UnitedMemoryLinks_Count(IntPtr handle, ulong* query, size_t queryLen); + +int main() { + // Create a new links database + IntPtr links = UInt64UnitedMemoryLinks_New("test.links"); + if (links == 0) { + printf("Failed to create links database\\n"); + return 1; + } + + // Create a link: [any, any] -> returns created link + ulong query[] = {0, 0}; + ulong result = UInt64UnitedMemoryLinks_Create(links, query, 2); + printf("Created link: %llu\\n", result); + + // Count all links + ulong count = UInt64UnitedMemoryLinks_Count(links, query, 2); + printf("Total links: %llu\\n", count); + + // Clean up + UInt64UnitedMemoryLinks_Drop(links); + return 0; +} +``` + +## Architecture + +The native library implementation: + +1. **Uses NativeAOT**: Compiles C# code to native machine code ahead-of-time +2. **C-style exports**: Uses `[UnmanagedCallersOnly]` attribute to export functions +3. **Handle-based API**: Manages C# objects through integer handles to avoid direct memory management +4. **Thread-safe**: Uses locks to ensure thread safety across FFI calls +5. **Error handling**: Returns 0 or null on errors, avoiding exceptions across FFI boundary + +## Compatibility + +This native library is compatible with the existing FFI interface defined in `c/ffi.h`, providing the same function signatures and behavior as the Rust implementation. + +## Benefits over CoreRT + +- **Modern approach**: Uses .NET 8 NativeAOT instead of deprecated CoreRT +- **Better performance**: Optimized AOT compilation with latest .NET runtime +- **Self-contained**: No .NET runtime dependency required +- **Smaller footprint**: Native compilation reduces deployment size +- **Cross-platform**: Supports Windows, Linux, and macOS \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets.sln b/csharp/Platform.Data.Doublets.sln index 7e89e198b..76cc79673 100644 --- a/csharp/Platform.Data.Doublets.sln +++ b/csharp/Platform.Data.Doublets.sln @@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Platform.Data.Doublets.Test EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Platform.Data.Doublets.Benchmarks", "Platform.Data.Doublets.Benchmarks\Platform.Data.Doublets.Benchmarks.csproj", "{B3526338-1437-41A6-A613-B5B1222D3BB4}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Platform.Data.Doublets.NativeLibrary", "Platform.Data.Doublets.NativeLibrary\Platform.Data.Doublets.NativeLibrary.csproj", "{C7F7E2A0-8E3D-4B9C-A1F5-3D2E1A4F8C9B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {B3526338-1437-41A6-A613-B5B1222D3BB4}.Debug|Any CPU.Build.0 = Debug|Any CPU {B3526338-1437-41A6-A613-B5B1222D3BB4}.Release|Any CPU.ActiveCfg = Release|Any CPU {B3526338-1437-41A6-A613-B5B1222D3BB4}.Release|Any CPU.Build.0 = Release|Any CPU + {C7F7E2A0-8E3D-4B9C-A1F5-3D2E1A4F8C9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7F7E2A0-8E3D-4B9C-A1F5-3D2E1A4F8C9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7F7E2A0-8E3D-4B9C-A1F5-3D2E1A4F8C9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7F7E2A0-8E3D-4B9C-A1F5-3D2E1A4F8C9B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/csharp/build-native-library.sh b/csharp/build-native-library.sh new file mode 100755 index 000000000..3ac10b9b5 --- /dev/null +++ b/csharp/build-native-library.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Build script for Platform.Data.Doublets Native Library using NativeAOT + +echo "Building Platform.Data.Doublets Native Library with NativeAOT..." + +# Navigate to the native library project directory +cd "$(dirname "$0")/Platform.Data.Doublets.NativeLibrary" + +# Build for different platforms +echo "Building for Linux x64..." +dotnet publish -r linux-x64 -c Release --self-contained + +echo "Building for Windows x64..." +dotnet publish -r win-x64 -c Release --self-contained + +echo "Building for macOS x64..." +dotnet publish -r osx-x64 -c Release --self-contained + +echo "Build completed. Check the bin/Release/net8/[runtime]/publish/ directories for the native libraries." + +# Show the output files +echo "" +echo "Generated native libraries:" +find bin/Release/net8 -name "*Platform.Data.Doublets.NativeLibrary*" -type f 2>/dev/null || echo "No output files found yet." \ No newline at end of file diff --git a/csharp/test-native-build.sh b/csharp/test-native-build.sh new file mode 100755 index 000000000..dba502ed0 --- /dev/null +++ b/csharp/test-native-build.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +echo "Testing native build for Platform.Data.Doublets..." + +# First, let's try a simple build to see if the project compiles +cd "$(dirname "$0")/Platform.Data.Doublets.NativeLibrary" + +echo "Building native library project..." +dotnet build -c Release + +if [ $? -eq 0 ]; then + echo "✓ Build successful!" + + echo "Attempting NativeAOT publish for linux-x64..." + dotnet publish -r linux-x64 -c Release --self-contained + + if [ $? -eq 0 ]; then + echo "✓ NativeAOT publish successful!" + + echo "Output files:" + find bin -name "*" -type f | head -20 + else + echo "✗ NativeAOT publish failed" + exit 1 + fi +else + echo "✗ Build failed" + exit 1 +fi \ No newline at end of file diff --git a/examples/c-native-library-test/Makefile b/examples/c-native-library-test/Makefile new file mode 100644 index 000000000..e0a3b6876 --- /dev/null +++ b/examples/c-native-library-test/Makefile @@ -0,0 +1,20 @@ +CC=gcc +CFLAGS=-Wall -Wextra -std=c99 +LIBS=-ldl + +# Path to the native library (adjust as needed) +LIB_PATH=../../csharp/Platform.Data.Doublets.NativeLibrary/bin/Release/net8/linux-x64/publish + +test: test.c + $(CC) $(CFLAGS) -o test test.c $(LIBS) + +run: test + LD_LIBRARY_PATH=$(LIB_PATH) ./test + +clean: + rm -f test test-native.links + +copy-lib: + cp $(LIB_PATH)/Platform.Data.Doublets.NativeLibrary.so . + +.PHONY: test run clean copy-lib \ No newline at end of file diff --git a/examples/c-native-library-test/README.md b/examples/c-native-library-test/README.md new file mode 100644 index 000000000..71a38dc63 --- /dev/null +++ b/examples/c-native-library-test/README.md @@ -0,0 +1,53 @@ +# C Native Library Test Example + +This example demonstrates how to use the Platform.Data.Doublets native library from C code. + +## Building and Running + +1. First, build the native library: +```bash +cd ../../csharp/Platform.Data.Doublets.NativeLibrary +dotnet publish -r linux-x64 -c Release --self-contained +``` + +2. Build the C test: +```bash +make test +``` + +3. Copy the library to local directory (optional): +```bash +make copy-lib +``` + +4. Run the test: +```bash +make run +``` + +## What the test does + +The test program: +1. Dynamically loads the native library +2. Gets function pointers to the exported functions +3. Creates a new links database +4. Creates several links +5. Counts links in the database +6. Updates a link +7. Cleans up resources + +## Expected Output + +``` +Platform.Data.Doublets Native Library Version: 0.1.0-nativeaot +Successfully created links database +Creating links... +Created link 1: 1 +Created link 2: 2 +Created link 3: 3 +Total links in database: 3 +Created specific link [1, 2]: 4 +Total links after creating specific link: 4 +Updated link 3 to [1, 2]: result=3 +Test completed successfully! +``` \ No newline at end of file diff --git a/examples/c-native-library-test/test.c b/examples/c-native-library-test/test.c new file mode 100644 index 000000000..bd946a4b8 --- /dev/null +++ b/examples/c-native-library-test/test.c @@ -0,0 +1,90 @@ +#include +#include +#include +#include + +// Function pointer types matching the exported functions +typedef void* (*CreateLinksFunc)(const char* path); +typedef void (*DropLinksFunc)(void* handle); +typedef uint64_t (*CreateLinkFunc)(void* handle, uint64_t* query, size_t queryLen); +typedef uint64_t (*CountLinksFunc)(void* handle, uint64_t* query, size_t queryLen); +typedef uint64_t (*UpdateLinkFunc)(void* handle, uint64_t* query, size_t queryLen, uint64_t* replacement, size_t replacementLen); +typedef uint64_t (*DeleteLinkFunc)(void* handle, uint64_t* query, size_t queryLen); +typedef const char* (*GetVersionFunc)(void); + +int main() { + // Load the native library + void* lib = dlopen("./Platform.Data.Doublets.NativeLibrary.so", RTLD_LAZY); + if (!lib) { + fprintf(stderr, "Failed to load library: %s\n", dlerror()); + return 1; + } + + // Load function pointers + CreateLinksFunc createLinks = (CreateLinksFunc)dlsym(lib, "UInt64UnitedMemoryLinks_New"); + DropLinksFunc dropLinks = (DropLinksFunc)dlsym(lib, "UInt64UnitedMemoryLinks_Drop"); + CreateLinkFunc createLink = (CreateLinkFunc)dlsym(lib, "UInt64UnitedMemoryLinks_Create"); + CountLinksFunc countLinks = (CountLinksFunc)dlsym(lib, "UInt64UnitedMemoryLinks_Count"); + UpdateLinkFunc updateLink = (UpdateLinkFunc)dlsym(lib, "UInt64UnitedMemoryLinks_Update"); + DeleteLinkFunc deleteLink = (DeleteLinkFunc)dlsym(lib, "UInt64UnitedMemoryLinks_Delete"); + GetVersionFunc getVersion = (GetVersionFunc)dlsym(lib, "GetLibraryVersion"); + + if (!createLinks || !dropLinks || !createLink || !countLinks || !getVersion) { + fprintf(stderr, "Failed to load required functions\n"); + dlclose(lib); + return 1; + } + + // Get and display library version + const char* version = getVersion(); + printf("Platform.Data.Doublets Native Library Version: %s\n", version); + + // Create a new links database in memory + void* links = createLinks("test-native.links"); + if (!links) { + printf("Failed to create links database\n"); + dlclose(lib); + return 1; + } + + printf("Successfully created links database\n"); + + // Create some links + uint64_t query[2] = {0, 0}; // [any, any] + + printf("Creating links...\n"); + uint64_t link1 = createLink(links, query, 2); + printf("Created link 1: %llu\n", link1); + + uint64_t link2 = createLink(links, query, 2); + printf("Created link 2: %llu\n", link2); + + uint64_t link3 = createLink(links, query, 2); + printf("Created link 3: %llu\n", link3); + + // Count all links + uint64_t count = countLinks(links, query, 2); + printf("Total links in database: %llu\n", count); + + // Create a specific link [link1, link2] + uint64_t specific_query[2] = {link1, link2}; + uint64_t specific_link = createLink(links, specific_query, 2); + printf("Created specific link [%llu, %llu]: %llu\n", link1, link2, specific_link); + + // Count again + count = countLinks(links, query, 2); + printf("Total links after creating specific link: %llu\n", count); + + // Test updating a link (change link3 to point from link1 to link2) + uint64_t update_query[3] = {link3, 0, 0}; // link to update + uint64_t update_replacement[3] = {link3, link1, link2}; // new values + uint64_t update_result = updateLink(links, update_query, 3, update_replacement, 3); + printf("Updated link %llu to [%llu, %llu]: result=%llu\n", link3, link1, link2, update_result); + + // Clean up + dropLinks(links); + dlclose(lib); + + printf("Test completed successfully!\n"); + return 0; +} \ No newline at end of file