1. 程式人生 > 實用技巧 >C# PInvoke 方式呼叫 C/C++ 庫 簡單例子

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