P / Invoke,Pinning和KeepAlive最佳实践

时间:2009-02-09 15:06:31

标签: c# .net interop pinvoke

在工作中,我们有一个本机C代码,负责读取和写入专有的平面文件数据库。我有一个用C#编写的包装器,它将P / Invoke调用封装到OO模型中。自项目启动以来,P / Invoke调用的托管包装器的复杂性大大增加。有趣的是,当前的包装器做得很好,但是,我认为我确实需要做更多工作以确保正确的操作。

答案提出了几点说明:

  1. 可能不需要KeepAlive
  2. 可能不需要GCHandle固定
  3. 如果您确实使用GCHandle,请尝试...最终该业务(CER问题未解决)
  4. 以下是修订代码的示例:

    [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);
        }
    }
    

    我的(修改过的)问题是:

    1. 数据,详细信息和状态是自动固定/ KeepAlive'd吗?
    2. 我是否错过了正确操作所需的其他任何内容?
    3. 编辑:我最近发现了一个图表,这引起了我的好奇心。它基本上表明,一旦你调用P / Invoke方法GC can preempt your native code。因此,虽然可以同步进行本机调用,但GC 可以选择运行并移动/删除我的内存。我想我现在想知道自动锁定是否足够(或者甚至是否运行)。

4 个答案:

答案 0 :(得分:2)

除非您的非托管代码直接操作内存,否则我认为您不需要固定该对象。固定基本上告知GC,在收集周期的紧凑阶段,它不应该在存储器中移动该对象。这对于非托管内存访问非常重要,其中非托管代码期望数据始终位于传入时的相同位置.GC操作的“模式”(并发或抢占)应该对固定没有影响作为固定行为规则的对象适用于任一模式。 .NET中的编组基础结构试图了解它如何在托管/非托管代码之间整理数据。在这种特定情况下,您正在创建的两个阵列将在编组过程中自动固定。

除非您的非托管ADD方法是异步的,否则可能不需要调用GC.KeepAlive。 GC.KeepAlive仅用于防止GC在长时间运行期间回收它认为已死的对象。由于文件作为参数传入,因此可能在调用托管Add函数后在代码的其他地方使用,因此不需要GC.KeepAlive调用。

您编辑了代码示例并删除了对GCHandle.Alloc()和Free()的调用,这是否意味着代码不再使用这些代码?如果您仍在使用它,则锁(OPERATION_LOCK)块中的代码也应该包含在try / finally块中。在你的finally块中,你可能想要做这样的事情:

if (dataHandle.IsAllocated)
{
   dataHandle.Free();
}

此外,您可能需要验证调用GCHandle.Alloc()不应该在您的锁内。通过让它锁定锁定,您将有多个线程在分配内存。

就自动固定而言,如果数据在编组过程中自动固定,则它会被固定,如果在非托管代码运行时发生了一个GC收集周期,它将不会被移动。我不确定我是否完全理解您关于继续调用GC.KeepAlive的原因的代码注释。 unamanged代码是否实际为file.Id字段设置了值?

答案 1 :(得分:1)

  1. 我不确定你的KeepAlive是什么意思,因为你已经释放了GCHandle - 那时似乎不再需要这些数据了吗?
  2. 与#1类似,为什么你觉得你需要打电话给KeepAlive?您发布的代码之外的东西是不是我们没有看到的东西?
  3. 可能不是。如果这是一个同步P / Invoke,则编组程序将实际固定传入的变量,直到它返回。事实上,您可能不需要固定数据(除非这是异步,但您的构造表明它不是)。
  4. 不,没有错过。我认为你实际上增加了超出你需要的数量。
  5. 编辑回复原始问题编辑和评论:

    该图表只显示GC 模式发生变化,该模式对固定对象没有影响。类型可以是pinned or copied during marshaling,具体取决于类型。在这种情况下,您使用的是docs say is a blittable type字节数组。您将看到它还明确指出“作为优化,仅包含blittable成员的blittable类型和类的数组被固定而不是在编组期间被复制。”这意味着数据在调用期间被固定,如果GC运行,则无法移动或释放数组。状态也是如此。

    传递的字符串略有不同,字符串数据被复制,指针在堆栈上传递。这种行为也使它不受收集和压缩的影响。 GC无法触摸副本(它对它一无所知)并且指针在堆栈上,GC不会影响。

    我仍然没有看到调用KeepAlive的重点。据推测,该文件不可用于收集,因为它已传入方法并且有一些其他根(声明它的地方)可以使它保持活动状态。

答案 2 :(得分:0)

一个直接的问题似乎是,如果你抛出异常,就永远不会调用dataHandle.Free(),导致泄漏。

答案 3 :(得分:0)