【问题标题】:Pinning an updateble struct before passing to unmanaged code?在传递给非托管代码之前固定一个可更新结构?
【发布时间】:2010-12-23 10:58:17
【问题描述】:

我使用一些旧 API,需要将结构的指针传递给异步运行的非托管代码。

换句话说,在我将结构指针传递给非托管代码后,非托管代码复制指针并立即返回。非托管代码可以在后台,在另一个线程中访问该结构。 我无法控制在另一个线程中运行的非托管代码,也无法控制线程本身。

固定的 { } 语句不能用于固定,因为它不是为异步非托管固定而设计的。

GCHandle 只能固定引用,因此必须将结构装箱才能使用 GCHandle。我试过了,它有效。它的主要问题是您无法从托管代码更新结构。要更新一个结构,首先我们需要将它拆箱,然后更新,然后再装箱,但是……哎呀……又装箱了?!?这意味着内存中的前一个指针仍然指向旧的非最新结构,而新结构有另一个指针,这意味着我需要将新指针传递给非托管代码......不适用于我的案例。

如何在没有固定 { } 语句的情况下将结构固定在内存中,以便我可以从托管代码更新它而不更改它的指针?

谢谢。

编辑:

只是想...有没有办法固定包含结构的父对象,然后获取 struct 的指针而不是容器对象?

【问题讨论】:

  • 我原以为 GCHandle 是这里的解决方案。如果一切都失败了,你可以在非托管代码中分配内存,所以它不会被 GC 移动。
  • 好问题。我想我已经在 CLI/C++ 中做到了,我需要查看我的工作笔记。不过,不确定是否有 C# 等效机制。
  • 也许在您自己的后台线程中进行调用,使用固定的 {} 并且永远不要离开固定的 {} 块(至少只要非托管代码可以访问该结构)?
  • 不,您只需要自己编组数据,而不是让 GC 使用它。
  • @DxCK,您能否澄清一下“如何在没有固定 {} 语句的情况下将结构固定在内存中,以便我可以在不更改指针的情况下更新它?”...它是托管代码,还是您希望在固定时更新内存的非托管代码?

标签: c# .net struct unmanaged pinning


【解决方案1】:

在这种情况下使用固定内存不是一个好主意,因为结构的内存需要长时间有效。 GCHandle.Alloc() 会将结构装箱并将其存储在堆上。随着它被固定,这将是垃圾收集器的长期负担,因为它需要不断地在路上的岩石周围寻找出路。

简单的解决方案是在非托管内存中为结构分配内存。使用 Marshal.SizeOf() 获取结构的大小,使用 Marshal.AllocCoTaskMem() 分配内存。这为您提供了需要传递给非托管代码的指针。使用 Marshal.StructureToPtr() 初始化内存。并使用 PtrToStructure() 读取由非托管代码编写的结构的更新。

如果您经常这样做,您将不断地复制结构。这可能很昂贵,具体取决于结构的大小。为避免这种情况,请使用不安全指针直接访问非托管内存。一些基本语法:

using System;
using System.Runtime.InteropServices;

class Program {
  unsafe static void Main(string[] args) {
    int len = Marshal.SizeOf(typeof(Test));
    IntPtr mem = Marshal.AllocCoTaskMem(len);
    Test* ptr = (Test*)mem;
    ptr->member1 = 42;
    // call method
    //..
    int value = ptr->member1;
    Marshal.FreeCoTaskMem(mem);
  }
  public struct Test {
    public int member1;
  }
}

【讨论】:

  • 使用所有这些方法有什么区别:Marshal.AllocCoTaskMem 和 Marshal.FreeCoTaskMem VS Marshal.AllocHGlobal 和 Marshal.FreeHGlobal,sizeof VS Marshal.SizeOf 以及我应该在我的案例,为什么? tnx.
  • 我无法在评论框中填写答案。你为什么不用这个问题开始一个新线程?
【解决方案2】:

不安全的代码是一种选择吗?

// allocate unmanaged memory
Foo* foo = (Foo*)Marshal.AllocHGlobal(sizeof(Foo));

// initialize struct
foo->bar = 0;

// invoke unmanaged function which remembers foo
UnsafeNativeMethods.Bar(foo);
Console.WriteLine(foo->bar);

// update struct
foo->bar = 10;

// invoke unmanaged function which uses remembered foo
UnsafeNativeMethods.Qux();
Console.WriteLine(foo->bar);

// free unmanaged memory
Marshal.FreeHGlobal((IntPtr)foo);

这会编译并且不会抛出异常,但我手头没有非托管函数来测试它是否有效。

来自MSDN

当 AllocHGlobal 调用 LocalAlloc 时,它会传递一个 LMEM_FIXED 标志,这会导致分配的内存被锁定到位。此外,分配的内存不是零填充的。

【讨论】:

  • 谢谢,工作就像一个魅力!实际上,我没有使用 AllocHGlobal 和 FreeHGlobal,只是获取了 GCHandle 给我的 IntPtr 并通过不安全的代码更新了结构。请原谅我仍然没有将其标记为答案,因为我想等待更多可能的答案,也许没有不安全的代码会有更好的解决方案。
【解决方案3】:

您需要使用Marshal.StructureToPtrMarshal.PtrToStructure 将结构编组到可用于本机代码的内存中,而不是固定。

【讨论】:

  • 看来他想在将结构传递给非托管代码后从 C# 更新结构...
  • “StructureToPtr 将结构的内容复制到预先分配的内存块中......”这并不是 OP 所要求的。他希望允许本机代码直接操作项目内存。我认为这是出于性能原因......也许我错了。
  • 你不能。您必须将它复制到一个编组分配的内存块,在 C++ 中更新它,然后将它复制回堆栈。否则没有直接的方法可以做到这一点。如果您不想这样做,最好的选择是使用 C++/CLI。
  • +1 Reed 就在这里。请参阅我的答案以获取示例,或查看 Reed 上面的任一链接的示例部分
【解决方案4】:

结构示例:

[StructLayout(LayoutKind.Sequential)]
public struct OVERLAPPED_STRUCT
{
   public IntPtr InternalLow;
   public IntPtr InternalHigh;
   public Int32 OffsetLow;
   public Int32 OffsetHigh;
   public IntPtr EventHandle;
}

如何将其固定到结构并使用它:

OVERLAPPED_STRUCT over_lapped = new OVERLAPPED_STRUCT();
// edit struct in managed code
over_lapped.OffsetLow = 100;
IntPtr pinned_overlap_struct = Marshal.AllocHGlobal(Marshal.SizeOf(over_lapped));
Marshal.StructureToPtr(over_lapped, pinned_overlap_struct, true);

// Pass pinned_overlap_struct to your unmanaged code
// pinned_overlap_struct changes ...

// Get resulting new struct
OVERLAPPED_STRUCT nat_ov = (OVERLAPPED_STRUCT)Marshal.PtrToStructure(pinned_overlap_struct, typeof(OVERLAPPED_STRUCT));
// See what new value is
int offset_low = nat_ov.OffsetLow;
// Clean up
Marshal.FreeHGlobal(pinned_overlap_struct);

【讨论】:

    【解决方案5】:

    让结构包含一个ActOnMe() 接口和方法怎么样:

    委托 void ActByRef(ref T1 p1, ref T2 p2); 接口 IActOnMe {ActOnMe(ActByRef proc, ref T param);} 结构 SuperThing : IActOnMe { 诠释这个; 诠释那个; ... void ActOnMe(ActByRef, ref T 参数) { 过程(参考这个,参考参数); } }

    由于委托通过引用获取泛型参数,因此在大多数情况下,通过将委托传递给静态方法以及对结构的引用以将数据传入或传出该方法,应该可以避免创建闭包的开销.此外,将SuperThing 的已装箱实例转换为IActOnMe<SuperThing> 并在其上调用ActOnMe<T> 将公开该装箱实例 的字段以进行更新,而不是创建它们的另一个副本作为将发生在结构的类型转换中。

    【讨论】:

      【解决方案6】:

      回答您的编辑:

      只是想...有没有办法固定包含结构的父对象,然后获取 struct 的指针而不是容器对象?

      我想是的。如果有的话,您应该能够使用托管结构数组(可能是一个数组)。

      这是一个示例代码:

          [StructLayout(LayoutKind.Sequential)]
          struct SomeStructure
          {
              public int first;
              public int second;
              public SomeStructure(int first, int second) { this.first=first; this.second=second; }
          }
          
          /// <summary>
          /// For this question on Stack Overflow:
          /// https://stackoverflow.com/questions/1850488/pinning-an-updateble-struct-before-passing-to-unmanaged-code
          /// </summary>
          private static void TestModifiableStructure()
          {
              SomeStructure[] objArray = new SomeStructure[1];
              objArray[0] = new SomeStructure(10, 10);
              
              GCHandle hPinned = GCHandle.Alloc(objArray, GCHandleType.Pinned);
      
              //Modify the pinned structure, just to show we can
              objArray[0].second = 42;
      
              Console.WriteLine("Before unmanaged incrementing: {0}", objArray[0].second);
              PlaceholderForUnmanagedFunction(hPinned.AddrOfPinnedObject());
              Console.WriteLine("Before unmanaged incrementing: {0}", objArray[0].second);
              
              //Cleanup
              hPinned.Free();
          }
          
          //Simulates an unmanaged function that accesses ptr->second
          private static void PlaceholderForUnmanagedFunction(IntPtr ptr)
          {
              int secondInteger = Marshal.ReadInt32(ptr, 4);
              secondInteger++;
              Marshal.WriteInt32(ptr, 4, secondInteger);
          }
      

      及其输出:

      Before unmanaged incrementing: 42
      Before unmanaged incrementing: 43
      

      【讨论】:

      • 等等,这个问题已经有十年了?我怎么看出来的?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-27
      • 1970-01-01
      • 1970-01-01
      • 2016-06-01
      相关资源
      最近更新 更多