C# 使用C/C++动态链接库(dll) ——指针 IntPtr

需求

C/C++程序中,最经典的就数指针了,如果对指针没有一定认识,那么C/C++语言的学习深度还不够。因此在一般的C/C++程序开发中使用指针作为参数传递也尤为普遍,我们前面的示例中,使用到的char* ByteArray* 都是指针。本节使用C#的IntPtr对接口参数进行定义。

环境

Windows 10

Visual Studio 2017

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

实现

char * 与 String

  • 打开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# IntPtr)
        /// 
        /// 
        /// 
        [DllImport("cpplib/Melphi.Test.dll", EntryPoint = "GetFileName", CallingConvention = CallingConvention.StdCall)]
        public static extern void GetFileName(IntPtr ptr);
    }
}
  • 函数调用
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);
            
            // IntPtr 与 string之间的转换
            string txt = "hah";
            IntPtr ptr = Marshal.StringToHGlobalAnsi(txt);
            DllImportCore.GetFileName(ptr);
            // 把 IntPtr 指针指向的内存数据字符串获取出来
            string rest = Marshal.PtrToStringAnsi(ptr);
            // 释放 ptr 的内存
            Marshal.FreeHGlobal(ptr);
            Console.WriteLine("intprt data:" + rest);

            Console.ReadKey();
        }
    }
}
  • 运行
    C# 使用C/C++动态链接库(dll) ——指针 IntPtr_第1张图片

自定义结构体指针 与 自定义结构体

  • 打开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# IntPtr)
            /// 
            /// 
            [DllImport("cpplib/Melphi.Test.dll", EntryPoint = "GetStructValue", CallingConvention = CallingConvention.StdCall)]
            public static extern void GetStructValue(IntPtr ptr);
        }
    
        /// 
        /// 字符项
        /// 
        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();
                string name = "";
                
                  // 获取结构体占用空间的大小
                // 声明一个相同大小的内存空间
                IntPtr myintPtr = Marshal.AllocHGlobal(Marshal.SizeOf(byteArray));
                // IntPtr->Struct
                Marshal.StructureToPtr(byteArray, myintPtr, true);
                DllImportCore.GetStructValue(myintPtr);
                // Struct->IntPtr
                byteArray = (ByteArray)Marshal.PtrToStructure(myintPtr, typeof(ByteArray));
    
                // 释放 intPtr 内存
                Marshal.FreeHGlobal(myintPtr);
                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) ——指针 IntPtr_第2张图片

总结

根据上面的实验,我们发现:C/C++的导出函数没有发生任何变化,只是C#是用来IntPtr指针对参数进行了封装与解析。虽然代码更多更复杂(设计数据结构的转换、指针内存的释放),加大了代码风险。但是使用指针会减少出现内存泄漏之类的事故,因为C/C++与C#都使用了相同(.NET框架中Marshal进行转换的)的指针接口。

如果需要更好的使用好非托管代码,请参考MSDN上的文档说明。


Over

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

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