Introduction
I recently re-wrote one of my old MFC dialog based Windows applications in GTK, so it runs on both Windows and Linux platforms. Converting the dialog box code from Windows to GTK was fairly painless (see my other article “Designing Dialog based applications with Glade and GTK”), but converting Windows messaging to GTK was difficult.
The old application has a thread that listens for data on a USB port, and uses Windows messaging to send the data to a dialog box. The dialog box receives the Windows messages, parses the USB data, and displays the results.
For the purposes of this article, I stripped my application down to a bare minimum; a thread which sends a “hello world” message to a dialog box every second, and a dialog box which displays the message in a listbox.
The Windows Version
For those of you who are not familiar with Windows messaging, a quick look at the code should explain it. The thread code sends the “hello world” message by allocating memory to store the message, copying the message into allocated memory, then calling the function “SendMessage
” to send the message to the dialog box. The SendMessage
parameters are the message ID, the address of the allocated memory, and the size of the message.
DWORD WINAPI MsgThread( LPVOID lpParam )
{
char HelloMsg[] = "Hello, World";
char *MsgBuffer;
while(1)
{
MsgBuffer = (char *)malloc(sizeof(HelloMsg));
memcpy(MsgBuffer, HelloMsg, sizeof(HelloMsg));
hWinMsg_Dialog->SendMessage(WM_SHOW_MESSAGE,
(WPARAM)MsgBuffer, (LPARAM)(sizeof(HelloMsg)));
Sleep(1000);
}
}
The dialog box code maps the handler function to the message ID used by the thread with the ON_MESSAGE
macro shown below.
ON_MESSAGE(WM_SHOW_MESSAGE, ShowMessage)
The message handler inserts the message in the listbox, and then frees the memory used by the sender.
LRESULT CWinMsgDlg::ShowMessage(WPARAM wpBuffer, LPARAM luiBufferLength)
{
m_MsgList.InsertString(EmptyLineIndex++, (LPCTSTR)wpBuffer );
free((void*)wpBuffer);
return(0);
}
The GTK Version
The GTK function g_io_channel_win32_new_messages
receives Windows messages. I tried using it, but I could not get it to work. It is not a good choice for my application anyway, since it has no direct Linux equivalent. The scheme I chose uses sockets, which are available on both Linux and Windows.
Sockets are most commonly used to send messages from a PC to the internet, but they can also be used to send messages between applications in the same PC. Sockets use port numbers to distinguish between one application and another. For example, to get the Google screen on a browser, messages arrive at your PC because they were sent to the IP address of your PC, and are displayed on the correct instance of your browser, assuming more than one is open, because they were sent to the port number of that instance. The special IP address 127.0.0.1 is used to send messages within the same PC; the IP stack in the computer knows to route messages with this IP address within the computer rather than send them to the internet.
To begin the conversion from the Windows application, we need two sockets, one for the thread to send the message, the other for the dialog box to receive the message. The code below shows both sockets being created. The code lets the computer select the port numbers, then reads the assigned receive port and stores it for use by the sender, then sets the receive IP address to 127.0.0.1, defined as LOCAL_IP_ADDRESS
in the code. The code is identical for both Windows and Linux, but Windows requires an additional WSAStartup
function.
int GtkSock_Init(SOCKET *RecvSocket)
{
int status;
int RecvAddressLen = sizeof(RecvAddress);
#ifdef WIN32
WSADATA wsaData;
if(WSAStartup(MAKEWORD(1,1),&wsaData) != 0)
{
return(-1);
}
#endif
SendSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
SendAddress.sin_family = AF_INET;
SendAddress.sin_addr.s_addr = INADDR_ANY;
SendAddress.sin_port = htons(0);
status = bind(SendSocket, (struct sockaddr*)&SendAddress,
sizeof(SendAddress));
*RecvSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
RecvAddress.sin_family = AF_INET;
RecvAddress.sin_addr.s_addr = INADDR_ANY;
RecvAddress.sin_port = htons(0);
status = bind(*RecvSocket, (struct sockaddr*)&RecvAddress,
sizeof(RecvAddress));
getsockname(*RecvSocket, (struct sockaddr*)&RecvAddress, &RecvAddressLen);
RecvAddress.sin_addr.s_addr = inet_addr(LOCAL_IP_ADDRESS);
return(0);
}
Now, we can use the send socket to send the message to the dialog box. We do not need to allocate memory for the message, the whole text string is sent to the receiver through the socket.
DWORD WINAPI MsgThread( LPVOID lpParam )
{
char HelloMsg[] = "Hello, World\n";
while(1)
{
GtkSock_Send(HelloMsg, sizeof(HelloMsg));
Sleep(1000);
}
}
int GtkSock_Send( char *Buffer, int BufferLen)
{
int count;
count = sendto(SendSocket, Buffer, BufferLen, 0,
(struct sockaddr*)&RecvAddress, sizeof(RecvAddress));
return(count);
}
Mapping the receive socket to the handler in GTK takes a little work. The code below shows the full initialization code for the application, which creates the sockets, attaches the handler function to the receive socket, and starts the sender thread.
The handler attaches to a channel, which is created from the socket with the Windows specific function g_io_channel_win32_new_socket
. The equivalent Linux function is g_io_channel_unix_new
. My real application code uses a define to switch between the two function names. This is the only line of code that changes between Linux and Windows.
Once the channel is created, I set the encoding so the channel passes binary data rather than text. It does not matter for this code, but my real application uses binary data. I set the channel to non-blocking, and map the handler function GtkMsg_ShowMessage
to the channel.
void GtkMsg_Init(GtkDialog *dialog1)
{
pthread_t MsgThreadId;
HANDLE DownlinkThread;
SOCKET RecvSocket;
GIOChannel *RecvChannel;
MainWindow = (GtkWidget *)dialog1;
GtkSock_Init(&RecvSocket);
RecvChannel = g_io_channel_win32_new_socket((gint)RecvSocket);
g_io_channel_set_encoding (RecvChannel, NULL, NULL);
g_io_channel_set_flags(RecvChannel, G_IO_FLAG_APPEND| G_IO_FLAG_NONBLOCK,
NULL);
g_io_add_watch(RecvChannel, G_IO_IN | G_IO_HUP, GtkMsg_ShowMessage, 0);
DownlinkThread = CreateThread( NULL, 0, MsgThread, 0, 0, &MsgThreadId);
}
All that’s left to do is have the handler read the data from the socket and display it. The function g_io_channel_read_chars
reads the message from the socket, and the rest of the code displays the text in a textview widget.
gboolean GtkMsg_ShowMessage( GIOChannel *channel, GIOCondition condition,
GtkEntry *entry )
{
gchar message[MAX_MSG_LEN];
gsize length;
GtkTextMark* MarkEnd;
GtkWidget *widgetMsgList = lookup_widget(MainWindow, "textview1");
GtkTextBuffer *textMsgList;
textMsgList = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widgetMsgList));
g_io_channel_read_chars (channel, message, MAX_MSG_LEN, &length, NULL);
gtk_text_buffer_insert_at_cursor(textMsgList, message, -1);
MarkEnd = gtk_text_buffer_get_insert (textMsgList);
gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(widgetMsgList), MarkEnd );
return(TRUE);
}
Here is the GTK dialog box showing the message. Hope this was useful to you. The attached zip file has both Windows and GTK versions of the code. WinMsg contains the source files for the Windows version, and GtkMsg, the source files for the GTK version.
The GTK version needs the GTK development libraries to be installed on your PC. The libraries that are easiest to install can be found at http://sourceforge.net/project/showfiles.php?group_id=98754, or search the web for gtk-dev-2.10.11-win32-1.exe.
The Linux version of the code is compiled by typing “make” in the GtkMsg directory. If you have Ubuntu Linux, all the libraries you need will already be installed. For other Linux distributions, you may need to install the "libgtk2.0-dev" GTK development libraries.
Since the program uses sockets, make sure the firewall on your PC is set to recognize gtkmsg.exe as a trusted program.
History
- 14 April, 2009 -- Original version posted.