GTK3: Multiple event loops within one application

Following is the source code "loops.c" disussed in the stackoverflow question GTK3: Multiple event loops within one application.

Compile the GTK3 version on Linux/MSYS2 by means of the command

gcc -D_GTK `pkg-config --cflags gtk+-3.0` loops.c -o loops-gtk `pkg-config --libs gtk+-3.0`

Compile the Windows version on MSYS2 by means of the command

gcc -mwindows -municode loops.c -o loops-win

The Windows version demonstrates the intended behavior (even if the button is dead, simply close a window to make it disappear.)

#if defined (_GTK) // [
#include <gtk/gtk.h>
#include <stdlib.h>
#else // ] [
#if ! defined (UNICODE) // [
#define UNICODE
#endif // ]
#include <windows.h>
#endif // ]
#include <stdio.h>

///////////////////////////////////////////////////////////////////////////////
#if ! defined (UNUSED) // [
#ifdef __GNUC__ // [
#define UNUSED __attribute__((__unused__))
#else // ] [
#define UNUSED
#endif // ]
#endif // ]

#if defined (_MSC_VER) && ! defined (__func__) // [
#define __func__ __FUNCTION__
#endif // ]

#define DMESSAGE(format) { \
  fprintf(stderr,"Error " format); \
  fprintf(stderr," (%s:%d:%s)\n",__FILE__,__LINE__,__func__); \
  fflush(stderr); \
}
#define DVMESSAGE(format,...) { \
  fprintf(stderr,"Error " format,__VA_ARGS__); \
  fprintf(stderr," (%s:%d:%s)\n",__FILE__,__LINE__,__func__); \
  fflush(stderr); \
}
#define DMARKLN() { \
  fprintf(stderr,"%s:%d:%s\n",__FILE__,__LINE__,__func__); \
  fflush(stderr); \
}
#define DWRITELN(format) { \
  fprintf(stderr,format); \
  fprintf(stderr," (%s:%d:%s)\n",__FILE__,__LINE__,__func__); \
  fflush(stderr); \
}
#define DVWRITELN(format,...) { \
  fprintf(stderr,format,__VA_ARGS__); \
  fprintf(stderr," (%s:%d:%s)\n",__FILE__,__LINE__,__func__); \
  fflush(stderr); \
}
#define DVWRITELNW(format,...) { \
  fwprintf(stderr,L ## format,__VA_ARGS__); \
  fprintf(stderr," (%s:%d:%s)\n",__FILE__,__LINE__,__func__); \
  fflush(stderr); \
}

///////////////////////////////////////////////////////////////////////////////
#if ! defined (_GTK) // [
static const wchar_t CLASS_NAMEW[]=L"Loops";
#endif // ]
typedef struct _Loop Loop;
typedef struct _Loops Loops;

///////////////////////////////////////////////////////////////////////////////
struct _Loop {
  Loops *loops;
  Loop *prev;
  Loop *next;
#if defined (_GTK) // [
  int id;
  GThread *thread;
#else // ] [
  HANDLE hThread;
#endif // ]
};

Loop *loop_new(Loops *loops);
void loop_destroy(Loop *loop);

#if defined (_GTK) // [
gpointer loop_thread_func(gpointer data);
#else // ] [
DWORD WINAPI loop_thread_proc(LPVOID lpParameter);
#endif // ]

///////////////////////////////////////////////////////////////////////////////
struct _Loops {
  Loop *head;
  Loop *tail;

  struct {
    Loop *loop;
  } destroy;

#if defined (_GTK) // [
  int id;
  GCond cond;
  GMutex mutex;
  GThread *thread;
#else // ] [
  HANDLE hEvent;
  HANDLE hMutex;
  HANDLE hTimer;
  HANDLE hThread;
#endif // ]
};

Loops *loops_new(int ms);
void loops_destroy(Loops *loops);

void loops_lock(Loops *loops);
void loops_unlock(Loops *loops);
void loops_signal(Loops *loops);
void loops_append(Loops *loops, Loop *loop);
void loops_remove(Loops *loops, Loop *loop);

#if defined (_GTK) // [
gboolean loops_source_func(gpointer user_data);
gpointer loops_thread_func(gpointer data);
#else // ] [
DWORD WINAPI loops_thread_proc(LPVOID lpParameter);
#endif // ]

///////////////////////////////////////////////////////////////////////////////
Loop *loop_new(Loops *loops)
{
  Loop *loop=malloc(sizeof *loop);
#if ! defined (_GTK) // [
  DWORD dwLastError;
#endif // ]

  if (!loop) {
    DMESSAGE("allocating loop");
    exit(1);
  }

  loop->loops=NULL;
  loop->prev=NULL;
  loop->next=NULL;
#if defined (_GTK) // [
  loop->id=++loops->id;
  loop->thread=NULL;
#else // ] [
  loop->hThread=NULL;
#endif // ]

  if (loops)
    loops_append(loops,loop);

#if defined (_GTK) // [
  loop->thread=g_thread_new(NULL,loop_thread_func,loop);

  if (!loop->thread) {
    DMESSAGE("creating thread");
    exit(1);
  }
#else // ] [
  loop->hThread=CreateThread(
    NULL,             // LPSECURITY_ATTRIBUTES   lpThreadAttributes,
    0,                // SIZE_T                  dwStackSize,
    loop_thread_proc, // LPTHREAD_START_ROUTINE  lpStartAddress,
    loop,             // __drv_aliasesMem LPVOID lpParameter,
    0ul,              // DWORD                   dwCreationFlags,
    NULL              // LPDWORD                 lpThreadId
  );

  if (!loop->hThread) {
    dwLastError=GetLastError();

    switch (dwLastError) {
    case ERROR_NOT_ENOUGH_MEMORY:
      DMESSAGE("creating thread: ERROR_NOT_ENOUGH_MEMORY");
      break;
    default:
      DVMESSAGE("creating thread: %lu",dwLastError);
      break;
    }

    exit(1);
  }
#endif // ]

  return loop;
}

void loop_destroy(Loop *loop)
{
  if (loop->loops)
    loops_remove(loop->loops,loop);

#if defined (_GTK) // [
  g_thread_join(loop->thread);
#else // ] [
  WaitForSingleObject(loop->hThread,INFINITE);
  CloseHandle(loop->hThread);
#endif // ]

  free(loop);
}

#if defined (_GTK) // [
void button_clicked(gpointer window, gpointer data)
{
  GtkApplication *app=gtk_window_get_application(GTK_WINDOW(window));

  gtk_application_remove_window(app,GTK_WINDOW(window));
}

void button_new_with_label(GtkWidget *window, const gchar *label)
{
  GtkWidget *button;

  button=gtk_button_new_with_label(label);
  g_signal_connect_swapped(G_OBJECT(button),"clicked",
      G_CALLBACK(button_clicked),window);
  gtk_widget_show(button);
  gtk_container_add(GTK_CONTAINER(window),button);
}

void loop_activate(GApplication *app, gpointer user_data)
{
  GtkWidget *window;

  window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_application_add_window(GTK_APPLICATION(app),GTK_WINDOW(window));
  gtk_window_set_title(GTK_WINDOW(window),"Loop");
  button_new_with_label(window,"OK");
  gtk_widget_show(window);
}
#endif // ]

#if defined (_GTK) // [
gpointer loop_thread_func(gpointer data)
#else // ] [
DWORD WINAPI loop_thread_proc(LPVOID lpParameter)
#endif // ]
{
#if defined (_GTK) // [
  Loop *loop=data;
  gchar id[256];
  GtkApplication *app;

  snprintf(id,(sizeof id)-1,"net.sourceforge.pbelkner%d",loop->id);
  app=gtk_application_new(id,G_APPLICATION_FLAGS_NONE);
  g_signal_connect(app,"activate",G_CALLBACK(loop_activate),NULL);
  g_application_run(G_APPLICATION(app),0,NULL);
  g_object_unref(app);
#else // ] [
  Loop *loop=lpParameter;
  MSG msg={ 0 };
  HWND hWnd;

  hWnd=CreateWindowExW(
    0,                      // Optional window styles.
    CLASS_NAMEW,            // Window class
    L"Loops",               // Window text
    WS_OVERLAPPEDWINDOW,    // Window style
    CW_USEDEFAULT,CW_USEDEFAULT,100,140,
                            // Size and position
    NULL,                   // Parent window    
    NULL,                   // Menu
    NULL,                   // Instance handle
    NULL                    // Additional application data
  );

  if (!hWnd) {
    DMESSAGE("creating window");
    exit(1);
  }

  ShowWindow(hWnd,TRUE);

  while (GetMessageW(&msg,NULL,0,0)) {
    TranslateMessage(&msg);
    DispatchMessageW(&msg);
  }
#endif // ]

  loop->loops->destroy.loop=loop;
  loops_signal(loop->loops);

#if defined (_GTK) // [
  return NULL;
#else // ] [
  return 0ul;
#endif // ]
}

///////////////////////////////////////////////////////////////////////////////
Loops *loops_new(int ms)
{
  Loops *loops=malloc(sizeof *loops);
#if ! defined (_GTK) // [
  LARGE_INTEGER DueTime;
  BOOL b;
#endif // ]

  if (!loops) {
    DMESSAGE("allocating loops");
    exit(1);
  }

  loops->head=NULL;
  loops->tail=NULL;
  loops->destroy.loop=NULL;
#if defined (_GTK) // [
  loops->id=0;
#endif // ]
  loop_new(loops);

#if defined (_GTK) // [
  g_cond_init(&loops->cond);
  g_mutex_init(&loops->mutex);
  loops->thread=g_thread_new(NULL,loops_thread_func,loops);
  g_timeout_add(ms,loops_source_func,loops);

  if (!loops->thread) {
    DMESSAGE("creating thread");
    exit(1);
  }
#else // ] [
  loops->hEvent=CreateEventW(
    NULL,               // LPSECURITY_ATTRIBUTES lpEventAttributes,
    FALSE,              // BOOL                  bManualReset,
    FALSE,              // BOOL                  bInitialState,
    NULL                // LPCWSTR               lpName
  );

  if (!loops->hEvent) {
    DMESSAGE("creating event");
    exit(1);
  }

  loops->hMutex=CreateMutexW(
    NULL,               // LPSECURITY_ATTRIBUTES lpMutexAttributes,
    FALSE,              // BOOL                  bInitialOwner,
    NULL                // LPCWSTR               lpName
  );

  if (!loops->hMutex) {
    DMESSAGE("creating mutex");
    exit(1);
  }

  loops->hTimer=CreateWaitableTimerW(
    NULL,               // LPSECURITY_ATTRIBUTES lpTimerAttributes,
    FALSE,              // BOOL                  bManualReset,
    NULL                // LPCWSTR               lpTimerName
  );

  if (!loops->hTimer) {
    DMESSAGE("creating waitable timer");
    exit(1);
  }

  loops->hThread=CreateThread(
    NULL,               // LPSECURITY_ATTRIBUTES   lpThreadAttributes,
    0,                  // SIZE_T                  dwStackSize,
    loops_thread_proc,  // LPTHREAD_START_ROUTINE  lpStartAddress,
    loops,              // __drv_aliasesMem LPVOID lpParameter,
    0ul,                // DWORD                   dwCreationFlags,
    NULL                // LPDWORD                 lpThreadId
  );

  if (!loops->hThread) {
    DMESSAGE("creating thread");
    exit(1);
  }

  DueTime.QuadPart=-10000ll*ms;

  b=SetWaitableTimer(
    loops->hTimer,      // HANDLE              hTimer,
    &DueTime,           // const LARGE_INTEGER *lpDueTime,
    ms,                 // LONG                lPeriod,
    NULL,               // PTIMERAPCROUTINE    pfnCompletionRoutine,
    NULL,               // LPVOID              lpArgToCompletionRoutine,
    FALSE               // BOOL                fResume
  );

  if (!b) {
    DMESSAGE("setting timer");
    exit(1);
  }
#endif // ]

  return loops;
}

void loops_destroy(Loops *loops)
{
#if defined (_GTK) // [
  g_thread_join(loops->thread);
  g_mutex_clear(&loops->mutex);
  g_cond_clear(&loops->cond);
#else // ] [
  WaitForSingleObject(loops->hThread,INFINITE);
  CloseHandle(loops->hThread);
  CloseHandle(loops->hTimer);
  CloseHandle(loops->hMutex);
  CloseHandle(loops->hEvent);
#endif // ]
  free(loops);
}

void loops_lock(Loops *loops)
{
#if defined (_GTK) // [
  g_mutex_lock(&loops->mutex);
#else // ] [
  if (WAIT_OBJECT_0!=WaitForSingleObject(loops->hMutex,INFINITE)) {
    DMESSAGE("locking mutex");
    exit(1);
  }
#endif // ]
}

void loops_unlock(Loops *loops)
{
#if defined (_GTK) // [
  g_mutex_unlock(&loops->mutex);
#else // ] [
  ReleaseMutex(loops->hMutex);
#endif // ]
}

void loops_signal(Loops *loops)
{
#if defined (_GTK) // [
  g_cond_signal(&loops->cond);
#else // ] [
  SetEvent(loops->hEvent);
#endif // ]
}

void loops_append(Loops *loops, Loop *loop)
{
  if (loop->prev||loop->next||loop->loops) {
    DMESSAGE("already linked");
    exit(1);
  }

  if (loops->tail) {
    loops->tail->next=loop;
    loop->prev=loops->tail;
  }
  else
    loops->head=loop;

  loops->tail=loop;
  loop->loops=loops;
}

void loops_remove(Loops *loops, Loop *loop)
{
  Loop *next=loop->next;
  Loop *prev=loop->prev;

  if (loop->loops!=loops) {
    DVMESSAGE("link: %p %p",loop->loops,loops);
    exit(1);
  }

  loop->loops=NULL;

  if (next) {
    if (next->prev)
      next->prev=prev;
    else
      loops->head=prev;
  }
  else if (prev) {
    if (prev->next)
      prev->next=next;
    else
      loops->tail=next;
  }
  else {
    loops->head=NULL;
    loops->tail=NULL;
  }
}

#if defined (_GTK) // [
gboolean loops_source_func(gpointer data)
{
  Loops *loops=data;

  if (loops->head) {
    loop_new(data);
    return TRUE;
  }
  else
    return FALSE;
}

gpointer loops_thread_func(gpointer data)
#else // ] [
DWORD WINAPI loops_thread_proc(LPVOID lpParameter)
#endif // ]
{
#if defined (_GTK) // [
  Loops *loops=data;
#else // ] [
  Loops *loops=lpParameter;
  HANDLE aHandles[] UNUSED={ loops->hEvent, loops->hTimer };
  DWORD dwWait;
#endif // ]

  loops_lock(loops); // [

  while (loops->head) {
#if ! defined (_GTK) // [
    loops_unlock(loops); // [

    dwWait=WaitForMultipleObjects(
      (sizeof aHandles)/(sizeof aHandles[0]),
                    // DWORD        nCount,
      aHandles,     // const HANDLE *lpHandles,
      FALSE,        // BOOL         bWaitAll,
      INFINITE      // DWORD        dwMilliseconds
    );

    loops_lock(loops); // ]

    switch (dwWait) {
    case WAIT_OBJECT_0+0:
      // when signalled from hEvent we've got a loop to destroy:
      loop_destroy(loops->destroy.loop);
      loops->destroy.loop=NULL;
      break;
    case WAIT_OBJECT_0+1:
      // when signalled from hTimer we've got to create a fresh loop:
      loop_new(loops);
      break;
    default:
      DMESSAGE("wait failed");
      exit(1);
    }
#else // ] [
    g_cond_wait(&loops->cond,&loops->mutex);
    // when signalled we've got a loop to destroy:
    loop_destroy(loops->destroy.loop);
    loops->destroy.loop=NULL;
#endif // ]
  }

  loops_unlock(loops); // ]

#if defined (_GTK) // [
  return NULL;
#else // ] [
  return 0ul;
#endif // ]
}

///////////////////////////////////////////////////////////////////////////////
#if ! defined (_GTK) // [
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam,
    LPARAM lParam)
{
  switch (uMsg) {
  case WM_CREATE:
    CreateWindowW(
      L"BUTTON",  // Predefined class; Unicode assumed 
      L"OK",      // Button text 
      WS_TABSTOP|WS_VISIBLE|WS_CHILD|BS_DEFPUSHBUTTON,
                  // Styles 
      10,         // x position 
      10,         // y position 
      80,         // Button width
      80,         // Button height
      hWnd,       // Parent window
      NULL,       // No menu.
      NULL,
      NULL
    );

    break;
  case WM_DESTROY:
    PostQuitMessage(0);
    break;
  default:
    break;
  }

  return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}
#endif // ]

#if defined (_GTK) || ! defined (UNICODE) // [
int main(int argc, char **argv)
#else // ] [
int wmain(int argc, wchar_t **argv)
#endif // ]
{
#if ! defined (_GTK) // [
  static WNDCLASSW wc={
    .lpfnWndProc=WindowProc,
    .hInstance=NULL,
    .lpszClassName=CLASS_NAMEW,
  };
#endif // ]

#if defined (_GTK) || ! defined (UNICODE) // [
  int ms=1<argc?atoi(argv[2]):5000;
#else // ] [
  int ms=1<argc?_wtoi(argv[2]):5000;
#endif // ]
  Loops *loops;

#if ! defined (_GTK) // [
  RegisterClassW(&wc);
#endif // ]
  loops=loops_new(ms);
  loops_destroy(loops);

  return 0;
}
Sourcecode "loops.c"