Initial commit

This commit is contained in:
2026-07-03 16:31:37 +07:00
commit 899c7c637d
1939 changed files with 641750 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}