SGI Windows NT Toolbox
|Download Files|

Porting Windows Code to IRIX

This document presents information on the following topics
related to porting Windows code to IRIX:


  1. Windows Message Handling
    A brief introduction to the way that messages are handled in Windows using the win32 function calls and the Microsoft Foundation Classes,

  2. Sending Messages Between Applications
    An explanation of a Windows message passing mechanism and a functional equivalent implementation for IRIX,

  3. Saving and Restoring Program State
    A description of the win32 serialization functions and example IRIX functions to do the same thing, and

  4. Byte Ordering and Alignment Issues
    A short segment on byte ordering and alignment issues when moving data from IRIX to Windows and back.


1.   Windows Message Handling

Instead of events windows uses messages to pass information to windows or buttons. Messages are just a unique identifier that is setup with a #define in a header file. The messages are defined with names WM_* (ie. WM_PAINT or WM_CREATE).

In the old windows 3.1 style code a main loop is created to handle and dispatch these messages. The function would look something like the following...

int APIENTRY WinMain( HINSTANCE hInstance,

                     HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine,
                     int nCmdShow )
{
  MSG msg;

  if ( !InitApplication(hInstance) )
    return (FALSE);

  if ( !InitInstance(hInstance, nCmdShow))
    return (FALSE);

  our_special_message = RegisterWindowMessage(_TEXT("OUR_MESSAGE"));

  // Message handling Loop 
  while( GetMessage(&msg, NULL, 0, 0) ) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return (msg.wParam);
}


The Equivalent Unix function would look something like this...


{

  XEvent event;

  while( 1 ) {
    if ( XtAppPending( app ) ) {
      XtAppNextEvent( app, &event );
      if ( DestroyNotify == event.type )
       break;
      else
       XtDispatchEvent(&event);
    }
  }
}


The InitApplication() function is where the windows are created and registered. Windows uses a WNDCLASS structure and a call to RegisterClass() in order to make things work. The WNDCLASS structure contains a member (lpfnWndProc) which holds a pointer to a function. It is this function that will receive messages for this window.

When DispatchMessage is called the message is passed to the lpfnWndProc function. An example of a simple one follows...


LONG APIENTRY MainWndProc(HWND hWnd, // window handle

                         UINT message, // type of message
                         UINT wParam, // more info
                         LONG lParam) // more info
{

  if (message == our_special_message) {
    return WClass_OnOurMsg( wParam, lParam );
  }

  switch (message) {
  case WM_CREATE:
    WClass_OnCreate( hwnd, message, wParam, lParam );
    break;
  case WM_DESTROY:
    PostQuitMessage(0);
    break;
  default:
    return (DefWindowProc( hWnd, message, wParam, lParam) );
  }

  // alternate implementation
  switch (message) {
    HANDLE_MSG( hWnd, WM_CREATE, WClass_OnCreate );
    HANDLE_MSG( hWnd, WM_DESTROY, WClass_OnDestroy );
  default:
    return (DefWindowProc( hWnd, message, wParam, lParam ));
  }
}


X applications don't need a function like this since callbacks automatically route the proper messages/events to the proper functions.

MFC has preset macros to do a lot of this work for you. It has it's own message handling loop that calls the appropriate WndProc() on the appropriate window. In order to use the MFC approach we need to use the Macros in a header file. The important macros are BEGIN_MESSAGE_MAP END_MESSAGE_MAP and any of the many ON_COMMAND or ON_NOTIFY macros. I have unrolled a very short message map setup so that we can see what it is really doing. Basically it sets up an array of structures that tell which function gets called for each message and what parameters are passed in.....


// original line

BEGIN_MESSAGE_MAP(CShowStringApp, CWinApp)
  
// sets up the functions for messaging
struct AFX_MSGMAP_ENTRY
{
  UINT nMessage;   // windows message
  UINT nCode;      // control code or WM_NOTIFY code
  UINT nID;        // control ID (or 0 for windows messages)
  UINT nLastID;    // used for entries specifying a range of control id's
  UINT nSig;       // signature type (action) or pointer to message #
  AFX_PMSG pfn;    // routine to call (or special value)
};

struct AFX_MSGMAP {
  const AFX_MSGMAP *pBaseMap;
  const AFX_MSGMAP_ENTRY *lpEntries;
}


const AFX_MSGMAP* CShowStringApp::GetMessageMap() const 
{
  return CShowStringApp::messageMap; 
}

AFX_DATADEF const AFX_MSGMAP CShowStringApp::messageMap = 
{ &CWinApp::messageMap, &CShowStringApp::_messageEntries[0] };

const AFX_MSGMAP_ENTRY CShowStringApp::_messageEntries[] = {

// next line
ON_COMMAND(ID_APP_ABOUT, OnAppAbout) 

{ WM_COMMAND, 0, (WORD)ID_APP_ABOUT, (WORD)ID_APP_ABOUT, 
  AfxSig_vv, (AFX_PMSG)&OnAppAbout },

// last line
END_MESSAGE_MAP()

{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
};


Each window class has a message handling function and it is the responsibility of this function to go through the _messageEntries[] array looking for the proper function to call and the parameters that it needs to specify on the call to that function.



2.   Sending Messages Between Applications

Windows allows you to send messages between different windows. If those two windows are in the same application then this can be easily simulated with XSendEvent() using a client message. If the two windows happen to lie in separate applications then it is equivalent to a Remote Procedure Call (RPC). There is a function called RegisterWindowMessage() that will return to us a message number that is unique throught the system. If in another application we call RegisterWindowMessage() and pass in the same string then we will be returned the same number. This effectively allows the applications to communicate. If one application has a window handle for the main window in another application then it can send it a message via the SendMessage() function call. The function might look something like this....


LRESULT SendMsg(UINT message_type, LONG offset)

{
  // this function effectively calls the message handling code in the
  // other app by sending the listed message to the listed window

  return SendMessage( hWnd, our_special_message, 
                     message_type, offset );

}
 

The SendMessage() function calls the WndProc() function for the supplied Window (hWnd). If this window lies in another application then this call effectively makes a RemoteProcedureCall and doesn't return until the WndProc() function in the other application returns. It also returns with the given return value. To simulate this on the Unix side is somewhat difficult. Possible solutions include RPC, XAtoms or XSendEvent and Unix message passing (ie. message queues or unix sockets). RPC is somewhat cumbersome and painful to implement and XAtoms and XSendEvent require that the two applications be on the same Display. This leaves Unix message passing as the alternative (namely the one I chose).

I created a class around Unix Sockets that would allow us to send messages between applications. In order to keep the calling function from returning before the message had been handled I used semaphores. The messages themselves consisted of a message_type stored as an unsigned int and offset into shared memory where we could store return values and other information.

Socket and Semaphore Classes

My socket class was called CSgMsg() (see sgmessage.h and sgmessage.c++) and my semaphore class was CSgLock() (see sglock.h and sglock.c++).

The lock has calls for both the server side and client side.

CSgLock::server_write() -> call to lock the server side of the shared memory

CSgLock::server_verify_write() -> call to see if the client is finished reading the message

CSgLock::server_finish_read() -> call to tell client the server has read the message (non-blocking)

the client has similar calls

CSgLock::client_write(), client_verify_write(), and client_finish_read()

This allows the server and client to send messages to one another and know when the message has been read in a semi-blocking manner.

For the actual message passing we use the CSgMsg class which works asynchronously for the client and semi-synchronously for the server.

For the server to connect .....

pMsg = new CSgMsg("MY_MESSAGE", &data_func());

pMsg->bind();

For the client ....

pMsg = new CSgMsg("MY_MESSAGE", app_context, input_callbackProc);

pMsg->connect();

Messages will be automatically delivered to the client and outgoing messages can be sent by using the write() function.

The Server has to call pMsg->check_for_messages(). This is usually called within the normal event loop, but it is also necessary to call it when waiting for the server_verify_write() to return TRUE. This guarantees that the client and server won't deadlock each other but also won't return until their messages have been read.

For the server to send a message he would do the following...

pMsgLock->server_write(); // obtain the write lock for his section of shared memory

// create the message in shared memory here

msg_data = createMessageInSharedMemory(&msg_len);

pMsg->write((void*)msg_data, msg_len); // send the actual message

while(!pMsgLock->server_verify_write()) // check to see if the message was received

pMsg->check_for_messages(); // check for more client messages

The client doesn't need to check for messages since the server is making sure that messages are being handled. His code would look something like this...

pMsgLock->client_write(); // obtain the write lock for his section of shared memory

// create the message in shared memory here

msg_data = createMessageInSharedMemory(&msg_len);

pMsg->write((void*)msg_data, msg_len); // send the actual message

while(!pMsgLock->client_verify_write()); // just spin waiting for message


When a message comes in, it is the readers responsibility to put the appropriate return value in shared memory and then call the appropriate finish_read() function. Doing something like this client snippet...

pMsg->read((void*)msg_data, msg_len); // read the message

// handle the message and call the appropriate functions here

int retVal = handleMsg(msg_data);

// set the return value in shared memory

setReturnValueInSharedMemory(retVal);

pMsgLock->client_finish_read(); // this frees the server to continue on




3.   Saving and Restoring Program State

Windows programs have a great need to save and restore program state by storing values in a *.ini file. Microsoft has implemented two functions to make this quite a bit easier GetPrivateProfileString() and WritePrivateProfileString() (see config.h and config.c++). There also exist functions called GetPrivateProfileInt() and WritePrivateProfileInt() that use integers instead of strings. They take a section name, a key name and a char pointer to store the return value in. It's a very cool way to get and save things to a file. Under MFC the ini files have been replaced by Registry files. Registry files are binary as opposed to the text based ini files they also allow multiple layers of key value nesting. This is far more difficult to implement. MFC adds a method called SetRegistryKey() which takes a string as a keyl If this function is called then the other functions will use the registry files instead of the ini files.





4.   Byte Ordering and Alignment Issues

Wintel boxes are little-endian machines (ie. the least significant byte is in the lowest address ) and Irix boxes are big-endian machines (ie. the most significant byte in the lowest address ). Some quick functions to swap 32 bit types and 16 bit types....


u32_t swap_u32(u32_t l)
{
  return (((l & 0x000000FF) << 24) |
         ((l & 0x0000FF00) << 8) |
         ((l & 0x00FF0000) >> 8) |
         ((l & 0xFF000000) >> 24));
}

u16_t swap_u16(u16_t w)
{
  return (((w & 0x00FF) << 8) |
         ((w & 0xFF00) >> 8));
}

The are 2 alignment issues that can bite you during a port. The first is the fact that members of structures are placed on 2 byte boundaries whereas the windows side can place them on single byte boundaries. This means we can't just pass the data back and forth between machines without packing it first on our side. The following code snippet exhibits the problem.


#include <stdio.h>

#include <stdlib.h>

#pragma pack(1) // SGI_PACK
struct weird {
   int first_entry;
   short second_entry;
   char third_entry;
   int fourth_entry;
};

int main(int argc, char *argv[])
{
  struct weird *ours;
  long addr_1, addr_2, addr_3, addr_4;

  ours = (struct weird *)malloc(sizeof(struct weird));

  addr_1 = (long)&ours->first_entry;
  addr_2 = (long)&ours->second_entry;
  addr_3 = (long)&ours->third_entry;
  addr_4 = (long)&ours->fourth_entry;

  printf("address first:   0x%x space:%d size:%d\n", addr_1, 
        addr_2 - addr_1, sizeof(int));
  printf("address second:  0x%x space:%d size:%d\n", addr_2,
        addr_3 - addr_2, sizeof(short));
  printf("address third:   0x%x space:%d size:%d\n", addr_3,
        addr_4 - addr_3, sizeof(char));
  printf("address fourth:  0x%x\n", addr_4);

  free(ours);
}
#pragma pack(0) // SGI_PACK

The packing pragmas are needed to pack the last int up against the char otherwise a one byte buffer is left between them. Unfortunately the pragma is ignored when built n32 (bug #470134).

The following is the output from running the program....


cerberus 638% align_nopack_n32
address first:   0x10013010 space:4 size:4
address second:  0x10013014 space:2 size:2
address third:   0x10013016 space:2 size:1
address fourth:  0x10013018
cerberus 639% align_nopack_o32
address first:   0x10002010 space:4 size:4
address second:  0x10002014 space:2 size:2
address third:   0x10002016 space:2 size:1
address fourth:  0x10002018
cerberus 640% align_pack_o32
address first:   0x10002010 space:4 size:4
address second:  0x10002014 space:2 size:2
address third:   0x10002016 space:1 size:1
address fourth:  0x10002017
cerberus 641% align_pack_n32
address first:   0x10013010 space:4 size:4
address second:  0x10013014 space:2 size:2
address third:   0x10013016 space:2 size:1
address fourth:  0x10013018


The second aligment issue comes about when we try to dereference non-aligned addresses. The following code snippet exhibits the problem....

#include <stdio.h>
#include <stdlib.h>
#include <sys/sysmips.h>

#pragma pack(1) // SGI_PACK
struct weird {
   int first_entry;
   short second_entry;
   char third_entry;
   int fourth_entry;
};

extern "C" int sysmips(int cmd, int arg1, int arg2, int arg3);

int main(int argc, char *argv[])
{
  struct weird *ours;
  int first = 1;
  short second = 2;
  char third = 3;
  int fourth = 4;

  sysmips( MIPS_FIXADE, 1, 0, 0 );

  ours = (struct weird *)malloc(sizeof(struct weird));
  int *p1 = &ours->first_entry;
  short *p2 = &ours->second_entry;
  char *p3 = &ours->third_entry;
  int *p4 = &ours->fourth_entry;

  *p1 = first;
  *p2 =  second;
  *p3 = third;
  *p4 = fourth;

  first = *p1;
  second = *p2;
  third = *p3;
  fourth = *p4;
  
  free(ours);
}
#pragma pack(0) // SGI_PACK


Without the lines in red this program would generate a SIGBUS when it tried to derefence the fourth_entry to the ours structure because it is misaligned. By adding the call to sysmips() and passing 1 as arg1 it causes the os to retry the operation using byte wise instructions when a SIGBUS would have occured. There is a performance penalty for this call but it does allow us to deal with non-aligned structures so that we can pass them back and forth between windows and Irix machines.