首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >P/Invoke、Pinning和KeepAlive最佳实践

P/Invoke、Pinning和KeepAlive最佳实践
EN

Stack Overflow用户
提问于 2009-02-09 15:06:31
回答 4查看 3.7K关注 0票数 4

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

由答案提出的几个注意事项:

  1. 可能不需要KeepAlive
  2. 可能不需要GCHandle钉扎
  3. 如果您确实使用了GCHandle,那么try...finally这个业务(CER的问题没有解决)

以下是修订后的守则的一个例子:

代码语言:javascript
复制
[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. 数据、细节和状态会自动固定/保存吗?
  2. 我是否错过了正确操作所需的其他任何东西?

编辑:我最近发现了一张引起我好奇心的图表。它基本上说明,一旦调用了P/Invoke方法,GC可以抢占您的本地代码。。因此,当本机调用可以同步执行时,GC可以选择运行并移动/删除我的内存。我想现在我想知道自动钉扎是否足够(或者它是否运行)。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2009-02-09 15:40:09

  1. 我不知道您的KeepAlive的意义是什么,因为您已经释放了GCHandle --此时似乎不再需要数据了?
  2. 与#1类似,您为什么觉得需要调用KeepAlive呢?是不是在你发布的代码之外我们没有看到什么?
  3. 可能不会。如果这是一个同步的P/Invoke,那么编组程序实际上会将传入的变量固定在一起,直到它返回为止。实际上,您可能也不需要插入数据(除非这是异步的,但您的构造表明它不是)。
  4. 不,什么都没错过。我觉得你实际加的比你需要的多。

根据原来的问题编辑和评论:

图表简单地显示GC模式改变了,模式对固定对象没有影响。类型是在封送过程中固定或复制,这取决于类型。在本例中,您使用的是一个字节数组,它是医生说这是一种闪电式。您将看到,它还特别指出,“作为一种优化,只包含blittable成员的闪存类型和类的数组在封送过程中被固定而不是复制。”因此,这意味着数据被固定在调用期间,如果GC运行,则无法移动或释放数组。地位也是如此。

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

我还是不明白打电话给KeepAlive有什么意义。据推测,该文件无法进行收集,因为它被传递给该方法,并且有一些其他的根(声明它的位置)来保持它的活力。

票数 1
EN

Stack Overflow用户

发布于 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块中。在最后一个块中,您可能希望这样做:

代码语言:javascript
复制
if (dataHandle.IsAllocated)
{
   dataHandle.Free();
}

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

至于自动固定,如果数据在编组过程中被自动固定,那么它将被固定,如果在运行非托管代码时发生GC收集周期,则不会移动数据。我不确定我完全理解您关于继续调用GC.KeepAlive的理由的代码评论。未加限制的代码实际上是否为file.Id字段设置了一个值?

票数 2
EN

Stack Overflow用户

发布于 2009-02-09 15:19:51

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

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/528517

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档