2

让我描述一下我的问题 - 我有一个包装非托管句柄的结构(我们称之为 Mem)。每当复制它时,我都需要此句柄来调用特定方法(例如“保留”或保持引用计数)。

换句话说,我需要一个在内部维护引用计数的结构(我在外部也有一个机制,但需要一种调用该机制的方法)。

不幸的是,C# 不允许我以任何方式这样做。

我也不能让 Mem 成为一个类,因为我会将这些结构的数组传递给非托管代码,并且我不想在传递它们之前将它们一一转换(只需固定并传递)。

有谁知道可用于添加此行为的任何解决方法(IL Weaving 等)?我相信 IL 不会阻止我这样做,只有 C#,对吗?

我很高兴回答有关我所拥有的框架和限制的任何问题,但我不是在寻找“请更改您的设计”“不要为此使用 C#”的答案,非常感谢。

4

3 回答 3

5

我相信 IL 不会阻止我这样做,只有 C#,对吗?

是的,“this”是“结构的无参数构造函数”。不久前我在博客上写过。

但是,就每次复制结构时通知您而言,拥有无参数构造函数并不能满足您的要求。据我所知,基本上没有办法做到这一点。当你最终得到一个“默认”值时,甚至不会在每种情况下都调用构造函数,即使是这样,它也肯定不仅仅是为了复制操作而调用的。

我知道您不想听到“请更改您的设计”,但您只是要求.NET中不存在的东西。

我建议对返回新副本的值类型使用某种方法,并采取适当的措施。然后,您需要确保始终在正确的时间调用该方法。除了您可以构建的任何测试之外,没有什么可以阻止您犯此错误。

于 2013-05-14T16:33:47.503 回答
4

有谁知道可用于添加此行为的任何解决方法(IL Weaving 等)?我相信 IL 不会阻止我这样做,只有 C#,对吗?

这是正确的,有点。C# 阻止这种情况的原因是,在许多情况下,即使它在 IL 中定义,也不会使用构造函数。您的情况就是其中之一 - 如果您创建一个结构数组,则不会调用构造函数,即使它们是在 IL 中定义的。

不幸的是,没有真正的解决方法,因为 CLR 不会调用构造函数,即使它们存在。

于 2013-05-14T16:33:15.923 回答
1

编辑:我在 GitHub 上托管了这个答案的工作:NOpenCL 库

根据您的评论,我确定以下是针对此处讨论的问题的适当的长期行动方案。显然,问题集中在托管代码中 OpenCL 的使用上。您需要为此 API 提供适当的互操作层。

作为一个实验,我为大部分 OpenCL API 编写了一个托管包装器,以评估SafeHandlewrap cl_memcl_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);

不幸的是,P/Invoke 层不支持编组SafeHandle对象数组,所以我实现了一个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;
}

ContextQueueAPI 最终在我的类中作为以下实例方法公开给用户代码。

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 回答