进行简单的API中一部分关于串口通信函数的测试,为以后的上位机编写做准备。
下面是一部分从网上和msdn整理的函数说明:

ReadFile:

BOOL ReadFile (
   HANDLE hFile, //用CreateFile获得的文件句柄
   LPVOID lpBuffer, //输入缓冲区首址
   DWORD nNumberOfBytesToRead,//设定读入字节数
   LPDWORD lpNumberOfByteRead, //实际读入字节数
   LPOVERLAPPED lpOverlapped //重叠操作方式数据结构地址
   );

在同步方式下,调用ReadFile或WriteFile后,当实际读写操作完成或发生超时时才返回调用程序。而异步方式函数在启动接收或发送过程后立即返回,程序继续向下执行(实际上是开启了另一线程来等待完成读写操作)。程序在调用ReadFile和WriteFile时必须提供一个Overlapped数据结构指针,(若通过激发事件来接收数据。则该结构中中的hevent需要创建,其后的程序必须借助于该事件同步对象,完成数据的接收和发送过程。若通过串口句柄来接收数据,可以不创建hevent。)如果你要求一个文件操作为overlapped,而操作系统把这个“操作请求”放到队列中等待执行,那么ReadFile()和WriteFile()都会传回FALSE表示失败。你必须调GetLastError()并确定它传回ERROR_IO_PENDING,那意味着“overlapped  I/O请求”被放进队列之中等待执行。

另外有这样一段:For an hFile that supports byte offsets, if you use this parameter you must specify a byte offset at which to start reading from the file or device. This offset is specified by setting the Offset and OffsetHigh members of the OVERLAPPED structure. For an hFile that does not support byte offsets, Offset and OffsetHigh are ignored.

即:对于支持字节偏移的hFile,如果使用此参数,则必须指定要从文件或设备开始读取的字节偏移量。通过设置OVERLAPPED结构的Offset和OffsetHigh成员 来指定此偏移量 。对于不支持字节偏移的 hFileOffset和 OffsetHigh将被忽略。

我本来想读串口时直接读帧尾,结果实验了一下,好像读串口就是不支持字节偏移的 hFile==

SetCommMask:

BOOL WINAPI SetCommMask(
 __in  HANDLE hFile,// 串口句柄
 __in  DWORD dwEvtMask//事件,可通过位或的方式指定多个事件

);

事件屏蔽 含义 
EV_BREAK  检测到一个输入中断 
EV_CTS  CTS信号发生变化 
EV_DSR  DSR信号发生变化 
EV_ERR  发生行状态错误 
EV_RING  检测到振铃信号 
EV_RLSD  RLSD(CD)信号发生变化 
EV_RXCHAR  输入缓冲区接收到新字符 
EV_RXFLAG  使用SetCommState()函数设置的DCB结构中的等待字符已被传入输入缓冲区中。 
EV_TXEMPTY  发送缓冲区为空

设置你关心的事件,当此事件发生时,将得到事件通知

EV_RXCHAR 每当一个新字符到达都会收到一次通知。。。

EV_RXFLAG特定字符到达收到通知

WaitCommEvent:

BOOL WINAPI WaitCommEvent(
 __in  HANDLE hFile,// 串口句柄
 __out LPDWORD lpEvtMask,// Out型指针,接收事件标志
 __in  LPOVERLAPPED lpOverlapped);//接收事件信息事件状态

当由SetCommMask函数所指定的事件产生时这个函数将返回TRUE。

如果hfile是以FILE_FLAG_OVERLAPPED即异步方式打开,而且lpOverlappedis 即指向串口异步结构体overlapped的指针不为空,waitcommevent将表现为异步操作。在这种情况下,异步结构体OVERLAPPED 必须有一个由人工复位的事件的句柄hevent(用CreateEvent创建 )。

如果异步操作没有立即完成,WaitCommEvent返回FALSE ,用GetLastError可以得到返回值 ERROR_IO_PENDING,这表明操作在后台执行。当不能立即返回时,系统把hevent设置成无信号状态,而当等待事件发生,或一个错误产生,函数会返回,而hevent被设置为有信号。运行线程可以调用等待函数(如waitforsingleobject等) 来确定执行状态,然后用GetOverlappedResult 来确定WaitCommEvent操作的结构

SetCommMask(fd,EV_RXCHAR);//设定串口接收事件
if(!WaitCommEvent(fd,&Event,&m_ov))//等待事件没有立即发生{switch(GetLastError()){case ERROR_IO_PENDING: std::cout<<"pending...\n";break;default :return ;}}WaitForSingleObject(m_ov.hEvent,INFINITE);std::cout<<"data is arrived\n";ReadFile(fd,ch,1,&read,&m_ov);std::cout<<"read complete\n";//ResetEvent(m_ov.hEvent);WaitForSingleObject(m_ov.hEvent,INFINITE);std::cout<<"function leave\n";

hevent被设置为必须人工置位,通过上面测试程序可知,wait和read都不会将hevent置为无信号,必须手动置位。指定串口监视事件后,hevent被用作保存监视事件激发状态,此时readfile用串口句柄通知是否异步操作完成。

GetOverlappedResult:

BOOL GetOverlappedResult(
HANDLE hFile, //用CreateFile获得的文件句柄

LPOVERLAPPED lpOverlapped,//指向一个在启动重叠操作时指定的OVERLAPPED结构(即读写函数中指定的OverLapped结构)

LPDWORD lpNumberOfBytesTransferred,//实际传输的字节数
 BOOL bWait, //是否等待悬挂的重叠操作完成,若为TRUE,则此函数直到操作完成后才返回
     );

还未完成异步传输返回0

WaitForSingleObject:

DWORD WaitForSingleObject(

  HANDLE hHandle,// 一个事件的句柄,可以是串口句柄,也可以是事件句柄

  DWORD dwMilliseconds//时间间隔

  );

如果事件是有信号状态返回WAIT_OBJECT_0,如果时间超过dwMilliseconds值但时间事件还是无信号状态则返回WAIT_TIMEOUT。

WaitForSingleObject函数用来检测hHandle事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。可根据返回值确定函数执行 情况。参数dwMilliseconds有两个具有特殊意义的值:0和INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态时为止。

MsgWaitForMultipleObjects function

DWORD WINAPI MsgWaitForMultipleObjects(_In_       DWORD  nCount,_In_ const HANDLE *pHandles,_In_       BOOL   bWaitAll,_In_       DWORD  dwMilliseconds,_In_       DWORD  dwWakeMask
);

nCount:pHandles所指向的数组中的对象句柄数量,最大值为MAXIMUM_WAIT_OBJECTS
pHandles:对象句柄数组地址。这个数组可以包含不同类型的对象句柄。但不能包含同一对象的多个拷贝。
如果这些句柄中的一个关闭了,而等待仍在进行,那么此函数的行为是未定义的。这些句柄必须拥有同步
访问权限。
bWaitAll:若此参数为TRUE,那么当所有在pHandles所指向的数组中的对象状态都被置为有信号并且输入事件
已被收到时函数返回。而当参数为FALSE,只要任意对象状态被置为信号态或输入事件被收到,函数都可返回。
在这种情况下,函数返回值指示了是哪个对象的状态导致了函数返回。
dwMilliseconds:超时间隔。若非零值被设置,那么函数将会等待直到特定对象被激发或超时时间已到。若值
为零,那么当特定对象未被激发,那么函数不会进入等待状态。它总会立即返回。若其值为 INFINITE,那么
只有特定对象被激发函数才会返回。
dwWakeMask:将会被添加到对象句柄数组的输入事件对象句柄的类型。参数值可以是下面值的任意结合:

含义
QS_ALLEVENTS 0x04BF

一个输入,WM_TIMERWM_PAINTWM_HOTKEY或已发消息在队列中。

此值是QS_INPUTQS_POSTMESSAGEQS_TIMERQS_PAINT,和QS_HOTKEY的组合。

QS_ALLINPUT 0x04FF

任意消息在队列中。

此值是QS_INPUTQS_POSTMESSAGEQS_TIMERQS_PAINTQS_HOTKEY,和QS_SENDMESSAGE的组合。

QS_ALLPOSTMESSAGE 0100

发送的消息在队列中。

当您调用GetMessagePeekMessage而不过滤消息时,此值将被清除。

QS_HOTKEY 0x0080

队列中有一个WM_HOTKEY消息。

QS_INPUT 0x407

输入消息在队列中。

该值是QS_MOUSEQS_KEY和 QS_RAWINPUT的组合。

QS_KEY 0×0001

WM_KEYUPWM_KEYDOWNWM_SYSKEYUP,或WM_SYSKEYDOWN消息在队列中。

QS_MOUSE 0x0006

WM_MOUSEMOVE消息或鼠标按钮信息(WM_LBUTTONUPWM_RBUTTONDOWN,等等)。

此值是QS_MOUSEMOVEQS_MOUSEBUTTON的组合。

QS_MOUSEBUTTON 0x0004

鼠标按钮消息(WM_LBUTTONUPWM_RBUTTONDOWN等)。

QS_MOUSEMOVE 0×0002

一个WM_MOUSEMOVE消息在队列中。

QS_PAINT 0×0020

一个WM_PAINT消息在队列中。

QS_POSTMESSAGE ×0008

发送的消息在队列中。

当您调用GetMessagePeekMessage时,无论您是否过滤邮件,此值都将被清除。

QS_RAWINPUT 的0x0400

原始输入消息在队列中。有关详细信息,请参阅 原始输入

QS_SENDMESSAGE 即0x0040

另一个线程或应用程序发送的消息在队列中。

QS_TIMER 0×0010

队列中有WM_TIMER消息。

返回值:

返回代码/值 描述
WAIT_OBJECT_0到( WAIT_OBJECT_0 +  nCount - 1)

如果bWaitAllTRUE,则返回值表示指示了所有指定对象处于激发状态。如果bWaitAllFALSE,返回值减WAIT_OBJECT_0表示满足等待的对象的pHandles数组索引。

WAIT_OBJECT_0 +  nCount

dwWakeMask参数中指定的类型的新输入在线程的输入队列中可用。诸如 PeekMessage, GetMessageWaitMessage之类的功能将队列中的消息标记为旧消息。因此,在调用其中一个函数后,随后对MsgWaitForMultipleObjects的调用 将不会返回,直到指定类型的新输入到达为止。

发生需要线程操作的系统事件(如前台激活)时也会返回此值。因此,尽管没有适当的输入可用,MsgWaitForMultipleObjects也可以返回,即使dwWakeMask设置为0.如果发生这种情况,请在尝试再次调用MsgWaitForMultipleObjects之前调用GetMessagePeekMessage来处理系统事件 。

WAIT_ABANDONED_0至( WAIT_ABANDONED_0 +  nCount - 1)

如果bWaitAllTRUE,则返回值表示所有指定对象的状态都发出信号,至少有一个对象是遗弃互斥量。如果bWaitAllFALSE,则返回值减WAIT_ABANDONED_0表示满足等待的遗弃互斥量的pHandles数组索引。互斥对象的所有权被授予调用线程,并且互斥体被设置为非指定的。

如果互斥体正在保护持久状态信息,则应检查它的一致性。

WAIT_TIMEOUT 258L

超时时间间隔和由bWaitAlldwWakeMask参数指定的条件不满足。

WAIT_FAILED (DWORD)0xFFFFFFFF的

该功能失败。要获取扩展错误信息,请调用 GetLastError

Abandon mutex

  • 当mutex的拥有者线程被中止时,该mutex称为遗弃mutex.
  • 遗弃mutex是signaled状态.下一个等待线程会获取该mutex的拥有权.
  • 在2.0以后的Framework版本里,会抛出AbandonedMutexException异常(当下一个线程获得其拥有权时).
  • 它通常意味着代码错误,可能会造成数据结构的破坏.
  • 下一个获取mutex拥有权的线程,在可能的情况下,应该处理该异常并保证数据结构的正确性.
  • 对于一个系统级别的遗弃mutex,可能指示了一个应用被突然中止.

bWaitAllTRUE时,只有当所有对象的状态都被设置为激发态并且已经接收到输入事件时,才能完成该功能的等待。因此,将bWaitAll设置为TRUE可以防止输入被处理,直到pHandles数组中的所有对象的状态都被设置为发出信号。因此,如果将bWaitAll设置为TRUE,则应在dwMilliseconds中使用一个短暂的超时值。

如果您有一个线程来创建等待pHandles数组中所有对象的线程,包括由dwWakeMask指定的输入事件,没有超时间隔,则系统将死锁。这是因为创建窗口的线程必须处理消息。DDE向系统中的所有窗口发送消息。因此,如果一个线程创建了窗口,那么在调用由该线程生成的MsgWaitForMultipleObjects时 ,不要将bWaitAll参数设置为TRUE

bWaitAllFALSE时,此函数按索引0开始按顺序检查数组中的句柄,直到其中一个对象发出信号。如果多个对象变得信号通知,该函数将返回其对象发出信号的数组中第一个句柄的索引。

如果在线程调用了一个函数来检查队列之后,如果在消息队列中存在指定类型的未读输入,则MsgWaitForMultipleObjects不返回

这是因为诸如PeekMessage, GetMessage, GetQueueStatus和 WaitMessage之类的函数检查队列,然后会更改队列的状态信息,以至于输入不再被认为是新的。对MsgWaitForMultipleObjects的后续调用 将不会返回,直到指定类型的新输入到达为止。现有的未读输入(在上一次线程检查队列之前接收到)被忽略。

该函数修改某些类型的同步对象的状态。修改仅针对信号状态导致功能返回的对象或对象。例如,信号量对象的计数减1。有关详细信息,请参阅各个同步对象的文档。

此函数可以用在主线程和界面线程,以防止为等待事件而陷入无限等待状态或不能及时接受消息。

_COMMTIMEOUTS

typedef struct _COMMTIMEOUTS {

DWORD ReadIntervalTimeout;// 两字符之间最大的延时

DWORD ReadTotalTimeoutMultiplier;// 读取每字符间的超时。

DWORD ReadTotalTimeoutConstant;// 一次读取串口数据的固定超时。

DWORD WriteTotalTimeoutMultiplier;

DWORD WriteTotalTimeoutConstant;

} COMMTIMEOUTS, *LPCOMMTIMEOUTS;

讨论下面这种情况:

A value of MAXDWORD, combined withzero values for both the ReadTotalTimeoutConstant and ReadTotalTimeoutMultipliermembers, specifies that the read operation is to return immediately with thebytes that have already been received, even if no bytes have been received.

即     CommTimeOuts.ReadIntervalTimeout= MAXDWORD ;

CommTimeOuts.ReadTotalTimeoutMultiplier=0;

CommTimeOuts.ReadTotalTimeoutConstant=0;

读操作在读一次输入缓冲区中的内容后读操作就立即完成,而不管是否读入了要求的字符。 而当ReadIntervalTimeout的值为0时,表示不用字符间最大超时,即读操作会一直等待,直到读取到指定字符数。用GetLastError()可以验证,当为情况1(MAXDWORD ,0,0)时,readfile总是立即返回的,而情况2(0,0,0)时,若没有读取到指定字符数,GetLastError()会返回997,即ERROR_IO_PENDING,表明其一直在等待。之前一直以为是readfile之后才开始接收,实际是串口打开后就会自动保存接收到的数据到缓冲区。所以一直理解不了操作理解完成怎么能读到数据。。。。知道了之前的概念就能理解了

所以_COMMTIMEOUTS是用来设置读操作的异步行为的。当串口设为重叠操作,而此结构体参数为MAXDWORD,0,0那么readfile()总会返回false,而异步操作会读后立即返回。

ClearCommError:

BOOL WINAPIClearCommError(

_In_      HANDLE    hFile,

_Out_opt_ LPDWORD   lpErrors,

_Out_opt_ LPCOMSTAT lpStat

);

第二个参数指向COMSTAT,COMSTAT的成员cbInQue定义如下:

cbInQue

The number of bytes received bythe serial provider but not yet read by aReadFile operation.

即用ClearCommError可以获得缓冲区中收到的字符数。

测试代码:

#include <windows.h>
#include <iostream>
#define use_hfile 0//使用文件(串口)句柄
#define use_hevent 1//使用事件   可用SetEvent(m_ov.hEvent)来查看GetOverlappedResult确实根据hevent判定#define use_WaitForSingleObject 0
#define use_GetOverlappedResult 1
int com_open();
int com_read();HANDLE hCom;//串口句柄OVERLAPPED m_ov;//异步读写所需结构体char* buf=new char[128];//读取数据存储DWORD read;//读取字节数
int com_open()
{hCom=CreateFile("COM5",GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,NULL);if(hCom == INVALID_HANDLE_VALUE)  return -1;else std::cout<<"COM5 OPEN SUCCESS\n";if(!SetupComm(hCom,1024,1024)) return -2;else std::cout<<"COM5 setup SUCCESS\n";COMMTIMEOUTS cto = {0,0,0,0,0};//第一个参数为0表示无限等待下一字节,为MAXDWORD表示立即激活相应句柄if(!SetCommTimeouts(hCom,&cto)) return -3;else std::cout<<"COM5 SetCommTimeouts SUCCESS\n";DCB dcb;memset(&dcb,0,sizeof(dcb));//DCB初始化dcb.DCBlength = sizeof(dcb);GetCommState(hCom, &dcb);dcb.BaudRate =600;dcb.Parity = NOPARITY;dcb.StopBits = ONESTOPBIT;dcb.ByteSize = 8;/*dcb.fBinary = 1;//指定是否允许二进制模式dcb.fOutxCtsFlow=false;dcb.fDtrControl=false;dcb.fRtsControl = RTS_CONTROL_DISABLE;dcb.fOutX=false;dcb.fInX=false;dcb.XoffChar = 0x13;dcb.XonChar = 0x11;dcb.XonLim = (1024 >> 2) * 3;dcb.XoffLim = (1024 >> 2) * 3;*/if(!SetCommState(hCom,&dcb)) return -4;else std::cout<<"COM5 setCommState SUCCESS\n";PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);return 0;
}
int com_read()
{memset(&m_ov,0,sizeof(OVERLAPPED));#if use_heventm_ov.hEvent = CreateEvent(NULL,// LPSECURITY_ATTRIBUTES lpsa不可被继承TRUE, // BOOL fManualReset 是否人工复位false,//TRUE, // BOOL fInitialState 初始状态 有无信号NULL); // LPTSTR lpszEventName 匿名if(m_ov.hEvent == INVALID_HANDLE_VALUE) return -1;else std::cout<<"ovevent creat success\n";#endifif(!ReadFile(hCom,buf,50,&read,&m_ov)){if(GetLastError()==ERROR_IO_PENDING){std::cout<<"reading...\n";return 0;}else{return -2;}}else return -3;
}
int main()
{int i=0;i=com_open();if(!i){i=com_read();if(!i){#if use_hfile#if use_WaitForSingleObjectWaitForSingleObject(hCom,INFINITE);//通过串口句柄#endif#if use_GetOverlappedResultGetOverlappedResult(    hCom,&m_ov,&read,true);#endif#endif#if use_hevent#if use_WaitForSingleObjectWaitForSingleObject(m_ov.hEvent,INFINITE);//通过串口句柄#endif#if use_GetOverlappedResultGetOverlappedResult(hCom,&m_ov,&read,true);#endif#endif // use_heventstd::cout<<buf;delete[] buf;return 0;}else std::cout<<"start read err\n";delete[] buf;return i;}else std::cout<<"open com5 err\n";delete[] buf;return i;
}

使用宏来实现文件句柄与事件两种方式的切换,另外还可以用setcommmask对串口事件进行监视,进而读取。

只使用文件句柄时根据文件句柄激发状态判定,而当使用异步结构体的事件hevent时则是根据hevent的激发状态判定

WIN32API串口接收数据简单测试相关推荐

  1. STM32如何从串口接收数据,取出数据中的密码段再保存到flash中。

    STM32如何从串口接收数据,取出数据中的密码段再保存到flash中 STM32如何从串口接收数据,取出数据中的密码段再保存到flash中u8 Usart2_buf[256]; unsigned in ...

  2. 【嵌入式】——串口实验——实现芯片串口收发数据,按键中断串口发送数据,串口接收数据中断来控制LED亮/灭

    实验目的: 实现芯片串口收发数据,按键中断串口发送数据:按下按键,向串口发送数据,并通过虚拟终端显示出来: 串口接收数据中断来控制LED亮/灭:通过串口助手向MCU发送数据,"A" ...

  3. 多串口接收数据并显示-自定义串口通信类-调用委托显示数据-保存数据-实例:10串口接收数据并显示加保存

    当时搜了很久,没咋都到合适的思路,就自己整理一份吧 大致实现的功能: 通过串口接收数据,进行解析,然后显示,或者保存数据: 主要:多个串口接收显示都能用: 大致思路: 1.新建一个类SerialPor ...

  4. 单片机串口接收数据的一些经验与教训

    一些好文章 一种单片机数据解析方法:https://mp.weixin.qq.com/s/GVH8MZrlbVKXanD45wOYOw 单片机串口接收数据需要注意以下几点 1.接收数据要有顺序,接收到 ...

  5. STM32MP157实现串口接收数据上云-MP157连接4G模块和电脑

    文章目录 前言 一.打开设备文件 二.串口初始化 三.多线程 四.程序的移植和执行 五.源码(转载请注明出处) 结语 前言 本篇分享: 这次将会用几篇博客分享STM32MP157实现串口接收数据上云的 ...

  6. 串口通信——串口接收数据,发送数据

    十六进制        HEX /hexadecimal   /ˌheksəˈdesɪml/ 十进制            DEC /decimalism     /'desiməlizəm/ 二进制 ...

  7. arduino串口接收数据包_Arduino 串口接收转发十六进制数据,软串口收不到数据

    我是Arduino初学者,今日尝试用Arduino nano 收串口发来16进制数据,再由软串口转发给下级设备,下级设备返回16进制数据.首先简单测试代码如下: #include SoftwareSe ...

  8. STM32串口接收数据

    参考串口接收中断处理函数: //------------------------------------------------------------------ //函数名:void USART1 ...

  9. 解决QT接受串口数据时数据更新不及时,串口数据太多导致程序界面崩溃,串口接收数据过快等问题

    1.问题背景 最近在使用上位机测试传感器接受数据是否正常,发现了很多问题,由于没有系统的学过Qt,用到什么库就学什么库,导致库中的函数很多不清晰,产生了标题中的一系列问题,经过不断的尝试,终于解决上述 ...

最新文章

  1. c++ double 只输出五位_c 语言第四章 在控制台上数据的输入和输出
  2. System.Timers.Timer与System.Windows.Forms.Timer 区别
  3. 基于FFmpeg-4.0 SDK的PCM编码成AAC
  4. Gentoo 安装日记 02 (建立并启动虚拟机)
  5. springboot搭建的ssm项目
  6. html背景图片自适应纵向,HTML5 body设置全屏背景图片 如何让body的背景图片自适应整个屏----实战经验...
  7. AttributeError系列之:AttributeError: module 'scipy.misc' has no attribute 'imread'报错问题
  8. 用.net中的SqlBulkCopy类批量复制数据 (转载)
  9. 使用gdaldem生成山体阴影——thematicmapping.org译文(二)
  10. visio2016为图形添加和删除连接点
  11. Kotlin入门(27)文件读写操作
  12. vue 中的slot属性(插槽)的使用
  13. 12306 验证码验证流程
  14. 隐私浏览器DuckDuckGo爆出漏洞,可导致URL欺骗攻击
  15. 盘古开源:汽车芯片短缺怎样缓解?工信部回应热点问题
  16. python中文社区-python-chinese.GitHub.io
  17. 工业检测产品中,用到的PPM, DPPM和DPMO的定义
  18. 1 、(5分)短信中提取号码
  19. 新浪微博Android客户端开发之OAuth认证篇
  20. 【NP问题】P问题,NP问题,NPC问题,NPH问题

热门文章

  1. python二分查找时间复杂度_时间复杂度 二分查找
  2. android sdk 固态硬盘,使用TVM在android中进行Mobilenet SSD部署
  3. 用户分享率高达87% KilaKila恋爱小程序的女性营销逻辑
  4. Hadoop数据迁移工具DistCp
  5. 服务器的类型都有哪些
  6. 为什么5G能比4G快十倍?
  7. vue项目执行命令npm run serve运行项目时 停在 98% after emitting CopyPlugin
  8. JQuery使用及基础原理解析相关笔记(一)
  9. 怎样在VS中用C++调用METIS提供的API
  10. 基于内容可变长度分块Content Defined Chunking