ValueType到IntPtr在应用程序生命周期中使用

时间:2011-06-03 23:34:28

标签: c# pointers interop

假设我们有一个本地库,可以使用这样的数据:

 double *pointer = &globalValue; 
 SetAddress(pointer);

 //Then we can change value and write it to disk
 globalValue = 5.0;

 FlushValues(); // this function writes all values 
                // registered by SetAddress(..) functions

 ... //Then we change our value (or values)
 FlushValues(); //and do write any time we need

现在我必须将它与C#进行互操作。我想避免使用不安全......但......我不知道=)

所以在C#方面,我们将会有一些我们将编写的类。我们可以这样写:

 public class Data
 {
     [Serializable] <-- somehow we mark what we are going to write
     double myValue;

     ...

     [Serializable]
     double myValueN;
 }

 public class LibraryInterop
 {
      IntPtr[] pointers; //Pointers that will go to SetAddressMethod
      ...

      public void RegisterObject(Data data)
      {
          ... //With reflection we look for [Serializable] values
              //create a pointer for each and some kind of BindingInfo 
              //that knows connection of pointers[i] to data.myValueN

          //Then add this pointers to C++ library
          foreach(IntPtr pointer in pointers) { SetAddress(pointer);}
      }

      public void Flush() 
      {
          //Loop through all added pointers
          for(int i=0; i<pointers.Length; i++)
          {
              double value = ... //With reflections and BindingInfo we find data.myValueN
                                 // that corresponds to pointers[i]

              // then we write this value to memory of IntPtr
              // we have to brake value to bytes and write it one by one to memory
              // we could use BitConverter.DoubleToInt64Bits() but double - is just 
              // an example, so in general case we will use GetBytes
              int offset = 0;
              foreach(byte b in BitConverter.GetBytes(value))
              {
                  Marshal.WriteByte(pointer[i],offset,byte);
                  offset++;
              }
          }

          //Save values of IntPtr to disk
          FlushValues();
      }
  }

然后用户代码如下所示

  var library = new LibraryInterop();
  var data1 = new Data();
  var data2 = new AnotherData();
  library.RegisterObject(data1);
  library.RegisterObject(data2);

  ...//change data
  library.Flush();
  ...//change data
  library.Flush();
  ...//change data
  library.Flush();

所以在C ++中我们有一个非常简洁的结构。我们有指针,我们填充这些指针后面的数据,在FlushValues上,所有这些值都被写入。

C#部分不能有SetAddress(ref double value)。由于地址可能会发生变化,我们必须针对指针 - 使用unsafe + fixed或IntPtr,并且 SO 很多人头痛。

另一方面,我们可以通过装箱data_myValue到Object来获得“托管指针”。因此,如果有可能以某种方式将此ValueType data.myValue绑定到IntPtr而没有这种应对和反射 - 它将更加整洁。

是否可以进行此互操作,并且比我在此处列出的那个部分具有更少的丑陋和缓慢的C#部分?

1 个答案:

答案 0 :(得分:2)

(这有一些主要的缺点......)

您可以使用GCHandleAlloc(data1,GCHandleType.Pinned)“固定”一个对象,然后从GCHandle.AddrOfPinnedObject获取IntPtr。如果您执行此操作,则可以将此IntPtr传递给您的本机代码,该代码应按预期工作。

但是,这会在破坏垃圾收集器的效率方面造成很多问题。如果您决定做这样的事情,我建议您在程序中尽早分配所有对象(可能在调用GC.Collect()之后立即分配,这是我通常不建议的事情),并且离开在你的计划的一生中,他们一个人。这会(稍微)缓解GC问题,因为它会在早期将它们分配到“最佳”可能的位置,并留在它们只会在Gen2集合中触及的位置。