如何正确地将函数从 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 互操作代码是正确的。

备注:

  1. 在 windows 上的 C++ 中,intlong int 都是 32 位类型。因此,对于未签名的变体,它们映射到 C#, oruint 上的 int
  2. 在 C# 上,longulong 是 64 位类型,因此不匹配 C++ int 或 long int`。
  3. 将 C++ 上的 unsigned char 映射到 C# 上的 byte
  4. 这里不需要unsafe。如果你需要使用指针,你会使用 unsafe,但你不需要,而且很少这样做。
  5. 我使用out而不是ref,因为我推断这些参数仅用于流出DLL的数据。

如果您将此互操作代码用于您的 DLL 并且仍然遇到失败,那么有两个合理的解释:

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