views:

413

answers:

4

I'm trying to make a crossplatform C# application using C#, mono/GTK# on Linux and .NET/GTK# on Windows, however the startup sequence seems to need to be slightly different under the two platforms:

Under Linux:

public static void Main (string[] args)
{
    Gdk.Threads.Init ();
    // etc...

Under Windows:

public static void Main (string[] args)
{
    Glib.Thread.Init ();
    Gdk.Threads.Init ();
    // etc...

Both require it to be done that way: Windows complains about g_thread_init() not being called with the linux code, and linux complains about it already being called with the Windows code. Other than this, it all works wonderfully.

My first attempt at a "solution" looked like:

public static void Main (string[] args)
{
    try {
        Gdk.Threads.Init ();
    } catch (Exception) {
        GLib.Thread.Init ();
        Gdk.Threads.Init ();
    }
    // etc...

But even this messy solution doesn't work; the error is coming from GTK+, unmanaged code, so it can't be caught. Does anyone have any bright ideas about the cause of the problem, and how to fix it? Or, failing that, have bright ideas on how to detect which one should be called at runtime?

A: 

Umm, have you tried decorating your Main method with the [STAThread] attribute?

e.g.

#if !Mono //or whatever  
[STAThread]  
#endif  
public static void Main (string[] args)  
{  
    Gdk.Threads.Init ();  
    ...  
}

Failing that you could use conditional compilation like so ...

public static void Main (string[] args)  
{  
    Gdk.Threads.Init ();  
#if !Mono //or whatever  
    Gdk.Threads.Init ();  
#endif
    ...  
}
SDX2000
I'm under the impression that STAThread only affect COM, so this means nothing in this instance.
Matthew Scharley
+2  A: 

I'm not sure if this works on Mono, but you can use the Environment class to determine which OS the program is running on.

public static void Main (string[] args)
{
    PlatformID platform = Environment.OSVersion.Platform;
    if (platform == PlatformID.Win32NT ||
        platform == PlatformID.Win32S ||
        platform == PlatformID.Win32Windows)
        Glib.Thread.Init();
    else if (platform != PlatformID.Unix)
        throw new NotSupportedException("The program is not supported on this platform");

    Gdk.Threads.Init();
    // etc...

The PlatformID enum contains more than just Windows and Unix however, so you should check for other values.

Cameron MacFarland
+1  A: 

Actually, this might be a bug either in the C# bindings for GDK or your version of GDK. According to the documentation for gdk_threads_init(), g_thread_init() has to be called first, and the GTK# documentation says the same: GLib.Thread.Init() has to be called before Gdk.Threads.Init().

On my Linux machine (with GDK 2.14.4) a C program that calls gdk_threads_init() without having called g_thread_init() prints an error message and terminates with an error. Have you made sure that you have the same GDK version on both Linuc and Windows, and that the version on Linux (if it's different from the Windows version) also requires the call to g_thread_init(), or if it's something that has been changed between the two versions. Finally, check that this program terminates with an error:

#include <gdk/gdk.h>

int main(int argc, char **argv) {
    gdk_threads_init();

    return 0;
}

Compile it with gcc -o test \pkg-config --cflags --libs gdk-2.0\ test.c (assuming you've saved it as test.c. If that program terminates with an error, it's a bug in your GDK# library. If it doesn't, it's a bug in your GDK version.

arnsholt
It's not that it isn't being called, it IS being called, it's just being called as part of the wrapper in linux, and not being called as part of the wrapper in windows. I'm testing with the same versions of GTK# on both, but I'm not sure about the gtk+ versions.
Matthew Scharley
Confirmed by your little test, the difference is definitely in GTK#, but I'm using the same version between Linux and Windows.
Matthew Scharley
I've tried looking at the GTK# sources, but I can't find any obvious places that Gdk.Threads.Init() calls g\_thread\_init(), so I think your next stop should be the GTK# developers' mailing list or forum, but this looks kind of strange to me.
arnsholt
+6  A: 

Gtk+ on Win32 does not properly support threading. You need to do all your GUI calls from the same thread as you did called Gtk.Main().

It's not actually as bad as it sounds. There's a trick you can use to dispatch functions to the main thread. Just use GLib.Idle.Add() from any thread and the function will be called shortly in the same thread as the main loop is running in. Just remember to return false in the idle handler function, or it will go on running forever.

If you follow and use the above techniques you don't even need to call Gdk.Threads.Init() at all.

Johan Dahlin
Johan's right - you really don't need to call GDK functions from multiple threads, and it's a bad idea to do this anyways (Winforms and WPF don't even let you do this at all)
Paul Betts
Thankyou for going the extra mile and searching up the GTK# calls :)
Matthew Scharley
Woho! Thanks for the 300 points!
Johan Dahlin
JUst make sure to return false from your IdleHandler, otherwise it'll keep looping over and over, and you'll end up like me with your console window spammed with output :((
Matthew Scharley
thanks monoxide, I updated the text
Johan Dahlin