How do I properly import functions from a C++ DLL into my C# Project

前端 未结 2 679
情歌与酒
情歌与酒 2021-01-29 06:32

I could use help with how to properly import several functions from a C++ DLL into my C# application. Here are a couple examples from the C++ side that show what I am trying to

相关标签:
2条回答
  • 2021-01-29 06:45

    It depends what this DLL is besides written in C++.

    If it is a C++ .NET DLL, you can use it like any other .NET DLL. And like the ones you already use provided by the Framework.

    If it was written with .NET's predecessor COM in mind, you can use COM interop. Backwards compatibiltiy was thought about when making .NET

    If it is neither of those, there is P/Invoke.

    Note that COM interop and P/Invoke usually involve handling naked pointers. That means binarity issues and having to go into Unmanaged Code. I do not envy you for having to go that low level language.

    0 讨论(0)
  • 2021-01-29 07:00

    As you have stated in comments (subsequently deleted), you can't be sure whether the problem lies in the interop or the parameters passed to the function. How are you going to resolve that doubt?

    The way to do that is to create a test bed DLL that has functions with the same signatures, and then prove that you can move data correctly between that DLL and your C# p/invoke code. Once you can do that you can remove interop as a potential source of your problem, and concentrate on the parameters passed to the function. So, here is what is needed to make that test bed 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
    

    Note that I am using a .def file to ensure that functions are exported using their undecorated names.

    The C# program that calls this looks like so:

    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();
            }
        }
    }
    

    The output is:

    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
    

    As you can see, all the desired values travel between the two modules correctly. This demonstrates that the p/invoke interop code here is correct.

    Notes:

    1. In C++ on windows, both int and long int are 32 bit types. They therefore map to int on C#, oruint` for unsigned variants.
    2. On C#, long and ulong are 64 bit types, and so do not match C++ int or long int`.
    3. Map unsigned char on C++ to byte on C#.
    4. There is no need for unsafe here. You would use unsafe if you needed to use pointers, but you don't, and seldom do.
    5. I used out rather than ref because I infer that these parameters are only used for data flowing out of the DLL.

    If you use this interop code against your DLL and still encounter failure then there are two plausible explanations:

    1. The parameters you are passing to the DLL are incorrect.
    2. The DLL is not what you think it is. Perhaps you have a version of the DLL which was built against different header files.
    0 讨论(0)
提交回复
热议问题