2

私の問題を説明しましょう - アンマネージ ハンドルをラップする構造体があります (これを Mem と呼びましょう)。このハンドルは、コピーされるたびに特定のメソッドを呼び出す (「保持」または参照カウントを維持する) ために必要です。

つまり、参照カウントを内部で保持する構造体が必要です (外部にもメカニズムがありますが、そのメカニズムを呼び出す方法が必要です)。

残念ながら、C# ではこれを行うことはできません。

これらの構造体の配列をアンマネージ コードに渡すため、Mem をクラスにすることもできず、渡す前に 1 つずつ変換したくない (固定して渡すだけ)。

この動作を追加するために適用できる回避策 (IL Weaving など) を知っている人はいますか? ILは私がこれを行うことを妨げていないと思います.C#だけですよね?

私が持っているフレームワークと制限に関する質問には喜んでお答えしますが、「デザインを変更してください」または「これには C# を使用しないでください」という回答を求めているわけではありません。どうもありがとうございました。

4

3 に答える 3

1

編集: GitHub: the NOpenCL libraryでこの回答の作業をホストしました。

あなたのコメントに基づいて、ここで議論されている問題に対する適切な長期的な行動方針として、次のことを決定しました。どうやら問題は、マネージ コード内での OpenCL の使用に集中しているようです。必要なのは、この API の適切な相互運用レイヤーです。

実験として、OpenCL API の大部分のマネージ ラッパーを作成して、クリーンアップのために を呼び出す必要がある、 、およびその他のオブジェクトをSafeHandleラップする実行可能性を評価しました。最も困難な部分は、これらのハンドルの配列をパラメーターとして受け取ることができるメソッドの実装でした。このメソッドの最初の宣言は次のようになります。cl_memcl_eventclRelease*clEnqueueReadBuffer

[DllImport(ExternDll.OpenCL)]
private static extern ErrorCode clEnqueueReadBuffer(
    CommandQueueSafeHandle commandQueue,
    BufferSafeHandle buffer,
    [MarshalAs(UnmanagedType.Bool)] bool blockingRead,
    IntPtr offset,
    IntPtr size,
    IntPtr destination,
    uint numEventsInWaitList,
    [In, MarshalAs(UnmanagedType.LPArray)] EventSafeHandle[] eventWaitList,
    out EventSafeHandle @event);

SafeHandle残念ながら、P/Invoke レイヤーはオブジェクトの配列のマーシャリングをサポートしていないため、これを処理するICustomMarshaler呼び出しを実装しましたSafeHandleArrayMarshaler。現在の実装では制約付き実行領域を使用していないため、マーシャリング中の非同期例外によってメモリ リークが発生する可能性があることに注意してください。

internal sealed class SafeHandleArrayMarshaler : ICustomMarshaler
{
    private static readonly SafeHandleArrayMarshaler Instance = new SafeHandleArrayMarshaler();

    private SafeHandleArrayMarshaler()
    {
    }

    public static ICustomMarshaler GetInstance(string cookie)
    {
        return Instance;
    }

    public void CleanUpManagedData(object ManagedObj)
    {
        throw new NotSupportedException();
    }

    public void CleanUpNativeData(IntPtr pNativeData)
    {
        if (pNativeData == IntPtr.Zero)
            return;

        GCHandle managedHandle = GCHandle.FromIntPtr(Marshal.ReadIntPtr(pNativeData, -IntPtr.Size));
        SafeHandle[] array = (SafeHandle[])managedHandle.Target;
        managedHandle.Free();

        for (int i = 0; i < array.Length; i++)
        {
            SafeHandle current = array[i];
            if (current == null)
                continue;

            if (Marshal.ReadIntPtr(pNativeData, i * IntPtr.Size) != IntPtr.Zero)
                array[i].DangerousRelease();
        }

        Marshal.FreeHGlobal(pNativeData - IntPtr.Size);
    }

    public int GetNativeDataSize()
    {
        return IntPtr.Size;
    }

    public IntPtr MarshalManagedToNative(object ManagedObj)
    {
        if (ManagedObj == null)
            return IntPtr.Zero;

        SafeHandle[] array = (SafeHandle[])ManagedObj;
        int i = 0;
        bool success = false;
        try
        {
            for (i = 0; i < array.Length; success = false, i++)
            {
                SafeHandle current = array[i];
                if (current != null && !current.IsClosed && !current.IsInvalid)
                    current.DangerousAddRef(ref success);
            }

            IntPtr result = Marshal.AllocHGlobal(array.Length * IntPtr.Size);
            Marshal.WriteIntPtr(result, 0, GCHandle.ToIntPtr(GCHandle.Alloc(array, GCHandleType.Normal)));
            for (int j = 0; j < array.Length; j++)
            {
                SafeHandle current = array[j];
                if (current == null || current.IsClosed || current.IsInvalid)
                {
                    // the memory for this element was initialized to null by AllocHGlobal
                    continue;
                }

                Marshal.WriteIntPtr(result, (j + 1) * IntPtr.Size, current.DangerousGetHandle());
            }

            return result + IntPtr.Size;
        }
        catch
        {
            int total = success ? i + 1 : i;
            for (int j = 0; j < total; j++)
            {
                SafeHandle current = array[j];
                if (current != null)
                    current.DangerousRelease();
            }

            throw;
        }
    }

    public object MarshalNativeToManaged(IntPtr pNativeData)
    {
        throw new NotSupportedException();
    }
}

これにより、次の相互運用宣言を正常に使用できました。

[DllImport(ExternDll.OpenCL)]
private static extern ErrorCode clEnqueueReadBuffer(
    CommandQueueSafeHandle commandQueue,
    BufferSafeHandle buffer,
    [MarshalAs(UnmanagedType.Bool)] bool blockingRead,
    IntPtr offset,
    IntPtr size,
    IntPtr destination,
    uint numEventsInWaitList,
    [In, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(SafeHandleArrayMarshaler))] EventSafeHandle[] eventWaitList,
    out EventSafeHandle @event);

このメソッドはプライベートとして宣言されているため、OpenCL 1.2 API ドキュメントに従って引数numEventsInWaitListと引数を適切に処理するメソッドを介して公開できます。eventWaitList

internal static EventSafeHandle EnqueueReadBuffer(CommandQueueSafeHandle commandQueue, BufferSafeHandle buffer, bool blocking, IntPtr offset, IntPtr size, IntPtr destination, EventSafeHandle[] eventWaitList)
{
    if (commandQueue == null)
        throw new ArgumentNullException("commandQueue");
    if (buffer == null)
        throw new ArgumentNullException("buffer");
    if (destination == IntPtr.Zero)
        throw new ArgumentNullException("destination");

    EventSafeHandle result;
    ErrorHandler.ThrowOnFailure(clEnqueueReadBuffer(commandQueue, buffer, blocking, offset, size, destination, eventWaitList != null ? (uint)eventWaitList.Length : 0, eventWaitList != null && eventWaitList.Length > 0 ? eventWaitList : null, out result));
    return result;
}

API は最終的に、ContextQueueクラスの次のインスタンス メソッドとしてユーザー コードに公開されます。

public Event EnqueueReadBuffer(Buffer buffer, bool blocking, long offset, long size, IntPtr destination, params Event[] eventWaitList)
{
    EventSafeHandle[] eventHandles = null;
    if (eventWaitList != null)
        eventHandles = Array.ConvertAll(eventWaitList, @event => @event.Handle);

    EventSafeHandle handle = UnsafeNativeMethods.EnqueueReadBuffer(this.Handle, buffer.Handle, blocking, (IntPtr)offset, (IntPtr)size, destination, eventHandles);
    return new Event(handle);
}
于 2013-05-16T20:17:09.483 に答える