INtime SDK Help
iwin32 Porting
INtime SDK v7.1 > About INtime > Alternate APIs > iwin32 API > iwin32 Porting
In this topic:

Overview

In some cases, Windows alone is not enough to build a successful system, and a tool like the INtime for Windows may be necessary. This topic discusses where this need may arise and then shows how to port a pure Windows application to one that offloads real-time parts to the INtime environment.

Windows is a very powerful operating system, intended for desktop computers and servers. It includes facilities to communicate with the user in a graphical sense with one or more displays, a keyboard and a pointing device. There are standards to access data stored on local and remote storage devices, to communicate with peer systems and servers in local or wide area networks and many more functions exist that are required maybe not for all, but certainly for many common applications.

The user of a Windows application sees a common user interface shared by all Windows applications, which makes it easy for an experienced user to start using a new application. Windows decorations, dialogs, error handling all operate through well-defined standards.

For a programmer the system looks like a huge toolbox with functions for user interface, printing, file access, networking, etc. These functions are all part of the Win32 Application Programming Interface (API), which is available on all well-known flavors of Windows.

Sounds super, doesn't it? Many programs have been written for standard applications such as word processing, digital imaging and internet handling. For dedicated applications, companies have written their own (often proprietary) applications. But it is exactly in this area that sometimes a limitation of Windows emerges: although Windows is inherently a multi tasking operating system and (as in the case of a server) can operate for multiple users at the same time, it has problems in meeting the deterministic requirements of hardware.

There are many examples of demanding hardware, where a reply must be sent within a short, well-defined time in the order of microseconds or else the whole system malfunctions: a slow reply to a device that positions chips on a bonding machine may result in the wires being connected to the wrong contacts. Even if this happens only once per ten chips, it is unacceptable.

Replacing Windows with a deterministic operating system is not an option, as the user demands the well-known user interface. The need for this combination of standard Windows functionality and determinism led to the introduction of the INtime for Windows, which leaves all functions of Windows untouched, but adds a small real-time environment deep inside Windows.

The remainder of this document 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 a mixed Windows / INtime application that indeed provides the required real-time behavior.

The application

We do not want to spend many pages on describing a complex machine control system. Instead, we take an application that has little practical use, but that does have a highly visible real-time requirement. The application starts as a Windows application that lacks determinism. We then extract the real-time part and move that into the INtime environment, after which the resulting application does indeed behave as required.

The requirement for the application is to perform a time-critical action every 10 milliseconds (in a more realistic case, the action would be connected to an incoming interrupt request, but using the system clock makes our example easier to implement and understand). Instead of performing an action like reading position data or sending device commands, we verify our own precision by measuring the time elapsed between two successive actions. The minimum and maximum elapsed times are then visualized in a window.

The application was developed with Microsoft's Visual Studio. It is based on the Microsoft Foundation Classes (MFC) for its user interface, but that is completely independent of the real-time requirement.

The original Win 32 process:

#include "stdafx.h"
#include "iwin32Demo.h"
#include "iwin32DemoDlg.h"
////////////////////////////////////////////////////////
// The one and only CIwin32DemoApp object
CIwin32DemoApp theApp;
////////////////////////////////////////////////////////
// CIwin32DemoApp construction
CIwin32DemoApp::CIwin32DemoApp()
{
}
////////////////////////////////////////////////////////
// CIwin32DemoApp initialization
BOOLEAN CIwin32DemoApp::InitInstance()
{
  CIwin32DemoDlg dlg;
  if (!InitMeasure()) {
    MessageBox(NULL,
      "Failed to initialize Measure",
      "Iwin32demo",
      MB_ICONEXCLAMATION);
  }
  else {
    m_pMainWnd = &dlg;
    dlg.DoModal();
    StopMeasure();
  }
  return FALSE;
}
   
#include "resource.h"
#include "measure.h"
class CIwin32DemoApp : public CwinApp
{
public:
  CIwin32DemoApp();
public:
  virtual BOOLEAN InitInstance();
};
#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
BOOLEAN 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
}
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()
class CIwin32DemoDlg : public Cdialog
{
// Construction
public:
  CIwin32DemoDlg(CWnd* pParent = NULL);  // standard constructor
  // Dialog Data
  //{{AFX_DATA(CIwin32DemoDlg)
  enum { IDD = IDD_IWIN32DEMO_DIALOG };
  CString m_max;
  CString m_min;
  CString m_time;
  //}}AFX_DATA
  int          time;
protected:
  virtual void DoDataExchange(CDataExchange* pDX);
protected:
  HICON m_hIcon;
  virtual BOOLEAN OnInitDialog();
  afx_msg void OnTimer(UINT nIDEvent);
  afx_msg void OnRestart();
  DECLARE_MESSAGE_MAP()
};
#include "stdafx.h"
#include "measure.h"
////////////////////////////////////////////////////////
// The global data
typedef struct {
  DWORD min, max;
  BOOLEAN  bRun;
} VALUES, * PVALUES;
static PVALUES pVal;
////////////////////////////////////////////////////////
// Forward declaration
DWORD WINAPI MeasureThread(LPVOID Parameter);
 
////////////////////////////////////////////////////////
BOOLEAN InitMeasure(void)
{
  HANDLE hThread;
  pVal = (PVALUES)malloc(sizeof(VALUES));
  if (pVal == NULL)
    return FALSE;
  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
__int64 GetCounter(void)
{
  _asm rdtsc;  / return Time Stampt Counter in edx:eax
}
///////////////////////////////////////////////////////
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);
  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;
}
#define CPU_MHZ  1100
BOOLEAN InitMeasure(void);
void StopMeasure(void);
void GetMeasure(int * pMin, int * pMax);
void ClearMeasure(void);

The above figure shows the threads in the process and how they cooperate.

When we run the application, we see that there is a lot of variation in the maximum time: although the real-time requirement states that both minimum and maximum should be within 50 microseconds of the goal of 10,000 microseconds, the maximum easily gets as high as 30 milliseconds It helps a bit to apply Win32's real-time priorities, but the result is still not deterministic enough.

Real-time surgery

The steps to change the application into a real-time application collection can be summarized as:

Locate the real-time code

The actual code that handles real-time 'devices' is quite small in this example: it includes the initialization of the common data and the cycle of Sleep and calculating the elapsed time.

Design communication

Windows and INtime use the same physical memory, but as much as possible the address spaces are protected from each other; although this may sound as a limiting factor, it does increase the reliability of the total system by making either side immune to failures occurring on the other side. We can not simply share a piece of memory between a Windows and an INtime process by passing a pointer (this restriction applies in the same way to two Windows processes and in fact to concurrent processes on most multitasking operating systems). Fortunately INtime provides various communication mechanisms, some similar to Windows, others specially developed for real-time use.

We do not want to block the INtime application if the Windows application can not keep up with the data flow, so there should be no queuing of data. In practice, this implies that we may miss some values, such as when the maximum period changes from 10010 to 10020 and then 10025: we may see 10010 and then 10025, thus missing the value 10020. We create a piece of shared memory into which the INtime process writes its minimum and maximum data and from which the Windows application reads the same data. In this case, the two data items are not related, so we do not need synchronization; if we did, we could have used one of the INtime synchronization functions (mutex, region, semaphore).

Move real-time code

We need to create an INtime process and there are two ways of doing so: use a Visual C wizard to create an INtime project (this is the recommended method) or start from scratch and set all required project settings. To indicate where an INtime project deviates from a Windows project, we will use the second method.

So we create an empty Windows console application named iwin32DemoR and copy the files measure.c, measure.h from the iwin32Demo project. We have to modify compiler and linker settings as follows:

iwin32Demo project iwin32DemoR project Comments
General
use MFC in a shared DLL not using MFC MFC applies mainly to GUI
Compiler
/X Do not use standard include directories
/I "c:\program files\intime\rt\include" Add INtime include directory
/D "WIN32" /D "WIN32"
/D "_WINDOWS"
/D "_AFXDLL"
/D "_MBCS"
Linker
/subsystem:windows /subsystem:console Different program start (WinMain vs main)
/out:"Debug/iwin32Demo.exe" /out:"Release/iwin32DemoR.rta" The file name extension RTA is connected to the INtime loader
/nodefaultlib INtime does not use Windows libraries
/libpath:"c:\program files\intime\rt\lib" Add INtime library directory
iwin32.lib rt.lib ciff3m.lib Specific INtime libraries
/version:21076.20052 Marks the file as an INtime executable
/stack:0x4000 Stack size for the main thread
/incremental:yes /incremental:no
/HEAP:1048576 Sets the memory pool for the INtime process (to 1MB)

(We have omitted many compiler and linker settings that are not specifically different for the two environments, such as those for optimising code).

We have to make some minor adjustments to the include file lines (see source listings). To turn the measure thread into a complete process, we rename InitMeasure into main. The call to SetPriorityClass must be removed, as INtime has a linear set of 256 priorities instead of Windows' classes and relative priorities.

The calls to CreateThread and SetThreadPriority need no change, as INtime implements these (and many more Win32) functions in the real-time environment.

Communicate in INtime

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 (INtime or Windows) process.

The original code looked like this:

pVal = (PVALUES)malloc(sizeof(VALUES));
  if (pVal == NULL)
    return FALSE;

To use shared memory we need 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 closed, but only when all users of the shared memory (in this case the Windows process) close their handles, does the shared memory actually vanish.

Next we need to make sure that the INtime process exists as long as necessary. At the end of InitMeasure (now main), we must not return as such a return from the main thread terminates the process; instead we terminate the main thread:

ExitThread(0);

Communicate in Windows

The original Windows application called InitMeasure, which created the extra Windows thread. We must change this into the finding of the shared memory:

The original code in InitMeasure looked like this:

pVal = (PVALUES)malloc(sizeof(VALUES));
if (pVal == NULL)
  return FALSE;

To use shared memory we need this code:

hMem = RtOpenSharedMemory(0, 0, "iwin32DemoMem", &pVal);
if (hMem == NULL)
  return FALSE;

Of course, we remove the code that creates the measure thread.

Start and stop the INtime process

An INtime process must be loaded just like a Windows process; this can be done in various ways: by executing the INtime RT Application Loader, by double clicking on the RTA file name in the Windows Explorer or by activating an appropriate shortcut. For the user we should hide the INtime process and pretend that there is only the Windows process; the Windows process then creates the INtime process via the INtime RT Application Loader as follows:

if (RtCreateProcess ("iwin32DemoR.rta", NULL,
    NULL, NULL, FALSE, 0, NULL, NULL, NULL, &pi)) {
  // get rid of our handles, this does NOT terminate the INtime process
  RtCloseHandle(pi.hThread);
  RtCloseHandle(pi.hProcess);
}
else
  return FALSE;

Note that we use the function RtCreateProcess here; although the name differs from that of the Win32 function CreateProcess to indicate that it creates an INtime process rather than a Windows process, its syntax and semantics is identical. The same applies to the functions RtWaitForSingleObject and RtCloseHandle.

When the user terminates the Windows application, we should terminate the INtime process. We have already taken care of this by the bRun field in the shared memory data structure. Naturally, INtime also supports the Win32 function TerminateProcess for this purpose (now called RtTerminateProcess).

The result

Amazingly enough, you will not directly see any difference! Just starting the Windows process brings up the dialog, showing some numbers. And clicking on the Close button removes the dialog and - as the user assumes - terminates the application.

If we look below the surface, we see that starting the Windows process causes these actions:

  1. The Windows process is created.
  2. It attempts to load the INtime process.
  3. It then looks for the shared memory that the INtime process has created.
  4. If all of this worked out well, the dialog appears.

The actual numbers in the dialog are much closer to the requirement (which is 10000 microseconds), although some mobile microprocessors may still cause fluctuation.

When the user clicks the Close button, these actions take place:

  1. The Windows application clears the bRun variable in the shared memory.
  2. The Windows application terminates, which causes the dialog to disappear.
  3. The measuring thread in the INtime process notices that bRun is no longer true and therefore terminates the INtime process.

The new Win32 application

We only show the file that is different from the original one.

#include <afx.h>
#include <iwin32x.h>
#include "measureW.h"
////////////////////////////////////////////////////////
// The global data
typedef struct {
  DWORD min, max;
  BOOLEAN bRun;
} VALUES, * PVALUES;
static PVALUES pVal;
////////////////////////////////////////////////////////
BOOLEAN InitMeasure(void)
{
  HANDLE hMem;
  PROCESS_INFORMATION pi;
  if (CreateProcess ("iwin32DemoR.rta", NULL,
        NULL, NULL, FALSE, 0, NULL, NULL,
        NULL, &pi)) {
    // get rid of our handles, this does NOT terminate the INtime process
    RtCloseHandle(pi.hThread);
    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)
{
  pVal->bRun = FALSE;
}

The INtime process

#include <windows.h>
#include <stdlib.h>
#include <iwin32.h>
#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;
  pVal->max = 0;
}
////////////////////////////////////////////////////////
void StopMeasure(void)
{
  pVal->bRun = FALSE;
}
///////////////////////////////////////////////////////
// fetch the Pentium performance counter
__int64 GetCounter(void)
{
  _asm rdtsc;  // return Time Stamp Counter in edx:eax
}
////////////////////////////////////////////////////////
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);
}
See Also