提问人:PfunnyGuy 提问时间:11/1/2023 最后编辑:CharliefacePfunnyGuy 更新时间:11/2/2023 访问量:52
如何在双嵌套结构中封送数据
How to Marshal data in a double-nested structure
问:
以下代码已简化,但体现了常规功能。它目前只适用于 和 ,但我需要添加第三个结构。问题在于,第三个结构的大小可以因 而变化。我发现,当我单步执行代码时,传递到 C 库的数据仅包含我的 的初始化为零的值,而不是我分配给它的值。Struct1
Struct2
i
dataStr
"Hello World 0.0"
我一直无法找到一个很好的例子来说明如何做我需要做的事情。关于我的问题的一些想法/担忧/疑问:
- 我是否需要找到所有 ,并首先分配所有内存,然后再次循环以填充数据?
j
i
- 释放分配的内存的最佳位置在哪里?
s2.pStruct3
- 是否在正确的地方释放(遗留代码,我总是觉得可疑)?在某些情况下,> 1,在我看来,它只会释放最终分配的内存块,从而造成内存泄漏。
s1.pStruct2
i
- 我正在考虑保留所有已分配内存块的列表,然后在函数末尾循环该列表以以相反的分配顺序释放。这是最好的方法吗?
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 );
}
答:
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
无论如何,您现有的代码存在许多问题:
- 你当然不需要分配基地,你可以直接使用.
Struct1
ref
- 您无需将分配清零,将其复制并复制回来。您只需从普通的 C# 结构中复制一次。
- 释放内存需要在一个循环中进行,即你需要循环访问嵌套结构,释放你分配的所有指针。
- 释放需要在 中进行,以防发生异常,否则可能会泄漏内存。
finally
- 可以在 C 端分配内存,但编组和释放内存要困难得多。如果使用 PInvoking,则在 C# 中分配和释放要容易得多。
评论
0赞
PfunnyGuy
11/2/2023
您的解决方案中有什么?如果我尝试通过,那么我会得到一个错误,即“参数 2 可能不会使用'ref'关键字传递”。该结构必须作为指针传递,因为 C 函数就地修改了该结构。注意:这不是我的代码。我只需要更新它。IMO 一团糟。s1Ptr
ref s1
0赞
Charlieface
11/2/2023
是的,使用 .将 PInvoke 参数更改为 。编组员会将其复制出来并复制回来,您无需担心。你没有展示这一点,也没有展示 C 函数,所以真的帮不上更多忙。ref s1
ref Struct1 s1Ptr
0赞
PfunnyGuy
11/2/2023
PInvoke 参数让我迷失了 - 这是我需要在这个代码库中研究和找到的东西的焦点!
1赞
Charlieface
11/2/2023
查找 的声明,它应该具有属性。CallCFunction
[DllImport]
评论