在工作中,我们有一个本机C代码,负责读取和写入一个专有的平面文件数据库。我有一个用C#编写的包装器,它将P/Invoke调用封装到OO模型中。自项目启动以来,P/Invoke调用的托管包装器的复杂性大大增加。有趣的是,当前的包装程序做得很好,但是,我认为我实际上需要做更多的工作来确保正确的操作。
由答案提出的几个注意事项:
以下是修订后的守则的一个例子:
[DllImport(@"somedll", EntryPoint="ADD", CharSet=CharSet.Ansi,
ThrowOnUnmappableChar=true, BestFitMapping=false,
SetLastError=false)]
[ReliabilityContract(Consistency.MayCorruptProcess, Cer.None)]
internal static extern void ADD(
[In] ref Int32 id,
[In] [MarshalAs(UnmanagedType.LPStr)] string key,
[In] byte[] data, // formerly IntPtr
[In] [MarshalAs(UnmanagedType.LPArray, SizeConst=10)] Int32[] details,
[In] [MarshalAs(UnmanagedType.LPArray, SizeConst=2)] Int32[] status);
public void Add(FileId file, string key, TypedBuffer buffer)
{
// ...Arguments get checked
int[] status = new int[2] { 0, 0 };
int[] details = new int[10];
// ...Make the details array
lock (OPERATION_LOCK)
{
ADD(file.Id, key, buffer.GetBytes(), details, status);
// the byte[], details, and status should be auto
// pinned/keepalive'd
if ((status[0] != 0) || (status[1] != 0))
throw new OurDatabaseException(file, key, status);
// we no longer KeepAlive the data because it should be auto
// pinned we DO however KeepAlive our 'file' object since
// we're passing it the Id property which will not preserve
// a reference to 'file' the exception getting thrown
// kinda preserves it, but being explicit won't hurt us
GC.KeepAlive(file);
}
}我(修订的)问题如下:
编辑:我最近发现了一张引起我好奇心的图表。它基本上说明,一旦调用了P/Invoke方法,GC可以抢占您的本地代码。。因此,当本机调用可以同步执行时,GC可以选择运行并移动/删除我的内存。我想现在我想知道自动钉扎是否足够(或者它是否运行)。
发布于 2009-02-09 15:40:09
根据原来的问题编辑和评论:
图表简单地显示GC模式改变了,模式对固定对象没有影响。类型是在封送过程中固定或复制,这取决于类型。在本例中,您使用的是一个字节数组,它是医生说这是一种闪电式。您将看到,它还特别指出,“作为一种优化,只包含blittable成员的闪存类型和类的数组在封送过程中被固定而不是复制。”因此,这意味着数据被固定在调用期间,如果GC运行,则无法移动或释放数组。地位也是如此。
传递的字符串略有不同,字符串数据被复制,指针被传递到堆栈中。这种行为也使它不受收集和压缩的影响。GC不能触摸副本(它对副本一无所知),指针在堆栈上,GC不影响。
我还是不明白打电话给KeepAlive有什么意义。据推测,该文件无法进行收集,因为它被传递给该方法,并且有一些其他的根(声明它的位置)来保持它的活力。
发布于 2009-02-09 15:37:57
除非您的非托管代码直接操作内存,否则我不认为您需要插入对象。钉住本质上告诉GC,在收集周期的紧凑阶段,它不应该在内存中移动该对象。这对于非托管内存访问非常重要,因为非托管代码期望数据始终位于传入时相同的位置。GC操作的“模式”(并发或抢占)应该不会对固定对象产生影响,因为固定的行为规则在这两种模式中都适用。.NET中的编组基础结构试图在如何在托管/非托管代码之间封送数据方面变得聪明。在这种特殊情况下,您要创建的两个数组将在编组过程中自动固定。
除非您的非托管ADD方法是异步的,否则可能也不需要调用GC.KeepAlive。GC.KeepAlive只是为了防止GC回收它认为在长时间运行的操作中已死的对象。由于文件是作为参数传入的,因此在调用托管Add函数之后,可能会在代码的其他地方使用它,因此不需要GC.KeepAlive调用。
您编辑了代码示例并删除了对GCHandle.Alloc()和Free()的调用,这是否意味着代码不再使用这些?如果您仍然在使用它,那么您的锁(OPERATION_LOCK)块中的代码也应该包装在一个try/finally块中。在最后一个块中,您可能希望这样做:
if (dataHandle.IsAllocated)
{
dataHandle.Free();
}另外,您可能需要验证调用GCHandle.Alloc()不应该在您的锁内。通过将它放在锁的外面,您将有多个线程在分配内存。
至于自动固定,如果数据在编组过程中被自动固定,那么它将被固定,如果在运行非托管代码时发生GC收集周期,则不会移动数据。我不确定我完全理解您关于继续调用GC.KeepAlive的理由的代码评论。未加限制的代码实际上是否为file.Id字段设置了一个值?
发布于 2009-02-09 15:19:51
一个直接的问题似乎是,如果抛出异常,则永远不会调用dataHandle.Free(),从而导致泄漏。
https://stackoverflow.com/questions/528517
复制相似问题