Click here to Skip to main content
15,860,972 members
Articles / Programming Languages / C

Translating Windows Messaging to GTK

Rate me:
Please Sign up or sign in to vote.
4.33/5 (2 votes)
21 Apr 2009CPOL5 min read 26.5K   225   17   1
Translating applications using Windows messsaging to GTK

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.

C++
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.

C++
ON_MESSAGE(WM_SHOW_MESSAGE, ShowMessage)

The message handler inserts the message in the listbox, and then frees the memory used by the sender.

C++
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.

C++
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

    /* Create Send Socket */
    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));

    /* Create Receive Socket */
    *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));
    
    /* Modify the receive address, getsockname gets port number */
    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.

C++
DWORD WINAPI MsgThread( LPVOID lpParam )
{
    char HelloMsg[] = "Hello, World\n";

    while(1)
    {
        GtkSock_Send(HelloMsg, sizeof(HelloMsg));
        Sleep(1000);
    }
}
C++
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.

C++
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.

C++
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralGreat! and yet one question Pin
Win32nipuh28-Apr-09 4:47
professionalWin32nipuh28-Apr-09 4:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.