Preview only show first 10 pages with watermark. For full document please download

 iwin32 Api Porting Guide

   EMBED


Share

Transcript

T E C H N I C A L P A P E R INtime iWin32 Porting Guide June, 2005 TenAsys Corporation 1600 NW Compton Drive, Suite 104, Beaverton, OR 97006 USA +1 (503) 748-4720 fax +1 (503) 748-4730 www.tenasys.com This document is protected by US and international copyright laws. INtime and iRMX are registered trademarks of TenAsys Corporation. All trademarks, registered trademarks and brand names are the property of their respective owners. Products described in this document may be protected by patents, or pending patent applications. Information regarding products other than those from TenAsys has been compiled from available manufacturers’ material. TenAsys cannot be held responsible for inaccuracies in such material. TenAsys reserves the right to make changes to specifications and product descriptions at any time, without notice. Contact your local TenAsys sales office or distributor to obtain the latest specifications and product descriptions. PHONE: +1 (503) 748-4720 FAX: +1 (503) 748-4730 1600 NW Compton Drive Suite 104 Beaverton, OR 97006 USA EMAIL: [email protected] WEB: www.tenasys.com INtime iWin32 Porting Guide Contents Overview ...............................................................................................................................................2 The application ....................................................................................................................................3 Real-time surgery ................................................................................................................................4 Isolate the real-time code Communication Move real-time code Process communication Generating data Receiving data Start and stop the INtime process 4 4 6 7 7 7 8 Results ..................................................................................................................................................9 Appendix A: The original Win32 application................................................................................ 10 iwin32Demo.cpp iwin32Demo.h iwin32DemoDlg.cpp iwin32DemoDlg.h measure.c measure.h 10 10 11 12 12 14 Appendix B: The new Win32 application...................................................................................... 15 The Windows process measure.c The INtime process measure.c 15 15 16 16 Figures Figure 1: original Win32 process......................................................................................................................................................... 3 Figure 2: two separate processes......................................................................................................................................................... 4 Tables Table 1: compiler and linker modifications....................................................................................................................................... 6 Copyright © 2005 TenAsys Corporation http://www.tenasys.com June, 2005 page 1 of 17 Overview There are many instances where Windows is the desired platform on which to build a control system but is inadequate to insure that the system operates reliably. In those cases using the INtime real-time extension for Windows becomes necessary. In some cases the fact that one needs to use a real-time extension may not be determined until after a complete Windows control application has been developed. INtime includes the iWin32 API to facilitate porting an existing Windows application to the INtime real-time Windows environment. This paper describes how to port the control elements of an existing Windows application to an INtime real-time Windows application. Windows (Windows 2000, Windows XP, and Windows 2003 Server) is a very powerful generalpurpose operating system, designed for desktop computers and servers. It includes facilities to communicate with users via a keyboard, a pointing device, and a graphical user interface (GUI) on one or more displays. Data can be stored on local or remote storage devices, and Windows system can communicate with peer systems and servers over local and wide area networks. The Windows user sees a common GUI shared by all Windows applications, making it easy for experienced users to start using a new application. Windows decorations, dialogs, and error handling all operate through well-defined standards. To a programmer the Windows operating system appears to be an enormous toolbox with functions for the user interface, printing, file access, networking, etc. These functions are all part of the Win32 Application Programming Interface (API). Windows is an inviting platform. Many applications have been written to handle tasks such as word processing, digital imaging, and Internet access. Companies frequently write dedicated [proprietary] applications for Windows to serve very specific tasks. When an application requires deterministic response, as that which is required of a machine control application, the limitations of the Windows operating system emerge. Although Windows is inherently a multi-tasking operating system and (in the case of Windows 2003 Server) can even provide simultaneous services to multiple users, it does have problems meeting the deterministic requirements of precise hardware control applications. An example of a demanding hardware control application is one where a reply must be sent within a short, well-defined time period, usually on the order of milliseconds or even microseconds, or else the entire system malfunctions. For example, a delayed reply to a device that positions chips on a bonding machine may result in the wires being misconnected. Even if this were to happen only once per onethousand chip placements, it would be unacceptable. Replacing Windows with a standard real-time operating system (RTOS) is not an option, because most applications demand the well-know user interface and access to the variety of interfaces and third-party tools and applications that are supported on the Windows family of operating systems. The need for this combination of standard Windows functionality and determinism led to the introduction of the INtime real-time extension for Windows. The INtime real-time extension for Windows is an RTOS that cooperates intimately with Windows on one single hardware platform, but leaves all functions of Windows untouched. The INtime system adds a small real-time environment to the Windows platform. The remainder of this paper is devoted to a simple example of a Windows application that needs determinism; we will show how such an application can be ported, with minimal effort, to an INtime real-time Windows application which is capable of providing the required real-time behavior. June, 2005 page 2 of 17 Copyright © 2005 TenAsys Corporation http://www.tenasys.com The application Rather than using many pages to describe a complex machine control system we will assume an application that has little practical use but which does have a highly visible real-time requirement. The application starts as a standard Windows application that lacks sufficient determinism. We will identify the real-time part of the Windows application and move it to the INtime real-time environment, using the INtime iWin32 API, after which the resulting hybrid application exhibits the desired determinism. Our application is required to perform a time-critical action within 50 microseconds of a periodic 10 millisecond clock tick1 (the action could also be connected to an incoming interrupt request from some dedicated hardware). Instead of performing an action such as reading position data from a tachometer, we will verify the precision of our clock tick by measuring the exact time elapsed between two successive events. The minimum and maximum elapsed times are then displayed using a standard GUI window. The application was developed with Microsoft’s Visual Studio. Its user interface is based on the Microsoft Foundation Classes (MFC), which is independent of the real-time requirements of the application. iwin32 Demo process GUI thread Measure thread RT device Measure data Figure 1: original Win32 process2 Running the application we will quickly discover a significant variation in the elapsed time of each clock tick.3 Although our real-time requirement states that we should be within 50 microseconds of the goal of 10,000 microseconds elapsed time, the maximum elapsed time easily gets as high as 30 milliseconds. Our numbers improve if we apply Win32’s real-time priorities to our application thread, but the result is still not sufficiently deterministic. 1 Both Windows and INtime support shorter period timers, but for simplicity we stick to the 10 millisecond standard here. Figure 1 shows the threads in the process and how they cooperate. The appendices contain source listings. 3 Be careful when measuring on a mobile computer: these often use a processor that varies its clock speed to save power. 2 Copyright © 2005 TenAsys Corporation http://www.tenasys.com June, 2005 page 3 of 17 Real-time surgery The steps necessary to change the application into a real-time application can be summarized as follows: „ Isolate the real-time code: locate that part of the Windows process that must be moved to the INtime real-time environment „ Communication: devise a communication scheme to be used between the two processes (Windows and INtime) „ Move real-time code: convert the real-time parts of the Windows process to an INtime process „ Process communication: implement the Windows to INtime process communication scheme „ Start and stop the INtime process: add code to the Windows process to start the INtime process and initiate communication between the two processes „ Results: the final INtime real-time application for Windows Isolate the real-time code In the vast majority of applications, the code that performs real-time tasks, and interacts with real-time hardware, is quite small. In this example it includes the initialization of common data and measuring the elapsed time between real-time clock tick events. Communication Windows and INtime use memory from the same physical memory pool. The memory is isolated in two virtual segments in order to keep the two environments from interfering with each other. This approach increases the reliability of the total system by making both sides immune to failures occurring on the other side. Because the two environments exist in two separate virtual segments, we cannot simply share a piece of memory between a Windows and INtime process by simply passing a pointer to a block of memory (this restriction also applies to sharing memory between two Windows processes). The INtime iWin32 API does provide various communication mechanisms similar to those available for Windows processes, as well as others specially developed for real-time process use, to facilitate sharing memory and passing data structures between the real-time environment and the Windows environment. Windows INtime iwin32 DemoW process GUI thread iwin32 DemoR process Measure thread RT device Measure data Figure 2: two separate processes To transfer data between the two environments we will use shared memory into which the INtime process writes its minimum and maximum results and from which the Windows application reads the June, 2005 page 4 of 17 Copyright © 2005 TenAsys Corporation http://www.tenasys.com same data. In this case, the two data items are not related, so we do not need synchronization; otherwise, we could have used one of the INtime synchronization functions (mutex, region, or semaphore). In order to insure that determinism is preserved within the INtime real-time environment, we cannot “block” our INtime process if the Windows process cannot keep up with the data flow. Thus, there should be no waiting on data queued by the real-time side for the Windows side. In practice, this means that we might miss collecting and displaying some values generated by the INtime side on the Windows side, especially if our Windows process cannot keep up with data supplied by the real-time side. For example, if the maximum period reported went from 10010 to 10020 and then to 10025, we might see only 10010 and 10025 displayed by the Windows side, missing the intermediate value of 10020. Copyright © 2005 TenAsys Corporation http://www.tenasys.com June, 2005 page 5 of 17 Move real-time code There are two ways to create an INtime project: use the INtime Visual C wizard (this is the recommended method) or start from scratch and manually set the required project settings. To illustrate how an INtime project differs from a Windows project, we will use the second method. We create an empty Windows console application named iwin32DemoR and copy the files measure.c and measure.h from the iwin32Demo project.4 Compiler and linker settings must be modified as follows: iwin32Demo project iwin32DemoR project Comments not using MFC MFC applies mainly to the GUI functionality of an application /X INtime applications do not use the standard include directories /I "c:\program files\intime\rt\include" Add the INtime include directory General use MFC in a shared DLL Compiler /D "WIN32" /D "_WINDOWS" /D "_AFXDLL" /D "_MBCS" /D "_WIN32" Linker /subsystem:windows /out:"Debug/iwin32Demo.exe" /incremental:yes /subsystem:console Specifies a different program start (WinMain versus main) /out:"Release/iwin32DemoR.rta" INtime processes are named RTA to distinguish them from standard Windows processes, which are named with an EXE extension /nodefaultlib INtime processes do not use the standard Windows libraries /libpath:"c:\program files\intime\rt\lib" Add the INtime library directory iwin32.lib rt.lib ciff3m.lib Specify the INtime libraries to be included at link time /version:21076.20052 Marks the resulting output file as an INtime executable /stack:0x4000 Stack size for the main thread /incremental:no /HEAP:1048576 Sets the memory pool for the INtime process (to 1MB) Table 1: compiler and linker modifications5 We need to make some minor adjustments to the include file lines (see the source listings in the appendices). To turn the measure thread into a process, we rename InitMeasure() to main(). The call 4 5 See the section titled A: The original Win32 application for the Windows-only version of the sample application. We have omitted compiler and linker settings that are similar for the two environments, such as those for optimizing code. June, 2005 page 6 of 17 Copyright © 2005 TenAsys Corporation http://www.tenasys.com to SetPriorityClass() must be removed, because INtime has a linear set of 256 priorities instead of Windows’ classes and relative priorities. The calls to CreateThread() and SetThreadPriority() need not change, INtime implements these (and many more Win32) functions in the real-time environment. Process communication Generating data Instead of using the standard C library function malloc() (which allocates process-local storage), we make use of INtime shared memory. This is page-aligned memory identified by a name that can be mapped into the address space of any other process (INtime or Windows). The original code looked like this: pVal = (PVALUES)malloc(sizeof(VALUES)); if (pVal == NULL) return FALSE; To use shared memory we use this code: hMem = RtCreateSharedMemory(0, 0, sizeof(VALUES), "iwin32DemoMem", &pVal); if (hMem == NULL) return FALSE; At INtime process termination the local handle for the shared memory (hMem) is released, but only when all users of the shared memory release their handles will the shared memory referenced by the handles actually vanish. In this case the Windows process must also release its handle to the shared memory. We need to insure that the INtime process persists. We must not return at the end of main()(formerly InitMeasure()) because returning from main() terminates the entire process; instead we terminate only the main thread by a call to ExitThread(0). Receiving data The original Windows application called InitMeasure(), which created an extra Windows thread for clock tick measurement. We must change this to a function that reads the shared memory created by the INtime process for storing measurements. The original code in InitMeasure looked like this: pVal = (PVALUES)malloc(sizeof(VALUES)); if (pVal == NULL) return FALSE; To access the shared memory we need the following code: hMem = RtOpenSharedMemory(0, 0, "iwin32DemoMem", &pVal); if (hMem == NULL) return FALSE; Of course we must remove the code from the Windows side that creates the clock tick measurement thread. Copyright © 2005 TenAsys Corporation http://www.tenasys.com June, 2005 page 7 of 17 Start and stop the INtime process An INtime RTA process must be “loaded” just like a Windows EXE process. This can be done in a variety of ways: via the INtime real-time application loader, by double-clicking the RTA file name in Windows Explorer, or using a shortcut. We can also “hide” the process of loading the INtime executable and make it appear that there is only a single Windows EXE file. In that case, the Windows process loads and starts the INtime RTA using the INtime real-time application loader as follows: if (RtCreateProcess ("iwin32DemoR.rta", NULL, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &pi)) { // get rid of our handle, this does NOT terminate the INtime process RtCloseHandle(pi.hProcess); } else return FALSE; Note that we use the function RtCreateProcess() here. The name differs from that of the equivalent Win32 function called CreateProcess() to indicate that it creates an INtime process rather than a Windows process; however, its syntax and semantics are identical. The same applies to the function RtCloseHandle(). When the Windows application terminates we also should terminate the INtime process. This is taken care by the bRun field in the shared memory data structure. The INtime iWin32 API also supports the Win32 function TerminateProcess() for this purpose (called RtTerminateProcess()). June, 2005 page 8 of 17 Copyright © 2005 TenAsys Corporation http://www.tenasys.com Results When you run the new INtime real-time Windows application you will not see any differences! Starting the Windows process brings up the same dialog as before. Clicking the Close button removes the dialog and—as the user properly assumes—terminates the entire application. Looking below the surface we see that loading the Windows EXE results in the following actions: 1) the Windows process is created 2) it loads the INtime process 3) it then looks for the shared memory that the INtime process creates 4) if all of this worked the dialog appears The actual numbers in the dialog should now meet the requirement (10000 ±50 microseconds). Be careful that you are aware of the hardware you are using to run the application, some mobile microprocessors (esp. laptops) may display fluctuations from the desired results due to their dynamic CPU clocks which are changed to save power and generate less heat. Clicking the Close button causes the following: 1) the Windows application clears the bRun variable in the shared memory 2) the Windows application terminates and the dialog box disappears 3) the measuring thread in the INtime process notices that bRun is no longer TRUE and terminates the INtime process Copyright © 2005 TenAsys Corporation http://www.tenasys.com June, 2005 page 9 of 17 Appendix A: The original Win32 application In the source files, some empty lines and irrelevant comments and source code lines have been removed for brevity. iwin32Demo.cpp #include "stdafx.h" #include "iwin32Demo.h" #include "iwin32DemoDlg.h" //////////////////////////////////////////////////////////////////////////// // The one and only CIwin32DemoApp object CIwin32DemoApp theApp; //////////////////////////////////////////////////////////////////////////// // CIwin32DemoApp construction CIwin32DemoApp::CIwin32DemoApp() { } //////////////////////////////////////////////////////////////////////////// // CIwin32DemoApp initialization BOOL CIwin32DemoApp::InitInstance() { CIwin32DemoDlg dlg; if (!InitMeasure()) { MessageBox(NULL, "Failed to initialize Measure", "Iwin32demo", MB_ICONEXCLAMATION); } else { m_pMainWnd = &dlg; dlg.DoModal(); StopMeasure(); } return FALSE; } iwin32Demo.h #include "resource.h" #include "measure.h" class CIwin32DemoApp : public CwinApp { public: CIwin32DemoApp(); public: virtual BOOL InitInstance(); }; June, 2005 page 10 of 17 Copyright © 2005 TenAsys Corporation http://www.tenasys.com iwin32DemoDlg.cpp #include "stdafx.h" #include "iwin32Demo.h" #include "iwin32DemoDlg.h" //////////////////////////////////////////////////////////////////////////// // CIwin32DemoDlg dialog CIwin32DemoDlg::CIwin32DemoDlg(CWnd* pParent /*=NULL*/) : CDialog(CIwin32DemoDlg::IDD, pParent) { //{{AFX_DATA_INIT(CIwin32DemoDlg) m_max = _T(""); m_min = _T(""); m_time = _T(""); //}}AFX_DATA_INIT m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } //////////////////////////////////////////////////////////////////////////// // CIwin32DemoDlg message handlers BOOL CIwin32DemoDlg::OnInitDialog() { CDialog::OnInitDialog(); SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // start the 1 sec timer time = 0; SetTimer(1, 1000, NULL); return TRUE; } //////////////////////////////////////////////////////////////////////////// void CIwin32DemoDlg::OnRestart() { time = 0; ClearMeasure(); } //////////////////////////////////////////////////////////////////////////// void CIwin32DemoDlg::OnTimer(UINT nIDEvent) { int iMin, iMax; GetMeasure(&iMin, &iMax); time++; m_time.Format("%d", time); m_min.Format("%d", iMin); m_max.Format("%d", iMax); UpdateData(FALSE); CDialog::OnTimer(nIDEvent); } //////////////////////////////////////////////////////////////////////////// void CIwin32DemoDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CIwin32DemoDlg) DDX_Text(pDX, IDC_MAX, m_max); DDX_Text(pDX, IDC_MIN, m_min); DDX_Text(pDX, IDC_TIME, m_time); //}}AFX_DATA_MAP } Copyright © 2005 TenAsys Corporation http://www.tenasys.com June, 2005 page 11 of 17 BEGIN_MESSAGE_MAP(CIwin32DemoDlg, CDialog) //{{AFX_MSG_MAP(CIwin32DemoDlg) ON_WM_TIMER() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDC_RESTART, OnRestart) //}}AFX_MSG_MAP END_MESSAGE_MAP() iwin32DemoDlg.h class CIwin32DemoDlg : public Cdialog { // Construction public: CIwin32DemoDlg(CWnd* pParent = NULL); // Dialog Data //{{AFX_DATA(CIwin32DemoDlg) enum { IDD = IDD_IWIN32DEMO_DIALOG }; CString m_max; CString m_min; CString m_time; //}}AFX_DATA int time; // standard constructor protected: virtual void DoDataExchange(CDataExchange* pDX); protected: HICON m_hIcon; virtual BOOL OnInitDialog(); afx_msg void OnTimer(UINT nIDEvent); afx_msg void OnRestart(); DECLARE_MESSAGE_MAP() }; measure.c #include "stdafx.h" #include "measure.h" //////////////////////////////////////////////////////////////////////////// // The global data typedef struct { DWORD min, max; BOOL bRun; } VALUES, * PVALUES; static PVALUES pVal; //////////////////////////////////////////////////////////////////////////// // Forward declaration DWORD WINAPI MeasureThread( LPVOID Parameter); //////////////////////////////////////////////////////////////////////////// BOOL InitMeasure(void) { HANDLE hThread; pVal = (PVALUES)malloc(sizeof(VALUES)); if (pVal == NULL) return FALSE; June, 2005 page 12 of 17 Copyright © 2005 TenAsys Corporation http://www.tenasys.com pVal->min = 10000000; pVal->max = 0; pVal->bRun = TRUE; hThread = CreateThread(NULL, 0, MeasureThread, NULL, NULL, NULL); if (hThread == NULL) return FALSE; return TRUE; } //////////////////////////////////////////////////////////////////////////// void GetMeasure( int * pMin, int * pMax) { *pMin = pVal->min; *pMax = pVal->max; } //////////////////////////////////////////////////////////////////////////// void ClearMeasure(void) { pVal->min = 10000000; pVal->max = 0; } //////////////////////////////////////////////////////////////////////////// void StopMeasure(void) { pVal->bRun = FALSE; } //////////////////////////////////////////////////////////////////////////// // fetch the Pentium performance counter __declspec (naked) __int64 GetCounter(void) { _asm { _emit 0fH _emit 31H ;read the Pentium clock hi - edx, lo – eax ret } } //////////////////////////////////////////////////////////////////////////// DWORD WINAPI MeasureThread( LPVOID Parameter) { __int64 lastTime, newTime; DWORD elapsed; SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); pVal->min = 10000000; pVal->max = 0; // software debounce Sleep(10); Sleep(10); Copyright © 2005 TenAsys Corporation http://www.tenasys.com June, 2005 page 13 of 17 lastTime = GetCounter(); while (pVal->bRun) { Sleep(10); newTime = GetCounter(); elapsed = (DWORD)(newTime - lastTime) / CPU_MHZ; if (elapsed < pVal->min) pVal->min = elapsed; if (elapsed > pVal->max) pVal->max = elapsed; lastTime = newTime; } // when we get here, bRun is FALSE return 0; } measure.h #define CPU_MHZ 1100 BOOL InitMeasure(void); void StopMeasure(void); void int * int * GetMeasure( pMin, pMax); void ClearMeasure(void); June, 2005 page 14 of 17 Copyright © 2005 TenAsys Corporation http://www.tenasys.com Appendix B: The new Win32 application We only show that file which differs from the original. The Windows process measure.c #include #include #include "measureW.h" //////////////////////////////////////////////////////////////////////////// // The global data typedef struct { DWORD min, max; BOOL bRun; } VALUES, * PVALUES; static PVALUES pVal; //////////////////////////////////////////////////////////////////////////// BOOL InitMeasure(void) { HANDLE hMem; PROCESS_INFORMATION pi; if (CreateProcess ("iwin32DemoR.rta", NULL, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &pi)) { // get rid of our handle, this does NOT terminate the INtime process RtCloseHandle(pi.hProcess); } else return FALSE; hMem = RtOpenSharedMemory(0, 0, "iwin32DemoMem", (VOID**)&pVal); if (hMem == NULL) return FALSE; return TRUE; } //////////////////////////////////////////////////////////////////////////// void GetMeasure( int * pMin, int * pMax) { *pMin = pVal->min; *pMax = pVal->max; } //////////////////////////////////////////////////////////////////////////// void ClearMeasure(void) { pVal->min = 10000000; pVal->max = 0; } //////////////////////////////////////////////////////////////////////////// void StopMeasure(void) { Copyright © 2005 TenAsys Corporation http://www.tenasys.com June, 2005 page 15 of 17 pVal->bRun = FALSE; } The INtime process measure.c #include #include #include #include "measure.h" //////////////////////////////////////////////////////////////////////////// // The global data typedef struct { DWORD min, max; BYTE bRun; } VALUES, * PVALUES; static PVALUES pVal; static HANDLE hMem; //////////////////////////////////////////////////////////////////////////// // Forward declaration DWORD WINAPI MeasureThread( LPVOID Parameter); //////////////////////////////////////////////////////////////////////////// int main( int argc, char * argv[]) { HANDLE hThread; // create the shared memory hMem = RtCreateSharedMemory(0, 0, sizeof(VALUES), "iwin32DemoMem", &pVal); if (hMem == NULL) return FALSE; pVal->min = 10000000; pVal->max = 0; pVal->bRun = TRUE; // create the measuring thread hThread = CreateThread(NULL, 0, MeasureThread, NULL, 0, NULL); if (hThread == NULL) return FALSE; ExitThread(0); } //////////////////////////////////////////////////////////////////////////// void GetMeasure( int * pMin, int * pMax) { *pMin = pVal->min; *pMax = pVal->max; } //////////////////////////////////////////////////////////////////////////// void ClearMeasure(void) { pVal->min = 10000000; June, 2005 page 16 of 17 Copyright © 2005 TenAsys Corporation http://www.tenasys.com pVal->max = 0; } //////////////////////////////////////////////////////////////////////////// void StopMeasure(void) { pVal->bRun = FALSE; } //////////////////////////////////////////////////////////////////////////// // fetch the Pentium performance counter __declspec (naked) __int64 GetCounter(void) { _asm { _emit 0fH _emit 31H ;read the Pentium clock hi - edx, lo – eax ret } } //////////////////////////////////////////////////////////////////////////// DWORD WINAPI MeasureThread( LPVOID Parameter) { __int64 lastTime, newTime; DWORD elapsed; SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); pVal->min = 10000000; pVal->max = 0; // software debounce Sleep(10); Sleep(10); lastTime = GetCounter(); while (pVal->bRun) { Sleep(10); newTime = GetCounter(); elapsed = (DWORD)(newTime - lastTime) / CPU_MHZ; if (elapsed < pVal->min) pVal->min = elapsed; if (elapsed > pVal->max) pVal->max = elapsed; lastTime = newTime; } // when we get here, bRun is FALSE ExitProcess(0); } Copyright © 2005 TenAsys Corporation http://www.tenasys.com June, 2005 page 17 of 17