sailor-port-csharp 静态库&动态库
sailor-port-csharp 静态库&动态库
基于 vSeaskyPort.dll 可以快速的开发高性能的 WPF串口上位机,这对于产品上位机将是一个不错的选择。在此提供 C# 调用的静态库&动态库,底层串口机制基于WIN原生的API接口实现,数据处理同样是基于C++,协议基于 Seasky协议,C# 本身比较适合写界面,但是不适合进行比较复杂的计算,在此将所有数据处理通过C++实现,同时封装好了通用接口,配合此静态库&动态库则可以快速的开发高性能的串口上位机,可以帮助你快速的开发高性能的串口上位机程序具有较大的学习性。
实测使用USB虚拟串口转CAN可以满足2ms周期的通信频率,吊打C#自身的串口库,那么基于Wpf也可以做到和Qt性能差不太多的上位机,然而Wpf的开发周期可以说极短。
将动态库添加到你的工程
最后在此编写了一个简单的示例程序,不包含完整程序,以供参考。
vSeaskyPort 动态库用户函数接口
PROTOCOL_CLASS_REF vSeaskyPort
{
public:
vSeaskyPort(void);
~vSeaskyPort();
//协议依赖
protocol_struct* pTxProtocol = new protocol_struct();//指针内数据受保护,C#禁止访问
protocol_struct* pRxProtocol = new protocol_struct();//指针内数据受保护,C#禁止访问
bool GetStorageMethodIsSmall(void);
/// <summary>
/// 初始化协议所需内存
/// </summary>
/// <param name="uTxLen">pTxProtocol的uint32_t数据长度</param>
/// <param name="uRxLen">pRxProtocol的uint32_t数据长度</param>
void ProtocolInit(uint16_t uTxLen, uint16_t uRxLen);
/// <summary>
/// 通过动态 Data(uint32_t) 长度计算总数据Buffer(uint8_t) 的长度
/// </summary>
/// <param name="uLen"></param>
/// <returns></returns>
uint16_t ProtocolCalcLen(uint16_t uLen);
/// <summary>
/// 自动初始化Tx所需动态内存,uLen需小于128,内部分配内存,在外部托管无法访问
/// </summary>
/// <param name="uLen">pTxProtocol的uint32_t数据长度</param>
void ProtocolAutoInitTx(uint16_t uLen);
/// <summary>
/// 自动初始化Rx所需动态内存,uLen需小于128,内部分配内存,在外部托管无法访问
/// </summary>
/// <param name="uLen">pRxProtocol的uint32_t数据长度</param>
void ProtocolAutoInitRx(uint16_t uLen);
/// <summary>
/// 获取Tx动态内存地址和长度,返回长度为 pRxData长度,pRxBuffer长度为 uMaxLen*4+12
/// </summary>
/// <param name="pTxData"></param>
/// <param name="pTxBuffer"></param>
/// <param name="uMaxLen">pTxData的数据长度</param>
void ProtocolTxPointGet(uint32_t* pTxData, uint8_t* pTxBuffer, uint16_t& uMaxLen);
/// <summary>
/// 获取Rx动态内存地址和长度,返回长度为 pRxData长度,pRxBuffer长度为 uMaxLen*4+12
/// </summary>
/// <param name="pRxData"></param>
/// <param name="pRxBuffer"></param>
/// <param name="uMaxLen">pRxData的数据长度</param>
void ProtocolRxPointGet(uint32_t* pTxData, uint8_t* pTxBuffer, uint16_t& uMaxLen);
/// <summary>
/// 初始化协议Tx所需内存,外部分配,uLen需小于128
/// </summary>
/// <param name="pTxData">预先分配的内存地址</param>
/// <param name="pTxBuffer">预先分配的内存地址</param>
/// <param name="uLen">pTxData的数据长度</param>
void ProtocolInitTx(uint32_t* pTxData, uint8_t* pTxBuffer, uint16_t uLen);
/// <summary>
/// 初始化协议Rx所需内存,外部分配,uLen需小于128
/// </summary>
/// <param name="pRxData">预先分配的内存地址</param>
/// <param name="pRxBuffer">预先分配的内存地址</param>
/// <param name="uLen">pRxData的数据长度</param>
void ProtocolInitRx(uint32_t* pRxData, uint8_t* pRxBuffer, uint16_t uLen);
/// <summary>
/// 同步方式打开串口
/// </summary>
/// <param name="com_num">串口号</param>
/// <param name="baud_rate">波特率</param>
/// <param name="parity">奇偶校验位</param>
/// <param name="byte_size">数据位</param>
/// <param name="stop_bits">停止位</param>
/// <returns></returns>
bool vSerialOpen(uint32_t com_num, uint32_t baud_rate, uint32_t parity, uint32_t byte_size, uint32_t stop_bits);
/// <summary>
/// 关闭串口
/// </summary>
void vSerialClose();
/// <summary>
/// 判断串口是否打开
/// </summary>
/// <returns>正确返回为ture,错误返回为false</returns>
bool vSerialIsOpen();
/// <summary>
/// 清除缓冲区
/// </summary>
void vSerialClearBuffer(void);
/// <summary>
/// 得到最后一次失败的错误信息
/// </summary>
/// <returns></returns>
uint8_t vSerialGetLastError();
/// <summary>
/// 将该函数放在一个独立的线程中,以实现串口消息的接收处理
/// </summary>
void vSerialReceiveTask();
/// <summary>
/// 协议计算,并发送数据
/// </summary>
void ProtocolTransmit(uint16_t equipment_type, uint16_t equipment_id, uint16_t data_id, uint32_t* pData, uint16_t data_len);
/// <summary>
/// 设置接收数据处理完成回调函数
/// </summary>
/// <param name="pFun"></param>
void vSerialSetReceivCallbackFun(pReceivePointer^ pFun);
/// <summary>
/// 设置调试信息等级和调试信息回调显示函数
/// </summary>
/// <param name="debugLevel">调试信息等级</param>
/// <param name="pFun">调试信息回调显示函数</param>
void vSerialSetDebugCallbackFun(uint8_t debugLevel, pDebugPointer^ pFun);
private:
// 此部分为私有,C#无法访问
static bool vSerialIsOpenIt = false; //串口打开标志
static bool pSerialReceiveCppPointerIsEnable = false; //是否初始化数据处理完成函数回调
static bool pSerialDebugIsEnable = false; //是否初始化调试信息函数回调
static vSerialPort* vSerialPortClass = new vSerialPort(); //Win Api串口实例
static pReceivePointer^ pReceiveCallbackFun; //数据处理完成函数回调指针
static pDebugPointer^ pDebugCallbackFun; //调试信息函数回调指针
/// <summary>
/// 接收数据处理完成中断
/// </summary>
void vReceiveCallBack(void);
/// <summary>
/// 接收调试信息信息打印函数
/// </summary>
/// <param name="pStr"></param>
void vSerialDebugPrintf(char* pStr);
};
开始使用
在使用前需要先开启 unsafe 允许不安全的代码,否则 C# 将不支持指针
在添加动态库之后,在需要使用的地方引入 vSeaskyProtocol
命名空间
using vSeaskyProtocol;
初始化Seasky协议内存,Tx和Rx的不定长数据段(uint32_t)长度为24,具体的协议内容请查阅 Seasky协议 协议相关内容。 编写数据处理完成的函数回调 vSerialReceiveCallback (函数必须按规定格式) ,并传入初始化参数,C++内部每处理完一帧数据就会通过函数指针调用 vSerialReceiveCallback 以向 C# 提供解析完成的数据。 编写调试函数回调 vSerialDebugCallback(函数必须按规定格式) ,并传入初始化参数,C++内部数据处理出现错误,会调用此函数指针打印异常信息,必要的还需要传入信息等级,以过滤一些不重要的打印。
/// <summary>
/// 按需求初始化协议内存和相关回调函数,
/// </summary>
public unsafe void vSeaskyProtocolInit()
{
vSeaskyPortInit(24,24,new pReceivePointer(vSerialReceiveCallback), new pDebugPointer(vSerialDebugCallback), COM_LOG_LEVEL.COM_LOG_LEVEL_DEBUG);
}
/// <summary>
/// CLR 接收数据处理完成回调函数
/// </summary>
/// <param name="equipment_type"></param>
/// <param name="equipment_id"></param>
/// <param name="data_id"></param>
/// <param name="pData"></param>
/// <param name="data_len"></param>
public unsafe void vSerialReceiveCallback(UInt16 equipment_type, UInt16 equipment_id, UInt16 data_id, UInt32* pData, UInt16 data_len)
{
//Console.WriteLine("vSerialReceiveCallback");
//在此根据数据内容编写相关回调函数
}
/// <summary>
/// CLR debug 回调函数
/// </summary>
/// <param name="pStr"></param>
public unsafe void vSerialDebugCallback(sbyte* pStr)
{
//Console.WriteLine("vSerialDebugCallback");
//在此实现调试信息输出窗口
}
在此还需要初始化串口接收扫描,你需要提供一个线程循环调用 vSerialReceiveTask函数进行串口数据扫描和解析,务必在一个单独的线程中调用
{
// 关闭串口
vcSeaskyPort.vSerialClose();
// 创建一个接收线程
vReceivedThread = new Thread(new ThreadStart(ThreadReceivedFun));
vSerialState = false;
// 线程先挂起,现在串口未打开
vReceivedThread.Start();
vReceivedThread.IsBackground = true;
vReceivedThread.Suspend();
}
/// <summary>
/// 接收线程,在初始化之后就不用管了,提供给 vSeaskyPort 内部循环检测接收数据用。
/// </summary>
public void ThreadReceivedFun()
{
while (true)
{
vcSeaskyPort.vSerialReceiveTask();
}
}
必要的为了节省性能,你可以在串口关闭时挂起线程,在串口打开后,再恢复线程
/// <summary>
/// 打开串口,打开串口前应当提前设置串口参数。
/// </summary>
/// <returns></returns>
public bool vSerialOpen()
{
if (vFirstInit == true)
{
unsafe
{
try
{
// 需要提前设置参数
vcSeaskyPort.vSerialOpen(vSerialPortNameNum, vSerialBaudRate, vSerialParity, vSerialDataBits, vSerialStopBits);
Thread.Sleep(3);
// 获取串口打开状态
vSerialState = vcSeaskyPort.vSerialIsOpen();
if (vSerialState == true)
{
vReceivedThread.Resume();
}
}
catch (Exception)
{
};
}
}
return vSerialState;
}
/// <summary>
/// 关闭串口。
/// </summary>
/// <returns></returns>
public bool vSerialClose()
{
vSerialState = false;
Thread.Sleep(10);
vReceivedThread.Suspend();
vcSeaskyPort.vSerialClose();
return (vcSeaskyPort.vSerialIsOpen() == false);
}