在传递给非托管代码之前固定updateble结构?

时间:2009-12-05 00:11:10

标签: c# .net struct unmanaged pinning

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

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

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

GCHandle只能引用引用,因此结构必须加框以使用GCHandle。我尝试过,它的确有效。它的主要问题是您无法从托管代码更新结构。要更新结构,首先我们需要将其取消装箱,然后更新,然后重新装箱,但是......哎呀......再次装箱?!?这意味着内存中的前一个指针仍然指向旧的非最新结构,而新结构有另一个指针,这意味着我需要将新指针传递给非托管代码...不适用于我的情况下。

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

感谢。

修改

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

6 个答案:

答案 0 :(得分:6)

在这种情况下使用固定内存不是一个好主意,因为结构的内存需要长时间有效。 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;
  }
}

答案 1 :(得分:3)

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

// 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标志,这会导致分配的内存被锁定到位。此外,分配的内存不是零填充。

答案 2 :(得分:1)

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

答案 3 :(得分:0)

结构示例:

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

答案 4 :(得分:0)

让结构包含ActOnMe()接口和方法如下:

delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2);
interface IActOnMe<TT> {ActOnMe<T>(ActByRef<TT,T> proc, ref T param);}
struct SuperThing : IActOnMe<SuperThing>
{
  int this;
  int that;
  ...
  void ActOnMe<T>(ActByRef<SuperThing,T>, ref T param)
  {
    proc(ref this, ref param);
  }
}

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

答案 5 :(得分:0)

回答您的编辑:

只是想...有没有办法固定包含该结构的父对象,然后获取 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