如何将函数从C ++ DLL正确导入到C#Project中

我可以使用如何正确地将C ++ DLL中的几个函数导入到我的C#应用​​程序中的帮助。 以下是C ++方面的一些示例,它们展示了我在C#中尝试做的事情。 我不认为我正确地编组了一些/或一些返回类型和一些参数(尤其是指针/ 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(不可恢复的)或我可以尝试/捕获的不正确的编组。

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 

任何和所有帮助表示赞赏!

正如您在评论中所述(随后删除),您无法确定问题在于互操作或传递给函数的参数。 你是如何解决这个疑问的?

这样做的方法是创建一个具有相同签名的函数的测试床DLL,然后certificate您可以在该DLL和C#p / invoke代码之间正确地移动数据。 一旦你能做到这一点,你可以删除interop作为你的问题的潜在来源,并专注于传递给函数的参数。 因此,这是制作该测试床DLL所需的内容。

dllmain.cpp

 #include  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  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(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
返回值= 1

 mfcs_get_serial,12
 serial = 2
返回值= 3

 mfcs_read_chan,13,14
压力= 4.5
 chrono = 5
返回值= 6

如您所见,所有所需的值都正确地在两个模块之间传输。 这表明这里的p / invoke互操作代码是正确的。

备注

  1. 在Windows上的C ++中, intlong int都是32位类型。 因此,它们映射到C#上的int , or无符号变量的uint`。
  2. 在C#上, longulong是64位类型,因此不匹配C ++ int或long int`。
  3. 将C ++上的unsigned char映射到C#上的byte
  4. 这里没有必要不安全。 如果你需要使用指针,你会使用unsafe ,但你没有,很少这样做。
  5. 我使用而不是ref因为我推断这些参数仅用于流出DLL的数据。

如果你对你的DLL使用这个互操作代码仍然遇到失败,那么有两个似是而非的解释:

  1. 传递给DLL的参数不正确。
  2. DLL不是你想象的那样。 也许您有一个针对不同头文件构建的DLL版本。

这取决于除了用C ++编写的这个DLL。

如果它是C ++ .NET DLL,您可以像使用任何其他.NET DLL一样使用它。 就像你已经使用框架提供的那些。

如果它是用.NET的前任COM编写的,则可以使用COM互操作。 在制作.NET时考虑了向后兼容性

如果它们都不是那些,那么就有P / Invoke。

请注意,COM interop和P / Invoke通常涉及处理裸指针。 这意味着双重性问题,并且必须进入非托管代码。 我不羡慕你不得不去那种低级语言。