如何将函数从C ++ DLL正确导入到我的C#项目中

时间:2018-01-16 00:30:46

标签: c# c++ dll marshalling dllimport

我可以使用如何正确地将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

感谢任何和所有帮助!

2 个答案:

答案 0 :(得分:0)

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

这样做的方法是创建一个具有相同签名的函数的测试床DLL,然后证明您可以在该DLL和C#p / invoke代码之间正确地移动数据。一旦你能做到这一点,你可以删除interop作为你的问题的潜在来源,并专注于传递给函数的参数。因此,这是制作该测试床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#int uint`上的, or
  2. 在C#上,longulong是64位类型,因此不匹配C ++ int或long int`。
  3. 将C ++上的unsigned char映射到C#上的byte
  4. 这里不需要不安全。如果你需要使用指针,你可以使用unsafe,但是你不需要,很少这样做。
  5. 我使用了out而不是ref,因为我推断这些参数仅用于流出DLL的数据。
  6. 如果你对你的DLL使用这个互操作代码并且仍然遇到失败,那么有两个似是而非的解释:

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

答案 1 :(得分:-1)

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

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

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

如果不是那些,则有P / Invoke。

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