mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat: Optimize C# binding interop (#488)
- Introduce Utf8Marshaller helpers and SafeHandle wrappers so the managed API centralizes UTF-8 conversions and lifetime management for native pointers. - Update Engine, Compiler, CompiledPolicy, SchemaRegistry, and TargetRegistry to rely on the new marshaller/safe handles, tightening disposal and reducing transient allocations during interop calls. - Add allocation guard coverage in Regorus.Tests and report bytes/op in the compiled policy benchmark to surface future regressions. Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
1e4ff952e6
commit
091bbb2e5c
@@ -129,12 +129,12 @@ namespace Benchmarks
|
||||
Console.WriteLine($"Warming up with {threads} threads for {warmupSeconds} seconds...");
|
||||
|
||||
// Warmup phase
|
||||
var (_, _, _) = RunBenchmarkPhase(threads, warmupSeconds, policiesWithInputs, compiledPolicies, useSharedPolicies, isWarmup: true);
|
||||
var (_, _, _, _) = RunBenchmarkPhase(threads, warmupSeconds, policiesWithInputs, compiledPolicies, useSharedPolicies, isWarmup: true);
|
||||
|
||||
Console.WriteLine($"Running benchmark with {threads} threads for {durationSeconds} seconds...");
|
||||
|
||||
// Actual benchmark phase
|
||||
var (totalEvaluations, evaluationTime, policyCounters) = RunBenchmarkPhase(threads, durationSeconds, policiesWithInputs, compiledPolicies, useSharedPolicies, isWarmup: false);
|
||||
var (totalEvaluations, evaluationTime, policyCounters, allocatedBytes) = RunBenchmarkPhase(threads, durationSeconds, policiesWithInputs, compiledPolicies, useSharedPolicies, isWarmup: false);
|
||||
|
||||
// Calculate throughput based on pure evaluation time (consistent with Rust benchmark)
|
||||
var evalsPerSecond = totalEvaluations / evaluationTime.TotalSeconds;
|
||||
@@ -144,6 +144,12 @@ namespace Benchmarks
|
||||
Console.WriteLine($" time: [{evaluationTime.TotalMilliseconds:F2} ms]");
|
||||
Console.WriteLine($" thrpt: [{kelemsPerSecond:F2} Kelem/s]");
|
||||
|
||||
if (totalEvaluations > 0)
|
||||
{
|
||||
var bytesPerEval = allocatedBytes / (double)totalEvaluations;
|
||||
Console.WriteLine($" alloc: [{bytesPerEval:F2} B/op] (total {allocatedBytes} B)");
|
||||
}
|
||||
|
||||
// Clean up compiled policies if we created them
|
||||
if (compiledPolicies != null)
|
||||
{
|
||||
@@ -166,7 +172,7 @@ namespace Benchmarks
|
||||
}
|
||||
}
|
||||
|
||||
private static (int totalEvaluations, TimeSpan evaluationTime, Dictionary<string, int> policyCounters) RunBenchmarkPhase(
|
||||
private static (int totalEvaluations, TimeSpan evaluationTime, Dictionary<string, int> policyCounters, long allocatedBytes) RunBenchmarkPhase(
|
||||
int threads,
|
||||
int durationSeconds,
|
||||
List<(string Policy, string[] Inputs)> policiesWithInputs,
|
||||
@@ -180,6 +186,7 @@ namespace Benchmarks
|
||||
var evaluationTimes = new Dictionary<int, TimeSpan>();
|
||||
var lockObject = new object();
|
||||
var stopExecution = false;
|
||||
long allocatedBytes = 0;
|
||||
|
||||
// Initialize counters
|
||||
foreach (var policyName in PolicyNames)
|
||||
@@ -194,6 +201,12 @@ namespace Benchmarks
|
||||
int tid = threadId;
|
||||
tasks[threadId] = Task.Run(() =>
|
||||
{
|
||||
long allocationStart = 0;
|
||||
if (!isWarmup)
|
||||
{
|
||||
allocationStart = GC.GetAllocatedBytesForCurrentThread();
|
||||
}
|
||||
|
||||
barrier.SignalAndWait();
|
||||
|
||||
int evaluationCount = 0;
|
||||
@@ -256,6 +269,9 @@ namespace Benchmarks
|
||||
evaluationTimes[tid] = TimeSpan.Zero;
|
||||
evaluationTimes[tid] = localEvaluationTime;
|
||||
}
|
||||
|
||||
var allocationEnd = GC.GetAllocatedBytesForCurrentThread();
|
||||
System.Threading.Interlocked.Add(ref allocatedBytes, allocationEnd - allocationStart);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -272,7 +288,7 @@ namespace Benchmarks
|
||||
// Use pure evaluation time (consistent with Rust benchmark)
|
||||
var evaluationTime = totalEvaluationTime == TimeSpan.Zero ? stopwatch.Elapsed : totalEvaluationTime;
|
||||
|
||||
return (totalEvaluations, evaluationTime, policyCounters);
|
||||
return (totalEvaluations, evaluationTime, policyCounters, allocatedBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
namespace Regorus.Tests;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Regorus;
|
||||
|
||||
namespace Regorus.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class RegorusTests
|
||||
@@ -212,4 +214,43 @@ public class RegorusTests
|
||||
Assert.AreEqual("a", parameters![0]["parameters"][0]["name"].ToString());
|
||||
Assert.AreEqual("b", parameters![0]["modifiers"][0]["name"].ToString());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SetInputJson_has_negligible_allocations_after_warmup()
|
||||
{
|
||||
using var engine = new Engine();
|
||||
const string payload = "{}";
|
||||
|
||||
// Warm up the engine and JIT to ensure subsequent measurements are representative.
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
engine.SetInputJson(payload);
|
||||
}
|
||||
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
GC.Collect();
|
||||
|
||||
const int iterations = 256;
|
||||
var before = GC.GetAllocatedBytesForCurrentThread();
|
||||
|
||||
for (int i = 0; i < iterations; i++)
|
||||
{
|
||||
engine.SetInputJson(payload);
|
||||
}
|
||||
|
||||
var after = GC.GetAllocatedBytesForCurrentThread();
|
||||
var allocated = Math.Max(0, after - before);
|
||||
var bytesPerOp = allocated / (double)iterations;
|
||||
|
||||
// Runtime bookkeeping (delegate caches, GC write barriers) differs across platforms, so
|
||||
// we measure bytes per call rather than absolute totals and allow a small budget.
|
||||
// CI will flag regressions where marshalling starts allocating per invocation.
|
||||
|
||||
// Allow a small budget for delegates and runtime bookkeeping while still flagging regressions.
|
||||
Assert.IsTrue(
|
||||
bytesPerOp <= 512,
|
||||
$"Expected ≤512 B/op after warmup, but observed {bytesPerOp:F2} B/op (total {allocated} bytes)."
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
|
||||
#nullable enable
|
||||
namespace Regorus
|
||||
@@ -23,13 +24,14 @@ namespace Regorus
|
||||
/// </summary>
|
||||
public unsafe sealed class CompiledPolicy : IDisposable
|
||||
{
|
||||
private Internal.RegorusCompiledPolicy* _policy;
|
||||
private int _isDisposed;
|
||||
private int _activeEvaluations;
|
||||
private RegorusCompiledPolicyHandle? _handle;
|
||||
private readonly ManualResetEventSlim _idleEvent = new(initialState: true);
|
||||
private int _isDisposed;
|
||||
private int _activeEvaluations;
|
||||
|
||||
internal CompiledPolicy(Internal.RegorusCompiledPolicy* policy)
|
||||
internal CompiledPolicy(RegorusCompiledPolicyHandle handle)
|
||||
{
|
||||
_policy = policy;
|
||||
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -44,21 +46,34 @@ namespace Regorus
|
||||
public string? EvalWithInput(string inputJson)
|
||||
{
|
||||
// Increment active evaluations count
|
||||
System.Threading.Interlocked.Increment(ref _activeEvaluations);
|
||||
var active = System.Threading.Interlocked.Increment(ref _activeEvaluations);
|
||||
if (active == 1)
|
||||
{
|
||||
_idleEvent.Reset();
|
||||
}
|
||||
try
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
var inputBytes = Encoding.UTF8.GetBytes(inputJson + char.MinValue);
|
||||
fixed (byte* inputPtr = inputBytes)
|
||||
return Internal.Utf8Marshaller.WithUtf8(inputJson, inputPtr =>
|
||||
{
|
||||
return CheckAndDropResult(Internal.API.regorus_compiled_policy_eval_with_input(_policy, inputPtr));
|
||||
}
|
||||
return UseHandle(policyPtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Internal.API.regorus_compiled_policy_eval_with_input((Internal.RegorusCompiledPolicy*)policyPtr, (byte*)inputPtr));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Decrement active evaluations count
|
||||
System.Threading.Interlocked.Decrement(ref _activeEvaluations);
|
||||
var remaining = System.Threading.Interlocked.Decrement(ref _activeEvaluations);
|
||||
if (remaining == 0)
|
||||
{
|
||||
_idleEvent.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +87,13 @@ namespace Regorus
|
||||
public PolicyInfo GetPolicyInfo()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var jsonResult = CheckAndDropResult(Internal.API.regorus_compiled_policy_get_policy_info(_policy));
|
||||
var jsonResult = UseHandle(policyPtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Internal.API.regorus_compiled_policy_get_policy_info((Internal.RegorusCompiledPolicy*)policyPtr));
|
||||
}
|
||||
});
|
||||
|
||||
if (string.IsNullOrEmpty(jsonResult))
|
||||
{
|
||||
@@ -105,25 +126,22 @@ namespace Regorus
|
||||
{
|
||||
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
|
||||
{
|
||||
if (_policy != null)
|
||||
var handle = _handle;
|
||||
if (handle != null)
|
||||
{
|
||||
// Wait for all active evaluations to complete
|
||||
while (System.Threading.Volatile.Read(ref _activeEvaluations) > 0)
|
||||
{
|
||||
System.Threading.Thread.Yield();
|
||||
}
|
||||
_idleEvent.Wait();
|
||||
|
||||
Internal.API.regorus_compiled_policy_drop(_policy);
|
||||
_policy = null;
|
||||
handle.Dispose();
|
||||
_handle = null;
|
||||
}
|
||||
|
||||
_idleEvent.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
~CompiledPolicy() => Dispose(disposing: false);
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_isDisposed != 0)
|
||||
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
|
||||
throw new ObjectDisposedException(nameof(CompiledPolicy));
|
||||
}
|
||||
|
||||
@@ -164,5 +182,39 @@ namespace Regorus
|
||||
Internal.API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private RegorusCompiledPolicyHandle GetHandleForUse()
|
||||
{
|
||||
var handle = _handle;
|
||||
if (handle is null || handle.IsClosed || handle.IsInvalid)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(CompiledPolicy));
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
private T UseHandle<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
var handle = GetHandleForUse();
|
||||
bool addedRef = false;
|
||||
try
|
||||
{
|
||||
handle.DangerousAddRef(ref addedRef);
|
||||
var pointer = handle.DangerousGetHandle();
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(CompiledPolicy));
|
||||
}
|
||||
|
||||
return func(pointer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (addedRef)
|
||||
{
|
||||
handle.DangerousRelease();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
namespace Regorus
|
||||
@@ -54,49 +55,48 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when compilation fails</exception>
|
||||
public static CompiledPolicy CompilePolicyWithEntrypoint(string dataJson, IEnumerable<PolicyModule> modules, string entryPointRule)
|
||||
{
|
||||
var dataBytes = Encoding.UTF8.GetBytes(dataJson + char.MinValue);
|
||||
var entryPointBytes = Encoding.UTF8.GetBytes(entryPointRule + char.MinValue);
|
||||
var modulesArray = modules.ToArray();
|
||||
|
||||
// Convert C# modules to native structs
|
||||
var nativeModules = new Internal.RegorusPolicyModule[modulesArray.Length];
|
||||
var pinnedHandles = new List<GCHandle>();
|
||||
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(modulesArray.Length * 2);
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < modulesArray.Length; i++)
|
||||
{
|
||||
var idBytes = Encoding.UTF8.GetBytes(modulesArray[i].Id + char.MinValue);
|
||||
var contentBytes = Encoding.UTF8.GetBytes(modulesArray[i].Content + char.MinValue);
|
||||
|
||||
var idHandle = GCHandle.Alloc(idBytes, GCHandleType.Pinned);
|
||||
var contentHandle = GCHandle.Alloc(contentBytes, GCHandleType.Pinned);
|
||||
pinnedHandles.Add(idHandle);
|
||||
pinnedHandles.Add(contentHandle);
|
||||
var idPinned = Utf8Marshaller.Pin(modulesArray[i].Id);
|
||||
var contentPinned = Utf8Marshaller.Pin(modulesArray[i].Content);
|
||||
pinnedStrings.Add(idPinned);
|
||||
pinnedStrings.Add(contentPinned);
|
||||
|
||||
nativeModules[i] = new Internal.RegorusPolicyModule
|
||||
{
|
||||
id = (byte*)idHandle.AddrOfPinnedObject(),
|
||||
content = (byte*)contentHandle.AddrOfPinnedObject()
|
||||
id = idPinned.Pointer,
|
||||
content = contentPinned.Pointer
|
||||
};
|
||||
}
|
||||
|
||||
fixed (byte* dataPtr = dataBytes)
|
||||
fixed (byte* entryPointPtr = entryPointBytes)
|
||||
fixed (Internal.RegorusPolicyModule* modulesPtr = nativeModules)
|
||||
{
|
||||
var result = Internal.API.regorus_compile_policy_with_entrypoint(
|
||||
dataPtr, modulesPtr, (UIntPtr)modulesArray.Length, entryPointPtr);
|
||||
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
|
||||
Utf8Marshaller.WithUtf8(entryPointRule, entryPointPtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (Internal.RegorusPolicyModule* modulesPtr = nativeModules)
|
||||
{
|
||||
var result = Internal.API.regorus_compile_policy_with_entrypoint(
|
||||
(byte*)dataPtr, modulesPtr, (UIntPtr)modulesArray.Length, (byte*)entryPointPtr);
|
||||
|
||||
var policy = GetCompiledPolicyResult(result);
|
||||
return policy;
|
||||
}
|
||||
var policy = GetCompiledPolicyResult(result);
|
||||
return policy;
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var handle in pinnedHandles)
|
||||
foreach (var pinned in pinnedStrings)
|
||||
{
|
||||
handle.Free();
|
||||
pinned.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,47 +112,47 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when compilation fails</exception>
|
||||
public static CompiledPolicy CompilePolicyForTarget(string dataJson, IEnumerable<PolicyModule> modules)
|
||||
{
|
||||
var dataBytes = Encoding.UTF8.GetBytes(dataJson + char.MinValue);
|
||||
var modulesArray = modules.ToArray();
|
||||
|
||||
// Convert C# modules to native structs
|
||||
var nativeModules = new Internal.RegorusPolicyModule[modulesArray.Length];
|
||||
var pinnedHandles = new List<GCHandle>();
|
||||
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(modulesArray.Length * 2);
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < modulesArray.Length; i++)
|
||||
{
|
||||
var idBytes = Encoding.UTF8.GetBytes(modulesArray[i].Id + char.MinValue);
|
||||
var contentBytes = Encoding.UTF8.GetBytes(modulesArray[i].Content + char.MinValue);
|
||||
|
||||
var idHandle = GCHandle.Alloc(idBytes, GCHandleType.Pinned);
|
||||
var contentHandle = GCHandle.Alloc(contentBytes, GCHandleType.Pinned);
|
||||
pinnedHandles.Add(idHandle);
|
||||
pinnedHandles.Add(contentHandle);
|
||||
var idPinned = Utf8Marshaller.Pin(modulesArray[i].Id);
|
||||
var contentPinned = Utf8Marshaller.Pin(modulesArray[i].Content);
|
||||
pinnedStrings.Add(idPinned);
|
||||
pinnedStrings.Add(contentPinned);
|
||||
|
||||
nativeModules[i] = new Internal.RegorusPolicyModule
|
||||
{
|
||||
id = (byte*)idHandle.AddrOfPinnedObject(),
|
||||
content = (byte*)contentHandle.AddrOfPinnedObject()
|
||||
id = idPinned.Pointer,
|
||||
content = contentPinned.Pointer
|
||||
};
|
||||
}
|
||||
|
||||
fixed (byte* dataPtr = dataBytes)
|
||||
fixed (Internal.RegorusPolicyModule* modulesPtr = nativeModules)
|
||||
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
|
||||
{
|
||||
var result = Internal.API.regorus_compile_policy_for_target(
|
||||
dataPtr, modulesPtr, (UIntPtr)modulesArray.Length);
|
||||
unsafe
|
||||
{
|
||||
fixed (Internal.RegorusPolicyModule* modulesPtr = nativeModules)
|
||||
{
|
||||
var result = Internal.API.regorus_compile_policy_for_target(
|
||||
(byte*)dataPtr, modulesPtr, (UIntPtr)modulesArray.Length);
|
||||
|
||||
var policy = GetCompiledPolicyResult(result);
|
||||
return policy;
|
||||
}
|
||||
var policy = GetCompiledPolicyResult(result);
|
||||
return policy;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var handle in pinnedHandles)
|
||||
foreach (var pinned in pinnedStrings)
|
||||
{
|
||||
handle.Free();
|
||||
pinned.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,7 +185,8 @@ namespace Regorus
|
||||
throw new Exception("Expected compiled policy pointer but got different data type");
|
||||
}
|
||||
|
||||
return new CompiledPolicy((Internal.RegorusCompiledPolicy*)result.pointer_value);
|
||||
var handle = RegorusCompiledPolicyHandle.FromPointer((IntPtr)result.pointer_value);
|
||||
return new CompiledPolicy(handle);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Regorus.Internal;
|
||||
|
||||
|
||||
#nullable enable
|
||||
@@ -15,17 +16,14 @@ namespace Regorus
|
||||
/// Cloning is cheap and involves only incrementing reference counts for shared immutable objects like parsed policies,
|
||||
/// data etc. Mutable state is deep copied as needed.
|
||||
/// </summary>
|
||||
public unsafe sealed class Engine : System.IDisposable
|
||||
public unsafe sealed class Engine : IDisposable
|
||||
{
|
||||
private Regorus.Internal.RegorusEngine* E;
|
||||
// Detect redundant Dispose() calls in a thread-safe manner.
|
||||
// _isDisposed == 0 means Dispose(bool) has not been called yet.
|
||||
// _isDisposed == 1 means Dispose(bool) has been already called.
|
||||
private int isDisposed;
|
||||
private RegorusEngineHandle? _handle;
|
||||
private int _isDisposed;
|
||||
|
||||
public Engine()
|
||||
{
|
||||
E = Regorus.Internal.API.regorus_engine_new();
|
||||
_handle = RegorusEngineHandle.Create();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -49,182 +47,314 @@ namespace Regorus
|
||||
// other objects. Only unmanaged resources can be disposed.
|
||||
void Dispose(bool disposing)
|
||||
{
|
||||
// In case _isDisposed is 0, atomically set it to 1.
|
||||
// Enter the branch only if the original value is 0.
|
||||
if (System.Threading.Interlocked.CompareExchange(ref isDisposed, 1, 0) == 0)
|
||||
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
|
||||
{
|
||||
// If disposing equals true, dispose all managed
|
||||
// and unmanaged resources.
|
||||
if (disposing)
|
||||
{
|
||||
// No managed resource to dispose.
|
||||
}
|
||||
|
||||
// Call the appropriate methods to clean up
|
||||
// unmanaged resources here.
|
||||
// If disposing is false,
|
||||
// only the following code is executed.
|
||||
if (E != null)
|
||||
{
|
||||
Regorus.Internal.API.regorus_engine_drop(E);
|
||||
E = null;
|
||||
}
|
||||
|
||||
_handle?.Dispose();
|
||||
_handle = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Use C# finalizer syntax for finalization code.
|
||||
// This finalizer will run only if the Dispose method
|
||||
// does not get called.
|
||||
~Engine() => Dispose(disposing: false);
|
||||
|
||||
// Helper for implementing Clone
|
||||
private Engine(Internal.RegorusEngine* engine)
|
||||
private Engine(RegorusEngineHandle handle)
|
||||
{
|
||||
this.E = engine;
|
||||
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
|
||||
}
|
||||
|
||||
public Engine Clone() => new(Internal.API.regorus_engine_clone(E));
|
||||
public Engine Clone()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var clonePtr = Regorus.Internal.API.regorus_engine_clone((Regorus.Internal.RegorusEngine*)enginePtr);
|
||||
if (clonePtr is null)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to clone Regorus engine.");
|
||||
}
|
||||
|
||||
var handle = RegorusEngineHandle.FromPointer((IntPtr)clonePtr);
|
||||
return new Engine(handle);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void SetStrictBuiltinErrors(bool strict)
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_strict_builtin_errors(E, strict));
|
||||
ThrowIfDisposed();
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_strict_builtin_errors((Regorus.Internal.RegorusEngine*)enginePtr, strict));
|
||||
}
|
||||
});
|
||||
}
|
||||
byte[] NullTerminatedUTF8Bytes(string s)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(s + char.MinValue);
|
||||
}
|
||||
|
||||
public string? AddPolicy(string path, string rego)
|
||||
{
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
var regoBytes = NullTerminatedUTF8Bytes(rego);
|
||||
|
||||
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
fixed (byte* regoPtr = regoBytes)
|
||||
ThrowIfDisposed();
|
||||
return Utf8Marshaller.WithUtf8(path, pathPtr =>
|
||||
Utf8Marshaller.WithUtf8(rego, regoPtr =>
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
|
||||
}
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr, (byte*)regoPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public void SetRegoV0(bool enable)
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_rego_v0(E, enable));
|
||||
ThrowIfDisposed();
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_rego_v0((Regorus.Internal.RegorusEngine*)enginePtr, enable));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public string? AddPolicyFromFile(string path)
|
||||
{
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
ThrowIfDisposed();
|
||||
return Utf8Marshaller.WithUtf8(path, pathPtr =>
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy_from_file(E, pathPtr));
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy_from_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void AddDataJson(string data)
|
||||
{
|
||||
var dataBytes = NullTerminatedUTF8Bytes(data);
|
||||
fixed (byte* dataPtr = dataBytes)
|
||||
ThrowIfDisposed();
|
||||
Utf8Marshaller.WithUtf8(data, dataPtr =>
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_json(E, dataPtr));
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_json((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)dataPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void AddDataFromJsonFile(string path)
|
||||
{
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
ThrowIfDisposed();
|
||||
Utf8Marshaller.WithUtf8(path, pathPtr =>
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_from_json_file(E, pathPtr));
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_from_json_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void SetInputJson(string input)
|
||||
{
|
||||
var inputBytes = NullTerminatedUTF8Bytes(input);
|
||||
fixed (byte* inputPtr = inputBytes)
|
||||
ThrowIfDisposed();
|
||||
Utf8Marshaller.WithUtf8(input, inputPtr =>
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_json(E, inputPtr));
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_json((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)inputPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void SetInputFromJsonFile(string path)
|
||||
{
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
ThrowIfDisposed();
|
||||
Utf8Marshaller.WithUtf8(path, pathPtr =>
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_from_json_file(E, pathPtr));
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_from_json_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public string? EvalQuery(string query)
|
||||
{
|
||||
var queryBytes = NullTerminatedUTF8Bytes(query);
|
||||
fixed (byte* queryPtr = queryBytes)
|
||||
ThrowIfDisposed();
|
||||
return Utf8Marshaller.WithUtf8(query, queryPtr =>
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_query(E, queryPtr));
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_query((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)queryPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public string? EvalRule(string rule)
|
||||
{
|
||||
var ruleBytes = NullTerminatedUTF8Bytes(rule);
|
||||
fixed (byte* rulePtr = ruleBytes)
|
||||
ThrowIfDisposed();
|
||||
return Utf8Marshaller.WithUtf8(rule, rulePtr =>
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_rule(E, rulePtr));
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_rule((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)rulePtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void SetEnableCoverage(bool enable)
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_enable_coverage(E, enable));
|
||||
ThrowIfDisposed();
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_enable_coverage((Regorus.Internal.RegorusEngine*)enginePtr, enable));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void ClearCoverageData()
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_coverage_data(E));
|
||||
ThrowIfDisposed();
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_coverage_data((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public string? GetCoverageReport()
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report(E));
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public string? GetCoverageReportPretty()
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report_pretty(E));
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report_pretty((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void SetGatherPrints(bool enable)
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_gather_prints(E, enable));
|
||||
ThrowIfDisposed();
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_gather_prints((Regorus.Internal.RegorusEngine*)enginePtr, enable));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public string? TakePrints()
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_take_prints(E));
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_take_prints((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public string? GetAstAsJson()
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_ast_as_json(E));
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_ast_as_json((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public string? GetPolicyPackageNames()
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_package_names(E));
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_package_names((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public string? GetPolicyParameters()
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_parameters(E));
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_parameters((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
string? StringFromUTF8(IntPtr ptr)
|
||||
@@ -260,5 +390,56 @@ namespace Regorus
|
||||
return resultString;
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Engine));
|
||||
}
|
||||
}
|
||||
|
||||
private RegorusEngineHandle GetHandleForUse()
|
||||
{
|
||||
var handle = _handle;
|
||||
if (handle is null || handle.IsClosed || handle.IsInvalid)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Engine));
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
private void UseHandle(Action<IntPtr> action)
|
||||
{
|
||||
UseHandle<object?>(handlePtr =>
|
||||
{
|
||||
action(handlePtr);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private T UseHandle<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
var handle = GetHandleForUse();
|
||||
bool addedRef = false;
|
||||
try
|
||||
{
|
||||
handle.DangerousAddRef(ref addedRef);
|
||||
var pointer = handle.DangerousGetHandle();
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Engine));
|
||||
}
|
||||
|
||||
return func(pointer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (addedRef)
|
||||
{
|
||||
handle.DangerousRelease();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
90
bindings/csharp/Regorus/SafeHandles.cs
Normal file
90
bindings/csharp/Regorus/SafeHandles.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
#nullable enable
|
||||
namespace Regorus
|
||||
{
|
||||
internal sealed class RegorusEngineHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
private RegorusEngineHandle() : base(ownsHandle: true)
|
||||
{
|
||||
}
|
||||
|
||||
internal static RegorusEngineHandle Create()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var raw = Internal.API.regorus_engine_new();
|
||||
if (raw is null)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create Regorus engine.");
|
||||
}
|
||||
|
||||
var handle = new RegorusEngineHandle();
|
||||
handle.SetHandle((IntPtr)raw);
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static RegorusEngineHandle FromPointer(IntPtr pointer)
|
||||
{
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
throw new ArgumentException("Pointer cannot be zero.", nameof(pointer));
|
||||
}
|
||||
|
||||
var handle = new RegorusEngineHandle();
|
||||
handle.SetHandle(pointer);
|
||||
return handle;
|
||||
}
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (!IsInvalid && !IsClosed)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
Internal.API.regorus_engine_drop((Internal.RegorusEngine*)handle);
|
||||
}
|
||||
SetHandle(IntPtr.Zero);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RegorusCompiledPolicyHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
private RegorusCompiledPolicyHandle() : base(ownsHandle: true)
|
||||
{
|
||||
}
|
||||
|
||||
internal static RegorusCompiledPolicyHandle FromPointer(IntPtr pointer)
|
||||
{
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
throw new ArgumentException("Pointer cannot be zero.", nameof(pointer));
|
||||
}
|
||||
|
||||
var handle = new RegorusCompiledPolicyHandle();
|
||||
handle.SetHandle(pointer);
|
||||
return handle;
|
||||
}
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (!IsInvalid && !IsClosed)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
Internal.API.regorus_compiled_policy_drop((Internal.RegorusCompiledPolicy*)handle);
|
||||
}
|
||||
SetHandle(IntPtr.Zero);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
namespace Regorus
|
||||
@@ -21,14 +22,16 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when schema registration fails</exception>
|
||||
public static void RegisterResource(string name, string schemaJson)
|
||||
{
|
||||
var nameBytes = Encoding.UTF8.GetBytes(name + char.MinValue);
|
||||
var schemaBytes = Encoding.UTF8.GetBytes(schemaJson + char.MinValue);
|
||||
|
||||
fixed (byte* namePtr = nameBytes)
|
||||
fixed (byte* schemaPtr = schemaBytes)
|
||||
Utf8Marshaller.WithUtf8(name, namePtr =>
|
||||
{
|
||||
CheckAndDropResult(Internal.API.regorus_resource_schema_register(namePtr, schemaPtr));
|
||||
}
|
||||
Utf8Marshaller.WithUtf8(schemaJson, schemaPtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Internal.API.regorus_resource_schema_register((byte*)namePtr, (byte*)schemaPtr));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,12 +42,14 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when the operation fails</exception>
|
||||
public static bool ContainsResource(string name)
|
||||
{
|
||||
var nameBytes = Encoding.UTF8.GetBytes(name + char.MinValue);
|
||||
fixed (byte* namePtr = nameBytes)
|
||||
return Utf8Marshaller.WithUtf8(name, namePtr =>
|
||||
{
|
||||
var result = Internal.API.regorus_resource_schema_contains(namePtr);
|
||||
return GetBoolResult(result);
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
var result = Internal.API.regorus_resource_schema_contains((byte*)namePtr);
|
||||
return GetBoolResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -93,12 +98,14 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when the operation fails</exception>
|
||||
public static bool RemoveResource(string name)
|
||||
{
|
||||
var nameBytes = Encoding.UTF8.GetBytes(name + char.MinValue);
|
||||
fixed (byte* namePtr = nameBytes)
|
||||
return Utf8Marshaller.WithUtf8(name, namePtr =>
|
||||
{
|
||||
var result = Internal.API.regorus_resource_schema_remove(namePtr);
|
||||
return GetBoolResult(result);
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
var result = Internal.API.regorus_resource_schema_remove((byte*)namePtr);
|
||||
return GetBoolResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -118,14 +125,16 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when schema registration fails</exception>
|
||||
public static void RegisterEffect(string name, string schemaJson)
|
||||
{
|
||||
var nameBytes = Encoding.UTF8.GetBytes(name + char.MinValue);
|
||||
var schemaBytes = Encoding.UTF8.GetBytes(schemaJson + char.MinValue);
|
||||
|
||||
fixed (byte* namePtr = nameBytes)
|
||||
fixed (byte* schemaPtr = schemaBytes)
|
||||
Utf8Marshaller.WithUtf8(name, namePtr =>
|
||||
{
|
||||
CheckAndDropResult(Internal.API.regorus_effect_schema_register(namePtr, schemaPtr));
|
||||
}
|
||||
Utf8Marshaller.WithUtf8(schemaJson, schemaPtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Internal.API.regorus_effect_schema_register((byte*)namePtr, (byte*)schemaPtr));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -136,12 +145,14 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when the operation fails</exception>
|
||||
public static bool ContainsEffect(string name)
|
||||
{
|
||||
var nameBytes = Encoding.UTF8.GetBytes(name + char.MinValue);
|
||||
fixed (byte* namePtr = nameBytes)
|
||||
return Utf8Marshaller.WithUtf8(name, namePtr =>
|
||||
{
|
||||
var result = Internal.API.regorus_effect_schema_contains(namePtr);
|
||||
return GetBoolResult(result);
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
var result = Internal.API.regorus_effect_schema_contains((byte*)namePtr);
|
||||
return GetBoolResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -190,12 +201,14 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when the operation fails</exception>
|
||||
public static bool RemoveEffect(string name)
|
||||
{
|
||||
var nameBytes = Encoding.UTF8.GetBytes(name + char.MinValue);
|
||||
fixed (byte* namePtr = nameBytes)
|
||||
return Utf8Marshaller.WithUtf8(name, namePtr =>
|
||||
{
|
||||
var result = Internal.API.regorus_effect_schema_remove(namePtr);
|
||||
return GetBoolResult(result);
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
var result = Internal.API.regorus_effect_schema_remove((byte*)namePtr);
|
||||
return GetBoolResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
namespace Regorus
|
||||
@@ -22,11 +23,13 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when target registration fails</exception>
|
||||
public static void RegisterFromJson(string targetJson)
|
||||
{
|
||||
var targetBytes = Encoding.UTF8.GetBytes(targetJson + char.MinValue);
|
||||
fixed (byte* targetPtr = targetBytes)
|
||||
Utf8Marshaller.WithUtf8(targetJson, targetPtr =>
|
||||
{
|
||||
CheckAndDropResult(Internal.API.regorus_register_target_from_json(targetPtr));
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Internal.API.regorus_register_target_from_json((byte*)targetPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -37,12 +40,14 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when the operation fails</exception>
|
||||
public static bool Contains(string name)
|
||||
{
|
||||
var nameBytes = Encoding.UTF8.GetBytes(name + char.MinValue);
|
||||
fixed (byte* namePtr = nameBytes)
|
||||
return Utf8Marshaller.WithUtf8(name, namePtr =>
|
||||
{
|
||||
var result = Internal.API.regorus_target_registry_contains(namePtr);
|
||||
return GetBoolResult(result);
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
var result = Internal.API.regorus_target_registry_contains((byte*)namePtr);
|
||||
return GetBoolResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -63,12 +68,14 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when the operation fails</exception>
|
||||
public static bool Remove(string name)
|
||||
{
|
||||
var nameBytes = Encoding.UTF8.GetBytes(name + char.MinValue);
|
||||
fixed (byte* namePtr = nameBytes)
|
||||
return Utf8Marshaller.WithUtf8(name, namePtr =>
|
||||
{
|
||||
var result = Internal.API.regorus_target_registry_remove(namePtr);
|
||||
return GetBoolResult(result);
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
var result = Internal.API.regorus_target_registry_remove((byte*)namePtr);
|
||||
return GetBoolResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
154
bindings/csharp/Regorus/Utf8Marshaller.cs
Normal file
154
bindings/csharp/Regorus/Utf8Marshaller.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
#nullable enable
|
||||
namespace Regorus.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for marshaling managed strings to null-terminated UTF-8 buffers.
|
||||
/// Provides stack-based storage for short lived conversions and pooled backing
|
||||
/// for longer lived pinned buffers.
|
||||
/// </summary>
|
||||
internal static class Utf8Marshaller
|
||||
{
|
||||
// Mirrors BCL patterns (e.g., System.Text.Json encoding helpers) by stackalloc'ing
|
||||
// up to 512 bytes to cover common short strings while keeping the stack usage well
|
||||
// below typical per-frame limits; larger payloads fall back to pooled buffers.
|
||||
private const int StackAllocThreshold = 512;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a pooled and pinned UTF-8 buffer suitable for scenarios where
|
||||
/// the pointer must remain stable beyond the immediate call site (for example,
|
||||
/// when referenced by another buffer passed to native code).
|
||||
/// </summary>
|
||||
internal sealed class PinnedUtf8 : IDisposable
|
||||
{
|
||||
private GCHandle _handle;
|
||||
private byte[]? _buffer;
|
||||
private bool _disposed;
|
||||
|
||||
internal unsafe PinnedUtf8(string value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
var byteCount = Encoding.UTF8.GetByteCount(value);
|
||||
_buffer = ArrayPool<byte>.Shared.Rent(byteCount + 1);
|
||||
|
||||
try
|
||||
{
|
||||
var written = Encoding.UTF8.GetBytes(value, 0, value.Length, _buffer, 0);
|
||||
_buffer[written] = 0;
|
||||
|
||||
_handle = GCHandle.Alloc(_buffer, GCHandleType.Pinned);
|
||||
Pointer = (byte*)_handle.AddrOfPinnedObject();
|
||||
Length = written + 1;
|
||||
}
|
||||
catch
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(_buffer);
|
||||
_buffer = null;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe byte* Pointer { get; }
|
||||
|
||||
internal int Length { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_handle.IsAllocated)
|
||||
{
|
||||
_handle.Free();
|
||||
}
|
||||
|
||||
if (_buffer != null)
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(_buffer);
|
||||
_buffer = null;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe delegate void Utf8PointerAction(byte* pointer);
|
||||
|
||||
internal static unsafe void WithUtf8(string value, Utf8PointerAction action)
|
||||
{
|
||||
if (action is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
}
|
||||
|
||||
WithUtf8<object?>(value, ptr =>
|
||||
{
|
||||
action((byte*)ptr);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
internal static T WithUtf8<T>(string value, Func<IntPtr, T> func)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
if (func is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(func));
|
||||
}
|
||||
|
||||
var byteCount = Encoding.UTF8.GetByteCount(value);
|
||||
var required = byteCount + 1;
|
||||
|
||||
if (required <= StackAllocThreshold)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[required];
|
||||
return Invoke(value, func, buffer, byteCount);
|
||||
}
|
||||
|
||||
var rented = ArrayPool<byte>.Shared.Rent(required);
|
||||
try
|
||||
{
|
||||
Span<byte> buffer = rented;
|
||||
return Invoke(value, func, buffer, byteCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(rented);
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe T Invoke<T>(string value, Func<IntPtr, T> func, Span<byte> buffer, int byteCount)
|
||||
{
|
||||
fixed (char* charPtr = value)
|
||||
fixed (byte* bytePtr = buffer)
|
||||
{
|
||||
var written = Encoding.UTF8.GetBytes(charPtr, value.Length, bytePtr, byteCount);
|
||||
bytePtr[written] = 0;
|
||||
return func((IntPtr)bytePtr);
|
||||
}
|
||||
}
|
||||
|
||||
internal static PinnedUtf8 Pin(string value)
|
||||
{
|
||||
return new PinnedUtf8(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user