0

我正在尝试在非托管 dll 中存储和检索一些数据。我试图通过尽可能简化结构来缩小我的问题范围,这就是我要解决的问题:

结构定义

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public class MyStruct
{
  private UInt32 size;    
  public UInt16 SomeData;

  public MyStruct()
  {
    size = (UInt32)Marshal.SizeOf(this);
    this.SomeData = 66; //just put any non 0 value for test
  }
}

DLL 导入:

[DllImport(MY_DLL, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
[return:MarshalAs(UnmanagedType.U1)]
public static extern bool SetData(ref MyStruct ms);
[DllImport(MY_DLL, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern IntPtr GetData();

函数调用:

MyStruct ms_in = new MyStruct();
bool b = Wrapper.SetData(ref ms_in);
IntPtr ptr = Wrapper.GetData();
MyStruct ms_out = (MyStruct)Marshal.PtrToStructure(ptr, typeof(MyStruct));

我猜很简单。我知道字符集和打包是可以的,因为我只是将另一个结构定义中的结构布局属性粘贴到与我实际上对大多数代码所做的相同的 dll 中。

当读取 ms_out 的内容时,它充满了垃圾(随机大数)。

我终于通过反复试验找到了我的问题的答案,但我不太明白。这是工作版本:

[DllImport(MY_DLL, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
[return:MarshalAs(UnmanagedType.I1)]
public static extern bool SetData( [In, MarshalAs(UnmanagedType.LPStruct)] MyStruct ms);

用 [In, MarshalAs(UnmanagedType.LPStruct)] 替换 ref 成功了,但为什么呢?

感谢您的回答,祝您编码愉快。

4

2 回答 2

0

现在为什么从非托管 dll 返回时结构中充满了垃圾?

当我在非托管 dll 上设置数据然后将其取回时会发生这种情况,但当我在本地创建非托管指针、设置数据并将其读回时不会发生这种情况:

MyClass x = new MyClass(); //create class
IntPtr ptr2 = Marshal.AllocHGlobal(Marshal.SizeOf(x)); //allocate unmanaged memory
Marshal.StructureToPtr(x, ptr2, false); //marshall to unmanaged memory
MyClass xOut = (MyClass)Marshal.PtrToStructure(ptr2, typeof(MyClass)); //marshall from unmanaged memory
Marshal.FreeHGlobal(ptr2); //free unmanaged memory

如果您的数据“通过”上述测试,那么所有 StructLayout、字符集、编组等都可以。就我而言,它是。

如果您只是在 c# 中创建数据并将指向它的指针传递给非托管代码,您只能确定指针地址是有效的,只要 c# 变量超出范围,它指向的数据就会无效。

以下方法适用于我测试的任何类型的数据,包括字符串:

[return:MarshalAs(UnmanagedType.U1)]
public static extern bool SetData( IntPtr data); //no marshalling in, no ref, no problem...

MyClass x = new MyClass(); //create class
//...store some data in x....
IntPtr ptrIn = Marshal.AllocHGlobal(Marshal.SizeOf(x)); //allocate unmanaged memory
Marshal.StructureToPtr(x, ptrIn, false);    //marshall to unmanaged memory

bool b = Wrapper.SetData(ref ms_in); //store data in unmanaged dll
IntPtr ptrOut = Wrapper.GetData(); //get data back from unmanaged dll

MyClass xOut = (MyClass)Marshal.PtrToStructure(ptrOut, typeof(MyClass)); //marshall from unmanaged memory

Marshal.FreeHGlobal(ptrIn); //free unmanaged memory

这里的问题是调用者必须释放分配的内存。在我的场景中,这用于将数据传递给另一个非托管 dll(不要问...),但调用程序无法确保最终接收者实际读取了数据。

到底谁来负责清理内存?

我可能会与被调用者一起清理数据,调用者在创建新块之前检查数据是否已释放/清理,除非我发现场景更简单一些。

于 2012-11-19T11:40:46.743 回答
0

进一步研究这一点,我发现编组可以与 c# 结构或类一起用作目标。

使用结构:

[StructLayout...
struct MyStruct
{
  //some properties

  //can't have parameterless constructor
  public void MakeStruct()
  {
    size = ...;
    //initialize properties as needed
  }
}   
...
public static extern bool SetData(ref MyStruct ms); //ref is ok for struct, equivalent to c &struct

使用一个类:

[StructLayout...
class MyClass
{
  //some properties

  //parameterless constructor
  public void MyClass()
  {
    size = ...;
    //initialize properties as needed
  }
}

...
public static extern bool SetData([In, MarshalAs(UnmanagedType.LPStruct)]  MyClass ms); //no ref for class

使用类的优点是在构造函数中自动设置大小,而不是调用者必须使用结构版本显式设置它。

于 2012-11-19T11:03:26.990 回答