需求
在我們的方法調用也經常會遇到參數傳遞的情況,在傳遞過程中,如何讓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
頭文件,並添加我們自定義結構體ByteArray
與ByteItemArray
以及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
每次記錄一小步…點點滴滴人生路…