C# (Mono) 测试 C 应用程序 (Linux) 意外修改属性
C# (Mono) test for C application (Linux) modifies attribute unexpectedly
我正在将一个 .dll 项目从 VS 移植到 Linux,不幸的是测试是用 C# 编写的。我也在 porting/bit 重构它们,但我遇到了这种奇怪的情况:
我的 TestFixture
从 .so 导入一个函数:
[DllImport(LIB_NAME, SetLastError = true, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern UInt32 function(ref STRUCT structPtr);
然后,我有一个执行以下操作的测试:
[Test]
public void ...() {
...
STRUCT structVar = new STRUCT();
result = function(ref structVar);
//Check if function result is OK, which ensures the struct should have been properly modified
Assert.AreEqual(0x00, structVar.externalAttribute1.innerAttribute1); //OK!
Assert.AreEqual(0x00, structVar.externalAttribute2.innerAttribute1); //FAIL!
...
}
在这两种情况下 Assert
完全相同 最奇怪的 就是 STRUCT 包含另一个 相同的外部属性输入, 设置正确.
测试输出为:
1) Failed : ...
Expected: 0
But was: 108
当然,有人会认为是函数将其设置为该值,但实际上是只有行修改了这个属性:
structPtr->externalAttribute.innerAttribute = 0x00;
我认为这可能与结构的对齐和 C# 的编组配置不正确有关:
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
public struct EXT_STRUCT // externalAttribute
{
public byte innerAttribute;
public byte innerAttribute2;
}
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
public struct STRUCT {
EXT_STRUCT externalAttribute1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
//public string str1;
public byte[] str1;
public uint attrLong;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
//public string str2;
public byte[] str2;
EXT_STRUCT externalAttribute2;
}
(应@DavidHeffernan 的要求),这是 C 中定义的结构:
typedef struct EXT_STRUCT {
unsigned char extAttr1;
unsigned char extAttr2;
} EXT_STRUCT;
typedef struct STRUCT {
EXT_STRUCT extAttr1;
unsigned char str1[32];
unsigned long attrLong;
unsigned char str2[32];
EXT_STRUCT extAttr2;
} STRUCT;
那些 byte
在 C
中被表示为 unsigned char
,所以我认为起初不应该有问题,但这是唯一对我有意义的事情。但是,我无法调试它并查看发生了什么。
提前致谢!
更新:该结构有一些 str 成员,理论上可以从中编组:
unsigned char[32];
我看到 externalAttribute1 的值是正确的,而 externalAttribute2 的值很奇怪。 C 代码基本上是这样做的:
...
structPtr->externalAttribute1.innerAttribute = 0x02;
structPtr->externalAttribute1.innerAttribute2 = 0x20;
memset(structPtr->str1, ' ', sizeof(structPtr->str1));
memcpy(structPtr->str1, STR1_VALUE, strlen(STR1_VALUE));
...
memset(structPtr->str2, ' ', sizeof(structPtr->str2)); // OK without this
memcpy(structPtr->str2, STR2_VALUE, strlen(STR2_VALUE)); // OK without this
structPtr->externalAttribute2.innerAttribute = 0x00;
structPtr->externalAttribute2.innerAttribute2 = 0x05;
如果我注释掉 str2
的 memcpy
和 memset
,externalAttribute2 的值就完全没问题,无论是否它们是否用于 str1
。我检查了 STR2_VALUE
的大小,它有 30 个字符长(31 个 [=31=]
),所以它不应该写出它的边界(32 字节)。
然而,它确实似乎是在写 innerAttribute,因为当我只使用 memset
:
时,断言的失败会更改为此
Expected: 0
But was: 32 // 32 = ' ' (ASCII)
当使用 memcpy
时,它是一个 108(ASCII 中的 l
),并且在第 28 位有一个 l
.
虽然我想我知道问题出在哪里,但我似乎无法解决它。
提前致谢。
注意:我很乐意调试它并查看内存内容以找出问题所在。关键是,在与 Mono
苦苦挣扎并放弃使用 MonoDevelop
的想法之后(它只是崩溃了当我从源代码构建它时项目加载,Ubuntu 的 MD 版本太旧以至于 NUnit3
的插件甚至没有列出),我现在 运行 测试使用一个简单的 bash
脚本和一些 folder/file 管理和基本上这两行重要的行:
...
mcs -unsafe ${test_source_input} ${definitions_file} -target:library -r:${nunit_lib_name} -out:${test_lib_output}
...
LD_LIBRARY_PATH=./ mono ${console_runner} ${test_filename}.dll
...
关键是我希望能够使用 GDB
但是,令我惊讶的是,即使阅读了 Mono Debugging docs,我也不能弄清楚我到底该如何调试测试。
我试过 mdb
东西,但它看起来不像我想要的(单步执行代码以检查变量和内存内容) .
代码应如下所示
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct EXT_STRUCT //External attribute
{
public byte innerAttribute; //The inner attribute being modified
public byte innerAttribute2; //Another inner attribute having exactly the same issue
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct STRUCT
{
public int i1;
public int i2;
public int i3;
public EXT_STRUCT externalAttribute;
}
static void Main(string[] args)
{
EXT_STRUCT s1 = new EXT_STRUCT();
IntPtr s1Ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(EXT_STRUCT)));
//use StructureToPtr only when sending values to c++
Marshal.StructureToPtr(s1, s1Ptr, true);
int size1 = Marshal.SizeOf(s1); ;
STRUCT s2 = new STRUCT();
s2.externalAttribute = new EXT_STRUCT();
IntPtr s2Ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(STRUCT)));
//use StructureToPtr only when sending values to c++
Marshal.StructureToPtr(s2, s2Ptr, true);
int size2 = Marshal.SizeOf(s2);
STRUCT s3 = new STRUCT();
IntPtr s3Ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(STRUCT)));
//call c++
Marshal.PtrToStructure(s3Ptr, s3);
}
终于,感谢@David Heffernan,我得到了解决方案。
看起来 uint
存储在 4 个字节中,两个字符串之间的结构属性在 C 中是 unsigned long
(8 个字节),而 uint
(4字节)在 C# 中。这显然是胡说八道,但我什至不关心它,因为它出于某种原因在 Windows 中工作 我无法理解。
修复非常简单,只需将 C# 结构更改为:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct EXT_STRUCT
{
public byte innAttr1;
public byte innAttr2;
}
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet=CharSet.Ansi)]
public struct STRUCT
{
public EXT_STRUCT extAttr1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string str1;
public ulong attrLong; // That was uint
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string str2;
public EXT_STRUCT extAttr2;
}
那是混乱的编组,它完美地解释了为什么要在 extAttr2
:
的第一个字节中插入 108
108
= l
(ASCII) -> str2
值的第 28 位有一个 l
。
28+4 = 32 -> memcpy 在 extAttr2.innAttr1
中复制 l
,因为内存布局是:
| Field | C bytes | C# bytes |
|----------|---------|----------|
| extAttr1 | 0-1 | 0-1 |
| str1 | 2-33 | 2-33 |
| attrLong | 34-41 | 34-37 |
| str2 | 42-73 | 38-69 |
| extAttr2 | 74-75 | 70-71 |
现在说得通了,尽管我们不修改代码32 位[=39] 就无法运行 我们的测试 =] 平台,如果我们愿意,确实如此。
我正在将一个 .dll 项目从 VS 移植到 Linux,不幸的是测试是用 C# 编写的。我也在 porting/bit 重构它们,但我遇到了这种奇怪的情况:
我的 TestFixture
从 .so 导入一个函数:
[DllImport(LIB_NAME, SetLastError = true, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern UInt32 function(ref STRUCT structPtr);
然后,我有一个执行以下操作的测试:
[Test]
public void ...() {
...
STRUCT structVar = new STRUCT();
result = function(ref structVar);
//Check if function result is OK, which ensures the struct should have been properly modified
Assert.AreEqual(0x00, structVar.externalAttribute1.innerAttribute1); //OK!
Assert.AreEqual(0x00, structVar.externalAttribute2.innerAttribute1); //FAIL!
...
}
在这两种情况下 Assert
完全相同 最奇怪的 就是 STRUCT 包含另一个 相同的外部属性输入, 设置正确.
测试输出为:
1) Failed : ...
Expected: 0
But was: 108
当然,有人会认为是函数将其设置为该值,但实际上是只有行修改了这个属性:
structPtr->externalAttribute.innerAttribute = 0x00;
我认为这可能与结构的对齐和 C# 的编组配置不正确有关:
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
public struct EXT_STRUCT // externalAttribute
{
public byte innerAttribute;
public byte innerAttribute2;
}
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
public struct STRUCT {
EXT_STRUCT externalAttribute1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
//public string str1;
public byte[] str1;
public uint attrLong;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
//public string str2;
public byte[] str2;
EXT_STRUCT externalAttribute2;
}
(应@DavidHeffernan 的要求),这是 C 中定义的结构:
typedef struct EXT_STRUCT {
unsigned char extAttr1;
unsigned char extAttr2;
} EXT_STRUCT;
typedef struct STRUCT {
EXT_STRUCT extAttr1;
unsigned char str1[32];
unsigned long attrLong;
unsigned char str2[32];
EXT_STRUCT extAttr2;
} STRUCT;
那些 byte
在 C
中被表示为 unsigned char
,所以我认为起初不应该有问题,但这是唯一对我有意义的事情。但是,我无法调试它并查看发生了什么。
提前致谢!
更新:该结构有一些 str 成员,理论上可以从中编组:
unsigned char[32];
我看到 externalAttribute1 的值是正确的,而 externalAttribute2 的值很奇怪。 C 代码基本上是这样做的:
...
structPtr->externalAttribute1.innerAttribute = 0x02;
structPtr->externalAttribute1.innerAttribute2 = 0x20;
memset(structPtr->str1, ' ', sizeof(structPtr->str1));
memcpy(structPtr->str1, STR1_VALUE, strlen(STR1_VALUE));
...
memset(structPtr->str2, ' ', sizeof(structPtr->str2)); // OK without this
memcpy(structPtr->str2, STR2_VALUE, strlen(STR2_VALUE)); // OK without this
structPtr->externalAttribute2.innerAttribute = 0x00;
structPtr->externalAttribute2.innerAttribute2 = 0x05;
如果我注释掉 str2
的 memcpy
和 memset
,externalAttribute2 的值就完全没问题,无论是否它们是否用于 str1
。我检查了 STR2_VALUE
的大小,它有 30 个字符长(31 个 [=31=]
),所以它不应该写出它的边界(32 字节)。
然而,它确实似乎是在写 innerAttribute,因为当我只使用 memset
:
Expected: 0
But was: 32 // 32 = ' ' (ASCII)
当使用 memcpy
时,它是一个 108(ASCII 中的 l
),并且在第 28 位有一个 l
.
虽然我想我知道问题出在哪里,但我似乎无法解决它。
提前致谢。
注意:我很乐意调试它并查看内存内容以找出问题所在。关键是,在与 Mono
苦苦挣扎并放弃使用 MonoDevelop
的想法之后(它只是崩溃了当我从源代码构建它时项目加载,Ubuntu 的 MD 版本太旧以至于 NUnit3
的插件甚至没有列出),我现在 运行 测试使用一个简单的 bash
脚本和一些 folder/file 管理和基本上这两行重要的行:
...
mcs -unsafe ${test_source_input} ${definitions_file} -target:library -r:${nunit_lib_name} -out:${test_lib_output}
...
LD_LIBRARY_PATH=./ mono ${console_runner} ${test_filename}.dll
...
关键是我希望能够使用 GDB
但是,令我惊讶的是,即使阅读了 Mono Debugging docs,我也不能弄清楚我到底该如何调试测试。
我试过 mdb
东西,但它看起来不像我想要的(单步执行代码以检查变量和内存内容) .
代码应如下所示
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct EXT_STRUCT //External attribute
{
public byte innerAttribute; //The inner attribute being modified
public byte innerAttribute2; //Another inner attribute having exactly the same issue
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct STRUCT
{
public int i1;
public int i2;
public int i3;
public EXT_STRUCT externalAttribute;
}
static void Main(string[] args)
{
EXT_STRUCT s1 = new EXT_STRUCT();
IntPtr s1Ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(EXT_STRUCT)));
//use StructureToPtr only when sending values to c++
Marshal.StructureToPtr(s1, s1Ptr, true);
int size1 = Marshal.SizeOf(s1); ;
STRUCT s2 = new STRUCT();
s2.externalAttribute = new EXT_STRUCT();
IntPtr s2Ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(STRUCT)));
//use StructureToPtr only when sending values to c++
Marshal.StructureToPtr(s2, s2Ptr, true);
int size2 = Marshal.SizeOf(s2);
STRUCT s3 = new STRUCT();
IntPtr s3Ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(STRUCT)));
//call c++
Marshal.PtrToStructure(s3Ptr, s3);
}
终于,感谢@David Heffernan,我得到了解决方案。
看起来 uint
存储在 4 个字节中,两个字符串之间的结构属性在 C 中是 unsigned long
(8 个字节),而 uint
(4字节)在 C# 中。这显然是胡说八道,但我什至不关心它,因为它出于某种原因在 Windows 中工作 我无法理解。
修复非常简单,只需将 C# 结构更改为:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct EXT_STRUCT
{
public byte innAttr1;
public byte innAttr2;
}
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet=CharSet.Ansi)]
public struct STRUCT
{
public EXT_STRUCT extAttr1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string str1;
public ulong attrLong; // That was uint
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string str2;
public EXT_STRUCT extAttr2;
}
那是混乱的编组,它完美地解释了为什么要在 extAttr2
:
108
108
= l
(ASCII) -> str2
值的第 28 位有一个 l
。
28+4 = 32 -> memcpy 在 extAttr2.innAttr1
中复制 l
,因为内存布局是:
| Field | C bytes | C# bytes |
|----------|---------|----------|
| extAttr1 | 0-1 | 0-1 |
| str1 | 2-33 | 2-33 |
| attrLong | 34-41 | 34-37 |
| str2 | 42-73 | 38-69 |
| extAttr2 | 74-75 | 70-71 |
现在说得通了,尽管我们不修改代码32 位[=39] 就无法运行 我们的测试 =] 平台,如果我们愿意,确实如此。