我的自定义 C++ DLL 的 C# 项目中的 DLL 端口有什么问题?

What is wrong with my DLLImport(s) in my C# project for my custom C++ DLL?

本文关键字:DLL 什么 问题 项目 自定义 C++ 我的      更新时间:2023-10-16

我目前正在学习C++(最终为Hooks创建DLL)。 我在C#方面有优势,并希望通过pInvoke/DllImports访问DLL。

我正在使用VS 2019(和.NET Framework 4.7.2)。 作为学习目的的示例,我复制了 DLL 以及C++(控制台应用),如下所述:https://learn.microsoft.com/en-us/cpp/build/walkthrough-creating-and-using-a-dynamic-link-library-cpp?view=vs-2019。 这工作得很好(正如预期的那样)。

现在,当我开始尝试将DLL导入C#项目时,我遇到了问题(我认为与DllImport相关)

在示例 (c++) 控制台应用(正在运行)中:

int main()
{
// Initialize a Fibonacci relation sequence.
fibonacci_init(1, 1);
// Write out the sequence values until overflow.
do {
std::cout << fibonacci_index() << ": " << fibonacci_current() << std::endl;
} while (fibonacci_next());
// Report count of values written before overflow.
std::cout << fibonacci_index() + 1 << " Fibonacci sequence values fit in an " << "unsigned 64-bit integer." << std::endl;
}

在 C# 测试控制台应用中(无法正常工作):

class Program
{
[DllImport("MathLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern void fibonacci_init(ulong a, ulong b);
[DllImport("MathLibrary.dll", CallingConvention = CallingConvention.Cdecl))]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool fibonacci_next();
[DllImport("MathLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern ulong fibonacci_current();
[DllImport("MathLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern uint fibonacci_index();
static void Main(string[] args)
{
// Initialize a Fibonacci relation sequence.
fibonacci_init(1, 1);
// Write out the sequence values until overflow.
do {
Console.WriteLine($"{fibonacci_index()}: {fibonacci_current()}");
} while (fibonacci_next());
// Report count of values written before overflow.
Console.WriteLine($"{fibonacci_index() + 1} Fibonacci sequence values fit in an unsigned 64-bit integer.");
}
}

在工作 (C++) 应用中,循环达到 92 并打印92: 12200160415121876738,然后退出循环并打印93 Fibonacci sequence values fit in an unsigned 64-bit integer.

但是,在失败的 (C#) 应用中,它一直工作到92: 12200160415121876738,但不是退出循环fabonacci_next(),而是继续返回true,并且它继续循环行92: 12200160415121876738(不再增加,并且永远不会退出)。

对于 C# 中的 DllImport,我已经尝试了两种方法:

[DllImport("MathLibrary.dll", CallingConvention = CallingConvention.Cdecl))]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool fibonacci_next();

和:

[DllImport("MathLibrary.dll", CallingConvention = CallingConvention.Cdecl))]
public static extern bool fibonacci_next();

无论哪种方式,结果都相同。如果有人可以向我解释我在 DllImport 上做错了什么(当相同的 DLL 在链接到C++控制台应用程序时正常工作时,导致它永远不会返回 true)。

---更新---

因此,上面示例的解决方案将[return: MarshalAs(UnmanagedType.Bool)]更改为[return: MarshalAs(UnmanagedType.I1)]确实有效(GSerg 在下面的评论中给出的链接中提供了更多重要的相关信息)。

---更新 2 ---

(@David)我确实故意(并且不正确地)省略了导出,因为它们在我提供给原始DLL"演练"的链接中(我很抱歉)。 为了问题的完整性,导出是:

#ifdef MATHLIBRARY_EXPORTS
#define MATHLIBRARY_API __declspec(dllexport)
#else
#define MATHLIBRARY_API __declspec(dllimport)
#endif
extern "C" MATHLIBRARY_API void fibonacci_init(unsigned long long a, unsigned long long b);
extern "C" MATHLIBRARY_API bool fibonacci_next();
extern "C" MATHLIBRARY_API unsigned long long fibonacci_current();
extern "C" MATHLIBRARY_API unsigned fibonacci_index();

TLDR;

解决方案是简单地将 fibonacci_next() 导入的 DllImport 更改为:

[DllImport("MathLibrary.dll", CallingConvention = CallingConvention.Cdecl))]
[return: MarshalAs(UnmanagedType.I1)]
public static extern bool fibonacci_next();

一点额外的:

从技术上讲,这就是回答这个问题所需要的一切。 对于那些在类似术语下发现这个问题的人,我在下面添加了更多信息,因为问题的真正意义在于理解和学习,而不是复制和粘贴其他人的代码并试图让它工作。

从GSerg的评论开始:

C++的布尔值不是 C# 的布尔值。UnmanagedType.Bool是C++的BOOL,而不是bool。

我在转换数据类型时使用此列表:

下表取自将C++数据类型转换为 C#(我相信周围有更多官方转换图表)

C++ Type                       C# Type               Size
--------                       -------               ----
BOOL                           bool                  1 byte
BYTE                           byte                  1 byte
CHAR                           byte                  1 byte
DECIMAL                        Decimal               16 bytes
DOUBLE                         double                8 bytes
DWORD                          uint, UInt32          4 bytes
FLOAT                          float, single         4 bytes
INT, signed int                int, Int32            4 bytes
INT16, signed short int        short, Int16          2 bytes
INT32, signed int              int, Int32            4 bytes
INT64                          long, Int64           8 bytes
LONG                           int, Int32            4 bytes
LONG32, signed int             int, Int32            4 bytes
LONG64                         long, Int64           8 bytes
LONGLONG                       long, Int64           8 bytes
SHORT, signed short int        short, Int16          2 bytes
UCHAR, unsigned char           byte                  1 byte
UINT, unsigned int             uint, UInt32          4 bytes
UINT16, WORD                   ushort, UInt16        2 bytes
UINT32, unsigned int           uint, UInt32          4 bytes
UINT64                         ulong, UInt64         8 bytes
ULONG, unsigned long           uint, UInt32          4 bytes
ULONG32                        uint, UInt32          4 bytes
ULONG64                        ulong, UInt64         8 bytes
ULONGLONG                      ulong, UInt64         8 bytes
WORD                           ushort                2 bytes
void*, pointers                IntPtr                x86=4 bytes, x64=8 bytes

当我刚刚开始学习一点C++时,我假设上面列表中的 (c++) bool 等同于 (c++) bool,当没有[return: MarshalAs(UnmanagedType.Bool)]失败时,我尝试添加它,这也是失败的。

有关非托管类型的详细信息,请参阅Microsoft:非托管类型枚举:

UnmanagedType.I1:一个 1 字节的有符号整数。可以使用此成员将布尔值转换为 1 字节的 C 样式布尔值(true = 1,false = 0)。

我喜欢的另一点是公认的 https://stackoverflow.com/a/32115697/716296 解决方案,这与这个问题并不完全相关,因为我并没有真正尝试改变 c++ 方面的东西,而是将其用作基本代码片段来学习事物如何跨语言组合在一起。

也可以使用 ref 参数代替返回值。 这实际上是非托管互操作中的常见模式:

我已经看到这种"通用模式"经常与包装非托管库的第三方工具一起使用。 当然,如果我正在编写自己的非托管dll/库,我将编写它们以最适合其目的和用法,这与原始问题不同,它只是一个开始学习如何的工具。

我希望这个答案对一些人有所帮助(我不擅长术语),但这个问题非常基本,我不仅得到了答案,而且还找到了一些很好的参考资料,现在也进行了扩展。