C# 使用C/C++動態鏈接庫(dll) ——參數傳遞&類型移植

需求

在我們的方法調用也經常會遇到參數傳遞的情況,在傳遞過程中,如何讓C/C++與C#在類型上統一呢?

C/C++與C#中都有對應的傳入參數和傳出參數,簡稱入參出參。本次通過出參來記錄參數是如何傳遞的、數據如何接收的、數據類型如何統一對應等。

動態庫有個方法可以獲取文件名GetFileName的方法,C#調用以獲取文件名。

環境

Windows 10

Visual Studio 2017

平臺工具集:Visual Studio 2017 (v141)

實現

出參 char * 對應 StringBuilder

  • 打開Melphi.Test.h頭文件,並添加我們的GetFileName函數,代碼如下:
#pragma once

// 宏定義:說明通過 MelphiAPI 聲明的函數爲導出函數,供其他程序調用,作爲動態庫的對外接口函數
#define MelphiAPI _declspec(dllexport) _stdcall

// 獲取一個文件名(字符串)
extern "C" void MelphiAPI GetFileName(char * file);
  • 打開Melphi.Test.cpp文件,添加GetFileName函數的實現,代碼如下:
// Melphi.Test.cpp : 定義 DLL 應用程序的導出函數。
//

#include "stdafx.h"

#include "MelphiTest.h"

// 獲取文件名,並保存在出參裏面
void MelphiAPI GetFileName(char * file)
{
	// 棧上定義
	// char * result =new char[10];

	// TODO:獲取文件名

	// 獲取文件名(堆上)
	char result[10] = { 0 };
	result[0] = 'm';
	result[1] = 'e';
	result[2] = 'l';
	result[3] = 'p';
	result[4] = 'h';
	result[5] = 'i';
	result[6] = '.';
	result[7] = 'c';
	result[8] = '\0';
	result[9] = '\0';

	// 數據移植,填充出參
	strcpy_s(file, strlen(result) + 1, result);
}
  • C#中函數入口的聲明定義,代碼如下:
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace Melphi
{
    public class DllImportCore
    {
        /// <summary>
        /// 獲取字符  傳出參數(C++ char * == C# StringBuilder)
        /// </summary>
        /// <param name="stringBuilder"></param>
        /// <returns></returns>
        [DllImport("cpplib/Melphi.Test.dll", EntryPoint = "GetFileName", CallingConvention = CallingConvention.StdCall)]
        public static extern void GetFileName(StringBuilder filename);
    }
}
  • 函數調用
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;

namespace Melphi
{
    /// <summary>
    /// App.xaml 的交互邏輯
    /// </summary>
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            Console.WriteLine("2+2=" + DllImportCore.Add(2, 2));


            StringBuilder filename = new StringBuilder(10);
            DllImportCore.GetFileName(filename);
            Console.WriteLine("filename:" + filename);
            Console.ReadKey();
        }
    }
}
  • 運行
    在這裏插入圖片描述

出參 自定義結構體 * 對應 ref 自定義結構體

  • 打開Melphi.Test.h頭文件,並添加我們自定義結構體ByteArrayByteItemArray以及GetStructValue函數,代碼如下:

    #pragma once
    
    // 宏定義:說明通過 MelphiAPI 聲明的函數爲導出函數,供其他程序調用,作爲動態庫的對外接口函數
    #define MelphiAPI _declspec(dllexport) _stdcall
    
    // 數組結構體
    typedef struct ByteItemArray
    {
    	char Item[8];
    }ByteItemArray;
    
    // 嵌套數據結構
    typedef struct ByteArray
    {
    	ByteItemArray ByteItem[96];
    }ByteArray;
    
    // 獲取結構體數據
    extern "C" void MelphiAPI GetStructValue(ByteArray *const item);
    
  • 打開Melphi.Test.cpp文件,添加GetStructValue函數的實現,代碼如下:

    // 獲取結構體值
    void MelphiAPI GetStructValue(ByteArray * const item)
    {
    	ByteArray result = { 0 };
    
    	result.ByteItem->Item[0] = 'm';
    	result.ByteItem->Item[1] = 'e';
    	result.ByteItem->Item[2] = 'l';
    	result.ByteItem->Item[3] = 'p';
    	result.ByteItem->Item[4] = 'h';
    	result.ByteItem->Item[5] = 'i';
    
    
    	memcpy(item, &result, sizeof(ByteArray));
    }
    
  • C#中函數入口的聲明定義與對應結構體的定義,代碼如下:

    using System;
    using System.Runtime.InteropServices;
    using System.Text;
    
    namespace Melphi
    {
        public class DllImportCore
        {
            /// 獲取結構體數據  傳出參數(C++ ByteArray * == C# ref ByteArray)
            /// </summary>
            /// <param name="ptr"></param>
            [DllImport("cpplib/Melphi.Test.dll", EntryPoint = "GetStructValue", CallingConvention = CallingConvention.StdCall)]
            public static extern void GetStructValue(ref ByteArray byteArray);
        }
    
        /// <summary>
        /// 字符項
        /// </summary>
        public struct ByteItemArray
        {
            /// <summary>
            /// 字符數組:使用 MarshalAs 指定變量的類型與大小
            /// </summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
            public char[] Item;
        }
    
        /// <summary>
        /// 嵌套數據結構[結構體嵌套結構體]
        /// </summary>
        public struct ByteArray
        {
            /// <summary>
            /// 字符項結構體數組:使用 MarshalAs 指定變量的類型與大小
            /// </summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 96)]
            public ByteItemArray[] ByteItem;
        }
    }
    
  • 函數調用

    using System;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Windows;
    
    namespace Melphi
    {
        /// <summary>
        /// App.xaml 的交互邏輯
        /// </summary>
        public partial class App : Application
        {
            protected override void OnStartup(StartupEventArgs e)
            {
                base.OnStartup(e);
    
                // 聲明並初始化一個數組,用於保存返回的數據
                ByteArray byteArray = new ByteArray();
                System.Text.ASCIIEncoding aSCII = new ASCIIEncoding();
                DllImportCore.GetStructValue(ref byteArray);
                string name = "";
                foreach (var item in byteArray.ByteItem[0].Item)
                {
                    name += item;
                }
                Console.WriteLine("字  符:" + name);
                Console.WriteLine("16進制:" + BitConverter.ToString(aSCII.GetBytes(byteArray.ByteItem[0].Item)));
                
                Console.ReadKey();
                }
        	}
    	}
    }
    
  • 運行

在這裏插入圖片描述

附表

MSDN: 列出了WindowsAPI和C樣式函數中使用的數據類型與C#相應的.NETFramework內置值類型或可在託管代碼中使用的類

總結

動態鏈接庫與C#之間做數據交換時需要對應其數據結構。尤其需要注意鏈接庫中隊指針(如char *)的傳遞與使用,對於指針的傳遞,除開以上提到的方式,還可以使用C#的指針(Intptr)進行數據轉換,後面一章我們對其進行說明。


Over

每次記錄一小步…點點滴滴人生路…

發佈了52 篇原創文章 · 獲贊 10 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章