C# PInvoke 方式调用 C/C++ 库 简单例子

本文意在给出一个可用的 C# 调用 C/C++ lib 的实例。

C/C++ lib

C/C++ header

#ifndef DPS_H
#define DPS_H

#ifdef __cplusplus
extern "C" {
#endif

#if (defined _WIN32) || (defined _WINDOWS) || (defined WIN32)
#ifdef MM_EXPORTS
#define MMAPIEXP __declspec(dllexport)
#else
#define MMAPIEXP __declspec(dllimport)
#endif
#define MMAPI __cdecl
#else
#define MMAPIEXP
#define MMAPI
#endif

    // 定义一个回调
    typedef int(MMAPI *MMCallBack)(int handle, char* UserData);

    // 一个结构体
    typedef struct
    {
        int a;
        int b;
        char c;
        char* d;
    }DataInfo;

     // API 函数指针组成的结构体
    typedef struct
    {
        int (MMAPI* func01)(int a, int b);
        void (MMAPI* func02)(int c, DataInfo* d, MMCallBack cb);
    }MMAPIINFO;

      // 定义一个获取 API的函数,所有 API 通过该函数获取,注意,MMAPIEXP  用于指定WIN下导出函数名称
    MMAPIEXP  void  MMAPI GetAPI(MMAPIINFO* api);

#ifdef __cplusplus
}
#endif
#endif

CPP 实现


#include <cstddef>
#include <cstring>
#include "dps.h"

// func01 函数实现
int func01(int a, int b){
    return a + b;
}

// func01 函数实现
void func02(int c, DataInfo* d, MMCallBack cb){
    cb(c,d->d);
}

// 定义获取 API 的函数
void GetAPI(MMAPIINFO* api){
    if (api == NULL)
          return;
    memset(api, 0x00, sizeof(MMAPIINFO));
    api->func01 = func01;
    api->func02 = func02;
}

上述文件编译成为dll/lib. 对于C#而言,要调用的是dll. 上述文件可以生成为 dps.dll.
注:名称无实际意义, 该代码自说明。

C# 调用

using System;
using System.Runtime.InteropServices;

namespace loadLib
{
    class Program
    {
        static void Main(string[] args)
        {
            // 实例化一个 GetApi 类
            GetApi ss = new GetApi();

            Console.WriteLine(ss.Ffunc01(1,2));

            GetApi.DataInfo data = new GetApi.DataInfo
            {
                a = 1,
                b = 2,
                c = 'x'
            };
            string str1 = "adbcd";
            data.d = str1;

            ss.Ffunc02(1024,ref data);
        }
    }

    class GetApi
    {
        // 特性定义了 MMAPIINFO 函数指针结构体的序列化方式和字符集
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct MMAPIINFO
        {
            public IntPtr func01;
            public IntPtr func02;
        }

        // 特性定义了 DataInfo 结构体的序列化方式和字符集
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct DataInfo
        {
            public int a;
            public int b;
            public char c;
            // char* 要使用 MarshalAs
            [MarshalAs(UnmanagedType.LPStr)]
            public string d;
        }


        // 委托函数MMCB的定义用于传入回调函数,对应于 dps.h 中的
        // int(MMAPI *MMCallBack)(int handle, char* UserData)
        // 特别注意:C# 回调函数的调用约定必须声明为 CallingConvention.Cdecl
        // 因为 C# 的调用约定是 __stdcall, 如果不指定调用约定,会默认 __stdcall,
        // 不同的调用约定会导致冲突
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void MMCB(int handle, string userData);

        // 定义 MMAPIINFO.func01 的委托函数
        public delegate int Func01( int a, int b);

        // 定义 MMAPIINFO.func02 的委托函数
        public delegate void Func02(int c, ref DataInfo data, MMCB cbMmcb);


        // 封装函数,封装调用 MMAPIINFO.func01
        public int Ffunc01(int aa, int bb)
        {
            Func01 func = (Func01) Marshal.GetDelegateForFunctionPointer(A.func01, typeof(Func01));
            return func(aa, bb);
        
        }

        // 封装函数,封装调用 MMAPIINFO.func02
        public void Ffunc02(int c, ref DataInfo dat)
        {
            Func02 func = (Func02) Marshal.GetDelegateForFunctionPointer(A.func02, typeof(Func02));
            func(c, ref dat, apiCB);
        }

        // 成员变量 MMAPIINFO A 作为指针结构体
        public static MMAPIINFO A = new MMAPIINFO();


        // 声明 dsp.dll 的导出函数,注意调用约定 __cdecl 和字符集
        [DllImport("dps.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern void GetAPI(ref MMAPIINFO param);

        // 构造函数用于从 dsp.dll 中获取api函数结构体
        public GetApi()
        {
            GetAPI(ref A);
        }

        // 实际传入的回调(被委托)函数
        public void apiCB(int handle, string userData)
        {
            Console.WriteLine(handle);
            Console.WriteLine(userData);
        }
    }
}