【问题标题】:Calling from C# to C function which accept a struct array allocated by caller从 C# 调用 C 函数,该函数接受调用者分配的结构数组
【发布时间】:2011-06-02 10:23:03
【问题描述】:

我有以下 C 结构

struct XYZ
{
void            *a;
char            fn[MAX_FN];     
unsigned long   l;          
unsigned long   o;  
};

我想从 C# 中调用以下函数:

extern "C"  int     func(int handle, int *numEntries, XYZ *xyzTbl);

其中 xyzTbl 是由调用者分配的大小为 numEntires 的 XYZ 数组

我已经定义了以下 C# 结构:

[StructLayoutAttribute(Sequential, CharSet = CharSet.Ansi)]
public struct XYZ
{
   public System.IntPtr rva;
   [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 128)]
   public string fn;
   public uint l;
   public uint o;
}

还有一个方法:

 [DllImport(@"xyzdll.dll", CallingConvention = CallingConvention.Cdecl)]
 public static extern Int32 func(Int32 handle, ref Int32 numntries,
     [MarshalAs(UnmanagedType.LPArray)] XYZ[] arr);

然后我尝试调用该函数:

XYZ xyz = new XYZ[numEntries];
for (...) xyz[i] = new XYZ();
func(handle,numEntries,xyz);

当然不行。有人可以阐明我做错了什么吗?

【问题讨论】:

  • 顺便问一下,错误是什么?
  • 调用func时,第二个参数前面需要一个ref,因为它在函数声明中是ref

标签: c# pinvoke marshalling


【解决方案1】:
[StructLayoutAttribute(Sequential, CharSet = CharSet.Ansi)]
public struct XYZ
{
   public System.IntPtr rva;
   [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 128)]
   public string fn;
   public uint l;
   public uint o;
}

那些uint不应该是ulong吗?另外,MAX_FN 是 128 对吧?

XYZ xyz = new XYZ[numEntries];
for (...) xyz[i] = new XYZ(); 

XYZ是值类型(struct),所以这里的第二行是多余的(structs总是被初始化)

 [DllImport(@"xyzdll.dll", CallingConvention = CallingConvention.Cdecl)]
 public static extern Int32 func(Int32 handle, ref Int32 numntries,
 [MarshalAs(UnmanagedType.LPArray)] XYZ[] arr);

[MarshalAs(UnmanagedType.LPArray)] 是多余的,编译器会看到它是一个结构体数组。

【讨论】:

    【解决方案2】:

    检查这个:Marshal C++ struct array into C#,可能会有帮助。

    【讨论】:

    • 感谢 Nickolay,您所说的帖子与众不同。我需要在 C# 中分配结构数组....
    • 是的,实际上。我还找到了msdn.microsoft.com/en-us/library/…,MSDN 告诉你应该(可能)也传递 SizeConst 参数。
    【解决方案3】:

    我会手动编组这个。首先,将 xyzTbl 设为 IntPtr

    [DllImport(@"xyzdll.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern Int32 func(Int32 handle, ref Int32 numntries, IntPtr xyzTb);
    

    而不是像您正在做的那样分配 XYZ 数组 - 分配足够的非托管内存来存储表。

    IntPtr unmanaged = 
        Marshal.AllocHGlobal(Marshal.SizeOf(typeof(XYZ)) * numEntries);
    

    打电话给你的func(handle, ref numEntries, unmanaged); 然后工作就是将非托管内存解组回托管类型。

    IntPtr[] entries = new IntPtr[numEntries];
    List<XYZ> xyz = new List<XYZ>();
    Marshal.Copy(unmanaged, entries, 0, numEntries);
    foreach (IntPtr entry in entries)
        xyz.Add(Marshal.PtrToStructure(entry, typeof(XYZ)));
    
    Marsha.FreeHGlobal(unmanaged);
    

    【讨论】:

    • 这种手动编组只有在非托管代码保持对传入的内存的引用超出被调用函数返回点时才需要。
    • 而且你的终结代码也不是很健壮。但很可能这只在少数应用程序中很重要。通常是那些需要干净卸载 AppDomain 的。
    【解决方案4】:

    我不相信当你有一个托管结构时你可以使用LPArray。只需将其取出,然后改用 [In][Out](如果需要)。

    IIRC,如果你使用LPArray,它会尝试传递一个指向第一个元素的指针,这是非法的,因为该结构不是 blittable。您需要完全删除 [MarshalAs(...)]


    编辑:

    我不记得了,但您可能需要在传递字符串字段之前对其进行初始化...有机会我会检查一下。

    【讨论】:

    • 您好,我尝试了您的建议,但它不起作用。我认为编组是必需的,因为 C# 和 C++ 表示数组的方式不同。我可以将 C# 数组创建为连续的结构块吗?
    • @lifey: 嗯...你得到的错误是什么?知道这一点真的很有帮助。是的,编组确实需要发生,只是我认为您没有将其明确指定为LPArray
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多