Initial commit
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using CeresSharp.Exceptions;
|
||||
|
||||
namespace CeresSharp.Native;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for native interop operations.
|
||||
/// Provides utilities for error handling, StringBuilder management, and common patterns.
|
||||
/// </summary>
|
||||
internal static class NativeHelpers
|
||||
{
|
||||
private const int DefaultErrorMessageCapacity = 256;
|
||||
|
||||
// Thread-local buffer pool to avoid allocating a new StringBuilder every call
|
||||
[ThreadStatic]
|
||||
private static StringBuilder? t_errorMessageBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reusable StringBuilder for error messages.
|
||||
/// Uses thread-local storage to avoid allocation on each native call.
|
||||
/// </summary>
|
||||
internal static StringBuilder CreateErrorMessageBuffer(int capacity = DefaultErrorMessageCapacity)
|
||||
{
|
||||
var sb = t_errorMessageBuffer;
|
||||
if (sb != null && sb.Capacity >= capacity)
|
||||
{
|
||||
sb.Clear();
|
||||
return sb;
|
||||
}
|
||||
sb = new StringBuilder(capacity);
|
||||
t_errorMessageBuffer = sb;
|
||||
return sb;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts error message from StringBuilder, returning null if empty.
|
||||
/// </summary>
|
||||
internal static string? ExtractErrorMessage(StringBuilder sb)
|
||||
{
|
||||
return sb.Length > 0 ? sb.ToString() : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks error code and throws exception if not successful.
|
||||
/// </summary>
|
||||
/// <param name="errorCode">The error code to check.</param>
|
||||
/// <param name="errorMessage">Optional error message. If null, will be retrieved from native code.</param>
|
||||
/// <exception cref="CeresException">Thrown when error code is not Success.</exception>
|
||||
internal static void CheckError(CeresErrorCode errorCode, string? errorMessage = null)
|
||||
{
|
||||
if (errorCode != CeresErrorCode.Success)
|
||||
{
|
||||
if (errorMessage == null)
|
||||
{
|
||||
var errorMessagePtr = CeresNative.ceres_wrapper_get_error_message(errorCode);
|
||||
errorMessage = errorMessagePtr != IntPtr.Zero
|
||||
? System.Runtime.InteropServices.Marshal.PtrToStringAnsi(errorMessagePtr)
|
||||
: $"Unknown error code: {errorCode}";
|
||||
}
|
||||
throw new CeresException(errorCode, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a native operation with error handling.
|
||||
/// </summary>
|
||||
/// <param name="operation">The operation that returns an error code.</param>
|
||||
/// <param name="errorMessageBuffer">Optional StringBuilder for error messages.</param>
|
||||
/// <exception cref="CeresException">Thrown when the operation fails.</exception>
|
||||
internal static void ExecuteWithErrorHandling(
|
||||
Func<StringBuilder, CeresErrorCode> operation,
|
||||
StringBuilder? errorMessageBuffer = null)
|
||||
{
|
||||
errorMessageBuffer ??= CreateErrorMessageBuffer();
|
||||
var errorCode = operation(errorMessageBuffer);
|
||||
if (errorCode != CeresErrorCode.Success)
|
||||
{
|
||||
var errorMessage = ExtractErrorMessage(errorMessageBuffer);
|
||||
throw new CeresException(errorCode, errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CeresSharp.Native;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for managing GCHandle resources and ensuring proper cleanup.
|
||||
/// Provides utilities for tracking and disposing of pinned handles.
|
||||
/// </summary>
|
||||
internal sealed class ResourceManager : IDisposable
|
||||
{
|
||||
private readonly List<GCHandle> _handles = new List<GCHandle>();
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a GCHandle to be managed and disposed.
|
||||
/// </summary>
|
||||
/// <param name="handle">The GCHandle to track.</param>
|
||||
/// <exception cref="ObjectDisposedException">Thrown when the manager is disposed.</exception>
|
||||
public void AddHandle(GCHandle handle)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(ResourceManager));
|
||||
|
||||
lock (_handles)
|
||||
{
|
||||
_handles.Add(handle);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple GCHandles to be managed and disposed.
|
||||
/// </summary>
|
||||
/// <param name="handles">The GCHandles to track.</param>
|
||||
/// <exception cref="ObjectDisposedException">Thrown when the manager is disposed.</exception>
|
||||
public void AddHandles(IEnumerable<GCHandle> handles)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(ResourceManager));
|
||||
|
||||
lock (_handles)
|
||||
{
|
||||
_handles.AddRange(handles);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pins an array and adds the handle to the manager.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The unmanaged type of the array elements.</typeparam>
|
||||
/// <param name="array">The array to pin.</param>
|
||||
/// <returns>The pinned GCHandle.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when array is null.</exception>
|
||||
/// <exception cref="ObjectDisposedException">Thrown when the manager is disposed.</exception>
|
||||
public GCHandle PinArray<T>(T[] array) where T : unmanaged
|
||||
{
|
||||
if (array == null)
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
|
||||
var handle = GCHandle.Alloc(array, GCHandleType.Pinned);
|
||||
AddHandle(handle);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all handles as an array (for safe enumeration during disposal).
|
||||
/// </summary>
|
||||
/// <returns>Array of GCHandles.</returns>
|
||||
public GCHandle[] GetHandlesSnapshot()
|
||||
{
|
||||
lock (_handles)
|
||||
{
|
||||
return _handles.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all handles without disposing them.
|
||||
/// Useful when ownership is transferred to another object.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
lock (_handles)
|
||||
{
|
||||
_handles.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
// Get snapshot to avoid issues during enumeration
|
||||
var handlesToFree = GetHandlesSnapshot();
|
||||
|
||||
lock (_handles)
|
||||
{
|
||||
_handles.Clear();
|
||||
}
|
||||
|
||||
// Free handles outside the lock
|
||||
foreach (var handle in handlesToFree)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (handle.IsAllocated)
|
||||
handle.Free();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors during cleanup - handles may already be freed
|
||||
}
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace CeresSharp.Native.SafeHandles;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all Ceres native resource handles.
|
||||
/// </summary>
|
||||
public abstract class BaseSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
protected BaseSafeHandle() : base(true)
|
||||
{
|
||||
}
|
||||
|
||||
protected BaseSafeHandle(IntPtr handle, bool ownsHandle) : base(ownsHandle)
|
||||
{
|
||||
SetHandle(handle);
|
||||
}
|
||||
|
||||
private volatile bool _released = false;
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
// Prevent double-free by checking if already released
|
||||
// Use volatile read/write for thread safety in finalizer
|
||||
if (_released || IsInvalid)
|
||||
return true; // Already released or invalid
|
||||
|
||||
try
|
||||
{
|
||||
// Add try-catch to prevent crash in finalizer thread
|
||||
// If native handle is already freed or invalid, this will fail gracefully
|
||||
ReleaseNativeHandle(handle);
|
||||
_released = true;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors in finalizer - native handle may already be freed
|
||||
// Mark as released to prevent retry
|
||||
_released = true;
|
||||
// Return true to indicate handle was "released" (even if it failed)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the native handle. Must be implemented by derived classes.
|
||||
/// </summary>
|
||||
protected abstract void ReleaseNativeHandle(IntPtr handle);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using CeresSharp.Native;
|
||||
|
||||
namespace CeresSharp.Native.SafeHandles;
|
||||
|
||||
/// <summary>
|
||||
/// Safe handle for Ceres Context.
|
||||
/// </summary>
|
||||
internal sealed class ContextHandle : BaseSafeHandle
|
||||
{
|
||||
private ContextHandle() : base()
|
||||
{
|
||||
}
|
||||
|
||||
private ContextHandle(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle)
|
||||
{
|
||||
}
|
||||
|
||||
public static ContextHandle Create()
|
||||
{
|
||||
var handle = CeresNative.ceres_wrapper_create_context();
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Exceptions.CeresException(Exceptions.CeresErrorCode.OutOfMemory, "Failed to create context");
|
||||
}
|
||||
return new ContextHandle(handle, true);
|
||||
}
|
||||
|
||||
protected override void ReleaseNativeHandle(IntPtr handle)
|
||||
{
|
||||
CeresNative.ceres_wrapper_free_context(handle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using CeresSharp.Native;
|
||||
|
||||
namespace CeresSharp.Native.SafeHandles;
|
||||
|
||||
/// <summary>
|
||||
/// Safe handle for Ceres CostFunction.
|
||||
/// </summary>
|
||||
internal sealed class CostFunctionHandle : BaseSafeHandle
|
||||
{
|
||||
private volatile bool _ownedByProblem = false;
|
||||
|
||||
private CostFunctionHandle() : base()
|
||||
{
|
||||
}
|
||||
|
||||
private CostFunctionHandle(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle)
|
||||
{
|
||||
}
|
||||
|
||||
public static CostFunctionHandle Create(IntPtr handle)
|
||||
{
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Exceptions.CeresException(Exceptions.CeresErrorCode.NullPointer, "Cost function handle is null");
|
||||
}
|
||||
return new CostFunctionHandle(handle, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks this cost function as owned by a Problem.
|
||||
/// When owned by Problem, the native handle should NOT be freed here
|
||||
/// because Problem/Ceres will free it when Problem is destroyed.
|
||||
/// </summary>
|
||||
internal void MarkOwnedByProblem() => _ownedByProblem = true;
|
||||
|
||||
protected override void ReleaseNativeHandle(IntPtr handle)
|
||||
{
|
||||
if (_ownedByProblem)
|
||||
{
|
||||
// Problem owns this cost function - Ceres will delete it when Problem is freed.
|
||||
// Do NOT free here to avoid double-free.
|
||||
return;
|
||||
}
|
||||
// Standalone cost function - we must free it ourselves
|
||||
CeresNative.ceres_wrapper_free_autodiff_cost_function(handle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using CeresSharp.Native;
|
||||
|
||||
namespace CeresSharp.Native.SafeHandles;
|
||||
|
||||
/// <summary>
|
||||
/// Safe handle for Ceres Covariance.
|
||||
/// </summary>
|
||||
internal sealed class CovarianceHandle : BaseSafeHandle
|
||||
{
|
||||
private CovarianceHandle() : base()
|
||||
{
|
||||
}
|
||||
|
||||
private CovarianceHandle(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle)
|
||||
{
|
||||
}
|
||||
|
||||
public static CovarianceHandle Create()
|
||||
{
|
||||
var handle = CeresNative.ceres_wrapper_create_covariance();
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Exceptions.CeresException(Exceptions.CeresErrorCode.OutOfMemory, "Failed to create covariance");
|
||||
}
|
||||
return new CovarianceHandle(handle, true);
|
||||
}
|
||||
|
||||
public static CovarianceHandle CreateWithOptions(IntPtr options)
|
||||
{
|
||||
var handle = CeresNative.ceres_wrapper_create_covariance_with_options(options);
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Exceptions.CeresException(Exceptions.CeresErrorCode.OutOfMemory, "Failed to create covariance with options");
|
||||
}
|
||||
return new CovarianceHandle(handle, true);
|
||||
}
|
||||
|
||||
protected override void ReleaseNativeHandle(IntPtr handle)
|
||||
{
|
||||
CeresNative.ceres_wrapper_free_covariance(handle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using CeresSharp.Native;
|
||||
|
||||
namespace CeresSharp.Native.SafeHandles;
|
||||
|
||||
/// <summary>
|
||||
/// Safe handle for Ceres CovarianceOptions.
|
||||
/// </summary>
|
||||
internal sealed class CovarianceOptionsHandle : BaseSafeHandle
|
||||
{
|
||||
private CovarianceOptionsHandle() : base()
|
||||
{
|
||||
}
|
||||
|
||||
private CovarianceOptionsHandle(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle)
|
||||
{
|
||||
}
|
||||
|
||||
public static CovarianceOptionsHandle Create()
|
||||
{
|
||||
var handle = CeresNative.ceres_wrapper_create_covariance_options();
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Exceptions.CeresException(Exceptions.CeresErrorCode.OutOfMemory, "Failed to create covariance options");
|
||||
}
|
||||
return new CovarianceOptionsHandle(handle, true);
|
||||
}
|
||||
|
||||
protected override void ReleaseNativeHandle(IntPtr handle)
|
||||
{
|
||||
CeresNative.ceres_wrapper_free_covariance_options(handle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using CeresSharp.Native;
|
||||
|
||||
namespace CeresSharp.Native.SafeHandles;
|
||||
|
||||
/// <summary>
|
||||
/// Safe handle for Ceres GradientChecker.
|
||||
/// </summary>
|
||||
internal sealed class GradientCheckerHandle : BaseSafeHandle
|
||||
{
|
||||
private GradientCheckerHandle() : base()
|
||||
{
|
||||
}
|
||||
|
||||
private GradientCheckerHandle(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle)
|
||||
{
|
||||
}
|
||||
|
||||
public static GradientCheckerHandle Create(IntPtr handle)
|
||||
{
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Exceptions.CeresException(Exceptions.CeresErrorCode.NullPointer, "Gradient checker handle is null");
|
||||
}
|
||||
return new GradientCheckerHandle(handle, true);
|
||||
}
|
||||
|
||||
protected override void ReleaseNativeHandle(IntPtr handle)
|
||||
{
|
||||
CeresNative.ceres_wrapper_free_gradient_checker(handle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using CeresSharp.Native;
|
||||
|
||||
namespace CeresSharp.Native.SafeHandles;
|
||||
|
||||
/// <summary>
|
||||
/// Safe handle for Ceres GradientCheckerOptions.
|
||||
/// </summary>
|
||||
internal sealed class GradientCheckerOptionsHandle : BaseSafeHandle
|
||||
{
|
||||
private GradientCheckerOptionsHandle() : base()
|
||||
{
|
||||
}
|
||||
|
||||
private GradientCheckerOptionsHandle(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle)
|
||||
{
|
||||
}
|
||||
|
||||
public static GradientCheckerOptionsHandle Create()
|
||||
{
|
||||
var handle = CeresNative.ceres_wrapper_create_gradient_checker_options();
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Exceptions.CeresException(Exceptions.CeresErrorCode.OutOfMemory, "Failed to create gradient checker options");
|
||||
}
|
||||
return new GradientCheckerOptionsHandle(handle, true);
|
||||
}
|
||||
|
||||
protected override void ReleaseNativeHandle(IntPtr handle)
|
||||
{
|
||||
CeresNative.ceres_wrapper_free_gradient_checker_options(handle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using CeresSharp.Native;
|
||||
|
||||
namespace CeresSharp.Native.SafeHandles;
|
||||
|
||||
/// <summary>
|
||||
/// Safe handle for Ceres Interpolator (BiCubic or Cubic).
|
||||
/// </summary>
|
||||
internal sealed class InterpolatorHandle : BaseSafeHandle
|
||||
{
|
||||
private readonly bool _isBicubic;
|
||||
|
||||
private InterpolatorHandle() : base()
|
||||
{
|
||||
}
|
||||
|
||||
private InterpolatorHandle(IntPtr handle, bool ownsHandle, bool isBicubic) : base(handle, ownsHandle)
|
||||
{
|
||||
_isBicubic = isBicubic;
|
||||
}
|
||||
|
||||
public static InterpolatorHandle CreateBicubic(IntPtr handle)
|
||||
{
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Exceptions.CeresException(Exceptions.CeresErrorCode.NullPointer, "BiCubic interpolator handle is null");
|
||||
}
|
||||
return new InterpolatorHandle(handle, true, true);
|
||||
}
|
||||
|
||||
public static InterpolatorHandle CreateCubic(IntPtr handle)
|
||||
{
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Exceptions.CeresException(Exceptions.CeresErrorCode.NullPointer, "Cubic interpolator handle is null");
|
||||
}
|
||||
return new InterpolatorHandle(handle, true, false);
|
||||
}
|
||||
|
||||
protected override void ReleaseNativeHandle(IntPtr handle)
|
||||
{
|
||||
if (_isBicubic)
|
||||
{
|
||||
CeresNative.ceres_wrapper_free_bicubic_interpolator(handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
CeresNative.ceres_wrapper_free_cubic_interpolator(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using CeresSharp.Native;
|
||||
|
||||
namespace CeresSharp.Native.SafeHandles;
|
||||
|
||||
/// <summary>
|
||||
/// Safe handle for Ceres LossFunction.
|
||||
/// </summary>
|
||||
internal sealed class LossFunctionHandle : BaseSafeHandle
|
||||
{
|
||||
private volatile bool _ownedByProblem = false;
|
||||
|
||||
private LossFunctionHandle() : base()
|
||||
{
|
||||
}
|
||||
|
||||
private LossFunctionHandle(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle)
|
||||
{
|
||||
}
|
||||
|
||||
public static LossFunctionHandle Create(IntPtr handle)
|
||||
{
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Exceptions.CeresException(Exceptions.CeresErrorCode.NullPointer, "Loss function handle is null");
|
||||
}
|
||||
return new LossFunctionHandle(handle, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks this loss function as owned by a Problem.
|
||||
/// When owned by Problem, the native handle should NOT be freed here
|
||||
/// because Problem/Ceres will free it when Problem is destroyed.
|
||||
/// </summary>
|
||||
internal void MarkOwnedByProblem() => _ownedByProblem = true;
|
||||
|
||||
protected override void ReleaseNativeHandle(IntPtr handle)
|
||||
{
|
||||
if (_ownedByProblem)
|
||||
{
|
||||
// Problem owns this loss function - Ceres will delete it when Problem is freed.
|
||||
// Do NOT free here to avoid double-free.
|
||||
return;
|
||||
}
|
||||
// Standalone loss function - we must free it ourselves
|
||||
CeresNative.ceres_wrapper_free_loss_function(handle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using CeresSharp.Native;
|
||||
|
||||
namespace CeresSharp.Native.SafeHandles;
|
||||
|
||||
/// <summary>
|
||||
/// Safe handle for Ceres Manifold.
|
||||
/// </summary>
|
||||
internal sealed class ManifoldHandle : BaseSafeHandle
|
||||
{
|
||||
private ManifoldHandle() : base()
|
||||
{
|
||||
}
|
||||
|
||||
private ManifoldHandle(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle)
|
||||
{
|
||||
}
|
||||
|
||||
public static ManifoldHandle Create(IntPtr handle)
|
||||
{
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Exceptions.CeresException(Exceptions.CeresErrorCode.NullPointer, "Manifold handle is null");
|
||||
}
|
||||
return new ManifoldHandle(handle, true);
|
||||
}
|
||||
|
||||
protected override void ReleaseNativeHandle(IntPtr handle)
|
||||
{
|
||||
CeresNative.ceres_wrapper_free_manifold(handle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using CeresSharp.Native;
|
||||
|
||||
namespace CeresSharp.Native.SafeHandles;
|
||||
|
||||
/// <summary>
|
||||
/// Safe handle for Ceres Problem.
|
||||
/// Note: Uses official C API (ceres_create_problem/ceres_free_problem).
|
||||
/// </summary>
|
||||
internal sealed class ProblemHandle : BaseSafeHandle
|
||||
{
|
||||
private ProblemHandle() : base()
|
||||
{
|
||||
}
|
||||
|
||||
private ProblemHandle(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle)
|
||||
{
|
||||
}
|
||||
|
||||
public static ProblemHandle Create()
|
||||
{
|
||||
// Note: ceres_create_problem is from official C API, not wrapper
|
||||
// We'll need to import it separately or use wrapper function if available
|
||||
var handle = CeresNative.ceres_create_problem();
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Exceptions.CeresException(Exceptions.CeresErrorCode.OutOfMemory, "Failed to create problem");
|
||||
}
|
||||
return new ProblemHandle(handle, true);
|
||||
}
|
||||
|
||||
public static ProblemHandle CreateWithOptions(IntPtr problemOptions)
|
||||
{
|
||||
var handle = CeresNative.ceres_wrapper_create_problem_with_options(problemOptions);
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Exceptions.CeresException(Exceptions.CeresErrorCode.OutOfMemory, "Failed to create problem with options");
|
||||
}
|
||||
return new ProblemHandle(handle, true);
|
||||
}
|
||||
|
||||
protected override void ReleaseNativeHandle(IntPtr handle)
|
||||
{
|
||||
// Note: ceres_free_problem is from official C API
|
||||
// Add null check to prevent crash if handle is invalid
|
||||
if (handle != IntPtr.Zero)
|
||||
{
|
||||
try
|
||||
{
|
||||
CeresNative.ceres_free_problem(handle);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors - handle may already be freed or invalid
|
||||
// This can happen in finalizer thread
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using CeresSharp.Native;
|
||||
|
||||
namespace CeresSharp.Native.SafeHandles;
|
||||
|
||||
/// <summary>
|
||||
/// Safe handle for Ceres ProblemOptions.
|
||||
/// </summary>
|
||||
internal sealed class ProblemOptionsHandle : BaseSafeHandle
|
||||
{
|
||||
private ProblemOptionsHandle() : base()
|
||||
{
|
||||
}
|
||||
|
||||
private ProblemOptionsHandle(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle)
|
||||
{
|
||||
}
|
||||
|
||||
public static ProblemOptionsHandle Create()
|
||||
{
|
||||
var handle = CeresNative.ceres_wrapper_create_problem_options();
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Exceptions.CeresException(Exceptions.CeresErrorCode.OutOfMemory, "Failed to create problem options");
|
||||
}
|
||||
return new ProblemOptionsHandle(handle, true);
|
||||
}
|
||||
|
||||
protected override void ReleaseNativeHandle(IntPtr handle)
|
||||
{
|
||||
CeresNative.ceres_wrapper_free_problem_options(handle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using CeresSharp.Native;
|
||||
|
||||
namespace CeresSharp.Native.SafeHandles;
|
||||
|
||||
/// <summary>
|
||||
/// Safe handle for Ceres SolverOptions.
|
||||
/// </summary>
|
||||
internal sealed class SolverOptionsHandle : BaseSafeHandle
|
||||
{
|
||||
private SolverOptionsHandle() : base()
|
||||
{
|
||||
}
|
||||
|
||||
private SolverOptionsHandle(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle)
|
||||
{
|
||||
}
|
||||
|
||||
public static SolverOptionsHandle Create()
|
||||
{
|
||||
var handle = CeresNative.ceres_wrapper_create_solver_options();
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Exceptions.CeresException(Exceptions.CeresErrorCode.OutOfMemory, "Failed to create solver options");
|
||||
}
|
||||
return new SolverOptionsHandle(handle, true);
|
||||
}
|
||||
|
||||
protected override void ReleaseNativeHandle(IntPtr handle)
|
||||
{
|
||||
CeresNative.ceres_wrapper_free_solver_options(handle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using CeresSharp.Native;
|
||||
|
||||
namespace CeresSharp.Native.SafeHandles;
|
||||
|
||||
/// <summary>
|
||||
/// Safe handle for Ceres SolverSummary.
|
||||
/// </summary>
|
||||
internal sealed class SolverSummaryHandle : BaseSafeHandle
|
||||
{
|
||||
private SolverSummaryHandle() : base()
|
||||
{
|
||||
}
|
||||
|
||||
internal SolverSummaryHandle(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle)
|
||||
{
|
||||
}
|
||||
|
||||
public static SolverSummaryHandle Create()
|
||||
{
|
||||
var handle = CeresNative.ceres_wrapper_create_solver_summary();
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Exceptions.CeresException(Exceptions.CeresErrorCode.OutOfMemory, "Failed to create solver summary");
|
||||
}
|
||||
return new SolverSummaryHandle(handle, true);
|
||||
}
|
||||
|
||||
protected override void ReleaseNativeHandle(IntPtr handle)
|
||||
{
|
||||
CeresNative.ceres_wrapper_free_solver_summary(handle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CeresSharp.Native;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for unsafe code patterns and pointer operations.
|
||||
/// Provides utilities for array pinning and pointer conversions.
|
||||
/// </summary>
|
||||
internal static class UnsafeHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes an operation with a pinned array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The unmanaged type of the array elements.</typeparam>
|
||||
/// <param name="array">The array to pin.</param>
|
||||
/// <param name="operation">The operation to execute with the pinned pointer.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when array is null.</exception>
|
||||
internal static void WithPinnedArray<T>(T[] array, Action<IntPtr> operation) where T : unmanaged
|
||||
{
|
||||
if (array == null)
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (T* ptr = array)
|
||||
{
|
||||
operation((IntPtr)ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes an operation with a pinned array and returns a result.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The unmanaged type of the array elements.</typeparam>
|
||||
/// <typeparam name="TResult">The return type.</typeparam>
|
||||
/// <param name="array">The array to pin.</param>
|
||||
/// <param name="operation">The operation to execute with the pinned pointer.</param>
|
||||
/// <returns>The result of the operation.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when array is null.</exception>
|
||||
internal static TResult WithPinnedArray<T, TResult>(T[] array, Func<IntPtr, TResult> operation) where T : unmanaged
|
||||
{
|
||||
if (array == null)
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (T* ptr = array)
|
||||
{
|
||||
return operation((IntPtr)ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes an operation with multiple pinned arrays.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The unmanaged type of the array elements.</typeparam>
|
||||
/// <param name="arrays">The arrays to pin.</param>
|
||||
/// <param name="operation">The operation to execute with the pinned pointers.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when arrays is null or contains null elements.</exception>
|
||||
internal static void WithPinnedArrays<T>(T[][] arrays, Action<IntPtr[]> operation) where T : unmanaged
|
||||
{
|
||||
if (arrays == null)
|
||||
throw new ArgumentNullException(nameof(arrays));
|
||||
|
||||
unsafe
|
||||
{
|
||||
var pointers = new IntPtr[arrays.Length];
|
||||
var pins = new GCHandle[arrays.Length];
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < arrays.Length; i++)
|
||||
{
|
||||
if (arrays[i] == null)
|
||||
throw new ArgumentException($"Array at index {i} is null", nameof(arrays));
|
||||
|
||||
pins[i] = GCHandle.Alloc(arrays[i], GCHandleType.Pinned);
|
||||
pointers[i] = pins[i].AddrOfPinnedObject();
|
||||
}
|
||||
|
||||
operation(pointers);
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var pin in pins)
|
||||
{
|
||||
if (pin.IsAllocated)
|
||||
pin.Free();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pins an array and returns a GCHandle that must be freed by the caller.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The unmanaged type of the array elements.</typeparam>
|
||||
/// <param name="array">The array to pin.</param>
|
||||
/// <returns>A GCHandle that pins the array.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when array is null.</exception>
|
||||
internal static GCHandle PinArray<T>(T[] array) where T : unmanaged
|
||||
{
|
||||
if (array == null)
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
|
||||
return GCHandle.Alloc(array, GCHandleType.Pinned);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies data from an IntPtr array to a managed array of arrays.
|
||||
/// </summary>
|
||||
/// <param name="parameterPtrs">Pointer to array of IntPtr pointers.</param>
|
||||
/// <param name="parameterBlockSizes">Array of sizes for each parameter block.</param>
|
||||
/// <returns>Array of parameter blocks.</returns>
|
||||
internal static double[][] CopyParameterBlocks(IntPtr parameterPtrs, int[] parameterBlockSizes)
|
||||
{
|
||||
if (parameterPtrs == IntPtr.Zero)
|
||||
throw new ArgumentException("Parameter pointers cannot be zero", nameof(parameterPtrs));
|
||||
if (parameterBlockSizes == null || parameterBlockSizes.Length == 0)
|
||||
throw new ArgumentException("Parameter block sizes cannot be null or empty", nameof(parameterBlockSizes));
|
||||
|
||||
var parameterBlocks = new double[parameterBlockSizes.Length][];
|
||||
|
||||
unsafe
|
||||
{
|
||||
var paramPtrs = (IntPtr*)parameterPtrs;
|
||||
for (int i = 0; i < parameterBlockSizes.Length; i++)
|
||||
{
|
||||
var size = parameterBlockSizes[i];
|
||||
if (size <= 0)
|
||||
throw new ArgumentException($"Parameter block size at index {i} must be positive", nameof(parameterBlockSizes));
|
||||
|
||||
parameterBlocks[i] = new double[size];
|
||||
Marshal.Copy(paramPtrs[i], parameterBlocks[i], 0, size);
|
||||
}
|
||||
}
|
||||
|
||||
return parameterBlocks;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user