This document presents information on the following topics
related to porting Windows code to IRIX:
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,
Sending Messages Between Applications
An explanation of a Windows
message passing mechanism and a functional equivalent implementation
for IRIX,
Saving and Restoring Program State
A description of the win32 serialization functions and
example IRIX functions to do the same thing, and
Byte Ordering and Alignment Issues
A short segment on byte
ordering and alignment issues when moving data from IRIX to Windows and
back.
And is complemented with the following code snippets:
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.