假设我们有一个本地库,可以使用这样的数据:
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#部分?
答案 0 :(得分:2)
(这有一些主要的缺点......)
您可以使用GCHandle。Alloc(data1,GCHandleType.Pinned)“固定”一个对象,然后从GCHandle.AddrOfPinnedObject获取IntPtr
。如果您执行此操作,则可以将此IntPtr
传递给您的本机代码,该代码应按预期工作。
但是,这会在破坏垃圾收集器的效率方面造成很多问题。如果您决定做这样的事情,我建议您在程序中尽早分配所有对象(可能在调用GC.Collect()
之后立即分配,这是我通常不建议的事情),并且离开在你的计划的一生中,他们一个人。这会(稍微)缓解GC问题,因为它会在早期将它们分配到“最佳”可能的位置,并留在它们只会在Gen2集合中触及的位置。