使用非托管DLL中的非托管结构而不在C#中复制

本文关键字:复制 结构 DLL | 更新日期: 2023-09-27 18:25:10

我有一个用非托管语言编写的DLL,它返回一个指向C结构的指针。C#程序必须在结构中填充一些细节。接下来,必须将相同的指针(而不是副本)提供给同一DLL中的另一个方法现在,C#程序从C结构中收集数据。

数据类型:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1, Size = 18 * 2 + 24 * 256)]
public class Context {
    public UInt16 Magic;
    public UInt16 Method;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2*16)]
    public UInt16[] Status;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8*256)]
    public Field[]  InputFields;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8*256)]
    public Field[]  OutputFields;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8*256)]
    public Field[]  MetaData;
}
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode,Pack=1,Size=256)]
public class Field {
    public UInt16 Kind;
    public UInt16 Status;
    public UInt16 Length;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 125)]
    public string Data;
}

方法:

[DllImport("x.dll")]
//[return: MarshalAs(UnmanagedType.LPStruct)]
public static extern IntPtr  CreateContext    ( UInt16 ContextKind );
[DllImport("x.dll")]
public static extern UInt16  DestroyContext   ( IntPtr Context );
[DllImport("x.dll")]
public static extern UInt16  Execute          ( [In, MarshalAs(UnmanagedType.LPStruct)] Context Context );

如何填充/读取由C#程序中的DLL(而不是C#)管理的内存?

我试过了:

  • 使用

    [return: MarshalAs(UnmanagedType.LPStruct)]
    

    而不是CCD_ 1。但是内存需要由C#来管理,而不是由它来管理。

  • 使用IntPtrMarshal.PtrToStructure,但它试图将内存复制到另一个位置:

    IntPtr C = CreateContext(1);
    if (C == null) return;
    Context Ctx = (Context)Marshal.PtrToStructure(C, typeof( Context ) );
    Ctx.Method = 2;
    

(在具有ExecutionEngineExceptionPtrToStructure调用中失败)。

使用非托管DLL中的非托管结构而不在C#中复制

尝试修复您的声明-例如,删除Size参数(但不删除SizeConst),并确保C代码实际上提供了内联的、byval的值数组,这些数组中的条目数量正好合适(这是UnmanagedType.ByValArray, SizeConst的要求)。例如,这对我有效:

// Init array fields (required; supposed to happen on the C side)
ctx.Status = new UInt16[2 * 16];
ctx.InputFields = new Field[8 * 256];
ctx.OutputFields = new Field[8 * 256];
ctx.MetaData = new Field[8 * 256];
// Test data to survive the roundtrip (also supposed to happen on the C side)
ctx.Method = 42;
ctx.InputFields[42] = new Field() { Data = "Hi." };
ctx.OutputFields[42] = new Field() { Data = "Also hi." };

IntPtr buf = Marshal.AllocCoTaskMem(1024 * 100);
Marshal.StructureToPtr(ctx, buf, false);
var ctx_new = new Context();
Marshal.PtrToStructure(buf, ctx_new);
Marshal.FreeCoTaskMem(buf);

Console.WriteLine(ctx_new.Method);
Console.WriteLine(ctx_new.InputFields[42].Data);
Console.WriteLine(ctx_new.OutputFields[42].Data);

如果做不到,请尝试使用Marshal.Write*方法直接写入内存,如果IntPtr0对您不起作用。然后,您可以创建一个包装器类,该类将保存IntPtr,并提供触发Marshal.Write的属性,例如

public UInt16 Status {
    get { return unchecked((ushort)Marshal.ReadInt16(m_Ptr, 2)); }
    set { Marshal.WriteInt16(m_Ptr, 2, unchecked((short)value)); }
}