65.9K
CodeProject 正在变化。 阅读更多。
Home

将 Windows 消息转换为 GTK

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (2投票s)

2009年4月21日

CPOL

5分钟阅读

viewsIcon

27111

downloadIcon

241

将使用 Windows 消息的应用程序转换为 GTK

引言

我最近用 GTK 重写了我一个旧的 MFC 对话框应用程序,这样它就可以在 Windows 和 Linux 平台上运行了。将对话框代码从 Windows 移植到 GTK 相对比较容易(请参阅我的另一篇文章“使用 Glade 和 GTK 设计对话框应用程序”),但将 Windows Messaging 移植到 GTK 却很困难。

旧应用程序有一个线程,它监听 USB 端口上的数据,并使用 Windows Messaging 将数据发送到对话框。对话框接收 Windows 消息,解析 USB 数据,并显示结果。

为了本文的目的是,我将我的应用程序简化到了最基本的状态:一个每秒向对话框发送“hello world”消息的线程,以及一个在列表框中显示消息的对话框。

Windows 版本

对于不熟悉 Windows Messaging 的朋友们,快速看一下代码应该就能明白。线程代码通过分配内存来存储消息,将消息复制到分配的内存中,然后调用“SendMessage”函数将消息发送到对话框。SendMessage 的参数是消息 ID、分配内存的地址以及消息的大小。

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);
   }
}

对话框代码使用下面显示的 ON_MESSAGE 宏将处理函数映射到线程使用的消息 ID。

ON_MESSAGE(WM_SHOW_MESSAGE, ShowMessage)

消息处理程序将消息插入列表框,然后释放发送者使用的内存。

LRESULT CWinMsgDlg::ShowMessage(WPARAM wpBuffer, LPARAM luiBufferLength)
{
    m_MsgList.InsertString(EmptyLineIndex++, (LPCTSTR)wpBuffer );
    free((void*)wpBuffer);
    return(0);
}

GTK 版本

GTK 函数 g_io_channel_win32_new_messages 用于接收 Windows 消息。我尝试使用它,但未能使其工作。对我的应用程序来说,它也不是一个好的选择,因为它没有直接的 Linux 等效项。我选择的方案使用了套接字(sockets),它在 Linux 和 Windows 上都可用。

套接字最常用于将消息从 PC 发送到互联网,但它们也可以用于在同一 PC 的应用程序之间发送消息。套接字使用端口号来区分不同的应用程序。例如,要在浏览器中获取 Google 屏幕,消息会到达您的 PC,因为它们被发送到您 PC 的 IP 地址,并且显示在您浏览器的正确实例上(假设打开了多个实例),因为它们被发送到该实例的端口号。特殊的 IP 地址 127.0.0.1 用于在同一 PC 内发送消息;计算机中的 IP 堆栈知道在计算机内路由具有此 IP 地址的消息,而不是将其发送到互联网。

要开始从 Windows 应用程序进行转换,我们需要两个套接字,一个用于线程发送消息,另一个用于对话框接收消息。下面的代码显示了两个套接字是如何创建的。代码让计算机选择端口号,然后读取分配的接收端口并存储起来供发送者使用,然后将接收 IP 地址设置为 127.0.0.1,在代码中定义为 LOCAL_IP_ADDRESS。代码对 Windows 和 Linux 都是相同的,但 Windows 需要额外的 WSAStartup 函数。

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);
}

现在,我们可以使用发送套接字将消息发送到对话框。我们不需要为消息分配内存,整个文本字符串通过套接字发送到接收方。

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);
}

在 GTK 中将接收套接字映射到处理程序需要一些工作。下面的代码显示了应用程序的完整初始化代码,它创建了套接字,将处理函数附加到接收套接字,并启动发送线程。

处理程序附加到一个通道(channel),该通道是通过 Windows 特定的函数 g_io_channel_win32_new_socket 从套接字创建的。等效的 Linux 函数是 g_io_channel_unix_new。我的实际应用程序代码使用一个宏来切换这两个函数名。这是 Linux 和 Windows 之间唯一需要更改的代码行。

一旦创建了通道,我就会设置编码,以便通道传递二进制数据而不是文本。这对于此代码并不重要,但我的实际应用程序使用二进制数据。我将通道设置为非阻塞模式,并将处理函数 GtkMsg_ShowMessage 映射到该通道。

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);
}

剩下的就是让处理程序读取套接字中的数据并显示它。函数 g_io_channel_read_chars 从套接字读取消息,其余代码将文本显示在 textview 小部件中。

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);
}

这是显示消息的 GTK 对话框。希望这对您有所帮助。附带的 zip 文件包含该代码的 Windows 和 GTK 版本。WinMsg 包含 Windows 版本的源文件,GtkMsg 包含 GTK 版本的源文件。

GTK 版本需要在您的 PC 上安装 GTK 开发库。最容易安装的库可以在 http://sourceforge.net/project/showfiles.php?group_id=98754 找到,或者在网上搜索 gtk-dev-2.10.11-win32-1.exe

Linux 版本的代码可以通过在 GtkMsg 目录中键入“make”来编译。如果您使用的是 Ubuntu Linux,所有需要的库都将已安装。对于其他 Linux 发行版,您可能需要安装“libgtk2.0-dev”GTK 开发库。

由于程序使用套接字,请确保您的 PC 防火墙设置为允许 gtkmsg.exe 作为受信任的程序。

历史

  • 2009 年 4 月 14 日 -- 发布了原始版本。
© . All rights reserved.