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
    {
        /// 
        /// 获取字符  传出参数(C++ char * == C# StringBuilder)
        /// 
        /// 
        /// 
        [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
{
    /// 
    /// App.xaml 的交互逻辑
    /// 
    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();
        }
    }
}
  • 运行
    C# 使用C/C++动态链接库(dll) ——参数传递&类型移植_第1张图片

出参 自定义结构体 * 对应 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)
            /// 
            /// 
            [DllImport("cpplib/Melphi.Test.dll", EntryPoint = "GetStructValue", CallingConvention = CallingConvention.StdCall)]
            public static extern void GetStructValue(ref ByteArray byteArray);
        }
    
        /// 
        /// 字符项
        /// 
        public struct ByteItemArray
        {
            /// 
            /// 字符数组:使用 MarshalAs 指定变量的类型与大小
            /// 
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
            public char[] Item;
        }
    
        /// 
        /// 嵌套数据结构[结构体嵌套结构体]
        /// 
        public struct ByteArray
        {
            /// 
            /// 字符项结构体数组:使用 MarshalAs 指定变量的类型与大小
            /// 
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 96)]
            public ByteItemArray[] ByteItem;
        }
    }
    
  • 函数调用

    using System;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Windows;
    
    namespace Melphi
    {
        /// 
        /// App.xaml 的交互逻辑
        /// 
        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();
                }
        	}
    	}
    }
    
  • 运行

C# 使用C/C++动态链接库(dll) ——参数传递&类型移植_第2张图片

附表

MSDN: 列出了WindowsAPI和C样式函数中使用的数据类型与C#相应的.NETFramework内置值类型或可在托管代码中使用的类

总结

动态链接库与C#之间做数据交换时需要对应其数据结构。尤其需要注意链接库中队指针(如char *)的传递与使用,对于指针的传递,除开以上提到的方式,还可以使用C#的指针(Intptr)进行数据转换,后面一章我们对其进行说明。


Over

每次记录一小步…点点滴滴人生路…

你可能感兴趣的:(C#,.Net)