向结构体添加默认无参数构造函数的解决方法
本文关键字:构造函数 解决 方法 参数 结构体 添加 默认 | 更新日期: 2023-09-27 18:13:37
让我来描述一下我的问题——我有一个结构体,它包装了一个非托管句柄(我们称之为Mem)。我需要这个句柄在它被复制时调用一个特定的方法(比如"retain",或者维护一个引用计数)。
换句话说,我需要一个在内部维护引用计数的结构(我在外部也有一个机制,但需要一种调用该机制的方法)。
不幸的是,c#不允许我这样做。
我也不能使Mem成为一个类,因为我会将这些结构体的数组传递给非托管代码,并且我不想在传递它们之前逐个转换它们(只是pin和传递)。
有谁知道可以添加这种行为的变通方法(IL编织等)吗?我相信IL不会阻止我这样做,只有c#,对吗?
我很高兴回答关于框架和限制的任何问题,但我不是在寻找- "请更改您的设计"或"不要使用c#"答案,非常感谢。
我相信IL不会阻止我这样做,只有c#,对吗?
是的,这就是"this"是"一个结构的无参数构造函数"的地方。我之前在博客上写过。
然而,有一个无参数的构造函数并没有做你想要的,在每次复制一个结构时通知你。据我所知,基本上没有办法做到这一点。当你最终得到一个"默认"值时,构造函数甚至不是在所有情况下都被调用,即使是,它也肯定不是仅仅为了复制操作而被调用。
我知道你不想听到"请改变你的设计",但你只是要求一些在。net中不存在的东西。
我建议在值类型上使用某种方法,在采取适当的操作后返回一个新的副本。然后,您需要确保总是在正确的时间调用该方法。没有任何可以阻止您弄错,除了您可以构建的任何测试。
有谁知道可以添加这种行为的变通方法(IL编织等)吗?我相信IL不会阻止我这样做,只有c#,对吗?
这在某种程度上是正确的。c#阻止这种情况的原因是,在许多情况下,即使构造函数是在IL中定义的,也不会使用它。你的情况就是其中之一——如果你创建了一个结构体数组,构造函数将不会被调用,即使它们是在IL中定义的。
不幸的是,没有真正的解决方法,因为CLR不会调用构造函数,即使它们存在。
编辑:我在GitHub上托管了这个答案的工作:NOpenCL库。
根据您的意见,我决定以下是针对这里讨论的问题的适当的长期行动方案。显然,问题集中在托管代码中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);
不幸的是,p/Invoke层不支持封送SafeHandle
对象数组,所以我实现了一个名为SafeHandleArrayMarshaler
的ICustomMarshaler
来处理这个问题。请注意,当前实现不使用受限执行区域,因此封送处理期间的异步异常可能导致内存泄漏。
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);
}