C#调用C++详解
内存对齐规则
结构体大小计算
- 结构体的数据成员,第一个成员的偏移量为0,后面每个数据成员存储的起始位置要从自己大小的整数倍开始。
- 子结构体中的第一个成员偏移量应当是子结构体中最大成员的整数倍
- 结构体总大小必须是其内部最大成员的整数倍
struct MyStruct1
{
unsigned char id;//0-1
int width;//4-8
long long height;//8-16
unsigned char* data;//16-20
};
struct MyStruct2
{
char username[10];//0-10
int width;//12-16
};
struct MyStruct3
{
int width;//0-4
double height;//8-16
unsigned char* data;//16-20
MyStruct2 myStruct2;//20-30 32-36 最大成员8的整数倍40
};
int main()
{
int size1 = sizeof(MyStruct1); //24
int size2 = sizeof(MyStruct2); //16
int size3 = sizeof(MyStruct3); //40
return 0;
}
使用宏定义字段偏移量验证
#define FIELDOFFSET(TYPE,MEMBER) (int)(&(((TYPE*)0)->MEMBER))//宏定义字段偏移量
int main()
{
int size1 = sizeof(MyStruct1); //24
int size2 = sizeof(MyStruct2); //16
int size3 = sizeof(MyStruct3); //40
int width= FIELDOFFSET(MyStruct3, width);//0
int height= FIELDOFFSET(MyStruct3, height);//8
int data= FIELDOFFSET(MyStruct3, data);//16
int myStruct2= FIELDOFFSET(MyStruct3, myStruct2);//20
return 0;
}
结构体对齐方式
///结构体中的对齐方式
#pragma pack(push)
#pragma pack(1)
struct MyStruct4
{
int width;//0-4
double height;//4-12
unsigned char* data;//12-16
};
#pragma pack(pop)
C#中的示例
public struct TestStruct1
{
public byte id;
public int width;
public double height;
public int num;
}
[StructLayout(LayoutKind.Explicit)]
public struct TestStruct2
{
[FieldOffset(0)]
public byte id;
[FieldOffset(10)]
public int width;
[FieldOffset(20)]
public double height;
[FieldOffset(30)]
public int num;
}
[StructLayout(LayoutKind.Sequential,Pack =1)]
public struct TestStruct3
{
public byte id;
public int width;
public double height;
public int num;
}
class Program
{
static void Main(string[] args)
{
TestStruct1 testStruct1 = new TestStruct1();
TestStruct2 testStruct2 = new TestStruct2();
TestStruct3 testStruct3 = new TestStruct3();
int len1 = Marshal.SizeOf(testStruct1);//24
int len2 = Marshal.SizeOf(testStruct2);//40
int len3 = Marshal.SizeOf(testStruct3);//17
Console.ReadKey();
}
}
C#与C/C++之间类型的对应关系
C# | C/C++ |
---|---|
ubyte | char |
byte | unsigned char |
short | short |
int32 | int32_t |
long | int64_t |
float | float |
double | double |
IntPtr,[] | void* |
动态链接库调用
动态链接库的制作
- 使用VS新建一个C++动态链接库的空项目;
- 右击属性–>配置属性–>C/C+±->预编译头–预编译头改为不使用预编译头–>应用
- 添加自己写的cpp和h文件,并删除原先自带的头文件和CPP文件
- 编写.h文件
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif
#ifdef DLL_IMPORT
#define HEAD EXTERNC __declspec(dllimport)
#else
#define HEAD EXTERNC __declspec(dllexport)
#endif
#define CallingConvention _cdecl
HEAD void CallingConvention Test1();
- 编写.cpp文件
#include "Native.h"
#include <iostream>
#include <windows.h>
HEAD void CallingConvention Test1()
{
printf("call success\n");
}
- 设置动态链接库输出目录
- 生成项目,至此动态链接库创建完成
C++使用动态链接库
- 新建一个C++的空项目,并写好main.cpp文件
- 配置输出目录和工作目录与动态链接库在一个文件夹里
- 编写main.cpp文件
#include <stdio.h>
#include <stdarg.h>
#define DLL_IMPORT
#include "../NactiveDll/Native.h"
#pragma comment(lib, "../bin/Nactivedll.lib")
int main()
{
Test1();
return 0;
}
- 点击运行就可以看到控制台输出动态链接库里面输出的内容了
C语言调用动态链接库
只需要将.cpp改成.c将C++的特有的头文件改成C的即可
C#调用动态链接库
- 新建一个控制台的C#项目,配置属性改成X64位(与动态链接库的对应)
- 修改输出路径到生成的动态链接库路径
- 编写C#代码并运行即可
[DllImport("NactiveDll.dll")]
public static extern void Test1();
static void Main(string[] args)
{
Test1();
Console.ReadKey();
}
- 调试动态链接库—>右击属性–>调试–>启用本地代码调试
Dllimport 详解
相关参数介绍
- dllName: 动态链接库名称
- CallingConvention: 调用约定(C语言的调用约定和标准的调用约定)
- CharSet: 设置字符串编码格式
- EntryPoint: 函数入口名称,默认使用方法本身的名称
- ExactSpelling: 指示EntryPoint是否必须与指示的入口点的拼写完全匹配,默认为true,如果为false会根据CharSet查找对应入口函数的A版本或者W版本。找不到再去找入口函数
- SetLastError: 指示方法是否保留Win32“上一错误”。用默认值false,Win32错误是否设置到调用者线程中,C#中通过Marshal.GetLastWin32Error()获取错误码,设置true则可以获取
CharSet,默认格式Ansi(ASSIC)
//.h
HEAD void CallingConvention TestCharSet(const char* log);
//.cpp
HEAD void CallingConvention TestCharSet(const char* log)
{
printf("log:%s\n", log);
}
//C#调用
[DllImport("NactiveDll.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void TestCharSet(string log);
//调用
TestCharSet("Test");
TestCharSet("测试");
EntryPoint: 函数入口名称,默认使用方法本身的名称
//.h
HEAD void CallingConvention TestEntryPoint(const char* log);
//.cpp
HEAD void CallingConvention TestEntryPoint(const char* log)
{
printf("log:%s\n", log);
}
//C#调用
[DllImport("NactiveDll.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "TestEntryPoint")]
public static extern void TestEntryPoint2(string log);
//调用
TestEntryPoint2("测试EntryPoint");
ExactSpelling
//.h
HEAD void CallingConvention TestLogA(const char* log);
HEAD void CallingConvention TestLogW(const char* log);
//.cpp
HEAD void CallingConvention TestLogA(const char* log)
{
printf("logA:%s\n", log);
}
HEAD void CallingConvention TestLogW(const char* log)
{
printf("logW:%s\n", log);
}
//C#调用
[DllImport("NactiveDll.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "TestLog",ExactSpelling =true)]
public static extern void TestLog(string log);
//调用
TestLog("测试ExactSpelling");//如果ExactSpelling =true,则会匹配EntryPoint = "TestLog",因为动态链接库中没写TestLog,只有TestLogA和TestLogW,所以会报错
//C#调用
[DllImport("NactiveDll.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "TestLog",ExactSpelling =false,CharSet =CharSet.Unicode)]
public static extern void TestLog(string log);
//调用
TestLog("测试ExactSpelling");//如果ExactSpelling =false,则会根据CharSet的编码格式来,如果CharSet =CharSet.Unicode,则调用TestLogW,否则会调用TestLogA
///如果有TestLog函数,则不管ExactSpelling 设置为false还是true都会调用TestLog函数而不是TestLogA或者TestLogB
SetLastError 设置true则可以获取指示方法保留Win32“上一错误”
//.h
HEAD void CallingConvention TestSetLastError(const char* log);
//.cpp
HEAD void CallingConvention TestSetLastError(const char* log)
{
SetLastError(3);
printf("log:%s\n", log);
}
//C#调用
[DllImport("NactiveDll.dll", CallingConvention = CallingConvention.Cdecl, SetLastError =true)]
public static extern void TestSetLastError(string log);
//调用
int ret = Marshal.GetLastWin32Error();//0
TestSetLastError("测试SetLastError");
ret = Marshal.GetLastWin32Error();//3
基本数据传递和函数返回值
返回值
//.h
HEAD float CallingConvention Test_Add(float num1, float num2);
//.cpp
HEAD float CallingConvention Test_Add(float num1, float num2)
{
return num1 + num2;
}
//C#调用
[DllImport("NactiveDll.dll", CharSet = CharSet.Ansi)]
public static extern float Test_Add(float num1,float num2);
//调用
float f = Test_Add(1.1f,2.2f);
地址引用
//.h
HEAD void CallingConvention Test_BasicDataRef(char& d1, short& d2, int& d3, long long& d4, float& d5, double& d6);
//.cpp
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;
}
//C#调用
[DllImport("NactiveDll.dll", CharSet = CharSet.Ansi)]
public static extern float Test_BasicDataRef(ref sbyte d1, ref short d2, ref int d3, ref long d4, ref float d5, ref double d6);
//调用
sbyte d1=0; short d2 = 0; int d3 = 0; long d4 = 0; float d5 = 0; double d6 = 0;
Test_BasicDataRef(ref d1,ref d2,ref d3,ref d4,ref d5,ref d6);
指针引用
//.h
HEAD void CallingConvention Test_BasicDataPointer(char* d1, short* d2, int* d3, long long* d4, float* d5, double* d6);
//.cpp
HEAD void CallingConvention Test_BasicDataPointer(char* d1, short* d2, int* d3, long long* d4, float* d5, double* d6)
{
*d1 = 10;
*d2 = 20;
*d3 = 30;
*d4 = 40;
*d5 = 15.5f;
*d6 = 16.6;
}
//C#调用
[DllImport("NactiveDll.dll", CharSet = CharSet.Ansi)]
public static extern float Test_BasicDataRef(ref sbyte d1, ref short d2, ref int d3, ref long d4, ref float d5, ref double d6);
//调用
sbyte d1=0; short d2 = 0; int d3 = 0; long d4 = 0; float d5 = 0; double d6 = 0;
Test_BasicDataPointer(ref d1, ref d2, ref d3, ref d4, ref d5, ref d6);
结构体的解析之Marshal.Read
//.h
HEAD void* CallingConvention Test_StructRet();
//.cpp
struct FrameInfo
{
char username[20];
double pts;
};
struct Frame
{
int width;
int height;
int format;
int linesize[4];
unsigned char* data[4];
FrameInfo* info;
};
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] = 100 * i;
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, 20);//字符串置空
memcpy(info.username, "hello world", strlen("hello world"));
frame.info = &info;
return &frame;
}
//C#调用
[DllImport("NactiveDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr Test_StructRet();
//调用
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, linesize.Length);
IntPtr[] datas = new IntPtr[4];//读取指针数组
Marshal.Copy(new IntPtr(ptr.ToInt64()+32), datas, 0,datas.Length);
for (int i = 0; i < datas.Length; i++)
{
byte[] temp = new byte[10];
Marshal.Copy(datas[i],temp,0,temp.Length);
}
IntPtr frameinfo = Marshal.ReadIntPtr(ptr,64);
byte[] username = new byte[20];
Marshal.Copy(frameinfo,username,0,username.Length);
string str = Encoding.ASCII.GetString(username);
double pts =BitConverter.Int64BitsToDouble(Marshal.ReadInt64(frameinfo,24));
结构体解析之整体读取
//.h
HEAD void* CallingConvention Test_StructRet();
//.cpp
struct FrameInfo
{
char username[20];
double pts;
};
struct Frame
{
int width;
int height;
int format;
int linesize[4];
unsigned char* data[4];
FrameInfo* info;
};
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] = 100 * i;
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, 20);//字符串置空
memcpy(info.username, "hello world", strlen("hello world"));
frame.info = &info;
return &frame;
}
//C#调用
[DllImport("NactiveDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr Test_StructRet();
[StructLayout(LayoutKind.Sequential)]
struct FrameInfo
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
public string username;
public double pts;
};
[StructLayout(LayoutKind.Sequential)]
struct Frame
{
public int width;
public int height;
public int format;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public int[] linesize;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.SysUInt)]
public IntPtr[] data;
public IntPtr info;
};
//调用
IntPtr ptr = Test_StructRet();
var value = Marshal.PtrToStructure<Frame>(ptr);
for (int i = 0; i < value.data.Length; i++)
{
byte[] temp = new byte[10];
Marshal.Copy(value.data[i], temp, 0, temp.Length);
}
byte[] username = new byte[20];
Marshal.Copy(value.info, username, 0, username.Length);
string str = Encoding.ASCII.GetString(username);
double pts = BitConverter.Int64BitsToDouble(Marshal.ReadInt64(value.info, 24));
结构体转换工具的使用
- 开源git地址:https://github.com/thrixton/clrinterop
- 下载下来编译PInvokeTool下面的WindowsToolLauncher项目
- 编译完成后打开应用程序输入C++结构体就会自动生成C#对应得结构体数据
- 生成得结果如下