《C# 语言入门 - 学习笔记 - C#调C/C++(Dll)方法总结》

文章目录

  • 前言
  • 一、基础知识
    • 1. 结构体对齐
      • 1. 内存对齐
      • 2. 调用约定
      • 3. C#与C/C++类型对应关系
    • 2. 创建并调用动态链接库
    • 3. Dllimport常用参数
  • 二、实例调用
    • 1. C#与Dll链接库的数据交换
      • 1. 基本数据类型
      • 2. 数组(引用传递)
      • 3. 结构体
    • 2. C++回调C#


前言

  • 视频资源:B站:C#与C/C++动态链接库
  • 本篇对C#与C/C++动态链接库做一个小结。

一、基础知识

1. 结构体对齐

1. 内存对齐

  • 定义: 计算机系统对基本数据类型合法地址做出一些限制,要求某些类型地址必须是某个值K的倍数
  • 目的: 简化处理器喝存储器系统之间的接口硬件设计,方便存储器操作读取或写入;
  • 规则:
  1. 结构体的数据成员,第一个成员的偏移量为0,后面每个数据成员存储的起始位置要从自己大小的整数倍开始;
  2. 子结构体中的第一个成员偏移量应当是子结构体中最大成员的整数倍
  3. 结构体总大小必须是其内部最大成员的整数倍
#include 
#define FIELDOFFSET(TYPE,MEMBER)(int)(&(((TYPE*)0)->MEMBER))
//定义判断字符的字节方法

#pragma pack(push)	//入栈 (先进后出)
#pragma pack(1)	//设置字节对齐方式,1字节对齐(一个接一个,紧挨着)
struct Info 
{
	char username[10];   //24-34
	double userdata;	 //40-48(8的倍数)
};	
#pragma pack(pop)	//出栈

struct Frame			 
{						 //字节:
	unsigned char id;	 //0-1
	int width;			 //4-8
	long long height;	 //8-16
	unsigned char* data; //16-20
	Info info;			 //24-
};
int main()
{
	int len = sizeof(Frame);//result:48
	int len2 = sizeof(Info);//result:24;	设置出入栈后字节为18
	int offset_width = FIELDOFFSET(Frame, width);//result:4;	FIELDOFFSET用来判断字符所占字节
	int offset_longlong = FIELDOFFSET(Frame, height); //result:8;
	return 0;
}
  • 查看结构体偏移量方法:#define FIELDOFFSET(TYPE,MEMBER)(int)(&(((TYPE*)0)->MEMBER))

2. 调用约定

  • _cdecl: C调用约定,参数从 右至左的方式入栈,函数本身不清理栈,由调用者负责,故,允许可变参数函数存在;
  • _stdcall: 标准调用约定,参数按照从 右至左的方式入栈,函数本身清理栈
    《C# 语言入门 - 学习笔记 - C#调C/C++(Dll)方法总结》_第1张图片

3. C#与C/C++类型对应关系

  • 说明:ubyte<—>char;byte<—>unsigned char
    《C# 语言入门 - 学习笔记 - C#调C/C++(Dll)方法总结》_第2张图片

2. 创建并调用动态链接库

  1. 新建文件夹,存放项目(src 源码文件夹)
    《C# 语言入门 - 学习笔记 - C#调C/C++(Dll)方法总结》_第3张图片
    《C# 语言入门 - 学习笔记 - C#调C/C++(Dll)方法总结》_第4张图片

  2. 创建C# 项目(.Framework)

  • 设置输出目录、目标平台(x64)
    《C# 语言入门 - 学习笔记 - C#调C/C++(Dll)方法总结》_第5张图片
  • 启用本地代码调试(方便调试跳转dll文件):
    《C# 语言入门 - 学习笔记 - C#调C/C++(Dll)方法总结》_第6张图片
  • 链接Dll示例:
    《C# 语言入门 - 学习笔记 - C#调C/C++(Dll)方法总结》_第7张图片
  1. 创建C++项目
  • 设置输出/调试目录
    《C# 语言入门 - 学习笔记 - C#调C/C++(Dll)方法总结》_第8张图片
  • 链接Dll示例:
    《C# 语言入门 - 学习笔记 - C#调C/C++(Dll)方法总结》_第9张图片
  1. 创建Dll动态链接库
  • 取消预编译头
    《C# 语言入门 - 学习笔记 - C#调C/C++(Dll)方法总结》_第10张图片

  • 输出目录
    《C# 语言入门 - 学习笔记 - C#调C/C++(Dll)方法总结》_第11张图片

  • 配置预编译、宏定义
    《C# 语言入门 - 学习笔记 - C#调C/C++(Dll)方法总结》_第12张图片
    《C# 语言入门 - 学习笔记 - C#调C/C++(Dll)方法总结》_第13张图片

  1. 配置项目依赖项
    《C# 语言入门 - 学习笔记 - C#调C/C++(Dll)方法总结》_第14张图片
  • 重新生成解决方案:
    《C# 语言入门 - 学习笔记 - C#调C/C++(Dll)方法总结》_第15张图片

3. Dllimport常用参数

《C# 语言入门 - 学习笔记 - C#调C/C++(Dll)方法总结》_第16张图片

二、实例调用

1. C#与Dll链接库的数据交换

  • C/C++基本数据字节:
    《C# 语言入门 - 学习笔记 - C#调C/C++(Dll)方法总结》_第17张图片

1. 基本数据类型

  • 值传递(C# ----> C/C++)
  1. Dll代码(.h / .cpp)
#pragma once
//预编译
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif // __cplusplus

//宏定义
#ifdef DLL_IMPORT
#define HEAD EXTERNC __declspec(dllimport)
#else
#define HEAD EXTERNC __declspec(dllexport)
#endif // DLL_IMPORT

#define CallingConvention _cdecl

//示例方法
HEAD void CallingConvention Test_BasicData(char d1,short d2,int d3,long long d4,float d5,double d6);
#include "Native.h"
#include 

HEAD void CallingConvention Test_BasicData(char d1, short d2, int d3, long long d4, float d5, double d6)
{
	d1, d2, d3, d4, d5, d6;
}
  1. C#代码
using System;
using System.Runtime.InteropServices;

namespace CallNativeDllCSharp
{
    class Program
    {
        [DllImport("NativeDll.dll",CallingConvention = CallingConvention.Cdecl)]
        public static extern void Test_BasicData(sbyte d1, short d2, int d3, long d4, float d5, double d6);
        static void Main(string[] args)
        {
            Test_BasicData(-56,128,446,11223344,12.5f,3.1415926d);
            Console.Read();
        }
    }
}
  • 引用传递(C# < ---- > C/C++ ;若C/C++有值,则数据是由C/C++ ----> C#)
  1. Dll代码(.h / .cpp)–两种方式(地址或指针)
#pragma once
//预编译
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif // __cplusplus

//宏定义
#ifdef DLL_IMPORT
#define HEAD EXTERNC __declspec(dllimport)
#else
#define HEAD EXTERNC __declspec(dllexport)
#endif // DLL_IMPORT

#define CallingConvention _cdecl

//方式一,地址
HEAD void CallingConvention Test_BasicDataRef(char &d1, short &d2, int &d3, long long &d4, float &d5, double &d6);
//方式二,指针
HEAD void CallingConvention Test_BasicDataPointer(char* d1, short* d2, int* d3, long long* d4, float* d5, double* d6);
#include "Native.h"
#include 


HEAD void CallingConvention Test_BasicDataRef(char& d1, short& d2, int& d3, long long& d4, float& d5, double& d6)
{
	d1 = 1, d2 = 2, d3 = 3, d4 = 4, d5 = 5.5f, d6 = 6.6;
}

HEAD void CallingConvention Test_BasicDataPointer(char* d1, short* d2, int* d3, long long* d4, float* d5, double* d6)
{
	*d1 = 100, *d2 = 200, *d3 = 300, *d4 = 400, *d5 = 500.5f, *d6 = 600.6;
}

  1. C#代码
using System;
using System.Runtime.InteropServices;

namespace CallNativeDllCSharp
{
    class Program
    {

        [DllImport("NativeDll.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void Test_BasicDataRef(ref sbyte d1, ref short d2, ref int d3, ref long d4, ref float d5, ref double d6);

        [DllImport("NativeDll.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void Test_BasicDataPointer(ref sbyte d1, ref short d2, ref int d3, ref long d4, ref float d5, ref double d6);
        static void Main(string[] args)
        {
            sbyte num1 = 10;
            short num2 = 20;
            int num3 = 30;
            long num4 = 40;
            float num5 = 50.5f;
            double num6 = 60.6;
            //Test_BasicDataRef(ref num1, ref num2, ref num3, ref num4, ref num5, ref num6);
            Test_BasicDataPointer(ref num1, ref num2, ref num3, ref num4, ref num5, ref num6);
            Console.Read();
        }
    }
}

2. 数组(引用传递)

  1. Dll代码(.h / .cpp)
#pragma once
//预编译
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif // __cplusplus

//宏定义
#ifdef DLL_IMPORT
#define HEAD EXTERNC __declspec(dllimport)
#else
#define HEAD EXTERNC __declspec(dllexport)
#endif // DLL_IMPORT

#define CallingConvention _cdecl

//示例方法
//C# 传值至C++的方法,数组使用指针传值
HEAD void CallingConvention Test_BasicDataArr(int* d1, char* d2);
//C++ 回传给C#,返回类型为指针
HEAD void* CallingConvention Test_BasicDataArrRet();
#include "Native.h"
#include 


HEAD void CallingConvention Test_BasicDataArr(int* d1, char* d2)
{
	int num1[3];
	char num2[6];
	memcpy(num1, d1, sizeof(num1));
	memcpy(num2, d2, sizeof(num2));
}

char str[6] = "test1";
HEAD void* CallingConvention Test_BasicDataArrRet()
{
	return &str;
}

  1. C# 代码
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace CallNativeDllCSharp
{
    class Program
    {

        [DllImport("NativeDll.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void Test_BasicDataArr(int[] arry1,string arry2);

        [DllImport("NativeDll.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr Test_BasicDataArrRet();
        static void Main(string[] args)
        {
            //C# 传值至C++
            int[] d1 = new int[3]{10,20,30};
            string d2 = "test2";
            Test_BasicDataArr(d1, d2);

            //C++ 回传给C#
            byte[] d3 = new byte[10];
            IntPtr strPtr = Test_BasicDataArrRet();
            Marshal.Copy(strPtr,d3,0,10);
            string d32str = Encoding.Default.GetString(d3);

            Console.Read();
        }
    }
}

3. 结构体

  • 普通传值与回传:(需在C#中构造对应的结构体)
  1. Dll代码(.h / .cpp)
#pragma once
//预编译
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif // __cplusplus

//宏定义
#ifdef DLL_IMPORT
#define HEAD EXTERNC __declspec(dllimport)
#else
#define HEAD EXTERNC __declspec(dllexport)
#endif // DLL_IMPORT

#define CallingConvention _cdecl

struct ChildStruct
{	
	int num;
	char describ[10];
	double pi;
};
struct StructA
{
	short id;
	char name[10];
	int number;
	ChildStruct cs;
};
//示例方法
//C# 传值至C++的方法,数组使用指针传值
HEAD void CallingConvention Test_Struct(StructA parameter);
//C++ 回传给C#,返回类型为指针
HEAD void* CallingConvention Test_StructRet();
#include "Native.h"
#include 


HEAD void CallingConvention Test_Struct(StructA parameter)
{
	parameter.id = 123;
}

StructA structa;

HEAD void* CallingConvention Test_StructRet()
{
	structa.id = 2;
 	char name[10] =  "peter";
	memcpy(structa.name, name, sizeof(structa.name));
	structa.number = 2;
	structa.cs.num = 2;
	return &structa;
}
  1. C# 代码
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace CallNativeDllCSharp
{
    class Program
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct ChildStruct
        {
            public int num;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
            public string describ;
            public double pi;
        };
        [StructLayout(LayoutKind.Sequential)]
        public struct StructA
        {
            public short id;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
            public string name;
            public ChildStruct cs;
        };

        [DllImport("NativeDll.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void Test_Struct(ref StructA parameter);//加ref为引用传递,否则为值传递

        [DllImport("NativeDll.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr Test_StructRet();
        static void Main(string[] args)
        {
            //C# 传值至C++
            StructA structA = new StructA();
            structA.id = 1;
            structA.name = "Tom";
            structA.cs.num = 10;
            structA.cs.describ = "cool";
            structA.cs.pi = 1.2;

            Test_Struct(ref structA);

            //C++ 回传给C#
            byte[] d3 = new byte[10];
            IntPtr structPtr = Test_StructRet();
            StructA restructA = Marshal.PtrToStructure<StructA>(structPtr);
            int reid = restructA.id;

            Console.Read();
        }
    }
}
  • 计算偏移量回传:(无需在C#中构造结构体)
  1. Dll代码(.h / .cpp)
#pragma once
//预编译
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif // __cplusplus

//宏定义
#ifdef DLL_IMPORT
#define HEAD EXTERNC __declspec(dllimport)
#else
#define HEAD EXTERNC __declspec(dllexport)
#endif // DLL_IMPORT

#define CallingConvention _cdecl

struct FrameInfo
{	
	char username[10];//0-10
	double pts;//16-24
};
struct Frame
{
	int width;//0-4
	int height;//4-8
	int format;//8-12
	int linesize[4];//12-28
	unsigned char* data[4];//32-64
	FrameInfo* info;//64-72
};
//示例方法
//C++ 回传给C#,返回类型为指针
HEAD void* CallingConvention Test_StructRet();
#include "Native.h"
#include 


Frame frame;
FrameInfo info;
HEAD void* CallingConvention Test_StructRet()
{
	frame.width = 1920;
	frame.height = 1080;
	frame.format = 0;

	for (int i = 0; i < 4; i++)
	{
		frame.linesize[i] = i * 100;
		frame.data[i] = new unsigned char[10];
		for (int j = 0; j < 10; j++)
		{
			frame.data[i][j] = i;
		}
	}
	
	info.pts = 12.5;
	memset(info.username, 0, 10);
	memcpy(info.username, "Test", sizeof(info.username));
	frame.info = &info;

	return &frame;
}

  1. C# 代码
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace CallNativeDllCSharp
{
    class Program
    {
        [DllImport("NativeDll.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr Test_StructRet();
        static void Main(string[] args)
        {
            //C++ 回传给C#,计算偏移量回传
            IntPtr ptr = Test_StructRet();

            int width = Marshal.ReadInt32(ptr, 0);
            int height = Marshal.ReadInt32(ptr, 4);
            int format = Marshal.ReadInt32(ptr, 8);
            int[] linesize = new int[4];
            Marshal.Copy(new IntPtr(ptr.ToInt64()+12), linesize,0,4);
            
            IntPtr[] datas = new IntPtr[4];
            Marshal.Copy(new IntPtr(ptr.ToInt64() + 32),datas,0,4);

            for (int i = 0; i < 4; i++)
            {
                byte[] temp = new byte[10];
                Marshal.Copy(datas[i],temp,0,10);
            }

            IntPtr infoptr = Marshal.ReadIntPtr(ptr, 64);
            byte[] username = new byte[20];
            Marshal.Copy(infoptr,username,0,10);
            string str = Encoding.ASCII.GetString(username);

            double pts = BitConverter.ToDouble(BitConverter.GetBytes(Marshal.ReadInt64(infoptr, 16)),0);
            Console.Read();
        }
    }
}

2. C++回调C#

  • 视频资源:B站:C++回调C#、传递可变参数

你可能感兴趣的:(学习笔记,c#,c++,dll)