如何正确地将函数从 C++ DLL 导入到我的 C# 项目中
How do I properly import functions from a C++ DLL into my C# Project
我可以使用有关如何将多个函数从 C++ DLL 正确导入我的 C# 应用程序的帮助。以下是 C++ 方面的几个示例,它们展示了我在 C# 中尝试做的事情。我认为我没有正确编组 either/or 一些 return 类型和一些参数(尤其是 pointers/ref/out)。
C++ 头文件声明:
unsigned long __stdcall mfcsez_initialisation(unsigned short serial);
unsigned char __stdcall mfcs_get_serial(unsigned long int handle,
unsigned short * serial);
unsigned char __stdcall mfcs_read_chan(unsigned long int handle,
unsigned char canal,
float * pressure,
unsigned short * chrono);
C++代码:
/* Define functions prototype */
typedef unsigned long(__stdcall *init)(int);
typedef unsigned char(__stdcall *serial)(unsigned long handle, unsigned
short *serial);
typedef unsigned char(__stdcall *readChannel)(unsigned long handle,
unsigned char chan,
float * pressure,
unsigned short * chrono);
int main(int argc, char *argv[])
{
unsigned char pressureChannel = 1;
HINSTANCE hGetProcIDDLL=NULL;
/* Load DLL into memory */
hGetProcIDDLL = LoadLibrary(TEXT("mfcs64_c.dll"));
/* Declare pointers on dll functions */
init dll_init;
serial dll_serial;
readChannel dll_readChannel;
/* Link dll pointers with functions prototype */
dll_init = (init)GetProcAddress(hGetProcIDDLL,
"mfcsez_initialisation");
dll_serial = (serial)GetProcAddress(hGetProcIDDLL,
"mfcs_get_serial");
dll_readChannel = (readChannel)GetProcAddress(hGetProcIDDLL,
"mfcs_read_chan");
/* Define variables used for MFCS device */
unsigned long mfcsHandle;
unsigned short mySerial;
float read_pressure;
unsigned short chrono;
int loop_index;
if (hGetProcIDDLL != NULL)
{
std::cout << "mfcs_c.dll is loaded" << std::endl;
/* Initialize device */
if (dll_init != NULL)
{
/* Initialize the first MFCS in Windows enumeration list */
mfcsHandle = dll_init(0);
}
/* Read device serial number */
dll_serial(mfcsHandle, &mySerial);
for (loop_index = int(start_pressure);
loop_index<target_pressure; loop_index++)
{
Sleep(1000);
dll_readChannel(mfcsHandle, pressureChannel,
&read_pressure, &chrono);
}
}
return EXIT_SUCCESS;
}
我试过用各种足迹导入它们。我可以调用 mfcsez_initialisation 并且它工作得很好,就像下面导入的那样。另外两个我尝试了很多不同的方法并且总是得到一个异常 - 无论是来自 DLL(不可恢复)还是来自我可以 try/catch.
的不正确编组
C# 导入语句示例:
[DllImport("mfcs_c_64.dll", CallingConvention =
CallingConvention.StdCall)]
protected static unsafe extern uint mfcsez_initialisation(ushort
serial_number);
[DllImport("mfcs_c_64.dll", CallingConvention =
CallingConvention.StdCall)]
public static unsafe extern byte mfcs_get_serial(uint handle, ref
ushort serial);
[DllImport("mfcs_c_64.dll", CallingConvention =
CallingConvention.StdCall)]
protected static unsafe extern byte mfcs_read_chan(ulong handle, byte
canal, ref float pressure, ref ushort chrono);
C# 代码示例:
unit mfcsHandle = mfcsez_initialisation(0); // Returns with valid handle
mfcs_get_serial(mfcsHandle, mySerial); // Memory write exception
float pressure = -1.0f;
ushort chrono = 0;
mfcs_read_chan(mfcsHandle, 1, ref pressure, ref chrono); // Same ex
感谢任何帮助!
除了用C++写的,还要看这个DLL是什么。
如果它是 C++ .NET DLL,您可以像使用任何其他 .NET DLL 一样使用它。以及框架提供的您已经使用的那些。
如果它是用 .NET 的前身 COM 编写的,您可以使用 COM 互操作。制作 .NET 时考虑了向后兼容性
如果两者都不是,则有P/Invoke。
请注意,COM 互操作和 P/Invoke 通常涉及处理裸指针。这意味着二进制问题并且必须进入非托管代码。我不羡慕你不得不去那种低级语言。
正如您在评论中所述(随后被删除),您无法确定问题出在互操作还是传递给函数的参数上。你打算如何解决这个疑问?
这样做的方法是创建一个具有相同签名的函数的测试床 DLL,然后证明您可以在该 DLL 和 C# p/invoke 代码之间正确移动数据。一旦你能做到这一点,你就可以将互操作作为问题的潜在来源移除,并专注于传递给函数的参数。因此,这是制作该测试床 DLL 所需的内容。
dllmain.cpp
#include <Windows.h>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Dll1.cpp
#include <iostream>
extern "C"
{
unsigned long __stdcall mfcsez_initialisation(unsigned short serial)
{
std::cout << "mfcsez_initialisation, " << serial << std::endl;
return 1;
}
unsigned char __stdcall mfcs_get_serial(unsigned long int handle,
unsigned short * serial)
{
std::cout << "mfcs_get_serial, " << handle << std::endl;
*serial = 2;
return 3;
}
unsigned char __stdcall mfcs_read_chan(unsigned long int handle,
unsigned char canal,
float * pressure,
unsigned short * chrono)
{
std::cout << "mfcs_read_chan, " << handle << ", " << static_cast<int>(canal) << std::endl;
*pressure = 4.5f;
*chrono = 5;
return 6;
}
}
Dll1.def
LIBRARY Dll1
EXPORTS
mfcsez_initialisation
mfcs_get_serial
mfcs_read_chan
请注意,我使用的是 .def 文件以确保使用未修饰的名称导出函数。
调用它的 C# 程序如下所示:
Program1.cs
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace ConsoleApp1
{
class Program
{
const string dllname = "Dll1.dll";
[DllImport(dllname, CallingConvention = CallingConvention.StdCall)]
static extern uint mfcsez_initialisation(ushort serial);
[DllImport(dllname, CallingConvention = CallingConvention.StdCall)]
static extern byte mfcs_get_serial(uint handle, out ushort serial);
[DllImport(dllname, CallingConvention = CallingConvention.StdCall)]
static extern byte mfcs_read_chan(uint handle, byte canal, out float pressure, out ushort chrono);
static void Main(string[] args)
{
uint retval1 = mfcsez_initialisation(11);
Console.WriteLine("return value = " + retval1.ToString());
Console.WriteLine();
ushort serial;
byte retval2 = mfcs_get_serial(12, out serial);
Console.WriteLine("serial = " + serial.ToString());
Console.WriteLine("return value = " + retval2.ToString());
Console.WriteLine();
float pressure;
ushort chrono;
byte retval3 = mfcs_read_chan(13, 14, out pressure, out chrono);
Console.WriteLine("pressure = " + pressure.ToString());
Console.WriteLine("chrono = " + chrono.ToString());
Console.WriteLine("return value = " + retval3.ToString());
Console.ReadLine();
}
}
}
输出为:
mfcsez_initialisation, 11
return value = 1
mfcs_get_serial, 12
serial = 2
return value = 3
mfcs_read_chan, 13, 14
pressure = 4.5
chrono = 5
return value = 6
如您所见,所有需要的值都在两个模块之间正确传输。这表明此处的 p/invoke 互操作代码是正确的。
备注:
- 在 windows 上的 C++ 中,
int
和 long int
都是 32 位类型。因此,对于未签名的变体,它们映射到 C#, or
uint 上的 int
。
- 在 C# 上,
long
和 ulong
是 64 位类型,因此不匹配 C++ int
或 long int`。
- 将 C++ 上的
unsigned char
映射到 C# 上的 byte
。
- 这里不需要unsafe。如果你需要使用指针,你会使用
unsafe
,但你不需要,而且很少这样做。
- 我使用
out
而不是ref
,因为我推断这些参数仅用于流出DLL的数据。
如果您将此互操作代码用于您的 DLL 并且仍然遇到失败,那么有两个合理的解释:
- 您传递给 DLL 的参数不正确。
- DLL 不是你想的那样。也许您有一个针对不同头文件构建的 DLL 版本。
我可以使用有关如何将多个函数从 C++ DLL 正确导入我的 C# 应用程序的帮助。以下是 C++ 方面的几个示例,它们展示了我在 C# 中尝试做的事情。我认为我没有正确编组 either/or 一些 return 类型和一些参数(尤其是 pointers/ref/out)。
C++ 头文件声明:
unsigned long __stdcall mfcsez_initialisation(unsigned short serial);
unsigned char __stdcall mfcs_get_serial(unsigned long int handle,
unsigned short * serial);
unsigned char __stdcall mfcs_read_chan(unsigned long int handle,
unsigned char canal,
float * pressure,
unsigned short * chrono);
C++代码:
/* Define functions prototype */
typedef unsigned long(__stdcall *init)(int);
typedef unsigned char(__stdcall *serial)(unsigned long handle, unsigned
short *serial);
typedef unsigned char(__stdcall *readChannel)(unsigned long handle,
unsigned char chan,
float * pressure,
unsigned short * chrono);
int main(int argc, char *argv[])
{
unsigned char pressureChannel = 1;
HINSTANCE hGetProcIDDLL=NULL;
/* Load DLL into memory */
hGetProcIDDLL = LoadLibrary(TEXT("mfcs64_c.dll"));
/* Declare pointers on dll functions */
init dll_init;
serial dll_serial;
readChannel dll_readChannel;
/* Link dll pointers with functions prototype */
dll_init = (init)GetProcAddress(hGetProcIDDLL,
"mfcsez_initialisation");
dll_serial = (serial)GetProcAddress(hGetProcIDDLL,
"mfcs_get_serial");
dll_readChannel = (readChannel)GetProcAddress(hGetProcIDDLL,
"mfcs_read_chan");
/* Define variables used for MFCS device */
unsigned long mfcsHandle;
unsigned short mySerial;
float read_pressure;
unsigned short chrono;
int loop_index;
if (hGetProcIDDLL != NULL)
{
std::cout << "mfcs_c.dll is loaded" << std::endl;
/* Initialize device */
if (dll_init != NULL)
{
/* Initialize the first MFCS in Windows enumeration list */
mfcsHandle = dll_init(0);
}
/* Read device serial number */
dll_serial(mfcsHandle, &mySerial);
for (loop_index = int(start_pressure);
loop_index<target_pressure; loop_index++)
{
Sleep(1000);
dll_readChannel(mfcsHandle, pressureChannel,
&read_pressure, &chrono);
}
}
return EXIT_SUCCESS;
}
我试过用各种足迹导入它们。我可以调用 mfcsez_initialisation 并且它工作得很好,就像下面导入的那样。另外两个我尝试了很多不同的方法并且总是得到一个异常 - 无论是来自 DLL(不可恢复)还是来自我可以 try/catch.
的不正确编组C# 导入语句示例:
[DllImport("mfcs_c_64.dll", CallingConvention =
CallingConvention.StdCall)]
protected static unsafe extern uint mfcsez_initialisation(ushort
serial_number);
[DllImport("mfcs_c_64.dll", CallingConvention =
CallingConvention.StdCall)]
public static unsafe extern byte mfcs_get_serial(uint handle, ref
ushort serial);
[DllImport("mfcs_c_64.dll", CallingConvention =
CallingConvention.StdCall)]
protected static unsafe extern byte mfcs_read_chan(ulong handle, byte
canal, ref float pressure, ref ushort chrono);
C# 代码示例:
unit mfcsHandle = mfcsez_initialisation(0); // Returns with valid handle
mfcs_get_serial(mfcsHandle, mySerial); // Memory write exception
float pressure = -1.0f;
ushort chrono = 0;
mfcs_read_chan(mfcsHandle, 1, ref pressure, ref chrono); // Same ex
感谢任何帮助!
除了用C++写的,还要看这个DLL是什么。
如果它是 C++ .NET DLL,您可以像使用任何其他 .NET DLL 一样使用它。以及框架提供的您已经使用的那些。
如果它是用 .NET 的前身 COM 编写的,您可以使用 COM 互操作。制作 .NET 时考虑了向后兼容性
如果两者都不是,则有P/Invoke。
请注意,COM 互操作和 P/Invoke 通常涉及处理裸指针。这意味着二进制问题并且必须进入非托管代码。我不羡慕你不得不去那种低级语言。
正如您在评论中所述(随后被删除),您无法确定问题出在互操作还是传递给函数的参数上。你打算如何解决这个疑问?
这样做的方法是创建一个具有相同签名的函数的测试床 DLL,然后证明您可以在该 DLL 和 C# p/invoke 代码之间正确移动数据。一旦你能做到这一点,你就可以将互操作作为问题的潜在来源移除,并专注于传递给函数的参数。因此,这是制作该测试床 DLL 所需的内容。
dllmain.cpp
#include <Windows.h>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Dll1.cpp
#include <iostream>
extern "C"
{
unsigned long __stdcall mfcsez_initialisation(unsigned short serial)
{
std::cout << "mfcsez_initialisation, " << serial << std::endl;
return 1;
}
unsigned char __stdcall mfcs_get_serial(unsigned long int handle,
unsigned short * serial)
{
std::cout << "mfcs_get_serial, " << handle << std::endl;
*serial = 2;
return 3;
}
unsigned char __stdcall mfcs_read_chan(unsigned long int handle,
unsigned char canal,
float * pressure,
unsigned short * chrono)
{
std::cout << "mfcs_read_chan, " << handle << ", " << static_cast<int>(canal) << std::endl;
*pressure = 4.5f;
*chrono = 5;
return 6;
}
}
Dll1.def
LIBRARY Dll1
EXPORTS
mfcsez_initialisation
mfcs_get_serial
mfcs_read_chan
请注意,我使用的是 .def 文件以确保使用未修饰的名称导出函数。
调用它的 C# 程序如下所示:
Program1.cs
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace ConsoleApp1
{
class Program
{
const string dllname = "Dll1.dll";
[DllImport(dllname, CallingConvention = CallingConvention.StdCall)]
static extern uint mfcsez_initialisation(ushort serial);
[DllImport(dllname, CallingConvention = CallingConvention.StdCall)]
static extern byte mfcs_get_serial(uint handle, out ushort serial);
[DllImport(dllname, CallingConvention = CallingConvention.StdCall)]
static extern byte mfcs_read_chan(uint handle, byte canal, out float pressure, out ushort chrono);
static void Main(string[] args)
{
uint retval1 = mfcsez_initialisation(11);
Console.WriteLine("return value = " + retval1.ToString());
Console.WriteLine();
ushort serial;
byte retval2 = mfcs_get_serial(12, out serial);
Console.WriteLine("serial = " + serial.ToString());
Console.WriteLine("return value = " + retval2.ToString());
Console.WriteLine();
float pressure;
ushort chrono;
byte retval3 = mfcs_read_chan(13, 14, out pressure, out chrono);
Console.WriteLine("pressure = " + pressure.ToString());
Console.WriteLine("chrono = " + chrono.ToString());
Console.WriteLine("return value = " + retval3.ToString());
Console.ReadLine();
}
}
}
输出为:
mfcsez_initialisation, 11 return value = 1 mfcs_get_serial, 12 serial = 2 return value = 3 mfcs_read_chan, 13, 14 pressure = 4.5 chrono = 5 return value = 6
如您所见,所有需要的值都在两个模块之间正确传输。这表明此处的 p/invoke 互操作代码是正确的。
备注:
- 在 windows 上的 C++ 中,
int
和long int
都是 32 位类型。因此,对于未签名的变体,它们映射到 C#, or
uint 上的int
。 - 在 C# 上,
long
和ulong
是 64 位类型,因此不匹配 C++int
或 long int`。 - 将 C++ 上的
unsigned char
映射到 C# 上的byte
。 - 这里不需要unsafe。如果你需要使用指针,你会使用
unsafe
,但你不需要,而且很少这样做。 - 我使用
out
而不是ref
,因为我推断这些参数仅用于流出DLL的数据。
如果您将此互操作代码用于您的 DLL 并且仍然遇到失败,那么有两个合理的解释:
- 您传递给 DLL 的参数不正确。
- DLL 不是你想的那样。也许您有一个针对不同头文件构建的 DLL 版本。