Sonntag, 29. November 2009

BackgroundWorker in mixed-mode applications

If you have a mixed-mode application, it is very tempting to use the BackgroundWorker class even for tasks initiated from the native side. After all, you often have a good use-case for tasks running in the background, not even considering the performance gain from multi-core processors, and the BackgroundWorker provides a framework which is not easily got using only native code.

One of the big advantages of that class is that it automatically notifies you of completion (and progress) on the GUI thread, so you can show some result to the user. How does it do that? Internally, it resuses the message loop which every application in Windows has (it sends a special message). On a higher abstraction level, the mechanism provided by the .NET framework for such threading callbacks is called a SynchronizationContext. On CodeProject someone has a good description on how this works.

Now, the interesting thing is that there is a thread-local variable (SynchronizationContext::Current) which must be set before the BackgroundWorker is used. This variable is initially null, and it gets set by the first Control instance which is created.

So, no problem for the normal C# applications, but in a mixed-mode application, your main window and every other window may be native, so no .NET control is created, so the SynchronizationContext is still null ... Unfortunately, the BackgroundWorker doesn't throw a nice exception if that is the case. It just makes the notifications on the worker thread, which can lead to subtle errors and may only be detected if you try to modify some MFC GUI.

An easy workaround is to create a dummy control before the background worker is used. That control needs not be shown on the screen and it can be disposed immediately, just the constructor must have been called. In C++/CLI, the workaround is one line of code.

But be careful: I first created the control only if the synchronization context was null. But it turns out that some other .NET framework methods (e.g. Bitmap::GetHbitmap) set another synchronization context if it is still null when they are called. This other context seems to be a dummy which doesn't do anything, so the notifications will once again come on the worker thread.

So, to be sure, either create that dummy control always, or create it very early, while you're sure that no one else has tempered yet with the synchronization context. Of course, all that is not a problem if you have some .NET control or form shown anyway.

Labels:

Sonntag, 22. November 2009

SoundMixer-Fernsteuerung

Wer Rollenspiele macht, benutzt vielleicht auch den RPG SoundMixer. Wenn nicht, kann ich das nur empfehlen, ist für Hintergrundmusik und den einen oder anderen Effekt echt spitze.

Der SoundMixer hat aber auch ein paar Nachteile, die mich mehr und mehr gestört haben:

  • Man kann ihn nur per Tastatur bedienen. Wenn man z.B. einen Touchscreen verwenden will, ist man aufgeschmissen.
  • Man muss sich alle Kommandos merken, oder sich einen Zettel schreiben / drucken, der unter den ganzen anderen Zetteln am Spieltisch dann verloren geht.
  • Man kann ihn schlecht auf einem Media-Center-PC im Wohnzimmer laufen lassen.
Der letzte Punkt muss vielleicht noch etwas erläutert werden. Die Musiksammlung hat man ja gerne auf dem Media-Center-PC, und der ist auch an die guten Boxen oder die Stereoanlage angeschlossen. Nur, wie bedient man dann den Soundmixer? Oder wenn man den Soundmixer doch auf einem Notebook laufen lässt, wie kommt der Sound zu den Boxen? Die Alternativen sind alle nicht toll:
  1. Man verwendet eine Funktastatur. Die muss man dann als extra Gerät am Spieltisch haben, und sie ist normalerweise auch nicht wahnsinnig handlich.
  2. Man verwendet eine Fernbedienung. Die Programmierung ist allerdings nicht einfach, und die Tasten reichen kaum aus für die Fernsteuerung des SoundMixers; man kann sich noch viel schwieriger merken, ob jetzt '1' für Dungeon- oder für Kampfmusik steht.
  3. Man holt den Sound per Funk oder per Kabel aus dem Notebook und speist ihn in den PC ein. Für Funk gibt's aber nur wenig gute Lösungen und ein Kabel stört.
  4. Man steuert den PC über einen remote desktop wie vnc fern. Das ist aber etwas langsam und man muss immer noch die Tastatur verwenden.
Von daher hatte ich die Idee, eine Fernsteuerungssoftware für den SoundMixer zu schreiben. Über's Netzwerk werden die Kommandos an den PC übertragen und dort als Tastendrücke an den SoundMixer gegeben. Das Prinzip ist auch ganz gut ... nur der SoundMixer so programmiert, dass es schwierig wird. Einfach eine Windows-Message zu senden, funktioniert nicht, weil er DirectX verwendet. Auch low-level-Keyboardfunktionen der Windows-API helfen nicht. Im Forum des SoundMixer stand, dass er sich über Scripting (wie etwa AutoHotkey) steuern ließe, hat aber bei mir auch in allen Varianten nicht funktioniert.

In Verzweiflung verfiel ich auf DirectX-Hooks aus dem Internet (etwas angepasst). Die helfen auch nicht, weil der SoundMixer DirectX über COM ansteuert (wer macht denn so was?). Aber das brachte mich letztendlich zur Lösung: wenn ich die entsprechende COM-Komponente nachbaue und die Registry auf den Nachbau umbiege, kann ich dem SoundMixer eine andere Implementierung unterschieben und kriege endlich die Tastendrücke in ihn hinein. Hurra!

Die GUI für den Client war dann rasch geschrieben und hat sich im Test an einem Spieleabend schon bewährt. Ich werde das Ganze demnächst wahrscheinlich noch etwas aufpolieren und der Allgemeinheit zur Verfügung stellen.

Labels: ,