[gtkmm] Dispatcher



First of all, really sorry for delay. Unfortunatelly, we couldnt catch gtkmm release.

Here we found the way the Dispatcher class could be used on Win32 systems as well as on UNIX based machines. I guess, there is some people who gonna be happy to have this functionality in their projects.

There is only attached file (no patch), just to show how we could do this to some person (Cedric, I guess), who was taking care about Win32 compatibility all the time. Use this file, fix Makefile for glibmm (no more changes needed), then use dispatcher example files for testing.

Hope, it gonna work as well as for us.

Regards,
-andrew

// -*- c++ -*-
/* $Id: dispatcher.cc,v 1.8 2002/10/11 18:05:43 murrayc Exp $ */

/* Copyright 2002 The gtkmm Development Team
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <iostream>

#include <glibmm/dispatcher.h>
#include <glibmm/exceptionhandler.h>
#include <glibmm/fileutils.h>
#include <glibmm/main.h>
#include <glibmm/thread.h>
#include <sigc++/class_slot.h>

#include <cerrno>
#include <glib.h>
#include <fcntl.h>

#ifndef G_OS_WIN32
#include <unistd.h>
#else
#include <list>
/* This is what glib/gspawn-win32.c includes, I've no idea whether it works :) */
#include <windows.h>
#include <io.h>
#include <direct.h>
#endif /* G_OS_WIN32 */

namespace
{

#ifdef GTKMM_WIN32

template<class TQ> class Locked_Queue
{
 private:
    Glib::Mutex     mutex_;
    std::list<TQ*>  lqueue_;
    static TQ* delete_ptr (TQ* p) { delete p; return 0; }

 public:
    Locked_Queue<TQ>() : mutex_(), lqueue_() {}
    ~Locked_Queue<TQ>()
    {
     transform ( lqueue_.begin(),
             lqueue_.end(),
             lqueue_.begin(),
             delete_ptr );
    }

    TQ* get (void)
    {
        TQ* ret = 0;

        Glib::Mutex::Lock lock ( mutex_ );
        if ( lqueue_.size() > 0)
        {
            ret = lqueue_.front();
            lqueue_.pop_front();
        }
        return ret;
    }

    void put ( TQ* e )
    {
        Glib::Mutex::Lock lock ( mutex_ );
        lqueue_.push_back ( e );
    }
};

#define G_ERR(err_no) g_win32_error_message (err_no)
#define FD_TYPE HANDLE
#define INVALID_FD (0)
#else
#define G_ERR(err_no) g_strerror (err_no)
#define FD_TYPE int
#define INVALID_FD (-1)
#endif

struct DispatchNotifyData
{
  unsigned long           tag;
  Glib::Dispatcher*       dispatcher;
  Glib::DispatchNotifier* notifier;
};

void warn_failed_pipe_io(const char* what, int err_no)
{
  g_warning("Error in inter-thread communication: %s() failed: %s", what, G_ERR( err_no ) );
}

/* Try to set the close-on-exec flag of the file descriptor,
 * so that it won't be leaked if a new process is spawned.
 */
#ifndef GTKMM_WIN32
void fd_set_close_on_exec(int fd)
{
  const int flags = fcntl(fd, F_GETFD, 0);
  g_return_if_fail(flags >= 0);

  fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
}
#endif
/* One word: paranoia.
 */
void fd_close_and_invalidate( FD_TYPE& fd )
{
  if( fd != INVALID_FD )
  {
#ifdef GTKMM_WIN32
    if ( !CloseHandle ( fd ) )
      warn_failed_pipe_io( "close", GetLastError() );
#else
    int result;

    do { result = close( fd ); }
    while(result < 0 && errno == EINTR);

    if(result < 0)
      warn_failed_pipe_io("close", errno);
#endif

    fd = INVALID_FD;
  }
}

} // anonymous namespace


namespace Glib
{

class DispatchNotifier
{
public:
  ~DispatchNotifier();

  static DispatchNotifier* reference_instance(const Glib::RefPtr<MainContext>& context);
  static void unreference_instance(DispatchNotifier* notifier);

  void send_notification(Dispatcher* dispatcher);
  bool pipe_io_handler(Glib::IOCondition condition);

protected:
  // Only used by reference_instance().  Should be private, but that triggers
  // a silly gcc warning even though DispatchNotifier has static methods.
  explicit DispatchNotifier(const Glib::RefPtr<MainContext>& context);

private:
  static Glib::StaticPrivate<DispatchNotifier> thread_specific_instance_;

  Glib::RefPtr<MainContext> context_;
  int                       ref_count_;
  FD_TYPE                   fd_receiver_;
#ifdef GTKMM_WIN32
  Locked_Queue<DispatchNotifyData>  notify_queue_;
#else
  FD_TYPE                   fd_sender_;
#endif
  SigC::Connection          conn_io_handler_;

  void create_pipe();

  // noncopyable
  DispatchNotifier(const DispatchNotifier&);
  DispatchNotifier& operator=(const DispatchNotifier&);
};


/**** Glib::DispatchNotifier ***********************************************/

Glib::StaticPrivate<DispatchNotifier>
DispatchNotifier::thread_specific_instance_ = GLIBMM_STATIC_PRIVATE_INIT;

DispatchNotifier::DispatchNotifier(const Glib::RefPtr<MainContext>& context)
:
  context_      (context),
  ref_count_    (0),
  fd_receiver_  (INVALID_FD),
#ifdef GTKMM_WIN32
  notify_queue_ ()
#else
  fd_sender_    (INVALID_FD)
#endif
{
  create_pipe();

  conn_io_handler_ = context_->signal_io().connect(
      SigC::slot_class(*this, &DispatchNotifier::pipe_io_handler),
      (int)fd_receiver_, Glib::IO_IN);
}

DispatchNotifier::~DispatchNotifier()
{
  // Disconnect manually because we're using SigC::slot_class().
  conn_io_handler_.disconnect();

#ifndef GTKMM_WIN32
  fd_close_and_invalidate(fd_sender_);
#endif
  fd_close_and_invalidate(fd_receiver_);
}

void DispatchNotifier::create_pipe()
{
#ifdef GTKMM_WIN32
    if ( !(fd_receiver_ = CreateEvent (NULL, FALSE, FALSE, NULL)) )
	{
        gint err_no = GetLastError ();
        GError *const error = g_error_new(
            G_FILE_ERROR, g_file_error_from_errno( err_no ),
            "Failed to create pipe for inter-thread communication: %s",
            g_win32_error_message( err_no ) );

        throw Glib::FileError( error );
    }
#else
  int filedes[2] = { -1, -1 };

  if(pipe(filedes) < 0)
  {
    GError *const error = g_error_new(
        G_FILE_ERROR, g_file_error_from_errno(errno),
        "Failed to create pipe for inter-thread communication: %s", g_strerror(errno));

    throw Glib::FileError(error);
  }

  fd_receiver_ = filedes[0];
  fd_sender_   = filedes[1];

  fd_set_close_on_exec(fd_receiver_);
  fd_set_close_on_exec(fd_sender_);
#endif
}

// static
DispatchNotifier* DispatchNotifier::reference_instance(const Glib::RefPtr<MainContext>& context)
{
  DispatchNotifier* instance = thread_specific_instance_.get();

  if(!instance)
  {
    instance = new DispatchNotifier(context);
    thread_specific_instance_.set(instance);
  }
  else
  {
    // Prevent massive mess-up.
    g_return_val_if_fail(instance->context_ == context, 0);
  }

  ++instance->ref_count_; // initially 0

  return instance;
}

// static
void DispatchNotifier::unreference_instance(DispatchNotifier* notifier)
{
  DispatchNotifier *const instance = thread_specific_instance_.get();

  // Yes, the notifier argument is only used to check for sanity.
  g_return_if_fail(instance == notifier);

  if(--instance->ref_count_ <= 0)
  {
    g_return_if_fail(instance->ref_count_ == 0); // could be < 0 if messed up

    // This will cause deletion of the notifier object.
    thread_specific_instance_.set(0);
  }
}

void DispatchNotifier::send_notification(Dispatcher* dispatcher)
{
#ifdef GTKMM_WIN32
  DispatchNotifyData* data = new DispatchNotifyData();

  data->tag        = 0xdeadbeef;
  data->dispatcher = dispatcher;
  data->notifier   = this;

  notify_queue_.put ( data );

  if ( !SetEvent ( fd_receiver_ ) )
  {
      warn_failed_pipe_io( "write", GetLastError() );
      return;
  }
#else
  DispatchNotifyData data =
  {
    0xdeadbeef,
    dispatcher,
    this
  };

  gsize n_written = 0;
  // Look at this code -- we're prepared for the worst.  Hah!
  do
  {
    void * const buffer = reinterpret_cast<guint8*>(&data) + n_written;
    const gssize result = write ( fd_sender_, buffer,
            sizeof(data) - n_written);
    if(result < 0)
    {
      if(errno == EINTR)
        continue;

      warn_failed_pipe_io("write", errno);
      return;
    }

    n_written += result;
  }
  while(n_written < sizeof(data));

#endif
}

bool DispatchNotifier::pipe_io_handler(Glib::IOCondition)
{
#ifdef GTKMM_WIN32
  DispatchNotifyData* data;

  while ( (data = notify_queue_.get()) )
  {
    DispatchNotifyData local_data = *data;
    delete data;
    g_return_val_if_fail(local_data.tag == 0xdeadbeef, true);
    g_return_val_if_fail(local_data.notifier == this, true);

    // Actually, we wouldn't need the try/catch block because the Glib::Source
    // C callback already does it for us.  However, we do it anyway because the
    // default return value is 'false', which is not what we want.
    try
    {
      local_data.dispatcher->signal_();
    }
    catch(...)
    {
      Glib::exception_handlers_invoke();
    }
  }

#else

  DispatchNotifyData data = { 0, 0, 0, };
  gsize n_read = 0;

  // Look at this code -- we're prepared for the worst.  Hah!
  do
  {
    void * const buffer = reinterpret_cast<guint8*>(&data) + n_read;
    const gssize result = read(fd_receiver_, buffer, sizeof(data) - n_read);

    if(result < 0)
    {
      if(errno == EINTR)
        continue;

      warn_failed_pipe_io("read", errno);
      return true;
    }

    n_read += result;
  }
  while(n_read < sizeof(data));

  g_return_val_if_fail(data.tag == 0xdeadbeef, true);
  g_return_val_if_fail(data.notifier == this, true);

  // Actually, we wouldn't need the try/catch block because the Glib::Source
  // C callback already does it for us.  However, we do it anyway because the
  // default return value is 'false', which is not what we want.
  try
  {
    data.dispatcher->signal_();
  }
  catch(...)
  {
    Glib::exception_handlers_invoke();
  }
#endif
  return true;
}


/**** Glib::Dispatcher *****************************************************/

Dispatcher::Dispatcher()
:
  signal_   (),
  notifier_ (DispatchNotifier::reference_instance(MainContext::get_default()))
{}

Dispatcher::Dispatcher(const Glib::RefPtr<MainContext>& context)
:
  signal_   (),
  notifier_ (DispatchNotifier::reference_instance(context))
{}

Dispatcher::~Dispatcher()
{
  DispatchNotifier::unreference_instance(notifier_);
}

void Dispatcher::emit()
{
  notifier_->send_notification(this);
}

void Dispatcher::operator()()
{
  emit();
}

SigC::Connection Dispatcher::connect(const SigC::Slot0<void>& slot)
{
  return signal_.connect(slot);
}

} // namespace Glib



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]