在工業(yè)生產(chǎn)控制系統(tǒng)中,有許多需要定時(shí)完成的操作,如定時(shí)顯示當(dāng)前時(shí)間,定時(shí)刷新屏幕上的進(jìn)度條,上位 機(jī)定時(shí)向下位機(jī)發(fā)送命令和傳送數(shù)據(jù)等。特別是在對控制性能要求較高的實(shí)時(shí)控制系統(tǒng)和數(shù)據(jù)采集系統(tǒng)中,就更需要精確定時(shí)操作。
眾所周知,Windows 是基于消息機(jī)制的系統(tǒng),任何事件的執(zhí)行都是通過發(fā)送和接收消息來完成的。 這樣就帶來了一些問題,如一旦計(jì)算機(jī)的CPU被某個(gè)進(jìn)程占用,或系統(tǒng)資源緊張時(shí),發(fā)送到消息隊(duì)列 中的消息就暫時(shí)被掛起,得不到實(shí)時(shí)處理。因此,不能簡單地通過Windows消息引發(fā)一個(gè)對定時(shí)要求 嚴(yán)格的事件。另外,由于在Windows中已經(jīng)封裝了計(jì)算機(jī)底層硬件的訪問,所以,要想通過直接利用 訪問硬件來完成精確定時(shí),也比較困難。所以在實(shí)際應(yīng)用時(shí),應(yīng)針對具體定時(shí)精度的要求,采取相適 應(yīng)的定時(shí)方法。
VC中提供了很多關(guān)于時(shí)間操作的函數(shù),利用它們控制程序能夠精確地完成定時(shí)和計(jì)時(shí)操作。本文詳細(xì)介紹了 VC中基于Windows的精確定時(shí)的七種方式,如下圖所示:
方式一:VC中的WM_TIMER消息映射能進(jìn)行簡單的時(shí)間控制。首先調(diào)用函數(shù)SetTimer()設(shè)置定時(shí) 間隔,如SetTimer(0,200,NULL)即為設(shè)置200ms的時(shí)間間隔。然后在應(yīng)用程序中增加定時(shí)響應(yīng)函數(shù) OnTimer(),并在該函數(shù)中添加響應(yīng)的處理語句,用來完成到達(dá)定時(shí)時(shí)間的操作。這種定時(shí)方法非常 簡單,可以實(shí)現(xiàn)一定的定時(shí)功能,但其定時(shí)功能如同Sleep()函數(shù)的延時(shí)功能一樣,精度非常低,最小 計(jì)時(shí)精度僅為30ms,CPU占用低,且定時(shí)器消息在多任務(wù)操作系統(tǒng)中的優(yōu)先級很低,不能得到及時(shí)響 應(yīng),往往不能滿足實(shí)時(shí)控制環(huán)境下的應(yīng)用。只可以用來實(shí)現(xiàn)諸如位圖的動(dòng)態(tài)顯示等對定時(shí)精度要求不高的情況。如示例工程中的Timer1。
方式二:VC中使用sleep()函數(shù)實(shí)現(xiàn)延時(shí),它的單位是ms,如延時(shí)2秒,用sleep(2000)。精度非常 低,最小計(jì)時(shí)精度僅為30ms,用sleep函數(shù)的不利處在于延時(shí)期間不能處理其他的消息,如果時(shí)間太 長,就好象死機(jī)一樣,CPU占用率非常高,只能用于要求不高的延時(shí)程序中。如示例工程中的Timer2。
方式三:利用COleDateTime類和COleDateTimeSpan類結(jié)合WINDOWS的消息處理過程來實(shí)現(xiàn)秒級延時(shí)。如示例工程中的Timer3和Timer3_1。以下是實(shí)現(xiàn)2秒的延時(shí)代碼: COleDateTime start_time = COleDateTime::GetCurrentTime();COleDateTimeSpan end_time= COleDateTime::GetCurrentTime()-start_time;while(end_time.GetTotalSeconds()< 2) //實(shí)現(xiàn)延時(shí)2秒{ MSG msg;GetMessage(&msg,NULL,0,0);TranslateMessage(&msg); DispatchMessage(&msg);//以上四行是實(shí)現(xiàn)在延時(shí)或定時(shí)期間能處理其他的消息, //雖然這樣可以降低CPU的占有率,//但降低了延時(shí)或定時(shí)精度,實(shí)際應(yīng)用中可以去掉。end_time = COleDateTime::GetCurrentTime()-start_time;}//這樣在延時(shí)的時(shí)候我們也能夠處理其他的消息。
方式四:在精度要求較高的情況下,VC中可以利用GetTickCount()函數(shù),該函數(shù)的返回值是 DWORD型,表示以ms為單位的計(jì)算機(jī)啟動(dòng)后經(jīng)歷的時(shí)間間隔。精度比WM_TIMER消息映射高,在較 短的定時(shí)中其計(jì)時(shí)誤差為15ms,在較長的定時(shí)中其計(jì)時(shí)誤差較低,如果定時(shí)時(shí)間太長,就好象死機(jī)一樣,CPU占用率非常高,只能用于要求不高的延時(shí)程序中。如示例工程中的Timer4和Timer4_1。下列代碼可以實(shí)現(xiàn)50ms的精確定時(shí): DWORD dwStart = GetTickCount();DWORD dwEnd = dwStart;do{ dwEnd = GetTickCount()-dwStart;}while(dwEnd <50);
為使GetTickCount()函數(shù)在延時(shí)或定時(shí)期間能處理其他的消息,可以把代碼改為: DWORD dwStart = GetTickCount();DWORD dwEnd = dwStart;do{ MSG msg; GetMessage(&msg,NULL,0,0); TranslateMessage(&msg); DispatchMessage(&msg); dwEnd = GetTickCount()-dwStart;}while(dwEnd <50);
雖然這樣可以降低CPU的占有率,并在延時(shí)或定時(shí)期間也能處理其他的消息,但降低了延時(shí)或定時(shí)精度。
方式五:與GetTickCount()函數(shù)類似的多媒體定時(shí)器函數(shù)DWORD timeGetTime(void),該函數(shù)定時(shí)精 度為ms級,返回從Windows啟動(dòng)開始經(jīng)過的毫秒數(shù)。微軟公司在其多媒體Windows中提供了精確定時(shí)器的底 層API持,利用多媒體定時(shí)器可以很精確地讀出系統(tǒng)的當(dāng)前時(shí)間,并且能在非常精確的時(shí)間間隔內(nèi)完成一 個(gè)事件、函數(shù)或過程的調(diào)用。不同之處在于調(diào)用DWORD timeGetTime(void) 函數(shù)之前必須將 Winmm.lib 和 Mmsystem.h 添加到工程中,否則在編譯時(shí)提示DWORD timeGetTime(void)函數(shù)未定義。由于使用該 函數(shù)是通過查詢的方式進(jìn)行定時(shí)控制的,所以,應(yīng)該建立定時(shí)循環(huán)來進(jìn)行定時(shí)事件的控制。如示例工程中的Timer5和Timer5_1。
方式六:使用多媒體定時(shí)器timeSetEvent()函數(shù),該函數(shù)定時(shí)精度為ms級。利用該函數(shù)可以實(shí)現(xiàn)周期性的函數(shù)調(diào)用。如示例工程中的Timer6和Timer6_1。函數(shù)的原型如下: MMRESULT timeSetEvent( UINT uDelay, UINT uResolution, LPTIMECALLBACK lpTimeProc, WORD dwUser, UINT fuEvent )
該函數(shù)設(shè)置一個(gè)定時(shí)回調(diào)事件,此事件可以是一個(gè)一次性事件或周期性事件。事件一旦被激活,便調(diào)用指定的回調(diào)函數(shù), 成功后返回事件的標(biāo)識符代碼,否則返回NULL。函數(shù)的參數(shù)說明如下:
uDelay:以毫秒指定事件的周期。 Uresolution:以毫秒指定延時(shí)的精度,數(shù)值越小定時(shí)器事件分辨率越高。缺省值為1ms。 LpTimeProc:指向一個(gè)回調(diào)函數(shù)。 DwUser:存放用戶提供的回調(diào)數(shù)據(jù)。 FuEvent:指定定時(shí)器事件類型: TIME_ONESHOT:uDelay毫秒后只產(chǎn)生一次事件 TIME_PERIODIC :每隔uDelay毫秒周期性地產(chǎn)生事件。
具體應(yīng)用時(shí),可以通過調(diào)用timeSetEvent()函數(shù),將需要周期性執(zhí)行的任務(wù)定義在LpTimeProc回調(diào)函數(shù) 中(如:定時(shí)采樣、控制等),從而完成所需處理的事件。需要注意的是,任務(wù)處理的時(shí)間不能大于周期間隔時(shí)間。另外,在定時(shí)器使用完畢后, 應(yīng)及時(shí)調(diào)用timeKillEvent()將之釋放。
方式七:對于精確度要求更高的定時(shí)操作,則應(yīng)該使用QueryPerformanceFrequency()和 QueryPerformanceCounter()函數(shù)。這兩個(gè)函數(shù)是VC提供的僅供Windows 95及其后續(xù)版本使用的精確時(shí)間函數(shù),并要求計(jì)算機(jī)從硬件上支持精確定時(shí)器。如示例工程中的Timer7、Timer7_1、Timer7_2、Timer7_3。
QueryPerformanceFrequency()函數(shù)和QueryPerformanceCounter()函數(shù)的原型如下: BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);BOOL QueryPerformanceCounter(LARGE_INTEGER *lpCount);
數(shù)據(jù)類型ARGE_INTEGER既可以是一個(gè)8字節(jié)長的整型數(shù),也可以是兩個(gè)4字節(jié)長的整型數(shù)的聯(lián)合結(jié)構(gòu), 其具體用法根據(jù)編譯器是否支持64位而定。該類型的定義如下: typedef union _LARGE_INTEGER{ struct { DWORD LowPart ;// 4字節(jié)整型數(shù) LONG HighPart;// 4字節(jié)整型數(shù) }; LONGLONG QuadPart ;// 8字節(jié)整型數(shù)}LARGE_INTEGER ;
在進(jìn)行定時(shí)之前,先調(diào)用QueryPerformanceFrequency()函數(shù)獲得機(jī)器內(nèi)部定時(shí)器的時(shí)鐘頻率, 然后在需要嚴(yán)格定時(shí)的事件發(fā)生之前和發(fā)生之后分別調(diào)用QueryPerformanceCounter()函數(shù),利用兩次獲得的計(jì)數(shù)之差及時(shí)鐘頻率,計(jì)算出事件經(jīng) 歷的精確時(shí)間。下列代碼實(shí)現(xiàn)1ms的精確定時(shí): LARGE_INTEGER litmp; LONGLONG QPart1,QPart2;double dfMinus, dfFreq, dfTim; QueryPerformanceFrequency(&litmp);dfFreq = (double)litmp.QuadPart;// 獲得計(jì)數(shù)器的時(shí)鐘頻率QueryPerformanceCounter(&litmp);QPart1 = litmp.QuadPart;// 獲得初始值do{ QueryPerformanceCounter(&litmp); QPart2 = litmp.QuadPart;//獲得中止值 dfMinus = (double)(QPart2-QPart1); dfTim = dfMinus / dfFreq;// 獲得對應(yīng)的時(shí)間值,單位為秒}while(dfTim<0.001);
其定時(shí)誤差不超過1微秒,精度與CPU等機(jī)器配置有關(guān)。 下面的程序用來測試函數(shù)Sleep(100)的精確持續(xù)時(shí)間: LARGE_INTEGER litmp; LONGLONG QPart1,QPart2;double dfMinus, dfFreq, dfTim; QueryPerformanceFrequency(&litmp);dfFreq = (double)litmp.QuadPart;// 獲得計(jì)數(shù)器的時(shí)鐘頻率QueryPerformanceCounter(&litmp);QPart1 = litmp.QuadPart;// 獲得初始值Sleep(100);QueryPerformanceCounter(&litmp);QPart2 = litmp.QuadPart;//獲得中止值dfMinus = (double)(QPart2-QPart1);dfTim = dfMinus / dfFreq;// 獲得對應(yīng)的時(shí)間值,單位為秒
由于Sleep()函數(shù)自身的誤差,上述程序每次執(zhí)行的結(jié)果都會有微小誤差。下列代碼實(shí)現(xiàn)1微秒的精確定時(shí): LARGE_INTEGER litmp; LONGLONG QPart1,QPart2;double dfMinus, dfFreq, dfTim; QueryPerformanceFrequency(&litmp);dfFreq = (double)litmp.QuadPart;// 獲得計(jì)數(shù)器的時(shí)鐘頻率QueryPerformanceCounter(&litmp);QPart1 = litmp.QuadPart;// 獲得初始值do{ QueryPerformanceCounter(&litmp); QPart2 = litmp.QuadPart;//獲得中止值 dfMinus = (double)(QPart2-QPart1); dfTim = dfMinus / dfFreq;// 獲得對應(yīng)的時(shí)間值,單位為秒}while(dfTim<0.000001);
其定時(shí)誤差一般不超過0.5微秒,精度與CPU等機(jī)器配置有關(guān)。 共2頁。 9 7 1 2
使用CPU時(shí)間戳進(jìn)行高精度計(jì)時(shí)
從一論壇看到的轉(zhuǎn)貼,不知道作者名?! ﹃P(guān)注性能的程序開發(fā)人員而言,一個(gè)好的計(jì)時(shí)部件既是益友,也是良師。計(jì)時(shí)器既可以作為程序組件幫助程序員精確的控制程序進(jìn)程,又是一件有力的調(diào)試武器,在有經(jīng)驗(yàn)的程序員手里可以盡快的確定程序的性能瓶頸,或者對不同的算法作出有說服力的性能比較?! ≡赪indows平臺下,常用的計(jì)時(shí)器有兩種,一種是timeGetTime多媒體計(jì)時(shí)器,它可以提供毫秒級的計(jì)時(shí)。但這個(gè)精度對很多應(yīng)用場合而言還是太粗糙了。另一種是QueryPerformanceCount計(jì)數(shù)器,隨系統(tǒng)的不同可以提供微秒級的計(jì)數(shù)。對于實(shí)時(shí)圖形處理、多媒體數(shù)據(jù)流處理、或者實(shí)時(shí)系統(tǒng)構(gòu)造的程序員,善用QueryPerformanceCount/QueryPerformanceFrequency是一項(xiàng)基本功?! ”疚囊榻B的,是另一種直接利用Pentium CPU內(nèi)部時(shí)間戳進(jìn)行計(jì)時(shí)的高精度計(jì)時(shí)手段。以下討論主要得益于《Windows圖形編程》一書,第15頁-17頁,有興趣的讀者可以直接參考該書。關(guān)于RDTSC指令的詳細(xì)討論,可以參考Intel產(chǎn)品手冊。本文僅僅作拋磚之用?! ≡贗ntel Pentium以上級別的CPU中,有一個(gè)稱為“時(shí)間戳(Time Stamp)”的部件,它以64位無符號整型數(shù)的格式,記錄了自CPU上電以來所經(jīng)過的時(shí)鐘周期數(shù)。由于目前的CPU主頻都非常高,因此這個(gè)部件可以達(dá)到納秒級的計(jì)時(shí)精度。這個(gè)精確性是上述兩種方法所無法比擬的?! ≡赑entium以上的CPU中,提供了一條機(jī)器指令RDTSC(Read Time Stamp Counter)來讀取這個(gè)時(shí)間戳的數(shù)字,并將其保存在EDX:EAX寄存器對中。由于EDX:EAX寄存器對恰好是Win32平臺下C++語言保存函數(shù)返回值的寄存器,所以我們可以把這條指令看成是一個(gè)普通的函數(shù)調(diào)用。像這樣:inline unsigned __int64 GetCycleCount(){__asm RDTSC}
但是不行,因?yàn)镽DTSC不被C++的內(nèi)嵌匯編器直接支持,所以我們要用_emit偽指令直接嵌入該指令的機(jī)器碼形式0X0F、0X31,如下:
inline unsigned __int64 GetCycleCount() { __asm _emit 0x0F __asm _emit 0x31 }
以后在需要計(jì)數(shù)器的場合,可以像使用普通的Win32 API一樣,調(diào)用兩次GetCycleCount函數(shù),比較兩個(gè)返回值的差,像這樣:
unsigned long t; t = (unsigned long)GetCycleCount(); //Do Something time-intensive ... t -= (unsigned long)GetCycleCount();
《Windows圖形編程》第15頁編寫了一個(gè)類,把這個(gè)計(jì)數(shù)器封裝起來。有興趣的讀者可以去參考那個(gè)類的代碼。作者為了更精確的定時(shí),做了一點(diǎn)小小的改進(jìn),把執(zhí)行RDTSC指令的時(shí)間,通過連續(xù)兩次調(diào)用GetCycleCount函數(shù)計(jì)算出來并保存了起來,以后每次計(jì)時(shí)結(jié)束后,都從實(shí)際得到的計(jì)數(shù)中減掉這一小段時(shí)間,以得到更準(zhǔn)確的計(jì)時(shí)數(shù)字。但我個(gè)人覺得這一點(diǎn)點(diǎn)改進(jìn)意義不大。在我的機(jī)器上實(shí)測,這條指令大概花掉了幾十到100多個(gè)周期,在Celeron 800MHz的機(jī)器上,這不過是十分之一微秒的時(shí)間。對大多數(shù)應(yīng)用來說,這點(diǎn)時(shí)間完全可以忽略不計(jì);而對那些確實(shí)要精確到納秒數(shù)量級的應(yīng)用來說,這個(gè)補(bǔ)償也過于粗糙了。 這個(gè)方法的優(yōu)點(diǎn)是: 1. 高精度??梢灾苯舆_(dá)到納秒級的計(jì)時(shí)精度(在1GHz的CPU上每個(gè)時(shí)鐘周期就是一納秒),這是其他計(jì)時(shí)方法所難以企及的。
2.成本低。timeGetTime 函數(shù)需要鏈接多媒體庫winmm.lib,QueryPerformance* 函數(shù)根據(jù)MSDN的說明,需要硬件的支持(雖然我還沒有見過不支持的機(jī)器)和KERNEL庫的支持,所以二者都只能在Windows平臺下使用(關(guān)于DOS平臺下的高精度計(jì)時(shí)問題,可以參考《圖形程序開發(fā)人員指南》,里面有關(guān)于控制定時(shí)器8253的詳細(xì)說明)。但RDTSC指令是一條CPU指令,凡是i386平臺下Pentium以上的機(jī)器均支持,甚至沒有平臺的限制(我相信i386版本UNIX和Linux下這個(gè)方法同樣適用,但沒有條件試驗(yàn)),而且函數(shù)調(diào)用的開銷是最小的。
3.具有和CPU主頻直接對應(yīng)的速率關(guān)系。一個(gè)計(jì)數(shù)相當(dāng)于1/(CPU主頻Hz數(shù))秒,這樣只要知道了CPU的主頻,可以直接計(jì)算出時(shí)間。這和QueryPerformanceCount不同,后者需要通過QueryPerformanceFrequency獲取當(dāng)前計(jì)數(shù)器每秒的計(jì)數(shù)次數(shù)才能換算成時(shí)間。 這個(gè)方法的缺點(diǎn)是:
1.現(xiàn)有的C/C++編譯器多數(shù)不直接支持使用RDTSC指令,需要用直接嵌入機(jī)器碼的方式編程,比較麻煩。
2. 數(shù)據(jù)抖動(dòng)比較厲害。其實(shí)對任何計(jì)量手段而言,精度和穩(wěn)定性永遠(yuǎn)是一對矛盾。如果用低精度的timeGetTime來計(jì)時(shí),基本上每次計(jì)時(shí)的結(jié)果都是相同的;而RDTSC指令每次結(jié)果都不一樣,經(jīng)常有幾百甚至上千的差距。這是這種方法高精度本身固有的矛盾。
關(guān)于這個(gè)方法計(jì)時(shí)的最大長度,我們可以簡單的用下列公式計(jì)算: 自CPU上電以來的秒數(shù) = RDTSC讀出的周期數(shù) / CPU主頻速率(Hz) 64位無符號整數(shù)所能表達(dá)的最大數(shù)字是1.8×10^19,在我的Celeron 800上可以計(jì)時(shí)大約700年(書中說可以在200MHz的Pentium上計(jì)時(shí)117年,這個(gè)數(shù)字不知道是怎么得出來的,與我的計(jì)算有出入)。無論如何,我們大可不必關(guān)心溢出的問題。 下面是幾個(gè)小例子,簡要比較了三種計(jì)時(shí)方法的用法與精度
//Timer1.cpp 使用了RDTSC指令的Timer類//KTimer類的定義可以參見《Windows圖形編程》P15 //編譯行:CL Timer1.cpp /link USER32.lib #include <stdio.h> #include "KTimer.h"
main() { unsigned t; KTimer timer; timer.Start(); Sleep(1000); t = timer.Stop(); printf("Lasting Time: %d\n",t); }
//Timer2.cpp 使用了timeGetTime函數(shù) //需包含<mmsys.h>,但由于Windows頭文件錯(cuò)綜復(fù)雜的關(guān)系 //簡單包含<windows.h>比較偷懶:) //編譯行:CL timer2.cpp /link winmm.lib #include <windows.h> #include <stdio.h>
main() { DWORD t1, t2; t1 = timeGetTime(); Sleep(1000); t2 = timeGetTime(); printf("Begin Time: %u\n", t1); printf("End Time: %u\n", t2); printf("Lasting Time: %u\n",(t2-t1)); }
//Timer3.cpp 使用了QueryPerformanceCounter函數(shù) //編譯行:CL timer3.cpp /link KERNEl32.lib #include <windows.h> #include <stdio.h>
main() { LARGE_INTEGER t1, t2, tc; QueryPerformanceFrequency(&tc); printf("Frequency: %u\n", tc.QuadPart); QueryPerformanceCounter(&t1); Sleep(1000); QueryPerformanceCounter(&t2); printf("Begin Time: %u\n", t1.QuadPart); printf("End Time: %u\n", t2.QuadPart); printf("Lasting Time: %u\n",( t2.QuadPart- t1.QuadPart)); } //////////////////////////////////////////////// //以上三個(gè)示例程序都是測試1秒鐘休眠所耗費(fèi)的時(shí)間 file://測/試環(huán)境:Celeron 800MHz / 256M SDRAM // Windows 2000 Professional SP2 // Microsoft Visual C++ 6.0 SP5 ////////////////////////////////////////////////
以下是Timer1的運(yùn)行結(jié)果,使用的是高精度的RDTSC指令 Lasting Time: 804586872
以下是Timer2的運(yùn)行結(jié)果,使用的是最粗糙的timeGetTime API Begin Time: 20254254 End Time: 20255255 Lasting Time: 1001
以下是Timer3的運(yùn)行結(jié)果,使用的是QueryPerformanceCount API Frequency: 3579545 Begin Time: 3804729124 End Time: 3808298836 Lasting Time: 3569712
對于wince, 只有采用KTimer那種方式,因?yàn)镼ueryPerformanceFrequency,timeGetTime均不支持。
Visual C++實(shí)現(xiàn)微秒級精度定時(shí)器 所屬類別:VC++ 推薦指數(shù):★★★☆ 文檔人氣:1117 本周人氣:10 發(fā)布日期:2006-6-25 在工業(yè)生產(chǎn)控制系統(tǒng)中,有許多需要定時(shí)完成的操作,如:定時(shí)顯示當(dāng)前時(shí)間,定時(shí)刷新屏幕上的進(jìn)度條,上位機(jī)定時(shí)向下位機(jī)發(fā)送命令和傳送數(shù)據(jù)等。特別是在對控制性能要求較高的控制系統(tǒng)和數(shù)據(jù)采集系統(tǒng)中,就更需要精確定時(shí)操作。眾所周知,Windows是基于消息機(jī)制的系統(tǒng),任何事件的執(zhí)行都是通過發(fā)送和接收消息來完成的。這樣就帶來了一些問題,如一旦計(jì)算機(jī)的CPU被某個(gè)進(jìn)程占用,或系統(tǒng)資源緊張時(shí),發(fā)送到消息隊(duì)列中的消息就暫時(shí)被掛起,得不到實(shí)時(shí)處理。因此,不能簡單地通過Windows消息引發(fā)一個(gè)對定時(shí)要求嚴(yán)格的事件。另外,由于在Windows中已經(jīng)封裝了計(jì)算機(jī)底層硬件的訪問,所以要想通過直接利用訪問硬件來完成精確定時(shí),也比較困難。在實(shí)際應(yīng)用時(shí),應(yīng)針對具體定時(shí)精度的要求,采取與之相適應(yīng)的定時(shí)方法。
本實(shí)例實(shí)現(xiàn)了一中微秒級的精確定時(shí),程序的界面提供了兩個(gè)"Edit"編輯框,其中一個(gè)編輯框輸入用戶理想的定時(shí)長度,另外一個(gè)編輯框返回實(shí)際的時(shí)間長度,經(jīng)過大量的實(shí)驗(yàn)測試,一般情況下誤差不超過5個(gè)微秒。程序的運(yùn)行界面如圖一所示: 圖一、實(shí)現(xiàn)微秒級的精確定時(shí)器
一、實(shí)現(xiàn)方法
Visual C++中提供了很多關(guān)于時(shí)間操作的函數(shù),利用它們控制程序能夠精確地完成定時(shí)和計(jì)時(shí)操作。Visaul C++中的WM_TIMER消息映射能進(jìn)行簡單的時(shí)間控制。首先調(diào)用函數(shù)SetTimer()設(shè)置定時(shí)間隔(退出程序時(shí)別忘了調(diào)用和SetTimer()配對使用的KillTimer()函數(shù)),如SetTimer(0,200,NULL)即為設(shè)置200ms的時(shí)間間隔。然后在應(yīng)用程序中增加定時(shí)響應(yīng)函數(shù)OnTimer(),并在該函數(shù)中添加響應(yīng)的處理語句,用來完成到達(dá)定時(shí)時(shí)間的操作。這種定時(shí)方法非常簡單,但其定時(shí)功能如同Sleep()函數(shù)的延時(shí)功能一樣,精度非常低,只可以用來實(shí)現(xiàn)諸如位圖的動(dòng)態(tài)顯示等對定時(shí)精度要求不高的情況。
微軟公司在其多媒體Windows中提供了精確定時(shí)器的底層API支持。利用多媒體定時(shí)器可以很精確地讀出系統(tǒng)的當(dāng)前時(shí)間,并且能在非常精確的時(shí)間間隔內(nèi)完成一個(gè)事件、函數(shù)或過程的調(diào)用。利用多媒體定時(shí)器的基本功能,可以通過兩種方法實(shí)現(xiàn)精確定時(shí)。1)使用timeGetTime()函數(shù),該函數(shù)定時(shí)精度為ms級,返回從Windows啟動(dòng)開始所經(jīng)過的時(shí)間。由于使用該函數(shù)是通過查詢的方式進(jìn)行定時(shí)控制的,所以,應(yīng)該建立定時(shí)循環(huán)來進(jìn)行定時(shí)事件的控制。2)使用timeSetEvent()函數(shù),該函數(shù)原型如下: MMRESULT timeSetEvent(UINT uDelay,UINT uResolution,LPTIMECALLBACK lpTimeProc,DWORD dwUser,UINT fuEvent);
該函數(shù)的參數(shù)說明如下:參數(shù)uDelay表示延遲時(shí)間;參數(shù)uResolution表示時(shí)間精度,在Windows中缺省值為1ms;lpTimeProc表示回調(diào)函數(shù),為用戶自定義函數(shù),定時(shí)調(diào)用; 參數(shù)dwUser表示用戶提供的回調(diào)數(shù)據(jù);參數(shù)fuEvent為定時(shí)器的事件類型,TIME_ONESHOT表示執(zhí)行一次;TIME_PERIODIC:周期性執(zhí)行。具體應(yīng)用時(shí),可以通過調(diào)用timeSetEvent()函數(shù),將需要周期性執(zhí)行的任務(wù)定義在lpTimeProc回調(diào)函數(shù)中(如:定時(shí)采樣、控制等),從而完成所需處理的事件。需要注意的是:任務(wù)處理的時(shí)間不能大于周期間隔時(shí)間。另外,在定時(shí)器使用完畢后,應(yīng)及時(shí)調(diào)用timeKillEvent()將之釋放。下面這段代碼的主要功能是設(shè)置兩個(gè)時(shí)鐘定時(shí)器,一個(gè)間隔是1ms,一個(gè)間隔是2s。每執(zhí)行一次,把當(dāng)前系統(tǒng)時(shí)鐘值輸入文件"cure.out"中,以比較該定時(shí)器的精確度。 # define ONE_MILLI_SECOND 1 //定義1ms和2s時(shí)鐘間隔,以ms為單位 ;# define TWO_SECOND 2000 # define TIMER_ACCURACY 1 //定義時(shí)鐘分辨率,以ms為單位 UINT wTimerRes_1ms,wTimerRes_2s; //定義時(shí)間間隔 UINT wAccuracy; //定義分辨率 UINT TimerID_1ms,TimerID_2s; //定義定時(shí)器句柄///////////////////////////////CCureApp::CCureApp():fout("cure.out", ios::out) //打開輸出文件"cure.out";{ // 給時(shí)間間隔變量賦值 wTimerRes_1ms = ONE_MILLI_SECOND; wTimerRes_2s = TWO_SECOND; TIMECAPS tc; //利用函數(shù)timeGetDevCaps取出系統(tǒng)分辨率的取值范圍,如果無錯(cuò)則繼續(xù); if(timeGetDevCaps(&tc,sizeof(TIMECAPS))==TIMERR_NOERROR) { wAccuracy=min(max(tc.wPeriodMin, //分辨率的值不能超出系統(tǒng)的取值范圍 TIMER_ACCURACY),tc.wPeriodMax); //調(diào)用timeBeginPeriod函數(shù)設(shè)置定時(shí)器的分辨率 timeBeginPeriod(wAccuracy); //設(shè)置定時(shí)器 InitializeTimer(); } } CCureApp:: ~CCureApp() { fout <<"結(jié)束時(shí)鐘"<< endl; //結(jié)束時(shí)鐘 timeKillEvent(TimerID_1ms); // 刪除兩個(gè)定時(shí)器 timeKillEvent(TimerID_2s); // 刪除設(shè)置的分辨率 timeEndPeriod(wAccuracy); } void CCureApp::InitializeTimer() { StartOneMilliSecondTimer(); StartTwoSecondTimer(); } //1ms定時(shí)器的回調(diào)函數(shù),類似于中斷處理程序,一定要聲明為全局PASCAL函數(shù),//否則編譯會有問題 void PASCAL OneMilliSecondProc(UINT wTimerID, UINT msg,DWORD dwUser,DWORD dwl,DWORD dw2) { // 定義計(jì)數(shù)器 static int ms = 0; CCureApp *app = (CCureApp *)dwUser; // 取得系統(tǒng)時(shí)間,以ms為單位 DWORD osBinaryTime = GetTickCount(); //輸出計(jì)數(shù)器值和當(dāng)前系統(tǒng)時(shí)間 app->fout<<++ms<<":1ms:" } // 加裝1ms定時(shí)器 void CCureApp::StartOneMilliSecondTimer() { if((TimerID_1ms = timeSetEvent(wTimerRes_1ms, wAccuracy, (LPTIMECALBACK) OneMil liSecondProc, // 回調(diào)函數(shù); (DWORD)this, // 用戶傳送到回調(diào)函數(shù)的數(shù)據(jù); TIME_PERIODIC)) == 0)//周期調(diào)用定時(shí)處理函數(shù); { AfxMessageBox("不能進(jìn)行定時(shí)!", MB_OK | MB_ICONASTERISK); } else fout << "16ms 計(jì) 時(shí):" << endl; //不等于0表明加裝成功,返回此定時(shí)器的句柄; }
在精度要求較高的情況下,如要求定時(shí)誤差不大于1ms時(shí),還可以利用GetTickCount()函數(shù)返回自計(jì)算機(jī)啟動(dòng)后的時(shí)間,該函數(shù)的返回值是DWORD型,表示以ms為單位的計(jì)算機(jī)啟動(dòng)后經(jīng)歷的時(shí)間間隔。通過兩次調(diào)用GetTickCount()函數(shù),然后控制它們的差值來取得定時(shí)效果.下列的代碼可以實(shí)現(xiàn)50ms的精確定時(shí),其誤差是毫秒級的。 // 起始值和中止值DWORD dwStart, dwStop ; dwStop = GetTickCount(); while(TRUE) { // 上一次的中止值變成新的起始值 dwStart = dwStop ; // 此處添加相應(yīng)控制語句 do { dwStop = GetTickCount() ; }while(dwStop - 50 < dwStart) ; }
用上述兩種方式取得的定時(shí)效果雖然在許多場合已經(jīng)滿足實(shí)際的要求,但由于它們的精度只有毫秒級的,而且在要求定時(shí)時(shí)間間隔小時(shí),實(shí)際定時(shí)誤差大。對于精確度要求更高的定時(shí)操作,則應(yīng)該使用QueryPerformanceFrequency()和QueryPerformanceCounter()函數(shù)。這兩個(gè)函數(shù)是Visual C++提供并且僅供Windows 95及其后續(xù)版本使用,其精度與CPU的時(shí)鐘頻率有關(guān),它們要求計(jì)算機(jī)從硬件上支持精確定時(shí)器。QueryPerformanceFrequency()函數(shù)和QueryPerformanceCounter()函數(shù)的原型如下: BOOL QueryPerformanceFrequency (LARGE_INTEGER *lpFrequency);BOOL QueryPerformanceCounter (LARGE_INTEGER *lpCount);
上述兩個(gè)函數(shù)的參數(shù)的數(shù)據(jù)類型LARGE_INTEGER既可以是一個(gè)8字節(jié)長的整型數(shù),也可以是兩個(gè)4字節(jié)長的整型數(shù)的聯(lián)合結(jié)構(gòu),其具體用法根據(jù)編譯器是否支持64位而定。該類型的定義如下: typedef union _LARGE_INTEGER{ struct{ DWORD LowPart ; // 4字節(jié)整型數(shù) LONG HighPart ; // 4字節(jié)整型數(shù) }; LONG QuadPart ; // 8字節(jié)整型數(shù)} LARGE_INTEGER ;
使用QueryPerformanceFrequency()和QueryPerformanceCounter()函數(shù)進(jìn)行精確定時(shí)的步驟如下:
1、首先調(diào)用QueryPerformanceFrequency()函數(shù)取得高精度運(yùn)行計(jì)數(shù)器的頻率f,單位是每秒多少次(n/s),此數(shù)一般很大;
2、在需要定時(shí)的代碼的兩端分別調(diào)用QueryPerformanceCounter()以取得高精度運(yùn)行計(jì)數(shù)器的數(shù)值n1、n2,兩次數(shù)值的差值通過f換算成時(shí)間間隔,t=(n2-n1)/f,當(dāng)t大于或等于定時(shí)時(shí)間長度時(shí),啟動(dòng)定時(shí)器;
二、編程步驟
1、啟動(dòng)Visual C++6.0,生成一個(gè)基于對話框的應(yīng)用程序,將程序命名為"HightTimer";
2、在對話框面板中添加控件,布局如圖一所示,其中包含兩個(gè)靜態(tài)文本框,兩個(gè)編輯框和兩個(gè)按紐。上面和下面位置的編輯框的ID分別為IDC_TEST和IDC_ACTUAL,"EXIT"按紐的ID為IDOK,"TEST"按紐ID為ID_TEST;
3、通過Class Wizard添加成員變量,兩個(gè)編輯框控件分別對應(yīng)為DWORD m_dwTest和DWORD m_dwAct,另外添加"TEST"按紐的鼠標(biāo)單擊消息處理函數(shù);
4、添加代碼,編譯運(yùn)行程序。
三、程序代碼 /////////////////////////////////////////////////////////////////////////LARGE_INTEGER MySleep(LARGE_INTEGER Interval) // 功能:執(zhí)行實(shí)際的延時(shí)功能,Interval 參數(shù)為需要執(zhí)行的延時(shí)與時(shí)間有關(guān)的數(shù)量,此函數(shù)返回執(zhí)//行后實(shí)際所用的時(shí)間有關(guān)的數(shù)量 ; { LARGE_INTEGER privious, current, Elapse; QueryPerformanceCounter( &privious ); current = privious; while( current.QuadPart - privious.QuadPart < Interval.QuadPart ) QueryPerformanceCounter( ¤t ); Elapse.QuadPart = current.QuadPart - privious.QuadPart; return Elapse; }void CHightTimerDlg::OnTest() { // TODO: Add your control notification handler code here UpdateData(TRUE); //取輸入的測試時(shí)間值到與編輯框相關(guān)聯(lián)的成員變量m_dwTest中 ; LARGE_INTEGER frequence; //取高精度運(yùn)行計(jì)數(shù)器的頻率,若硬件不支持則返回FALSE if(!QueryPerformanceFrequency( &frequence)) MessageBox("Your computer hardware doesn't support the high-resolution performance counter", "Not Support", MB_ICONEXCLAMATION | MB_OK); LARGE_INTEGER test, ret; //通過頻率換算微秒數(shù)到對應(yīng)的數(shù)量(與CPU時(shí)鐘有關(guān)),1秒=1000000微秒; test.QuadPart = frequence.QuadPart * m_dwTest / 1000000; ret = MySleep( test ); //調(diào)用此函數(shù)開始延時(shí),返回實(shí)際花銷的數(shù)量 ; m_dwAct = (DWORD)(1000000 * ret.QuadPart / frequence.QuadPart ); //換算到微秒數(shù); UpdateData(FALSE); //顯示到對話框面板 ;}
四、小結(jié)
本實(shí)例介紹了實(shí)現(xiàn)精確定時(shí)的不同方法,尤其是對于需要精確到微秒級別的定時(shí)處理,給出了實(shí)現(xiàn)的方法和代碼,細(xì)心的讀者朋友在運(yùn)行程序的過程中可能會發(fā)現(xiàn)要求的定時(shí)長度和實(shí)際返回的時(shí)間長度還是有一些差異的,造成上述情況的原因是由于在進(jìn)行定時(shí)處理時(shí),還需要運(yùn)行一些簡單的循環(huán)代碼,所以會產(chǎn)生微秒級的誤差
Windows下的高精度計(jì)時(shí)和高頻事件的產(chǎn)生
作者:戎亞新 南京航空航天大學(xué)仿真與控制實(shí)驗(yàn)室 下載源代碼
在開發(fā) Windows 下的應(yīng)用程序時(shí),經(jīng)常需要用的計(jì)時(shí),尤其在一些對時(shí)間要求比較高的程序中,計(jì)時(shí)的精確性是很重要的,本文介紹了兩種精確計(jì)時(shí)的方法,計(jì)時(shí)的精度可以達(dá)到ms級,而且可以認(rèn)為它是精確的,可以在大多數(shù)情況下作為時(shí)間的基準(zhǔn)。 1. 用API函數(shù)::timeGetTime()獲取從開機(jī)到現(xiàn)在經(jīng)過的ms數(shù),它的返回類型為DWORD類型,因此它的最大計(jì)時(shí)長度為2^32ms,約等于49天,::timeGetTime()是一個(gè)多媒體函數(shù),所以它的優(yōu)先級是很高的,一般可以將它看成是精確的。 2. 用查詢系統(tǒng)定時(shí)器的計(jì)數(shù)值的方法,用到的API函數(shù)是QueryPerformanceCounter、QueryPerformanceFrequency,方法是用當(dāng)前計(jì)數(shù)值減去開始計(jì)時(shí)時(shí)刻的計(jì)數(shù)值,得到計(jì)數(shù)差值,再除以系統(tǒng)定時(shí)器的頻率就是計(jì)的時(shí)間,通常系統(tǒng)定時(shí)器的頻率非常高,我在 intel845e 的主板上達(dá)到了3579545hz,當(dāng)然對于不同的主板,它的頻率是不同的。程序運(yùn)行的結(jié)果 如圖一所示:
圖一 這種計(jì)時(shí)方法要用另外一個(gè)線程專門來查詢系統(tǒng)定時(shí)器的計(jì)數(shù)值,這就用到了多線程的知識。由于線程的調(diào)用是需要處理器時(shí)間的,所以在本中,多線程定時(shí)器的時(shí)間總要落后于多媒體定時(shí)器時(shí)間。但在中間的任何一個(gè)讀取時(shí)間的時(shí)刻都是非常精確的,只是從讀取到顯示有一個(gè)延遲過程。 下面講一下Windows高頻事件的產(chǎn)生,還是利用上面兩種方法,Windows下有一個(gè)多媒體定時(shí)器,用法為一組API函數(shù)的調(diào)用,它們是: MMRESULT timeBeginPeriod( UINT uPeriod ) ;
MMRESULT timeSetEvent( UINT uDelay, UINT uResolution, LPTIMECALLBACK lpTimeProc, DWORD dwUser, UINT fuEvent );
void CALLBACK TimeProc( UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2 );
MMRESULT timeKillEvent( UINT uTimerID ); MMRESULT timeEndPeriod( UINT uPeriod ); 其中timeBeginPeriod是用來設(shè)置最高定時(shí)精度的,最高精度為1ms,如果要產(chǎn)生間隔為1ms的中斷,必須調(diào)用timeBeginPeriod(1);當(dāng)定時(shí)器用完之后就要用timeEndPeriod(1);來恢復(fù)默認(rèn)的精度。具體使用方法為在timeBeginPeriod(1)調(diào)用之后用timeSetEvent()注冊一個(gè)回調(diào)函數(shù),即一個(gè)中斷處理過程。它還可以向回調(diào)函數(shù)傳遞一個(gè)參數(shù),通??梢詡魉鸵粋€(gè)窗口句柄之類的東西。而回調(diào)函數(shù)TimeProc則從dwwUser參數(shù)中取出傳遞的參數(shù)使用。在Windows下,可以用這種方法進(jìn)行1ms精度的定時(shí)數(shù)據(jù)采集,數(shù)據(jù)發(fā)送,但要保證1ms能完成所有的操作和運(yùn)算。本人經(jīng)過實(shí)踐證明,用它來實(shí)現(xiàn)控制的精度是足夠的。 第二種方法還是使用多線程查詢系統(tǒng)定時(shí)器計(jì)數(shù)值,它與上面提到的方法相比有優(yōu)點(diǎn)也有缺點(diǎn),缺點(diǎn)是精度不夠高,優(yōu)點(diǎn)是產(chǎn)生的間隔能突破1ms的限制,可以達(dá)到更小的間隔,理論上事件產(chǎn)生的頻率可以和系統(tǒng)定時(shí)器的頻率一樣。主要示例代碼如下: UINT Timer(LPVOID pParam) { QueryPerformanceCounter((LARGE_INTEGER *)& gl_BeginTime ); while(gl_bStart) { QueryPerformanceCounter((LARGE_INTEGER *)&gl_CurrentTime ); If(gl_CurrentTime - gl_BeginTime > 1.0/Interval ) { //定時(shí)的事件,比如發(fā)送數(shù)據(jù)到端口,采集數(shù)據(jù)等 gl_BeginTime = gl_CurrentTime; } } return 1; } 這是多線程中的一個(gè)線程函數(shù),Interval是產(chǎn)生事件的間隔,如果為0.001則為1ms產(chǎn)生一次,理論上如果Interval為1,則以最大的頻率產(chǎn)生事件。即可以用Windows產(chǎn)生很高頻率的事件,但是由于線程的調(diào)用是要有時(shí)間的,有的時(shí)候可能會造成這個(gè)線程一直沒有得到執(zhí)行,從而造成有一段時(shí)間沒有進(jìn)行計(jì)數(shù),這段時(shí)間的定時(shí)事件就沒有產(chǎn)生了,如果定時(shí)的頻率越高,丟失的可能性就越大。但如果用它來產(chǎn)生高頻隨時(shí)間變化的隨機(jī)信號還是很有價(jià)值的。這在實(shí)時(shí)仿真中尤其如此。
具體的實(shí)現(xiàn)請參看詳細(xì)的例子代碼。
如有疑問,請與我聯(lián)系: qq:21881480 email:bsrong@elong.com 或 bsrong_nuaa@msn.com
1.project setting 的link設(shè)置winmm.lib 庫 2.#include <afxmt.h> #include "mmsystem.h" 3. DWORD dwOldTime=timeGetTime(); //... DWORD dwCurTime=timeGetTime(); DWORD dwLen = dwCurTime - dwOldTime;
使用多媒體定時(shí)器還需要加入 winmm.lib 庫文件! (project-setting里面)
主題 VC++的鏈接錯(cuò)誤LNK2001(ZT) 學(xué)習(xí)VC++時(shí)經(jīng)常會遇到鏈接錯(cuò)誤LNK2001,該錯(cuò)誤非常討厭,因?yàn)閷τ?/p> 編程者來說,最好改的錯(cuò)誤莫過于編譯錯(cuò)誤,而一般說來發(fā)生連接錯(cuò)誤時(shí), 編譯都已通過。產(chǎn)生連接錯(cuò)誤的原因非常多,尤其LNK2001錯(cuò)誤,常常使人不 明其所以然。如果不深入地學(xué)習(xí)和理解VC++,要想改正連接錯(cuò)誤LNK2001非 常困難。 初學(xué)者在學(xué)習(xí)VC++的過程中,遇到的LNK2001錯(cuò)誤的錯(cuò)誤消息主要為: unresolved external symbol “symbol”(不確定的外部“符號”)。 如果連接程序不能在所有的庫和目標(biāo)文件內(nèi)找到所引用的函數(shù)、變量或 標(biāo)簽,將產(chǎn)生此錯(cuò)誤消息。一般來說,發(fā)生錯(cuò)誤的原因有兩個(gè):一是所引用 的函數(shù)、變量不存在、拼寫不正確或者使用錯(cuò)誤;其次可能使用了不同版本 的連接庫。 以下是可能產(chǎn)生LNK2001錯(cuò)誤的原因: 一.由于編碼錯(cuò)誤導(dǎo)致的LNK2001。 1.不相匹配的程序代碼或模塊定義(.DEF)文件能導(dǎo)致LNK2001。例如, 如果在C++ 源文件內(nèi)聲明了一變量“var1”,卻試圖在另一文件內(nèi)以變量 “VAR1”訪問該變量,將發(fā)生該錯(cuò)誤。 2.如果使用的內(nèi)聯(lián)函數(shù)是在.CPP文件內(nèi)定義的,而不是在頭文件內(nèi)定 義將導(dǎo)致LNK2001錯(cuò)誤。 3.調(diào)用函數(shù)時(shí)如果所用的參數(shù)類型同函數(shù)聲明時(shí)的類型不符將會產(chǎn)生 LNK2001。 4.試圖從基類的構(gòu)造函數(shù)或析構(gòu)函數(shù)中調(diào)用虛擬函數(shù)時(shí)將會導(dǎo)致LNK2001。 5.要注意函數(shù)和變量的可公用性,只有全局變量、函數(shù)是可公用的。 靜態(tài)函數(shù)和靜態(tài)變量具有相同的使用范圍限制。當(dāng)試圖從文件外部訪問 任何沒有在該文件內(nèi)聲明的靜態(tài)變量時(shí)將導(dǎo)致編譯錯(cuò)誤或LNK2001。 函數(shù)內(nèi)聲明的變量(局部變量) 只能在該函數(shù)的范圍內(nèi)使用。 C++ 的全局常量只有靜態(tài)連接性能。這不同于C,如果試圖在C++的 多個(gè)文件內(nèi)使用全局變量也會產(chǎn)生LNK2001錯(cuò)誤。一種解決的方法是需要時(shí)在 頭文件中加入該常量的初始化代碼,并在.CPP文件中包含該頭文件;另一種 方法是使用時(shí)給該變量賦以常數(shù)。 二.由于編譯和鏈接的設(shè)置而造成的LNK2001 1.如果編譯時(shí)使用的是/NOD(/NODEFAULTLIB)選項(xiàng),程序所需要的運(yùn)行 庫和MFC庫在連接時(shí)由編譯器寫入目標(biāo)文件模塊, 但除非在文件中明確包含 這些庫名,否則這些庫不會被鏈接進(jìn)工程文件。在這種情況下使用/NOD將導(dǎo) 致錯(cuò)誤LNK2001。 2.如果沒有為wWinMainCRTStartup設(shè)定程序入口,在使用Unicode和MFC 時(shí)將得到“unresolved external on _WinMain@16”的LNK2001錯(cuò)誤信息。 3.使用/MD選項(xiàng)編譯時(shí),既然所有的運(yùn)行庫都被保留在動(dòng)態(tài)鏈接庫之內(nèi), 源文件中對“func”的引用,在目標(biāo)文件里即對“__imp__func” 的引用。 如果試圖使用靜態(tài)庫LIBC.LIB或LIBCMT.LIB進(jìn)行連接,將在__imp__func上發(fā) 生LNK2001;如果不使用/MD選項(xiàng)編譯,在使用MSVCxx.LIB連接時(shí)也會發(fā)生LNK2001。 4.使用/ML選項(xiàng)編譯時(shí),如用LIBCMT.LIB鏈接會在_errno上發(fā)生LNK2001。 5.當(dāng)編譯調(diào)試版的應(yīng)用程序時(shí),如果采用發(fā)行版模態(tài)庫進(jìn)行連接也會產(chǎn) 生LNK2001;同樣,使用調(diào)試版模態(tài)庫連接發(fā)行版應(yīng)用程序時(shí)也會產(chǎn)生相同的 問題。 6.不同版本的庫和編譯器的混合使用也能產(chǎn)生問題,因?yàn)樾掳娴膸炖锟?/p> 能包含早先的版本沒有的符號和說明。 7.在不同的模塊使用內(nèi)聯(lián)和非內(nèi)聯(lián)的編譯選項(xiàng)能夠?qū)е翷NK2001。如果 創(chuàng)建C++庫時(shí)打開了函數(shù)內(nèi)聯(lián)(/Ob1或/Ob2),但是在描述該函數(shù)的相應(yīng)頭 文件里卻關(guān)閉了函數(shù)內(nèi)聯(lián)(沒有inline關(guān)鍵字),這時(shí)將得到該錯(cuò)誤信息。 為避免該問題的發(fā)生,應(yīng)該在相應(yīng)的頭文件中用inline關(guān)鍵字標(biāo)志內(nèi)聯(lián)函數(shù)。 8.不正確的/SUBSYSTEM或/ENTRY設(shè)置也能導(dǎo)致LNK2001。 其實(shí),產(chǎn)生LNK2001的原因還有很多,以上的原因只是一部分而已,對初 學(xué)者來說這些就夠理解一陣子了。但是,分析錯(cuò)誤原因的目的是為了避免錯(cuò) 誤的發(fā)生。LNK2001錯(cuò)誤雖然比較困難,但是只要注意到了上述問題,還是能 夠避免和予以解決的。
1. Windows子系統(tǒng)設(shè)置錯(cuò)誤, 提示: libcmtd.lib(crt0.obj) : error LNK2001: unresolved external symbol _main Windows項(xiàng)目要使用Windows子系統(tǒng), 而不是Console, 可以這樣設(shè)置: [Project] --> [Settings] --> 選擇"Link"屬性頁, 在Project Options中將/subsystem:console改成/subsystem:windows
2. Console子系統(tǒng)設(shè)置錯(cuò)誤, 提示: LIBCD.lib(wincrt0.obj) : error LNK2001: unresolved external symbol _WinMain@16 控制臺項(xiàng)目要使用Console子系統(tǒng), 而不是Windows, 設(shè)置: [Project] --> [Settings] --> 選擇"Link"屬性頁, 在Project Options中將/subsystem:windows改成/subsystem:console
3. 程序入口設(shè)置錯(cuò)誤, 提示: msvcrtd.lib(crtexew.obj) : error LNK2001: unresolved external symbol _WinMain@16 通常, MFC項(xiàng)目的程序入口函數(shù)是WinMain, 如果編譯項(xiàng)目的Unicode版本, 程序入口必須改為wWinMainCRTStartup, 所以需要重新設(shè)置程序入口: [Project] --> [Settings] --> 選擇"Link"屬性頁, 在Category中選擇Output, 再在Entry-point symbol中填入wWinMainCRTStartup, 即可
(發(fā)表于2006-10-8 16:49:00)
adam830:4. 線程運(yùn)行時(shí)庫設(shè)置錯(cuò)誤, 提示: nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __beginthreadex nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __endthreadex 這是因?yàn)镸FC要使用多線程時(shí)庫, 需要更改設(shè)置: [Project] --> [Settings] --> 選擇"C/C++"屬性頁, 在Category中選擇Code Generation, 再在Use run-time library中選擇Debug Multithreaded或者multithreaded 其中, Single-Threaded 單線程靜態(tài)鏈接庫(release版本) Multithreaded 多線程靜態(tài)鏈接庫(release版本) multithreaded DLL 多線程動(dòng)態(tài)鏈接庫(release版本) Debug Single-Threaded 單線程靜態(tài)鏈接庫(debug版本) Debug Multithreaded 多線程靜態(tài)鏈接庫(debug版本) Debug Multithreaded DLL 多線程動(dòng)態(tài)鏈接庫(debug版本) 單線程: 不需要多線程調(diào)用時(shí), 多用在DOS環(huán)境下 多線程: 可以并發(fā)運(yùn)行 靜態(tài)庫: 直接將庫與程序Link, 可以脫離MFC庫運(yùn)行 動(dòng)態(tài)庫: 需要相應(yīng)的DLL動(dòng)態(tài)庫, 程序才能運(yùn)行 release版本: 正式發(fā)布時(shí)使用 debug版本: 調(diào)試階段使用 (發(fā)表于2006-10-8 16:49:00)
多媒體定時(shí)器對實(shí)現(xiàn)高精度定時(shí)是很理想的工具,而且其精度是十分可靠的。但是,多媒體定時(shí)器也并不是完美的。因?yàn)樗煽康木仁墙⒃趯ο到y(tǒng)資源的消耗之上的。因此,在利用多媒體定時(shí)器完成工作的同時(shí),必須注意以下幾點(diǎn): ---- 1. 多媒體定時(shí)器的設(shè)置分辨率不能超出系統(tǒng)許可范圍。
---- 2. 在使用完定時(shí)器以后,一定要及時(shí)刪除定時(shí)器及其分辨率,否則系統(tǒng)會越來越慢。
---- 3. 多媒體定時(shí)器在啟動(dòng)時(shí),將自動(dòng)開辟一個(gè)獨(dú)立的線程。在定時(shí)器線程結(jié)束之前,注意一定不能再次啟動(dòng)該定時(shí)器,不然將迅速造成死機(jī)。 |
|