c# - P/Invoke、Pinning 和 KeepAlive 最佳实践

标签 c# .net interop pinvoke

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

答案提出的一些注意事项:

  1. 可能不需要 KeepAlive
  2. 可能不需要 GCHandle 固定
  3. 如果您确实使用了 GCHandle,请尝试...最后尝试该业务(尽管未解决 CER 问题)

这是修改后的代码示例:

[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?
  2. 我是否遗漏了正常运行所需的任何其他内容?

编辑:我最近发现了一张图表,它激发了我的好奇心。它基本上指出,一旦您调用 P/Invoke 方法,GC can preempt your native code .因此,虽然 native 调用可能是同步进行的,但 GC 可以选择运行并移动/删除我的内存。我想现在我想知道自动固定是否足够(或者它是否运行)。

最佳答案

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

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

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

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

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

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

关于c# - P/Invoke、Pinning 和 KeepAlive 最佳实践,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/528517/

相关文章:

.Net Winform 图表库

c# - 从 C# 调用 C++ 时如何最好地处理未使用的指针?

c# - 根据值操作 div

c# - 为什么枚举参数不能接受重载方法中的 int 值(>0)

.net - NHibernate Linq 查询比 HQL 慢 3 倍

.net - 考虑使用NHapi

c++ - 从 IIS 7.5 Web 应用程序调用 Win32 CreateEvent() 失败

c# - 将 vector/数组从非托管 C++ 传递到 C#

c# - 如何创建自定义 .NET 基类库 (BCL) aka mscorlib 替代品?

c# - 我应该为 OnPropertyChanged 和 BooleanToVisibilityConverter 编写代码吗?