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;

那些 byteC 中被表示为 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;

如果我注释掉 str2memcpymemset,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] 就无法运行 我们的测试 =] 平台,如果我们愿意,确实如此。