靠!奋战6天,毕设论文终于写完了,欢迎挑毛病!(兼送180分)
用多线程技术实现串口实时监控的一个方法
张琦
(北京工业大学计算机学院软件工程系)
摘要:在Windows 操作系统中,每一个进程可以有多个线程。对于读写串口这种耗时的工作,使用多线程技术,创建辅助的串口监视线程来管理串口是常用的方案,这样在进行串口读写的同时,能对读入的数据进行处理。本文讨论了在Win32平台下,运用多线程技术,实现对串行口的实时监控和多线程访问数据库的方法。并且结合智能门禁系统实时监控模块的软件设计,说明门控服务器如何通过一个串行口对多个门控控制机发出的信号做出实时响应。
关键词:Win32API, 工作者线程,线程同步,DLL,子类化,Visual C++6.0,
(英文摘要翻译中)
问题点数:180、回复次数:61Top
1 楼temp()回复于 2001-05-19 22:32:00 得分 0
项目简介:
我要完成的是一个智能门禁系统的软件设计,门控服务器通过RS485串行口接收多个门控控制机(初步定为128个门控控制机)发来的信号,并作相应的处理。
下图时整个程序的DFD图:
当门控服务器的串口接到一个请求信号是,便接收这个字符串,然后查询SQL Server 数据库,根据返回的结果判断访客是否有进入的权限。如果有,就返回一个开门命令。如果没有进入的权限,就报警。设计时,要考虑到在较短的一个时间段内,多个门控控制机向服务器发出请求信号的情况。这就要求门控管理系统有一定的并行处理能力。
本文将主要描述监控模块的设计,为了便于说明监控模块在程序中的位置以及它与其它部分的关系,我在这里给出了相关部分的DFD图。
下图就是本文将要介绍的监控模块的DFD图:
详细的设计见后文《监控模块的设计》一节。
程序运行环境:
门控服务器使用Windows NT操作系统,数据库服务器使用SQL Server7.0,
开发环境:
Visual Basic 6.0, Visual C++ 6.0
Top
2 楼temp()回复于 2001-05-19 22:32:00 得分 0
4相关技术的说明
4.1)Win32 API:
在Windows平台下,操作系统将底层的硬件细节与用户隔离开,串行口作为系统资源,由设备驱动程序统一管理,一般情况下,用户不可以直接接触底层,而是通过API函数去访问硬件设备。Win32操作系统把串口看作一个文件,应用程序可以像访问一个文件那样访问串口,这样,我在编程时就不必考虑串口是232还是485,因为在API的层面上,对它们的处理没有什么区别。
下面介绍一下相关的API函数;
(1)打开串行口API函数
WindowsNT的串行通信会话是以调用CreateFile()函数打开串行口开始。调用CreateFile()打开串口成功,返回一个操作句柄。该句柄供随后对串行口的设置、读写等操作用。
CreateFile()函数原型:
HANDLE CreateFile(LPCTSTR szDevice,
DWORD dwAccess,
DWORD dwShareMode,
LPSECURITY-ATTRIBUTES lpSA,
DWORD dwCreate,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile );
调用此函数要注意这几个参数的设置:dwShareMode指定该端口的共享属性。该参数是为文件共享提供的,串行口不能作为共享设备。故参数值必须为0,这是文件与通信设备之间的主要差异之一;dwCreate必须为OPEN-EXISTING。因为CreateFile()只能打开存在的端口,而不能象创建新文件一样创建物理上不存在的新串口;dwFlagsAndAttributes描述了该端口的各种属性。对于文件来说,具有多种属性(只读、隐藏、系统)是可能的,但是对于串行口,唯一有意义的设置是FILE-FLAG-OVERLAPPED;参数hTemplateFile必须为NULL。
返回值:若成功,返回创建的句柄;否则返回,INVALID—HANDLE—VALUE.
举例:打开串行口COM1
HANDLE hComm; /*定义句柄变量*/
hComm = CreateFile( "COM1",
GENERIC-READ|GENERIC-WRITE,
NULL,
NULL,
OPEN-EXISTING,
FILE-FLAG-OVERLAPPED,
NULL);
if (hComm == INVALID-HANDLE-VALUE) { . . .;/* 打开串口错误的处理*/}
(2)配置串行口API函数
串行口打开成功,接下来可以配置串行口通信参数如波特率、数据位数、停止位、校验位等。修改这些参数时要和设备控制块DCB(Device Control Block)打交道,DCB有近30个数据成员,是一个很复杂的数据结构,全部弄清楚它们的含义相当费时。而对于采用3线方式的串行通信来说,DCB结构中绝大多数参数可以不予考虑,因为只要设置好波特率、数据位、停止位、校验位等几个关键参数就行。这里介绍一种简捷的方法可以做到不了解DCB的详细内容也可以设置好串行通信参数。
通过下面的程序来说明串行通信参数的设置方法。例程中利用BuildCommDCB函数来设置DCB,然后用函数SetCommState()配置串行通信口。
DCB dcb ; /*定义设备控制块*/
GetCommState(hComm,&dcb); /*取出系统缺省设备控制块*/
BuildCommDCB("COM1:9600,N,8,1",&dcb); /*设置DCB主要参数*/
SetCommState(hComm,&dcb);
(3)超时设置API函数
编写通信应用程序的一个很关键的问题就是如何处理通信中的不可预测的事件。譬如接收数据过程中突然被中断,或者发送数据突然停止等等。如果不认真对待,这些情况可能会引起I/O线程挂起或者线程被无限阻塞。WindowsNT对于这类问题提供了安全措施,它让你通过超时设置来决定通信是否异常并作相应处理。因此超时设置在串行通信中显得尤为重要。
超时设置过程分为两步,首先设置COMMTIMEOUTS结构中的五个变量,然后调用SetCommTimeouts()函数设置超时值。COMMTIMEOUTS结构的定义如下:
typedef struct - COMMTIMEOUTS
{
DWORD ReadIntervalTimeout; /* 读端口间隔超时 */
DWORD ReadTotalTimeoutMultiplier; /* 读端口总超时乘数 */
DWORD ReadTotalTimeoutConstant; /* 读端口总超时常数(ms) */
DWORD WriteTotalTimeoutMultiplier; /* 写端口总超时乘数 */
DWORD WriteTotalTimeoutConstant; /* 写端口总超时常数(ms) */
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
(4)读串口API函数
串行口打开后,可以对它进行读写操作。读串行口的函数原型:
BOOL ReadFile
(HANDLE hFile, LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead,
LPOVERLAPPED lpOverlapped data
);
其中,第一个参数hFile是由CreateFile()返回的句柄。参数lpBuffer是读取的数据缓冲区指针,要注意给该数据缓冲区分配足够的空间;参数nNumberOfBytesToRead是要读取的字节数;参数lpNumberOfBytesRead是实际读取的字节数;最后一个参数lpOverlapped是指向一个可重叠I/O(异步)的数据结构指针。如果lpOverlapped设置为NULL,则ReadFile()工作在同步方式;如果lpOverlapped指向一个重叠结构,则工作在异步方式。
(5)写串口API函数
BOOL WriteFile
(HANDLEhFile, /* 由CreateFile()返回的句柄 */
LPCVOID lpBuffer, /* 写缓冲区指针 */
DWORD nNumberOfBytesToWrite, /* 要写的字节数 */
LPDWORD lpNumberOfBytesWritten,/* 实际写的字节数 */
LPOVERLAPPED lpOverlapped /* 指向一个可重叠I/O的数据结构 */
);
WriteFile()函数的工作方式选择与ReadFile()的相同,在此不重复。
(6)关闭串口API函数
串行口是非共享资源,某应用程序打开串行口后,即独占该资源,使其它应用程序无法再访问,直到该应用程序释放串口。所以打开串口后,一定要关闭串口。关闭串口函数较简单。
函数原型:
BOOL CloseHandle( HANDLE hObject );
其中hObject参数为CreateFile()返回的端口句柄。返回值非0,则调用成功。
4.2)多线程
Windows NT是多线程(multi-threaded)、抢先多任务的(preemptible)。Windows NT 中,一个可执行程序的运行时刻实例称为进程(process)。一个进程可以有多个线程(thread ),Windows NT是按照线程分配CPU时间片的,而分配的机制就是抢先多任务方式。
微软的MSDN帮助文件中,对线程的定义是:
“A thread is the basic entity to which the operating system allocates CPU time. A thread can execute any part of the application S code,including
a part currently being executed by another thread. All thread of a process
share the virtual address space,global variables, and operating system
resources of the Process.”
对于读写串口这种耗时的工作,使用多线程技术,创建辅助线程来管理串口是一个常用的方案,这样在进行串口读写的同时,能对读入的数据进行处理。如果使用单线程,就需要在等待串口读写操作完成前,整个进程都会被阻塞在这里。数据库查询也类似,在使用单线程的情况下,应用程序向数据库服务器发出查询请求后,CPU要等待查询结果的返回,整个进程都会被阻塞于此,无法执行其它的操作,而使用多线程就可以避免这种情况。
多线程也会带来一些新的问题,其中的一个问题就是线程的同步,如果同步问题解决不好,程序的稳定性会受到很大的影响。在线程体内,如果该线程完全独立,与其它的线程没有数据存取等资源操作上的冲突,则可按照通常单线程的方法进行编程。但是,在多线程处理时情况常常不是这样,线程之间经常要同时访问一些资源。例如,一个线程负责公式计算,另一个线程负责结果的显示,两个线程都要访问同一个结果变量。这时如果不进行冲突控制的话,则很可能显示的是不正确的结果。
对共享资源进行访问引起冲突是不可避免的,但我们可用以下办法来进行操作控制:
(1) 互斥体对象(Mutex)
通过设置线程的互斥体对象,在可能冲突的地方进行同步控制。
首先,建立互斥体对象,得到句柄:
HANDLE CreateMutex( );
然后,在线程可能冲突区域的开始(即访问共享资源之前),调用WaitForSingleObject将句柄传给函数,请求占用互斥体对象:
dwWaitResult = WaitForSingleObject(hMutex, 5000L);
共享资源访问完后,释放对互斥体对象的占用:
ReleaseMutex(hMutex);
互斥体对象在同一时刻只能被一个线程占用。当互斥体对象被一个线程占用时,若有另一线程想占用它,则必须等到前一线程释放后才能成功。
(2) 利用信号(Semaphore)
在操作共享资源前,打开信号;完成操作后,关闭信号。这类似于互斥体对象的处理。
首先,创建信号对象:
HANDLE CreateSemaphore( );
或者打开一个信号对象:
HANDLE OpenSemaphore( );
然后,在线程的访问共享资源之前调用WaitForSingleObject。
共享资源访问完后,释放对信号对象的占用:
ReleaseSemaphore();
信号对象允许同时对多个线程共享资源的访问,在创建对象时指定最大可同时访问的线程数。当一个线程申请访问成功后,信号对象中的计数器减一;调用ReleaseSemaphore函数后,信号对象中的计数器加一。其中,计数器值大于等于0,小于等于创建时指定的最大值。利用信号对象,我们不仅可以控制共享资源的访问,还可以在应用的初始化时候使用。假定一个应用在创建一个信号对象时,将其计数器的初始值设为0,这样就阻塞了其它线程,保护了资源。待初始化完成后,调用ReleaseSemaphore函数将其计数器增加至最大值,进行正常的存取访问。
(3)利用事件对象 (Event)
用ResetEvent函数设置事件对象状态为不允许线程通过;用SetEvent函数设置事件对象状态为可以允许线程通过。
事件分为手工释放和自动释放。如果是手工释放,则按照上述两函数处理事件的状态;如果是自动释放,则在一个线程结束后,自动清除事件状态,允许其它线程通过。
(4) 设置排斥区(CriticalSection),亦称关键区
在关键区中异步执行时,它只能在同一进程的线程之间共享资源处理。虽然此时上面介绍的三种方法均可使用,但是,使用关键区的方法则使同步管理的效率更高;
先定义一个CRITICAL_SECTION结构的关键区对象,在进程使用之前先对对象进行初始化,调用如下函数:
VOID InitializeCriticalSection( LPCRITICAL_SECTION );
当一个线程使用关键区时,调用函数:
EnterCriticalSection或者TryEnterCriticalSection
当要求占用、退出关键区时,调用函数:
LeaveCriticalSection
释放对关键区对象的占用,供其它线程使用。
互斥体对象、信号对象和事件对象也可以用于进程间的线程同步操作。在用Win32函数创建了对象时,我们可以指定对象的名字,还可以设置同步对象在子进程的继承性。创建返回的是HANDLE句柄,我们可以用函数DuplicateHandle来复制对象句柄,这样每个进程都可以拥有同一对象的句柄,实现进程之间的线程同步操作。另外,在同一进程内,我们可以用OpenMutex、OpenSemaphore和OpenEvent来获得指定名字的同步对象的句柄。
关键区异步执行的线程同步方法只能用于同一进程的线程之间共享资源处理,但是这种方法的使用效率较高,而且编程也相对简单一些。
下面我们讨论四个同步对象分别适用的场合:
(1)如果某个线程必须等待某些事件发生后才能存取相应资源,则用Event;
(2)如果一个应用同时可以有多个线程存取相应资源,则用Semaphore;
(3)如果有多个应用(多个进程)同时存取相应资源,则用Mutex,否则用CriticalSection。
在门控管理系统中,主要的问题是:在同一时间内,只能有一个线程对串口执行读操作或写操作,在读操作或写操作尚未完成前,其它的线程禁止对串口执行同样的操作。这就是说,在上一次读操作尚未完成前,其它线程不可以访问接收字符串,同样,在上一次写操作尚未完成前,其它线程不可以修改发送字符串。我在程序中用同步对象(主要是Event 和CriticalSection)和等待函数实现了线程间的同步,具体的实现将在后文详细描述。
4.3)VB与VC++的混合编程以及DLL
DLL就是动态连接库(Dynamic-Link Library)的简称。它是Windows程序设计的一个非常重要的组成部分。在建立应用程序的可执行文件时,不必将DLL文件链接到程序中,而是在运行时动态装载DLL,装载时,DLL被映射到进程的地址空间中。
为什么要在这里使用DLL呢?我的考虑是:虽然用VB设计前台界面具有方便快捷的优点,但VB在处理串口通信和多线程编程方面,程序的运行效率和实时响应能力都不尽如人意。在这些方面,C语言可以达到令人满意的效果,但完全用VC会使开发进度受到一定的影响,工作量也太大。一个折衷的办法就是,用VB做界面和其他对效率要求不高的部分,后台的实时服务部分用VC做成DLL文件,然后由VB来调用这个DLL文件中的函数。这样,关键部分的效率并不受多大影响。
此处的另一个问题就是VB的窗体不能直接处理自定义的消息,我用窗口子类化改写了监控窗体的窗口函数,使它可以对C语言函数发来的自定义消息做出响应。具体的实现见后文。
Top
3 楼temp()回复于 2001-05-19 22:33:00 得分 0
4 监控模块的设计:
4.1)监控模块的总体设计思路
监控模块的设计与本程序的其它部分略有不同,
首先,它主要运行于后台。
其次,相对其它模块,监控模块属于高优先级。
第三,对程序的运行效率和实时性能也有一定的要求。
根据老师的要求,如果有非法的访问者发出进入请求,后台的工作者线程会向监控窗体发出警报消息。发出无论前台这在做什么,监控界面必须立刻刷新,并显示报警信息。
我曾考虑了两种方案:使用MSCOMM控件或者用API,如果MSCOMM控件,实现起来比较容易,也比较安全可靠,但是有一定的局限性,因为MSCOMM控件通常使用查询的方法(也可以使用事件驱动)。难以做到同时处理两个或两个以上的请求。而使用API函数,则可以使用多线程加事件驱动的方法更好的满足需求。
我的思路是:用VB编写的监控窗体调用DLL文件中的出口函数,依次完成
初始化ADO数据库,建立一个ADO连接(这个_ConnectionPtr是一个全局变量)
然后初始化串口,启动串口监视线程和数据分析线程,当串口接收线程检测到串口传来一个字符的时候,读这个字符,用Postthreadmessage()发相应的消息给数据分析线程,这个字符用wParam参数传递,当数据分析线程收到串口监视线程发来的一个自定义消息WM_RXCHAR时,分析它,把它加入到相应的接收字符串中,如果数据分析线程确认自己完整的接收到了一个数据包,就启动一个ADO数据库查询线程,这个数据库查询线程建立一个_RecordsetPtr,它与进程中可能存在的其它的ADO数据库查询线程共用一个连接,根据接收来的字符串内容,生成SQL语句,查询数据库,根据对数据库服务器返回的结果,判断来访者是否有进入的权限,如果有,就像串口回送开门的命令,有过来访者没有这种权限,就向前台的监控窗体发一个自定义的报警消息。前台的监控窗体在收到报警消息后,立刻刷新窗体,显示报警位置和非法访问者的卡号。
下图就是DLL中的后台启动函数的流程图:
串口监视线程与数据分析线程的关系:(线程间通信的具体方法将在后文描述)
WM_COMM_RXCHAR 消息 RS-485信号
下面是程序的伪代码
_ConnectionPtr m_pConnection; /*这是Ado连接*/
bool m_fInitialized;
DWORD dwmain; /*这是数据分析线程的句柄*/
DWORD WINAPI ThreadProc(void* p); /*数据分析线程的控制函数*/
DWORD WINAPI AdoThread(void* p); /*数据处理线程的控制函数*/
CSerialPort m_port; /*串口对象*/
int start()
{
/*初始化OLE*/
if (FAILED(CoInitialize(NULL)))
m_fInitialized=TRUE;
HRESULT hr;
/*连接数据库*/
try
{
hr = m_pConnection.CreateInstance("ADODB.Connection");
/*创建Connection对象*/
if(SUCCEEDED(hr))
{ hr=m_pConnection-> Open(. . .);
}
}
catch(_com_error e)/*捕捉异常*/
{
. . ./*错误处理*/
}
/* 启动数据分析线程*/
::CreateThread(NULL,0,ThreadProc,0,0,&dwmain);
/*初始化串口,并启动串口监视线程,把dwmain传递给串口监视线程 */
if (m_port.InitPort(dwmain, 2, 9600 ))
m_port.StartMonitoring();
/*串口监视线程的线程控制函数是串口类的一个静态成员函数*/
return 1;
}
下面是后台部分的流程图(不含串口监控线程)
Windows消息
Exit
WM_COMM_RXCHAR
No
Yes
No Yes
4.2)对数据处理部分的单线程方案与多线程方案的分析:
随着硬件技术的发展,CPU的速度越来越快,相对来说,串口是低速设备,向数据库服务器进行一次查询也要花去不少的时间。根据木桶理论,一个木桶能放多少水,取决于最短的那一块木板。打个比方,就像我们的上网速度与我们计算机的CPU的速度没有什么关系。因为瓶颈在传输速度。
类似的,对串口通信的上位机而言,多数时间是CPU在等待串口读写操作的完毕和数据库查询结果的返回,而不是相反。
如果使用单线程,其流程基本是,查询接收缓冲区里是否有等待接收的数据,如果有,则接收数据,处理数据,向数据库服务器发出查询请求,然后等待数据库服务器返回的查询结果(注意:此间,整个进程可能被阻塞于此),接到查询结果之后,再做相应的处理。处理完毕后,再对串口接收缓冲区进行查询。
如果不用查询,也可以使用事件驱动,效率略高于查询,流程如下。
使用单线程的流程图
Windows消息
Exit
WM_COMM_RXCHAR
这种方法在一些对实时性能要求不高的场合是可行的(其实,这个方案已基本满足了门控服务器得要求),它的优点是程序结构清晰易读,因为不存在多线程程序那复杂的同步问题,所以程序的稳定性,健壮性好,CPU的负担相对较小。但它也有一些缺点的,比如说,实时性能不佳,等待I/O操作的结束可能导致整个进程的阻塞。
现在,我们设想这么一种情况,当进程正在等待数据库服务器返回结果的时候,又有一个门控控制机发来了串口信号,可是它必须在串口的接收缓冲区里等待。因为整个进程由于等待数据库服务器的返回结果而阻塞于此了。在处理完手头的事情之前,CPU没有时间去接收串口传来的新数据。这样,系统分配给该进程的CPU时间片,就这样白白的浪费掉了。
设想一下:如果发生数据大量高速涌入的情况,比如说,在3秒钟内,有20个门控控制机发出请求。那么,会出现什么情况?同一时间,进程只能去处理一个请求,其它的信号必须等待。数据库查询所花的时间越长,问题就越严重。在门控系统里,这样的问题或许不重要。因为出现这种情况的概率并不高,即便出现了,让用户在门外多等几秒钟也算不上什么大事。但在其它的一些场合,比如工控系统,这样的就会因为不能在规定的时间内做出响应而误事。
为了提高CPU的利用率,为了保证程序的实时响应能力。我在这里使用了多线程技术,串口监视线程,数据分析线程,动态建立的数据处理线程可以‘同时’工作。注意,在单CPU的系统里,这样的‘同时’是一种错觉,实际上,还是多个线程轮流使用CPU。如前问所述,CPU的速度已经很快,系统效率的瓶颈不在CPU,而在串口和数据库服务器。所以,整个进程的效率会提高不少。
使用多线程的流程图:
Windows消息
Exit消息
WM_COMM_RXCHAR
4.3)使用多线程带来的负面影响
任何事物都有它的两面性,CPU在提高了系统‘通量’的同时。也带来了一些负面的影响,如果处理不好,会完全抵消使用多线程所带来的好处。
首先需要说明的是,使用多线程技术并不能节省CPU的操作,相反,系统还要在切换上下文中花一些时间。多线程只是提高了CPU的利用率而已。
加入额外的线程可以增加通量,但是加入过多的线程将会降低系统的性能,因为上下文交换将会成为一个重大的负担。上下文交换速度应该低的原因有两个:上下文交换占用了许多宝贵的时钟周期;更糟的是,上下文交换将处理器的缓存填满了没用的数据,替换这些数据可能是代价高昂的。
虽然Windows NT系统和Windows2000系统的稳定性可以承受系统内同时运行上千个线程,(我曾见过NT系统中运行1700个线程的时候)但是当线程开得过多的时候,将会出现整体运行效率大大降低的情况。因为,目前大多数的计算机都是单处理器(CPU)的,在这种机器上运行多线程程序,有时反而会降低系统的性能。如果两个非常活跃的线程为了抢夺对CPU的控制权,则在线程切换时会消耗很多的CPU资源,但对于大部分时间被阻塞的线程(例如等待文件I/O操作),则可用一个单独的线程来完成。这样,就可将CPU时间让出来,使程序获得更好的性能。因此,在设计多线程应用程序时,应慎重选择,并且视具体情况加以处理,使应用程序获得最佳的性能。
对于我的设计,还可能有另一个问题也需要说明,动态的建立线程也需要花时间,但我确信这个时间比查一次数据库所花的时间要小的多。综合来看,在数据流量较大的情况下,还是利大于弊。所以我个人更倾向于使用多线程。
对多线程所带来的负面影响问题,本文在这里不做进一步的讨论。
4.4)一个串口类
使用面向对象的方法的对API进行封装,可以大大简化程序的编写,提高软件的重用,而微软的MFC类库里,并没有专门的串口类。我对一个已有的串口通信的例子做了一些做了一些改进,把相关的API封装成一个串口类。
class CSerialPort
{
public:
CSerialPort(); /* 构造函数 */
virtual ~CSerialPort(); /* 析构函数 */
/*串口初始化函数*/
BOOL InitPort(DWORD pPortOwner,
UINT portnr,
UINT baud,
char parity,
UINT databits,
UINT stopsbits,
DWORD dwCommEvents,
UINT nBufferSize);
BOOL StartMonitoring(); /*启动串口监视线程*/
BOOL RestartMonitoring(); /*重新启动串口监视线程*/
BOOL StopMonitoring(); /*暂停串口监视线程*/
DWORD GetWriteBufferSize(); /*获取发送字符串长度*/
DWORD GetCommEvents();
DCB GetDCB(); /*获取DCB结构的信息*/
void WriteToPort(char* string); /*向串口发送字符串命令*/
protected:
void ProcessErrorMessage(char* ErrorText);
static UINT CommThread(LPVOID pParam); /*串口监视线程的控制函数*/
static void ReceiveChar(CSerialPort* port, COMSTAT comstat);
static void WriteChar(CSerialPort* port);
CWinThread* m_Thread; /*串口监视线程*/
CRITICAL_SECTION m_csCommunicationSync;
BOOL m_bThreadAlive; /*线程的状态*/
HANDLE m_hShutdownEvent; /*关闭事件*/
HANDLE m_hComm; /*串口事件*/
HANDLE m_hWriteEvent; /*写事件*/
HANDLE m_hEventArray[3]; /*事件队列,用于同步*/
OVERLAPPED m_ov; /*OVERLAPPED 结构*/
COMMTIMEOUTS m_CommTimeouts; /*超时结构*/
DCB m_dcb; /*DCB结构*/
DWORD m_pOwner; /*另一个线程的句柄*/
UINT m_nPortNr;
char* m_szWriteBuffer;
DWORD m_dwCommEvents;
DWORD m_nWriteBufferSize;
};
这个串口类封装了与串口操作相关的Win32 API函数,大大简化了程序的编写。
本文主要介绍这个串口类的串口监视线程,其余部分请看附录。
下面是线程控制函数的流程图。
No
Yes
RS-485信号
No Yes
Yes
No
读事件 写事件 Exit消息
WM_COMM_RXCHAR消息
4.5)线程间的同步
在监视线程多数时间里,是在等待特定事件的发生:
Event = WaitForMultipleObjects(3, port->m_hEventArray, FALSE, INFINITE);
有三个事件的发生会得到响应,使程序继续进行,它们是:
关闭事件(m_hShutdownEvent)
串口事件(m_hComm)
写事件 (m_hWriteEvent)
它们三个组成了一个事件队列m_hEventArray。当三者之一的任何事件发生(有信号)时,线程继续往下执行。如果没有事件发生(无信号),就无限期的等待。
那么,事件是怎么变成有信号的呢?
在之前执行的WaitCommEvent(port->m_hComm, &Event, &port->m_ov)函数中,如果串口事件,则m_hComm被置为有信号。当WaitForMultipleObjects()函数检测到m_hComm事件的发生,就会根据串口事件的类型做相映的处理。
在WriteToPort(char* string)函数中,m_hWriteEvent被置为有信号。当串口监视线程的WaitForMultipleObjects()函数发现m_hWriteEvent被置为有信号,就把发送字符串从串口发送出去。
下面说明一下互斥访问的同步问题,如前所述,这类同步问题可用互斥体(Mutex)解决,但在这里,我使用同步事件对象(Event)和关键区(CRITICAL_SECTION)。
比如,当监视线程执行WriteChar()向串口发送字符串的时候,不希望被别的线程打断。这时候,可以用排斥区排斥区(CRITICAL_SECTION)。
void CSerialPort::WriteChar(CSerialPort* port)
{
. . .
EnterCriticalSection(&port->m_csCommunicationSync);
/* 占用关键区 */
发送字符串;
LeaveCriticalSection(&port->m_csCommunicationSync);
/*离开关键区*/
. . .
SetEvent(port->m_sendfinished);
}
再比如:当有一个线程试图执行WriteToPort()的时候,它必须检查m_sendfinished是否有信号,如果有,表示可以执行,如果此时,m_sendfinished无信号,表示此刻不能执行发送任务,要等待一会。
void CSerialPort::WriteToPort(char* string)
{
assert(m_hComm != 0);
WaitForSingleObject(m_sendfinished,INFINITE);
memset(m_szWriteBuffer, 0, sizeof(m_szWriteBuffer));
strcpy(m_szWriteBuffer, string);
SetEvent(m_hWriteEvent);
}
void CSerialPort::WriteChar(CSerialPort* port)
{
ResetEvent(port->m_hWriteEvent);
发送字符串;
SetEvent(port->m_sendfinished);
}
如果省略了同步对象和等待函数,会有什么事情发生呢?我们设想一下,当串口监视线程正在执行一个发送字符串的操作,假设这个字符串有点长,当串口监视线程用完了自己的时间片后,仍然没有发完,它必须交出CPU,另一个线程开始执行,它也要发送一个字符串,于是,WriteToPort(char* string)函数就修改了发送缓冲字符串,问题就出在这里,那个上一个字符串还没发完呢。
另一种可能的情况是,WriteToPort(char* string)函数修改了发送字符串,并置写事件为有信号,而串口监视线程尚未发送此字符串,此间,另一个线程也执行了WriteToPort(char* string)函数。那么发送字符串将被修改。
我曾这么试了一回,连续执行了三个WriteToPort(char* string)命令,结果只有最后一个命令指定的字符串发了出去。可见,同步问题值得我们注意。
4.6)工作者线程间的通信
线程分为用户界面线程(UI线程)和工作者线程,UI线程有自己的窗口和消息队列,而工作者线程通常在后台工作,不能直接接收用户的命令。工作者线程可以通过PostThreadMessage()函数向一个已知ID的线程发消息传递消息,另一个线程通过GetMessage()接收消息。
BOOL PostThreadMessage
(
DWORD idThread, /* thread identifier */
UINT Msg, /* message to post */
WPARAM wParam, /* first message parameter */
LPARAM lParam /* second message parameter */
);
在CreateThread()函数中的lpThreadId 参数会设为新线程的ID,把它保存为全局变量或作为Dll的输出变量(反正要让对方得到你的ThreadID),在主程序或其他线程中就可以用PostThreadMessage发送线程消息了,比如:
PostThreadMessage(TargetThreadID, WM_ANYMESSAGE,0,0);
如果Target是有消息循环的线程,就会在GetMessage或peekMessage时得到一个WM_ANYMESSAGE的消息。如果Target没有消息循环,他不会响应你的消息,但也不会出错。
(注意:PostMessage由于需要一个hWnd参数,对于没有窗口的线程是不适用的。 )
举个例子:
int start()
{
. . .
::CreateThread(NULL,0,ThreadProc,0,0,&dwmain);
if (m_port.InitPort(dwmain, 2, 9600 ))
m_port.StartMonitoring();
. . .
}
dwmain就作为线程的ID保存为全局变量,然后传递给串口监视线程,以便串口监视线程在必要时向dwmain线程发送消息。
这是线程控制函数:
DWORD WINAPI ThreadProc(void* p)
{
MSG msg;
while (::GetMessage(&msg, NULL, 0, 0))
{
switch(msg.message)
{
case WM_COMM_RXCHAR:
. . .
break;
case WM_QUIT:
. . .
return 5;
default:
break;
}
}
return 10;
}
void CSerialPort::ReceiveChar(CSerialPort* port, COMSTAT comstat)
{
. . .
::PostThreadMessage(port->m_pOwner,WM_COMM_RXCHAR,(WPARAM)RXBuff, (LPARAM) port->m_nPortNr);
. . .
}
这样,我们就完成了两个工作者线程间的通信。
Top
4 楼temp()回复于 2001-05-19 22:34:00 得分 0
6)VB窗体和与DLL之间的消息传递
6.1)问题的提出
根据监控模块的总体设计,当遇到非法访问者时,需要向前台的监视界面发送一个WM_ALARM的自定义消息,但是VB编写的界面无法直接接收自定义的消息。于是就需要做些特殊处理。
6.2)VB窗口函数的工作机理和局限性
虽然VB由于简单易学,开发小项目速度快的优点而被广泛的应用。但它的某些先天的缺陷使它在许多场合仍有力不从心之感。
举例来说,VB编写的程序,不能直接的响应Windows消息,尤其是不能直接响应自定义的消息。
一般的,在VB编写的程序中,当一个窗体接到一个Windows消息时,会调用预设的窗口程序,这个窗口程序会把Windows某些消息转化成事件。
如果用VB函数表示窗口程序,可表示为:
Function WndProcName(ByVal hWnd As Long,
ByVal Msg As Long,
ByVal wParam As Long,
ByVal lParam As Long)
Select Case Msg
Case WM_XXXX:
转化为a事件
Case WM_XXXX:
转化为b事件
. . .
Case Else
Ret=DefWindowsProc(hWnd,Msg,wParam,lParam)
End Select
WndProcName=ret
End Function
Windows消息多达数百种,VB预设的窗口程序不会处理这么多消息,一般是交给DefWindowProc代劳。对VB提供的许多对象,(如Form,TextBox,ListBox)都含有某些事件。因此,当VB预设的窗口程序收到消息时,会判断这个消息是否应该转化为事件。如果是,则判断该程序是否含有该事件的事件过程,如果有,则调用这一过程。
6.2)窗口子类化
显而易见,VB预设的窗口程序会吃掉许多消息(也就是说,消息不会产生对应的事件),VB如此处理的初衷是只保留最常用的消息可以减轻程序设计人员的负担。但在另一方面,这么做的后果就是程序无法实现某些特殊的功能,过去,VB一直被批评为易于使用但功能不足,这是其中一个重要的原因。不过,VB在第5.0版之后,支持了AddressOf操作,使我们可以用窗口子类化来解决这一问题。
所谓窗口子类化,实际上就是改变窗口内存块中的有关参数。由于这种修改只涉及到一个窗口的窗口内存块,因此它不会影响到属于同一窗口类的其它窗口的功能和表现。窗口子类化中最常见的是修改窗口内存块中的窗口函数地址(lpfnWndProc),使其指向一个新的窗口函数,从而改变原窗口函数的处理方法,改进其功能。其基本步骤如下:
首先,编写子类化窗口函数。该函数必须为标准的窗口函数格式.
Function myWndProc(ByVal hwnd As Long,
ByVal Msg As Long,
ByVal wParam As Long,
ByVal lParam As Long
)As Long
在这个函数中对感兴趣的消息进行处理,而把未处理或者需要原窗口函数进一步处理的消息传送给原窗口函数;
然后,利用待子类化窗口的句柄hWnd,调用GetWindowLong ( hWnd , GWL_WNDPROC ) 函数获得原窗口函数的地址并保存起来;
然后,调用SetWIndowLong ( hWnd , GWL_WNDPROC , myWndProc ) 把窗口函数设置成子类化窗口函数,完成窗口子类化。
由于VB的窗口函数是隐藏的,我们无法改变其中的程序码,但如果已知其hWnd,则Windows允许我们将VB预设的窗口函数替换成我们编写的窗口函数。
SetWindowLong Me.hwnd, GWL_WNDPROC, AddressOf myWndProc
这句话的意思是:把Me.hwnd的窗口函数入口地址修改为函数myWndProc
的入口地址。于是,VB预设的窗口函数就会失效,取而代之的是我们预先写好的窗口函数来接收Windows消息了。
由此又产生了另一个问题,当我们换掉一个窗口函数,我们至少要提供与原窗口函数相同的功能,而VB 预设的窗口函数到提供了那些功能,我们并不清楚。
解决的办法是,在VB原来预设的窗口函数之前,插入另一个窗口函数,而不破坏原来的窗口函数。
当myWndProc(hWnd, Msg, wParam, lParam)函数接收到一个消息的时候,首先判断是不是我们希望特殊处理的消息。如果不是我们希望处理的消息,就把它转交给VB预设的窗口程序。这一技术,Windows称之为子类化(SubClassing)。
6.3)一个应用实例
我的监控界面就使用了子类化技术,以接收来自DLL传来的消息。
在模块文件中
Public Const GWL_WNDPROC = (-4)
Public Const WM_USER = &H400
Public Const WM_ALARM = WM_USER + 10
这里WM_ALARM是自定义的报警消息
Function myWndProc(ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Select Case Msg
Case WM_ALARM
报警处理
/*如果是WM_ALARM消息,就作报警处理*/
. . . . . .
Case Else
myWndProc = CallWindowProc(prevWndProc, hwnd, Msg, wParam, lParam)
/*如果是其它消息,就转交给VB预设的窗口函数处理*/
End Select
End Function
在窗体文件中
Private Sub Form_Load()
prevWndProc = GetWindowLong(Me.hwnd, GWL_WNDPROC)
SetWindowLong Me.hwnd, GWL_WNDPROC, AddressOf myWndProc
End Sub
这里prevWndProc保存了原来VB预设的窗口函数的地址
SetWindowLong Me.hwnd, GWL_WNDPROC, AddressOf myWndProc把Me.hwnd的窗口函数地址修改为myWndProc的入口地址。这样窗体接到消息后,就交给myWndProc函数处理了。
Private Sub Form_Unload(Cancel As Integer)
SetWindowLong Me.hwnd, GWL_WNDPROC, prevWndProc
End Sub
在退出窗体前,一定要恢复原来的设置,否则会影响系统的稳定性。
Top
5 楼temp()回复于 2001-05-19 22:34:00 得分 0
6总结
本文介绍了多线程技术在串口程序中的应用。我并不讳言,使用多线程所带来的问题,我们应该本着具体问题具体分析原则,分析利弊,明确究竟是利大,还是弊大,这样才能决定在什么情况下应该使用多线程,在什么情况下不应该使用多线程。那种认为多线程一定会提高整体效率,或者认为多线程一定会增加CPU负担的看法,是片面的,不完全的。
Top
6 楼temp()回复于 2001-05-19 23:14:00 得分 0
怎么没人理?Top
7 楼flyingkoala(飞飞)回复于 2001-05-20 10:05:00 得分 0
为什么不在一开始就建立几个线程呢?
一个监控串口,一个查询数据库,一个处理前台界面?
不过也差不多
几个线程之间的数据共享你是如何处理的?缓冲区?
比如说当监控程序监测到下位机的请求而去查询取数据库,却因为数据库忙而需要等待的时候又怎么办呢?Top
8 楼nosleep()回复于 2001-05-20 20:16:00 得分 0
先看看
再说吧。Top
9 楼sjy()回复于 2001-05-20 23:29:00 得分 180
据我所知,1。这个串口类一次只能收一个字节,你最好把它改了,要不影响性能,
2。这个串口类的初始化函数好象对流控等支持不够,还要自己做些改动,用是挺好用的。Top
10 楼sjy()回复于 2001-05-20 23:32:00 得分 0
提起来,这个串口类发二进制数据时也有些毛病,不过有现成的补丁,就在GURU里。Top
11 楼temp()回复于 2001-05-21 00:26:00 得分 0
哦?补丁在哪里?Top
12 楼lemonade(Wings of Summer)回复于 2001-05-21 07:10:00 得分 0
I made a copy if u don't mind...come back later after I ask my teacher...Top
13 楼temp()回复于 2001-05-21 08:21:00 得分 0
多提一点批评呀。Top
14 楼shelley_E(把悲伤留给记忆)回复于 2001-05-21 11:10:00 得分 0
嘿,哥们儿,我现在也在做毕业设计,我做的是关于串行和并行通信的,我现在基本已经把串行的写玩了,基本都是网上下载的,再加上一点基本原理,我发现咱两的论文方向基本一致,希望见到我的留言后,与我联系我的信箱是luanjiangyi@263.net,咱们交流一下.Top
15 楼temp()回复于 2001-05-21 21:42:00 得分 0
我发现这个串口类写串口writetoport(char *string)缺少同步,不过已经解决了。Top
16 楼temp()回复于 2001-05-21 21:45:00 得分 0
<<为什么不在一开始就建立几个线程呢?
<<一个监控串口,一个查询数据库,一个处理前台界面?
无法预知有多少个请求。
<<几个线程之间的数据共享你是如何处理的?缓冲区?
全局变量+同步对象
<<比如说当监控程序监测到下位机的请求而去查询取数据库,却因为数据库忙而需要等待的时候又<<怎么办呢?
等着,具体让操作系统去考虑
Top
17 楼sjy()回复于 2001-05-21 22:32:00 得分 0
补丁在CODEGURU,我看你很大编付在讲串口,很少讲数据库,
其实,你这个程序很大一部分工作由SQL来完成,我也不知道你的程序
是不是每接到一个请求就开一个线程来处理SQL语句,如果是这样的话,如果同时有
10个连接就会有10个线程。如果你是有存储过程来处理SQL请求的话,因为存储过程的
处理非常快,你的动态构成的SQL语句应该都是差不多的,所以跑的应该都是同一个存储过程,这种情况下可能只用一个线程处理SQL比10个线程来处理SQL要快。
如果您没有用存储过程,那就浪费了SQL7。
至于说哪个好,您不必听谁的,自己设计一段程序,跑上一会儿,算一算程序的运行时间,
就知道了。
以上是一家之言,错了别打我屁股。Top
18 楼temp()回复于 2001-05-22 19:13:00 得分 0
主程序结束了,DLL里建立的子线程一定会自动结束吗?
我在VB里调用一个VC写的DLL,DLL里启动新线程。
可是,好像VB的主程序结束时,DLL里的线程没结束干净,怎么办?Top
19 楼sjy()回复于 2001-05-23 19:03:00 得分 0
你要发一个信号给DLL的线程,让它停止才好让VB的程序结束。要不是确实会有些资源
会清不干净。
对于这个问题,你最好再发个贴子去VC论坛吧。Top
20 楼temp()回复于 2001-05-23 21:11:00 得分 0
为什么在按 !执行时没问题(0个警告)的程序,在按F5调试模式执行时却报错呢?
// testdll.cpp : Defines the initialization routines for the DLL.
//
#include "stdafx.h"
#include "testdll.h"
#include "SerialPort.h"
#include <windows.h>
#include <time.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
//
// Note!
//
// If this DLL is dynamically linked against the MFC
// DLLs, any functions exported from this DLL which
// call into MFC must have the AFX_MANAGE_STATE macro
// added at the very beginning of the function.
//
// For example:
//
// extern "C" BOOL PASCAL EXPORT ExportedFunction()
// {
// AFX_MANAGE_STATE(AfxGetStaticModuleState());
// // normal function body here
// }
//
// It is very important that this macro appear in each
// function, prior to any calls into MFC. This means that
// it must appear as the first statement within the
// function, even before any object variable declarations
// as their constructors may generate calls into the MFC
// DLL.
//
// Please see MFC Technical Notes 33 and 58 for additional
// details.
//
/////////////////////////////////////////////////////////////////////////////
// CTestdllApp
BEGIN_MESSAGE_MAP(CTestdllApp, CWinApp)
//{{AFX_MSG_MAP(CTestdllApp)
// NOTE - the ClassWizard will add and remove mapping macros here.
// DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CTestdllApp construction
#define WM_ALARM WM_USER+10
_ConnectionPtr m_pConnection;
bool m_fInitialized;
HWND myhwnd;
int mycom;
HANDLE m_startadoevent;
DWORD dwmain;
DWORD WINAPI ThreadProc(void* p);
DWORD WINAPI AdoThread(void* p);
CSerialPort m_port;
CString mystr;
CString doorstr;
CString cardstr;
CString addrstr;
CString wrongcardstr;
CString wrongdoorstr;
CTestdllApp::CTestdllApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}
/////////////////////////////////////////////////////////////////////////////
// The one and only CTestdllApp object
CTestdllApp theApp;
int WINAPI start(HWND hwnd,int comid)
{
myhwnd=hwnd;
mycom=comid;
if (m_startadoevent != NULL)
ResetEvent(m_startadoevent);
m_startadoevent = CreateEvent(NULL, FALSE, FALSE, NULL);
SetEvent(m_startadoevent);
if (FAILED(CoInitialize(NULL)))
m_fInitialized=TRUE;
/*连接数据库*/
HRESULT hr;
try
{
hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象
if(SUCCEEDED(hr))
{
hr = m_pConnection->Open("DSN=sql;UID=sa;PWD=","","",-1);
}
}
catch(_com_error e)///捕捉异常
{
return 2;
}
// handle=::CreateThread(NULL,0,ThreadProc,0,0,&dwmain);
::CreateThread(NULL,0,ThreadProc,0,0,&dwmain);
if (m_port.InitPort(dwmain, mycom, 9600 ))
m_port.StartMonitoring();
else return 3;
m_port.WriteToPort("aaaa111222b");
return 1;
}
DWORD WINAPI ThreadProc(void* p)
{
int i=0;
MSG msg;
while (::GetMessage(&msg, NULL, 0, 0))
{
switch(msg.message)
{
case WM_COMM_RXCHAR:
// mystr+=char(msg.wParam);
// ::MessageBox(myhwnd,mystr,0,0);
if(i==0)
WaitForSingleObject(m_startadoevent,INFINITE);
if(i==0 && char(msg.wParam)=='a')
{i++;}
else if( i>=1 && i<=3 )
{
addrstr+=char(msg.wParam);
i++;
}
else if( i>=4 && i<=6 )
{
cardstr+=char(msg.wParam);
i++;
}
else if(i>=7 && i<=9)
{
doorstr+=char(msg.wParam);
i++;
}
else if(i==10 && char(msg.wParam)=='b')
{
::CreateThread(NULL,0,AdoThread,0,0,NULL);
i=0;
}
break;
case WM_COMM_CTS_DETECTED:
break;
case WM_QUIT:
::MessageBox(myhwnd,"return 5;",0,0);
return 5;
default:
break;
}
}
::MessageBox(myhwnd,"return 10;",0,0);
return 10;
}
DWORD WINAPI AdoThread(void* p)
{
_RecordsetPtr m_pRecordset;
_variant_t adostr,vmenhao,vkahao;
CTime vtime;
CTime mytime;
CString mydoorstr;
CString mycardstr;
CString myaddrstr;
CString sqlstr;
CString daystr;
CString timestr;
CString sendstr;
mydoorstr.Format("%s",doorstr);
mycardstr.Format("%s",cardstr);
myaddrstr.Format("%s",addrstr);
doorstr="";
cardstr="";
addrstr="";
SetEvent(m_startadoevent);
mytime=CTime::GetCurrentTime();
daystr.Format("%d-%d-%d",mytime.GetYear(),mytime.GetMonth(),mytime.GetMonth());
timestr.Format("%s %d:%d:%d",daystr,mytime.GetHour(),mytime.GetMinute(),mytime.GetSecond());
sqlstr.Format("SELECT * FROM 权限总表 where (卡号=%s and 门号=%s and 日期='%s' and 准进起始时间<'%s' and '%s'<准进终止时间)",mycardstr,mydoorstr,daystr,timestr,timestr);
adostr=sqlstr;
try
{
m_pRecordset.CreateInstance("ADODB.Recordset");
m_pRecordset->Open(adostr,_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
if(!m_pRecordset->adoEOF)
{
sendstr.Format("\n%s---1",myaddrstr);
::MessageBox(myhwnd,"OK!",0,0);
// m_port.WriteToPort("OK!");
}
else
{
sendstr.Format("\n%s---0",myaddrstr);
wrongcardstr=mycardstr;
wrongdoorstr=mydoorstr;
::PostMessage(myhwnd,WM_ALARM,0,0);
// m_port.WriteToPort("Wrong!");
}
}
catch(_com_error e)///捕捉异常
{
}
return 10;
}
int WINAPI getcardstr(LPSTR pCh)
{
sprintf(pCh,"%s", wrongcardstr );
return 1;
}
int WINAPI getdoorstr(LPSTR pCh)
{
sprintf(pCh,"%s", wrongdoorstr );
return 1;
}
/*-------------------------------------------------------*/
Loaded 'D:\WINNT\System32\ntdll.dll', no matching symbolic information
found.
Loaded 'D:\WINNT\system32\kernel32.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\user32.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\gdi32.dll', no matching symbolic information
found.
Loaded 'D:\WINNT\system32\ole32.dll', no matching symbolic information
found.
Loaded 'D:\WINNT\system32\rpcrt4.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\advapi32.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\oleaut32.dll', no matching symbolic
information found.
Loaded symbols for 'D:\WINNT\system32\MFC42D.DLL'
Loaded symbols for 'D:\WINNT\system32\MSVCRTD.DLL'
Loaded symbols for 'D:\WINNT\system32\MSVCP60D.DLL'
Loaded 'D:\WINNT\system32\imm32.dll', no matching symbolic information
found.
Loaded 'D:\WINNT\system32\mfc42loc.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\indicdll.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\clbcatq.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\msvcrt.dll', no matching symbolic
information found.
Loaded 'D:\Program Files\Common Files\System\ado\msado15.dll', no
matching symbolic information found.
Loaded 'D:\WINNT\system32\msdart32.dll', no matching symbolic
information found.
Loaded 'D:\Program Files\Common Files\System\Ole DB\oledb32.dll', no
matching symbolic information found.
Loaded 'D:\Program Files\Common Files\System\Ole DB\oledb32r.dll', no
matching symbolic information found.
Loaded 'D:\Program Files\Common Files\System\Ole DB\msdasql.dll', no
matching symbolic information found.
Loaded 'D:\WINNT\system32\shell32.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\shlwapi.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\comctl32.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\odbc32.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\comdlg32.dll', no matching symbolic
information found.
Loaded 'D:\Program Files\Common Files\System\Ole DB\msdatl2.dll', no
matching symbolic information found.
Loaded 'D:\WINNT\system32\odbcint.dll', no matching symbolic
information found.
Loaded 'D:\Program Files\Common Files\System\Ole DB\msdasqlr.dll', no
matching symbolic information found.
Loaded 'D:\WINNT\system32\mswstr10.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\mswdat10.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\comsvcs.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\netapi32.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\secur32.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\netrap.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\samlib.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\ws2_32.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\ws2help.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\wldap32.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\dnsapi.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\wsock32.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\txfaux.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\msdtcprx.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\mtxclu.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\version.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\lz32.dll', no matching symbolic information
found.
Loaded 'D:\WINNT\system32\clusapi.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\resutils.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\userenv.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\sqlsrv32.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\sqlwoa.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\nddeapi.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\winspool.drv', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\odbccp32.dll', no matching symbolic
information found.
Loaded 'D:\WINNT\system32\dbnmpntw.dll', no matching symbolic
information found.
Initialisation for communicationport 2 completed.
Use Startmonitor to communicate.
Thread started
Loaded 'D:\Program Files\Common Files\System\ado\msadrh15.dll', no
matching symbolic information found.
The thread 0x140 has exited with code 10 (0xA).
The thread 0x114 has exited with code -1073741510 (0xC000013A).
The thread 0x590 has exited with code -1073741510 (0xC000013A).
The thread 0x588 has exited with code -1073741510 (0xC000013A).
The thread 0x528 has exited with code -1073741510 (0xC000013A).
The thread 0x110 has exited with code -1073741510 (0xC000013A).
The thread 0x430 has exited with code -1073741510 (0xC000013A).
The thread 0x260 has exited with code -1073741510 (0xC000013A).
The thread 0x4AC has exited with code -1073741510 (0xC000013A).
The thread 0x178 has exited with code -1073741510 (0xC000013A).
The thread 0x38C has exited with code -1073741510 (0xC000013A).
Detected memory leaks!
Dumping objects ->
strcore.cpp(118) : {66} normal block at 0x00C81A50, 16 bytes long.
Data: < 22 > 01 00 00 00 00 00 00 00 03 00 00 00 00 32 32
00
strcore.cpp(118) : {63} normal block at 0x00C81A90, 16 bytes long.
Data: < 11 > 01 00 00 00 00 00 00 00 03 00 00 00 00 31 31
00
strcore.cpp(118) : {60} normal block at 0x00C80030, 16 bytes long.
Data: < aa > 01 00 00 00 00 00 00 00 03 00 00 00 00 61 61
00
thrdcore.cpp(166) : {56} client block at 0x00C81B70, subtype 0, 112
bytes long.
a CWinThread object at $00C81B70, 112 bytes long
{53} normal block at 0x00C81C10, 512 bytes long.
Data: <Wrong! 222b > 57 72 6F 6E 67 21 00 32 32 32 62 00 CD CD CD
CD
Object dump complete.
The thread 0x594 has exited with code -1073741510 (0xC000013A).
The program 'D:\Documents and Settings\Administrator\桌面
\testmfcapi7\Debug\testmfcapi.exe' has exited with code -1073741510
(0xC000013A).
Top
21 楼rich_lee()回复于 2001-05-24 11:35:00 得分 0
程序有内存泄露。
最好自己控制线程的生存周期(启动和结束)。
至少可以肯定线程控制函数ThreadProc无法以ExitCode == 5退出,因为GetMessage(&msg, NULL, 0, 0)收到WM_QUIT消息后返回0,所以ExitCode将为10Top
22 楼sjy()回复于 2001-05-24 19:16:00 得分 0
bugs 还真不少:
1.
should be:
if (m_startadoevent != NULL)
{
ResetEvent(m_startadoevent);
}
else
{
m_startadoevent = CreateEvent(NULL, FALSE, FALSE, NULL);
}
你可能说SERIALPORT也就这样写的,实际上SERIALPROT的写法也不对。
2.strcore.cpp(118) : {66} normal block at 0x00C81A50, 16 bytes long.
Data: < 22 > 01 00 00 00 00 00 00 00 03 00 00 00 00 32 32
00
strcore.cpp(118) : {63} normal block at 0x00C81A90, 16 bytes long.
Data: < 11 > 01 00 00 00 00 00 00 00 03 00 00 00 00 31 31
00
strcore.cpp(118) : {60} normal block at 0x00C80030, 16 bytes long.
Data: < aa > 01 00 00 00 00 00 00 00 03 00 00 00 00 61 61
00
肯定是CSTRING操作有误造成的,(STRCORE。CPP就是操作CSTRING的)
帖出来的程序好象没有问题,但我想在你的SERIALPORT里可能有问题,
Data: < 11 > 01 00 00 00 00 00 00 00 03 00 00 00 00 31 31
Data: < aa > 01 00 00 00 00 00 00 00 03 00 00 00 00 61 61
Data: < 22 > 01 00 00 00 00 00 00 00 03 00 00 00 00 32 32
看看LEAK的部分;22 11 aa
就不是你操作的CSTRING的一部分吗?
3。不要用CTIME了,用COLEDATETIME吧,要不就出来个什么2076年虫了。
4。没有用_T(" ");
如果你没有用存储过程,开多个线程就比开一个好。
m_pRecordset.CreateInstance("ADODB.Recordset");
m_pRecordset->Open(adostr,_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
这两句肯定最花时间。
加油吧,我当年毕业时比你现在的水平差多了。Top
23 楼sjy()回复于 2001-05-24 19:24:00 得分 0
你这个贴子怎么看都应该在VC版的,还不行的话,你发那些去,那边的高手牛多了。Top
24 楼temp()回复于 2001-05-24 20:27:00 得分 0
To sjy(您好) :
谢谢!Top
25 楼temp()回复于 2001-05-24 20:47:00 得分 0
To sjy(您好) :
4。没有用_T(" ");
如果你没有用存储过程,开多个线程就比开一个好。
m_pRecordset.CreateInstance("ADODB.Recordset");
m_pRecordset->Open(adostr,_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
这两句肯定最花时间。
/*----------------------------------*/
怎么讲?怎么用_T(" ")?
Top
26 楼temp()回复于 2001-05-24 20:48:00 得分 0
<<至少可以肯定线程控制函数ThreadProc无法以ExitCode == 5退出,因为GetMessage(&msg, NULL, 0, 0)收到WM_QUIT消息后返回0,所以ExitCode将为10
/*-----------------------------*/
该怎么改呢?Top
27 楼sjy()回复于 2001-05-24 22:57:00 得分 0
1。加上_T后就是程序就比较容易修改为支持UNICODE,跑2000或NT就好办了。
用法:把"ddddd"改为_T("ddddd");不加_T也是可以的。但你要考虑到兼容问题。。。。
这不算是BUG。
2。m_pRecordset.CreateInstance("ADODB.Recordset");
m_pRecordset->Open(adostr,_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
这两句肯定最花时间。
不用说,头一句是分配一个RECORDSET,
第二句实际上执行查询。都是花时间,如果用单线程的话而且
请求又多的话可能会堵,
不过说实话,我ADO不熟,我用的是古老的ODBC,不过用ODBC好,还是ADO好,
存储过程都比你用RECORDSET执行SQL快,因为SQL引擎在底层支持存储过程。
Top
28 楼temp()回复于 2001-05-25 23:21:00 得分 0
_variant_t adostr
sqlstr.Format("SELECT * FROM 权限总表 where (卡号=%s ...);
adostr=_T("sqlstr");
m_pRecordset.CreateInstance("ADODB.Recordset");
m_pRecordset->Open(adostr,_variant_t...)
是这样吗?
Top
29 楼sjy()回复于 2001-05-26 01:25:00 得分 0
sqlstr.Format(_T("SELECT * FROM 权限总表 where (卡号=%s ...)");
adostr=_T("sqlstr");
m_pRecordset.CreateInstance(_T("ADODB.Recordset"));
m_pRecordset->Open(adostr,_variant_t...)
Top
30 楼temp()回复于 2001-05-26 13:07:00 得分 0
谢谢!
我这就去改。Top
31 楼temp()回复于 2001-05-26 20:42:00 得分 0
请问诸位前辈:
这篇论文可以打多少分?答辩时老师会问些什么问题?Top
32 楼temp()回复于 2001-05-27 12:41:00 得分 0
哪位大虾给打个分? Top
33 楼temp()回复于 2001-05-27 19:59:00 得分 0
如果你是老师,你会问什么问题呢?Top
34 楼sjy()回复于 2001-05-27 23:02:00 得分 0
我不是大虾,但我不会只看论文就打分。
我不知道硬件是不是你做的,这东西是软硬结合的,不应该只看论文而不看实际应用,实际依我看硬件的设计非学重要。
我的打分依据:
1。可靠性:我会设计一个测量方案,试不同的用户组合,用IC卡尝试开门。测量数据
要大于500次,如果有一次输入正确而得不到正常反应,扣10分。
2。稳定性:不要有什么内存疏漏,如果你上面提到的内存LEAK还存在的话,扣10分。因为你的系统是24小时运行的,稳定非常重要,这不同于其它只运行一两次的程序,想一想,如果因为你的程序死机(无论是硬件或软件原因,通常是控制部分的单片

