【问题标题】:C# DllImport with C++ boolean function not returning correctly带有 C++ 布尔函数的 C# DllImport 未正确返回
【发布时间】:2011-06-04 06:41:15
【问题描述】:

我在 C++ DLL 中有以下函数

extern "C" __declspec(dllexport) bool Exist(const char* name)
{
 //if (g_Queues.find(name) != g_Queues.end())
 // return true;
 //else
 // return false;
 return false;
}

在我的 C# 类中,我有以下内容:

[DllImport("Whisper.dll", EntryPoint="Exist", CallingConvention=CallingConvention.Cdecl)]
        public static extern bool Exist(string name);

然而,每当我调用我的函数时,它总是返回 true,即使我注释掉了我的小函数并使其返回 false。我觉得我的调用约定有问题,或者 P/Invoking 我的 DLL 有任何其他问题,可能与字符串和 const char* 相对应,但现在我完全一无所知。我究竟做错了什么?为什么返回的是 true 而不是 false?

编辑: 我发现这与 const char* 或字符串无关,因为空函数仍然存在问题。我尝试更改 Cdecl 和 StdCall 之间的调用约定,但都不能正常工作。我还设法调试了我的 DLL,它被正确调用并且确实返回 false,但是一旦回到 C#,它就以某种方式为真。更改 CharSet 也没有效果。我确保每次都为我的 C# 程序提供了最新且正确版本的 DLL,所以这也不应该成为问题。同样,当我实际上返回 false 时,我完全不知道为什么结果为 true。

EDIT2: SOReader 为我提供了解决另一个重要问题的建议,请参阅我的评论。遗憾的是,它并没有解决退货问题。

EDIT3: 我已经得出结论,将 Exist (bool) 的返回类型更改为 (int) 突然使它返回正确的数字 (true = 1, false = 0)。这意味着 C++ 的 bool 和 C# 的 bool 之间可能存在问题。我可以继续使用 int 作为布尔值,但这仍然不能解释原始问题。也许其他人可以在这个问题上启发我?也许这与我使用 x64 的事实有关(尽管两个项目都编译为 x86)

【问题讨论】:

  • 首先要检查的是该函数实际上是cdecl。如果您的makefile 将GzGr 传递给编译器,则上面的函数不是cdecl。将 __cdecl 添加到您的 C 代码中,或启用 pInvokeStackImbalance 托管调试助手。
  • 如果指定了 /Gr 或 /Gz,我认为它不会链接。关于托管调试助手的要点。
  • 我测试过,__fastcall 不会与/clr 链接。但是 __stdcall (/Gz) 链接,但是在运行时找不到 Exist 入口点,因为函数签名不同。
  • 调试到Exist,在函数右括号上设置断点。查看断点命中时EAX 寄存器的值。 EAX 中的任何内容都是 false 定义的内容。

标签: c# c++ dll import invoke


【解决方案1】:

我使用 signed int 可以正确返回真/假。

https://stackoverflow.com/a/42618042/1687981

【讨论】:

    【解决方案2】:

    我找到了解决您问题的方法。您的声明应该在此编组之前: [return:MarshalAs(UnmanagedType.I1)]

    所以一切都应该是这样的:

    [DllImport("Whisper.dll", EntryPoint="Exist", CallingConvention=CallingConvention.Cdecl)]  
    [return:MarshalAs(UnmanagedType.I1)]  
    public static extern bool Exist([MarshalAs(UnmanagedType.LPStr)] string name);
    

    我在我非常简单的示例中对其进行了测试,并且成功了!

    编辑
    为什么会发生这种情况? C 将 bool 定义为 4 个字节 int (正如你们中的一些人所说),而 C++ 将其定义为 1 个字节。 C# 团队决定在 PInvoke 期间使用 4 字节 bool 作为默认值,因为大多数系统 API 函数使用 4 字节值作为 bool。如果你想改变这种行为,你必须通过封送处理来指定你想要使用 1 字节值。

    【讨论】:

    • 这很奇怪。我已经在 VS 2010 中使用 DLL 和 C# 应用程序的 32 位版本以及两者的 64 位版本对其进行了测试,并且没有 Shammah 描述的问题。您所说的在 Big Endian 机器中是有意义的,但在 Little Endian 架构(如我们使用的基于 x86 的桌面)中则不然。请记住,在 bool 和 4 字节 int 的两种情况下,最低有效字节 1 或 0 是相同的。我猜这可能就是为什么它对我来说完美。
    • @SimonBrangwin:除非您想阅读生成的程序集或者自己是编译器供应商,否则几乎不可能理解返回类型错误时会发生什么。它只是行不通。 C++ 和 C 都将其标记为未定义行为是有原因的。
    • 我会接受你的推理:C bools 是 4 个字节,C++ bools 是 1 个字节,但是当 CallingConvention 设置为 ThisCall 时也会发生这种情况,这应该清楚地表明它是一个 C++ 方法。不过,你只是信使,毕竟,你的解决方案解决了我的问题,所以谢谢!
    • C 根本没有定义 bool。
    • @GlennMaynard 那是一条捷径。但是大多数环境都定义了#define TRUE 1,即int,大小为4B
    【解决方案3】:

    我使用以下系统发送布尔变量

    __declspec(dllexport) const bool* Read(Reader* instance) {
        try {
            bool result = instance->Read();
            bool* value = (bool*)::CoTaskMemAlloc(sizeof(bool));
            *value = result;
            return value;
        } catch (std::exception exp) {
            RegistryException(exp);
            return nullptr;
        }
    }
    

    在 C# 中,我会这样做

    DllImport(WrapperConst.dllName)]
    public static extern IntPtr Read(IntPtr instance);
    
    public bool Read() {
        IntPtr intPtr = ReaderWrapper.Read(instance));
        if(intPtr != IntPtr.Zero) {
            byte b = Marshal.ReadByte(intPtr);
            Marshal.FreeHGlobal(intPtr);
            return b != 0;
        } else {
            throw new Exception(GetLastException());
        }
    }
    

    【讨论】:

      【解决方案4】:

      这实际上是由返回 bool 的典型 C++ 代码未完全清除 EAX 引起的。 EAX 在输入函数时通常会包含一些虚假值,而对于return false,编译器通常会发出xor al, al。这只会清除 EAX 的 LSB,并导致 C# 代码将生成的非零值解释为 true 而不是 false

      【讨论】:

        【解决方案5】:

        C 的bool 实际上是int,因为原始 C 语言中没有布尔类型。这意味着如果 C# 的 DLLImport 旨在与 C 代码互操作,那么他们将期望 C# 的 bool 对应于 C 的 int。虽然这仍然不能解释为什么 false 会变为 true,但修复它应该可以解决问题。

        http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.unmanagedtype.aspx

        这表示 UnmanagedType.Bool 是 Win32 BOOL,即 int

        【讨论】:

          【解决方案6】:

          也许封送函数的参数可能会有所帮助:

          [MarshalAs(UnmanagedType.LPStr)]

          下面是声明的样子:

          [DllImport("Whisper.dll", EntryPoint="Exist", CallingConvention=CallingConvention.Cdecl)] public static extern bool Exist([MarshalAs(UnmanagedType.LPStr)] 字符串名称);

          【讨论】:

          • 调试时我发现确实应该使用它。如果我的字符串是“my_name”,则只有“m”会作为参数传递而无需编组。通过编组,整个“my_name”都会通过。可悲的是,这仍然不能解决退货问题:(
          • 我回顾了几年前写的项目,发现当返回布尔值时我使用了 PreserveSig。试试这个,让我知道它是否有效:[PreserveSig] bool foo();
          • 其实我只使用了接口函数——没有使用静态函数,所以我不知道我的技巧是否有用;(
          【解决方案7】:

          我测试了您的代码,但它对我返回 false。所以肯定有其他事情发生。

          您确定正确地重新编译 DLL 吗?尝试删除 .DLL 并进行重建。

          除此之外,假设一切似乎都很好。默认情况下,编组会将 .NET 字符串处理为 const char*,而无需使用 Marshal 属性对其进行修饰,无论 DLL 是编译为 ANSI 还是 Unicode。

          http://msdn.microsoft.com/en-us/library/s9ts558h.aspx#cpcondefaultmarshalingforstringsanchor5

          【讨论】:

            猜你喜欢
            • 2020-07-25
            • 2012-07-15
            • 2018-08-11
            • 2013-09-15
            • 2013-09-11
            • 1970-01-01
            • 2019-11-23
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多