01
将C++ SDK 集成到 IDE 中
以下是在 Microsoft Visual Studio 平台下 SDK 的集成。
2.1 Visual Studio 平台下 C/C++环境配置及集成到 IDE 中
xxx.lib 和 xxx.dll 适合在 Windows 操作系统平台使用,这里以 VS2022 环境为例。
2.1.1 C/C++ 工程环境配置与集成
1、C# SDK 接口文件包含
a、将 a.h,b.h,c.h,
a.cpp 拷贝到用户指定路径下。
b、点击项目 - 属性 - C/C++ -常规。在附加包含目录下指定头文件的路径。
2、添加库引用和库的路径
a 、 将 提 供 的 a_sdk.dll , b.dll 拷 贝 到 程 序 运 行 的 目 录 下 ; 将
a.lib 拷贝到用户指定路径下。
b、点击项目 - 属性 - 链接器 - 常规。在附加库目录中指定 a.lib 的路
径。
02
2.1.2 在用户 C/C++ 项目中使用 SDK
以下为获取数据的简单步骤,更详细的使用方法可以参考提供的 Demo 程序。
添加头文件,添加库以后,将dll放在可执行目录,即可调用外部SDK动态库使用。
03
2.2 Visual Studio 平台下 C#环境配置及集成到 IDE 中
CSharp_Lib 适合在 Windows 操作系统平台使用,这里以 VS2022 环境为例。
2.2.1 C# 工程环境配置与集成
1、工程文件包含
将 CSharp_Lib 文件夹拷贝到工程目录下,并把 dll 文件夹相对应 x64 和 x86
的 aa.dll 和bb_sdk.dll 拷贝到工程目录的运行目录下。
注意:将C++ DLL给C#上位机使用,有很多语法需要转换,这个我们会详细介绍。
04
C#为例
将C++ 的动态库依赖的头文件和源文件转为.cs格式。
C#需要的模块
using System;
:引用了System
命名空间,这是C#中最基础的命名空间,包含了基本的类和功能,如Console
类、Math
类等。using System.Collections.Generic;
:引用了System.Collections.Generic
命名空间,这个命名空间包含了泛型集合类,如List<T>
、Dictionary<TKey, TValue>
等,这些类在处理数据集合时非常有用。using System.Linq;
:引用了System.Linq
命名空间,这个命名空间包含了LINQ(Language Integrated Query)相关的类和功能,LINQ允许在C#中编写查询语句来操作数据集合。using System.Text;
:引用了System.Text
命名空间,这个命名空间包含了处理字符串和文本的类,如StringBuilder
类。using System.Threading.Tasks;
:引用了System.Threading.Tasks
命名空间,这个命名空间包含了异步编程相关的类和功能,如Task
类和async
/await
关键字。
代码格式
namespace DEVICE
{
classParameterDefine
{
//代码实现
public const string a = "123";
}
}
枚举格式
public enum StatusTypeDef
{
RED;
}
使用C++动态库的头文件的结构体的中含有字符串,需要进行转化。
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
这是一个特性,用于指定结构体在内存中的布局方式以及如何处理字符串数据。
LayoutKind.Sequential
表示结构体中的元素在内存中按它们在代码中定义的顺序进行布局,不会被打乱。这对于与非托管代码(如C++编写的DLL)进行交互非常重要,以确保数据在内存中的排列方式匹配。
CharSet = CharSet.Ansi
指定了结构体中的字符串使用ANSI字符集。ANSI字符集是一种单字节字符集,通常用于支持英语和其他西欧语言。这对于与需要ANSI字符串的非托管代码进行交互是有必要的。如果需要支持Unicode字符集,则应使用
CharSet = CharSet.Unicode
。这里使用ANSI是为了确保与SDK中的函数参数类型匹配。
使用C++动态库的头文件的字符数组,需要进行转化。如char a[32];
C++ 字符数组转为字符串
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
:[MarshalAs(...)]
: 这是一个C#中的特性(attribute),用于指定字段在封送到非托管代码时应如何处理。它属于
System.Runtime.InteropServices
命名空间,因此在使用之前需要引入该命名空间。UnmanagedType.ByValTStr
: 这个值指定字段应该被封送为固定长度的ANSI字符数组。ANSI字符通常是单字节字符,适合处理不包含Unicode字符的数据。
SizeConst = 32
: 这个值指定了字段在非托管代码中的大小(以字符为单位)。在这里,
SizeConst = 32
表示字符数组的长度为32个字符(包括结束符’\0’)。
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
//设备IP地址和端口号
public struct DeviceNetPara_t
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string ip_addr;
public UInt32 port;
}
C++ char*转为字符数组
特性(Attributes): 特性是用于向程序中的类型、方法或属性添加元数据的方式。C#中的特性可以用于编译时检查以及运行时的行为修改。
StructLayout: 这是一个特性,用于指定结构体在内存中的布局方式。它有几个参数可以配置,这里主要用到了
LayoutKind.Sequential
、CharSet
和Pack
。LayoutKind.Sequential: 这个参数指定结构体中的字段按照它们在代码中声明的顺序存储在内存中。这与C/C++中的结构体布局方式一致。
CharSet = CharSet.Ansi: 这个参数用于指定结构体中字符串字段的字符编码方式。
CharSet.Ansi
表示使用ANSI字符集来存储字符串。这意味着每个字符占用一个字节,适用于ASCII字符集。Pack = 4: 这个参数用于指定结构体字段的对齐方式。
Pack = 4
表示结构体中的字段按照4字节对齐。这意味着每个字段的起始地址必须是4字节的倍数。这通常用于优化性能,特别是在与非托管代码(如C/C++库)交互时。
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)]
//数据
public struct Info_t
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public char[] ip;
}
C++ void* 转换IntPtr
- 特性声明
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
:
StructLayout
是一个C#特性,用于定义结构体在内存中的布局方式。在C#中,结构体默认使用
LayoutKind.Auto
,这意味着编译器可以根据需要重新排列结构体中的字段以优化内存使用。LayoutKind.Sequential
选项强制编译器按结构体中字段定义的顺序在内存中布局这些字段。这对于与非托管代码(如C/C++代码)进行交互非常重要,因为你需要确保结构体的布局与非托管代码期望的布局一致。
CharSet = CharSet.Auto
选项指定了非托管字符串的类型。
CharSet.Auto
在Windows平台上通常被解释为CharSet.Unicode
,但在其他平台上则可能是CharSet.Ansi
。这确保了字符串在与非托管代码交互时的正确编码。
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
//异步事件参数
public struct Args_t
{
public IntPtr data; //数据
}
C++ 回调函数指针转为C#
特性(Attribute):
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
:这是一个特性,用于修饰一个委托(delegate),告诉编译器这个委托指向的函数是未托管代码中的函数,并且使用哪种调用约定(Calling Convention)来调用这些函数。
UnmanagedFunctionPointer 特性:
UnmanagedFunctionPointer
:这是一个.NET 特性,用于定义一个委托,该委托指向的函数存在于非托管代码中(例如C/C++编写的DLL)。通过这个特性,可以确保.NET 应用程序能够正确地调用这些非托管函数。
CallingConvention.Cdecl:
CallingConvention.Cdecl
:这是调用约定的一种类型。在这种约定下,函数的调用者负责清理堆栈。这通常用于C语言的函数调用,因此在调用C/C++编写的函数时,使用这种约定是比较常见的。Cdecl约定通常提供更好的性能,因为它让调用者(而不是被调函数)负责清理堆栈,从而避免了额外的堆栈操作。
// 事件委托类型
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
// public delegate void DBL_UserEventCallbackHandleDelegate: 这行代码定义了一个名为DBL_UserEventCallbackHandleDelegate的委托。
public delegate void UserEventCallbackHandleDelegate(int id, Args_t arg, IntPtr userThis);
05
C++ 动态库DLL转为C#
- DLL导入
: 通过
DllImport
属性,你可以在C#中调用a_sdk.dll
中的函数。这意味着你的程序可以利用这个DLL中的功能,例如硬件控制、数据采集等。 - 字符集
:
CharSet.Auto
允许程序根据运行时环境自动选择字符集,这在处理不同平台上的字符串时非常有用。 - 调用约定
:
CallingConvention.Cdecl
确保了函数调用的参数传递和清理方式符合C语言的规范,这对于确保你的C#代码能够正确地与非托管代码交互非常重要。
// 外部库,记得的放在可执行文件目录下
[DllImport("a_sdk.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
// 外部库dll中的函数
extern static int ScanDeviceList(ref IntPtr deviceList, ref UInt32 deviceNumber);
ref如同C++的& 代表引用。IntPtr如同C++的*
06
字节数组转为结构体
// 返回类型:object 表示该方法返回一个类型为 object 的对象。由于 object 是所有类型的基类,返回的值可以是任何类型。
// Type type:表示目标结构体的类型
public static object BytesToStuct(byte[] bytes, int pos, Type type)
{
//得到结构体的大小
int size = Marshal.SizeOf(type);
//byte数组长度小于结构体的大小
if (size > bytes.Length + pos)
{
//返回空
return null;
}
//分配结构体大小的内存空间
IntPtr structPtr = Marshal.AllocHGlobal(size);
//将byte数组拷到分配好的内存空间
Marshal.Copy(bytes, pos, structPtr, size);
//将内存空间转换为目标结构体
object obj = Marshal.PtrToStructure(structPtr, type);
//释放内存空间
Marshal.FreeHGlobal(structPtr);
//返回结构体
return obj;
}
其实以目前的AI来看,只要掌握其中一门语言,转化一下就好。