編集: GitHub: the NOpenCL libraryでこの回答の作業をホストしました。
あなたのコメントに基づいて、ここで議論されている問題に対する適切な長期的な行動方針として、次のことを決定しました。どうやら問題は、マネージ コード内での OpenCL の使用に集中しているようです。必要なのは、この API の適切な相互運用レイヤーです。
実験として、OpenCL API の大部分のマネージ ラッパーを作成して、クリーンアップのために を呼び出す必要がある、 、およびその他のオブジェクトをSafeHandle
ラップする実行可能性を評価しました。最も困難な部分は、これらのハンドルの配列をパラメーターとして受け取ることができるメソッドの実装でした。このメソッドの最初の宣言は次のようになります。cl_mem
cl_event
clRelease*
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);
}