如何在双嵌套结构中封送数据

How to Marshal data in a double-nested structure

提问人:PfunnyGuy 提问时间:11/1/2023 最后编辑:CharliefacePfunnyGuy 更新时间:11/2/2023 访问量:52

问:

以下代码已简化,但体现了常规功能。它目前只适用于 和 ,但我需要添加第三个结构。问题在于,第三个结构的大小可以因 而变化。我发现,当我单步执行代码时,传递到 C 库的数据仅包含我的 的初始化为零的值,而不是我分配给它的值。Struct1Struct2idataStr"Hello World 0.0"

我一直无法找到一个很好的例子来说明如何做我需要做的事情。关于我的问题的一些想法/担忧/疑问:

  • 我是否需要找到所有 ,并首先分配所有内存,然后再次循环以填充数据?ji
  • 释放分配的内存的最佳位置在哪里?s2.pStruct3
  • 是否在正确的地方释放(遗留代码,我总是觉得可疑)?在某些情况下,> 1,在我看来,它只会释放最终分配的内存块,从而造成内存泄漏。s1.pStruct2i
  • 我正在考虑保留所有已分配内存块的列表,然后在函数末尾循环该列表以以相反的分配顺序释放。这是最好的方法吗?

TIA!

[StructLayout( LayoutKind.Sequential, Pack = 1 )]
public struct Struct3 {
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 40)]
    public string dataStr;
}

[StructLayout( LayoutKind.Sequential, Pack = 1 )]
public struct Struct2 {
    public int    numStruct3;
    public IntPtr pStruct3;
}

[StructLayout( LayoutKind.Sequential, Pack = 1 )]
public struct Struct1 {
    public int    numStruct2;
    public IntPtr pStruct2;
}

public void MyFunc() {
    var sizeS1 = Marshal.SizeOf<Struct1>();
    var sizeS2 = Marshal.SizeOf<Struct2>();
    var sizeS3 = Marshal.SizeOf<Struct3>();

    var s1Ptr  = Marshal.AllocHGlobal( sizeS1 );
    var s1     = Marshal.PtrToStructure<Struct1>(s1Ptr);

    s1.numStruct2 = 1;
    s1.pStruct2   = Marshal.AllocHGlobal( sizeS2 * s1.numStruct2 );
    s1.pStruct2.Fill( 0, sizeS2 * s1.numStruct2 );

    IntPtr  s3Ptr;
    Struct3 s3;

    for( int i = 0; i < s1.numStruct2; ++i )
    {
        IntPtr s2Ptr = new IntPtr( s1.pStruct2.ToInt64() + ( i * sizeS2 ) );
        var    s2    = Marshal.PtrToStructure<Struct2>( s2Ptr );

        s2.numStruct3 = FindNumOfStruct2();
        s2.pStruct3   = Marshal.AllocHGlobal( s2.numStruct3 * sizeS3 );  // Doesn't get freed
        s2.pStruct3.Fill( 0, s2.numStruct3 * sizeS3 );

        for( uint j = 0; j < s2.numStruct3; ++j )
        {
            string data = String.Format( "Hello World #{0}.{1}", i, j );

            s3Ptr = new IntPtr( s2.pStruct3.ToInt64() + ( j * sizeS3 ) );
            s3    = Marshal.PtrToStructure<Struct3>( s3Ptr );

            s3.dataStr = data;
            Marshal.StructureToPtr<Struct3>( s3, s3Ptr, false );
        }

        Marshal.StructureToPtr<Struct2>( s2, s2Ptr, false );
    }

    Marshal.StructureToPtr<Struct1>( s1, s1Ptr, false );

    CallCFunction( CFacadeHandle, s1Ptr );

    Marshal.FreeHGlobal( s1.pStruct2 );
    Marshal.FreeHGlobal( s1Ptr );
}
C# pinvoke 封送处理

评论


答:

1赞 Charlieface 11/1/2023 #1

你想多了。封送处理程序可以自动将数组从 C# 封送到 C,甚至可以执行嵌套数组。只需在 C 语言中分配数组时手动封送它,并且在调用函数之前不知道数组的大小。

[StructLayout( LayoutKind.Sequential, Pack = 1 )]
public struct Struct3
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 40)]
    public string dataStr;
}

[StructLayout( LayoutKind.Sequential, Pack = 1 )]
public struct Struct2
{
    public int    numStruct3;
    public Struct3[] pStruct3;

    public Struct2(Struct3[] array)
    {
        pStruct3 = array;
        numStruct3 = array.Length;
    }
}

[StructLayout( LayoutKind.Sequential, Pack = 1 )]
public struct Struct1
{
    public int    numStruct2;
    public Struct2[] pStruct2;

    public Struct1(Struct2[] array)
    {
        pStruct2 = array;
        numStruct2 = array.Length;
    }
}

public void MyFunc()
{
    var length1 = 1
    var s1 = new Struct1(new Struct2[length1]);
    for (int i = 0; i < length2; ++i)
    {
        var length2 = FindNumOfStruct2();
        s1.pStruct2[i] = new Struct2(new Struct3[length2]);
        for (int j = 0; j < length2; ++j )
        {
            string data = $"Hello World #{i}.{j}";
            s1.pStruct2[i].pStruct3[j] = new Struct3 { dataStr = data };
        }
    }

    CallCFunction(CFacadeHandle, ref s1);
}

我不确定你为什么设置因为它是非标准的。我希望你已经检查过了。Pack = 1

无论如何,您现有的代码存在许多问题:

  • 你当然不需要分配基地,你可以直接使用.Struct1ref
  • 您无需将分配清零,将其复制并复制回来。您只需从普通的 C# 结构中复制一次。
  • 释放内存需要在一个循环中进行,即你需要循环访问嵌套结构,释放你分配的所有指针。
  • 释放需要在 中进行,以防发生异常,否则可能会泄漏内存。finally
  • 可以在 C 端分配内存,但编组和释放内存要困难得多。如果使用 PInvoking,则在 C# 中分配和释放要容易得多。

评论

0赞 PfunnyGuy 11/2/2023
您的解决方案中有什么?如果我尝试通过,那么我会得到一个错误,即“参数 2 可能不会使用'ref'关键字传递”。该结构必须作为指针传递,因为 C 函数就地修改了该结构。注意:这不是我的代码。我只需要更新它。IMO 一团糟。s1Ptrref s1
0赞 Charlieface 11/2/2023
是的,使用 .将 PInvoke 参数更改为 。编组员会将其复制出来并复制回来,您无需担心。你没有展示这一点,也没有展示 C 函数,所以真的帮不上更多忙。ref s1ref Struct1 s1Ptr
0赞 PfunnyGuy 11/2/2023
PInvoke 参数让我迷失了 - 这是我需要在这个代码库中研究和找到的东西的焦点!
1赞 Charlieface 11/2/2023
查找 的声明,它应该具有属性。CallCFunction[DllImport]