SucheBlog abonnierenVerwaltung des BlogsKategorienKontaktMarkus Brückner |
Mittwoch, 27. April 2005Template-Spielereien die ZweiteNachdem ich mich letztens über die netten Spielereien, die man mit Templates so veranstalten kann, ausgelassen habe, hat mich Q auf die libdebug hingewiesen. Nachdem ich mir das ein klein wenig angeschaut habe, ist mir aufgefallen, daß man die Spielereien eigentlich mal so aufbohren könnte, daß sie als Logging-Framework nutzbar sind. Beibehalten wollte ich die Stream-artige Verwendung und die Elimination von Code in der Releaseversion. Zusätzlich sollten Loglevel hinzukommen. Nach einigem Hin und Her habe ich mich für folgendes grobes Interface entschieden:
Logger logger(std::cout, TRACE); // erstelle einen Logger, der nach std::cout loggt und
// alle Message mit Level TRACE und höher schreibt
logger << TRACE << "Nachricht 1" << endl; // gibt Nachricht 1 mit Level TRACE aus
logger << INFO << "Nachricht 2" << endl; // gib Nachricht 2 mit Level INFO aus
Im obigen Beispiel sollte in der Releaseversion aller Code, welcher Nachrichten mit Level TRACE oder DEBUG ausgibt rausfliegen. Da die einzelnen Operatoren allerdings in einzelne Funktionsaufrufe gewandelt werden, muss man an der Stelle einen Status während des Kompilierens mitschleppen, damit der Compiler weiß, welcher Level zuletzt gesetzt wurde. Daß ist normalerweise so einfach nicht möglich, da man dem Compiler keine Variablen und Logik zuweisen kann, die er zur Compilezeit ausführen soll. Dank der Operatorüberladung ist etwas ähnliches allerdings trotzdem möglich. Grundsätzlich genügt es für die Aufgabe, die Codestücke in zwei Klassen zu teilen: solche, die DEBUG oder TRACE enthalten und solche, die das nicht tun. Erstere müssen in der Releaseversion wegoptimiert werden können, letztere sollen stehenbleiben. Um nun den Compiler entscheiden zu lassen, mit welcher Art von Code er es zu tun hat, bieten sich Typen an. Analysieren wir folgende Zeile: logger << TRACE << "Blubber"; Diese läßt sich gedanklich umformen zu: operator<<(operator<<(logger, TRACE), "Blubber"); Wir sehen also zwei operator<<-Aufrufe mit verschiedenen Parametern. Der innere (der mit TRACE) bekommt den Logger und TRACE als Parameter. Hier soll das TRACE entscheiden, daß dieser Aufruf letztlich wegoptimiert wird. Dafür definieren wir uns ein template TTraceStream<bool>, welches durch die Spezialisierung des Templateparameters die bekannte Unterscheidung zwischen Release- und Debugversion trifft. Immer wenn der Compiler auf diesen Typ trifft, soll er abhängig vom Templateparameter diese Entscheidung treffen. Nun brauchen wir noch einen Typ für die Klasse von Ausgaben, die immer im Programm enthalten sein sollen (INFO und wichtiger). Dieser heißt TLogStream und ist von TTraceStream Fehlt also nur noch die initiale Entscheidung für einen der Typen durch das Loglevel. Auch hier kommt uns wieder das Typsystem zu Hilfe. Wir teilen nämlich ebenso die Loglevel in zwei Klassen: TraceLevel (ein enum, der TRACE und DEBUG enthält) und LogLevel (ein enum, der INFO und andere enthält). Nun können wir als Einstiegspunkt zwei Operatoren definieren (TraceStream steht hier aus Platzgründen für die aktuelle Instanziierung von TTraceStream<bool>): TraceStream &operator<<(TLogStream &, const TraceLevel); TLogStream &operator<<(TraceStream &, const LogLevel); Was passiert hier? Die erste Form des Operators wird vom Compiler immer dann gewählt, wenn er ein Objekt vom Typ TLogStream erhält und auf dieses ein TraceLevel ausgeben soll. Damit muss er quasi in den Trace-Modus "umschalten". Das tut er, indem er ein Objekt vom Typ TraceLevel zurückliefert (korrekterweise liefert er eine Referenz auf das übergebene TLogStream-Objekt zurück, welches nach TraceStream gecastet wurde. Daher auch die Ableitung, damit der Cast funktioniert). Der umgekehrte Fall wird vom zweiten Operator gelöst: er liefert ein Objekt vom Typ TLogStream zurück, wenn er einen TraceStream erhält und dorthin ein LogLevel ausgeben soll. Damit der Upcast (TLogStream wurde ja von TraceStream abgeleitet) funktioniert, muss natürlich der ursprünglich angelegte Logger vom Typ TLogStream sein. Die restlichen Sachen sind eher einfach. Wir definieren ein paar Ausgabeoperatoren für verschiedene Sachen jeweils zweimal. Immer eine Version für den TLogStream und eine für den TTraceStream<bool>. Diese Operatoren liefern jeweils den übergebenen Steamtyp zurück um so in dem "Modus" zu bleiben. Die Definition der Operatoren für TTraceStream<bool> macht dabei wieder Gebrauch von der Templatespezialisierung um dem Compiler eine Wegoptimierung im Releasebinary zu ermöglichen. Da der Compiler für die Auswahl der überladenen Operatoren immer den Typ nimmt, der am genauesten zu der Parameterliste passt, können wir so durch den Typ des Rückgabewertes der Operatoren den Status über das gewählte Loglevel bereits zur Compilezeit durch die einzelnen Statements ziehen. Das ganze funktioniert natürlich nur innerhalb eines Statements. Der ursprüngliche Logger muss ja des Upcasts wegen vom Typ TLogStream sein. Daher beginnt das Spiel mit den Typein jedem neuen Statement von vorn. Aus diesem Grund MUSS der Loglevel auch immer der erste Wert sein, den man in einem Debugstatement ausgibt. Folgender Code würde beispielsweise nicht tun, was man erwartet: logger << DEBUG; logger << "Blubber"; Die Debugversion würde hier tatsächlich noch funktionieren. Zuerst wird der Loglevel auf DEBUG gesetzt (das eigentliche Setzen des Levels der Nachricht geschieht zu Laufzeit. Zur Compilezeit wird nur anhand des Typs gearbeitet) und dann wird die entsprechende Nachricht ausgegeben. Kompiliert man dieses Programm nun allerdings als Releaseversion, dann passiert etwas merkwürdiges. Die Zeile mit DEBUG wird durch die Optimierung gänzlich wegfallen. Da allerdings logger vom Typ TLogStream ist, wird in der nächsten Zeile wieder der Operator für die Ausgabe auf einem TLogStream gewählt. Dieser wird aber nie wegoptimiert werden, weswegen plötzlich die Nachricht "Blubber" mit dem zuletzt gesetzten Debuglevel ausgegeben wird. Das ist aber keineswegs das, was der Programmierer wollte. Damit nun nicht jeder anhand der paar Informationen in dem Artikel die Sache nachprogrammieren muss, habe ich das bereits fix und fertig in ein Headerfile gepackt, welches man hier herunterladen kann. Der Code ist getestet mit GCC 3.3, Visual C++ 2003 und dem Comeau C++ Compiler. Alle kompilieren das ganze ohne Warnungen und zumindest bei GCC und VC++ funktioniert der Code auch wie geplant (von Comeau habe ich den Onlinecompiler zum testen verwendet, kann also keinen Code ausführen). Zur Verwendung in größeren Programmen müßte man das ganze evtl. noch threadsafe machen. Das ist eine kleinere Übung, da man dazu nur eine Handvoll Funktionen synchronisieren muss (setLevel, setLastLevel, put und modify). Für den portablen Proof-of-Concept habe ich darauf verzichtet. Das Headerfile ist im Kopf nocheinmal umfassend dokumentiert, einschließlich Beispiel. Der Code ist public domain, kann also absolut frei verwendet werden. Download der logging.hxx Freitag, 22. April 2005Template-SpielereienIch hatte heute nachmittag etwas Langeweile (hach je, wenn das mein Betreuer liest...) und habe mit den Template-Fähigkeiten von C++ rumgespielt. Herausgekommen ist dabei eine rudimentäre Debug-Klasse, mit der man recht bequem Debugging-Code in ein C++-Programm einfügen kann, der sich auch quasi "auf Knopfdruck" ausschalten läßt. Das ganze basiert auf der Spezialisierung eines Templates mit bool als Parametertyp. Das allgemeine Template stellt die gesamte Funktionalität zur Ausgabe von Debugging-Informationen zur Verfügung. Die Spezialisierung für den Wert false enthält jeweils nur leere Funktionen. Nun wird abhängig von der Definition der Präprozessordirektive NDEBUG das Template entweder mit true oder false instanziiert. Die über das Programm verstreuten Debugsausgaben gehen also einmal gegen tatsächliche Ausgabefunktionen und beim anderen mal an leere Funktionsrümpfe. Schaltet man nun bei aktuellen Compilern die Optimierungen ein (bei GCC reicht schon -O), dann werden die leeren Funktionsrümpfe wegoptimiert und die gesamten Debug-Ausgaben entfallen im Kompilat. Somit hat man eine praktische Möglichkeit, ohne wilde Präprozessororgien die beim Programmieren verwendeten Tracing- und Debuggingausgaben aus der Releaseversion zu entfernen. Das Interface ist ein wenig nach den IOStreams modelliert, so daß der Krempel bequem zu verwenden ist. Da ich den Code nicht für besonders patentwürdig halte und wahrscheinlich nicht der erste bin, der auf so eine Idee kommt, ist der quasi public domain. Macht damit, was ihr wollt. debugstream.hxx
#ifndef DEBUGSTREAM_HXX
#define DEBUGSTREAM_HXX
#include <iostream>
namespace Debug
{
struct Tendl
{
};
static const Tendl endl = Tendl();
template <bool> class TDebugStream
{
std::ostream &output_stream;
public:
TDebugStream(std::ostream &os)
: output_stream(os)
{
};
TDebugStream &operator<<(const Tendl &)
{
output_stream << std::endl;
return *this;
};
template <class T> TDebugStream &operator<<(const T &t)
{
output_stream << t;
return *this;
};
};
template <> class TDebugStream<false>
{
public:
TDebugStream(std::ostream &os)
{
};
TDebugStream &operator<<(const Tendl &)
{
return *this;
};
template <class T> TDebugStream &operator<<(const T &t)
{
return *this;
};
};
#ifdef NDEBUG
typedef TDebugStream<false> DebugStream;
#else
typedef TDebugStream<true> DebugStream;
#endif
};
#endif
Verwendet wird das ganze dann wie folgt (DebugStream, der auf std::cout landet):
#include <iostream>
#include "debugstream.hxx"
int main()
{
Debug::DebugStream ds(std::cout);
ds << "Ein Test-Text mit einer Zahl:" << 10 << Debug::endl;
return 0;
};
Abhängig von der Definition von NDEBUG wird das Programm nun entweder "Ein Test-Text mit einer Zahl:10" ausgeben oder schweigen. Wenn man das mit Optimierungen kompiliert, dann wird im Fall von NDEBUG im Kompilat keine Referenz zu DebugStream vorhanden sein. Freitag, 15. April 2005Zu viele Tastaturen und Mäuse?Nachdem btk heute über ein nettes Tool namens Multiplicity einen Artikel geschrieben hat und darin Synergy erwähnt hat, wurde bei mir mal wieder der Spieltrieb geweckt. Erstmal vornweg zur Erklärung: Sowohl Synergy, als auch Multiplicity sind Tools um mehrere Maschinen zu einer Art virtuellen Monitor zu verbinden. Dabei werden mehrere grafische Oberflächen mit einer Maus und einer Tastatur gesteuert. Verläßt man den Bildschirm der einen Maschine mit dem Mauszeiger, so taucht er auf der anderen Maschine wieder auf und man kann dort tippen. Das ganze funktioniert ohne merkbare Verzögerung. Zusätzlich werden auch noch die Zwischenablagen der einzelnen Maschinen synchronisiert. Da Multiplicity ein Tool für Windows und MacOS ist, kam ein Test für mich leider nicht in Frage. Ich nenne nunmal nur eine einzige Windowsmaschine mein eigen und die ist auch noch emuliert. Also habe ich mir Synergy mal etwas näher angeschaut. Laut Webseite läuft das Tool unter Windows, Unix und MacOS X. Mein Testsetup:
section: screens
namenlos:
namenlos-mobil:
end
section: links
namenlos:
right = namenlos-mobil
namenlos-mobil:
left = namenlos
end
Im Grunde genommen nahezu selbsterklärend. Es existieren zwei Bildschirme: namenlos und namenlos-mobil. Diese sind so angeordnet, daß sich namenlos links von namenlos-mobil befinded (so, wie das tatsächlich auf meinem Schreibtisch ist). Wie bereits erklärt muss auch die Gegenrichtung angegeben werden. Damit ist dann aber der Konfigurationsaufwand schon beendet. Man startet den Server nun mit synergys -f --config syn.conf. Dieser lauscht nun auf Port 24800 auf die Anfragen von irgendwelchen Clients. (Hinweis: der Server muss unter X gestartet werden, damit er sich an den X-Server anhängen kann. Logisch, wo sollte sonst die Maus etc. herkommen?)
Synergy selbst bringt keinerlei Sicherheitsfunktionen wie Nutzerauthentisierung oder Verschlüsselung mit. Damit nun nicht jeder meine Tastendrücke und ähnliches im Netz mitlesen kann (namenlos und namenlos-mobil sind über ein öffentliches Netz verbunden) muss da also noch getunnelt werden. Glücklicherweise benutzt Synergy nur eine einzige TCP-Verbindung. Das perfekte "Opfer" für einen SSH-Tunnel also. Von namenlos aus habe ich mittles ssh -L 24800:localhost:24800 namenlos@namenlos-mobil den entsprechenden Tunnel aufgesetzt. Nun werden alle Verbindungen, die auf namenlos an Port 24800 gehen auf denselben Port auf namenlos-mobil weitergeleitet. Auf dem Client wird nun (unter X) das Clientprogramm gestartet und mit dem Server verbunden. Da der Tunnel auf localhost:24800 endet, ist das auch der Server. Der Befehl lautet also synergyc -f localhost. Sowohl Server als auch Client werden nun ein paar Ausgaben machen und dann melden, daß alles erfolgreich geklappt hat. Wenn ich jetzt den Mauszeige auf namenlos-mobil über den linken Bildschirmrand hinaus bewege, dann taucht er auf namenlos wieder auf und ich kann auch dort Tasteneingaben machen. Beispielsweise schreibe ich diesen Text gerade auf der Tastatur von namenlos-mobil in ein Fenster auf namenlos. Ziemlich brauchbar, das ganze. Leider ist da, wo Licht ist auch etwas Schatten. Der Versuch, ein Bild von einer Gimp-Session auf namenlos in eine Session auf namenlos-mobil zu kopieren scheitert leider. Hier macht sich das fehlen einer standardisierten Schnittstelle für Bilddaten unter X11 bemerkbar. Texte lassen sich wunderbar zwischen den einzelnen Maschinen hin- und herkopieren. Bspw. habe ich die gesamte URLs in dem Text aus dem auf namenlos-mobil laufenden Browser kopiert und in den auf namenlos laufenden eingefügt. Wenn das mit Bildern jetzt auch noch so ginge.... Fazit: einfach geil. Ich frage mich jetzt schon, wieso ich hier eigentlich immer so eine klobige Tastatur stehen hatte. Zumindest solange mein Schreibtisch derart begrenzt Platz bietet ist Synergy eine perfekte Lösung, die ich schon viel früher hätte entdecken sollen. Donnerstag, 14. April 2005Subversion und Code 502 (Bad Gateway)Letztens habe ich ja mal eine Anleitung gepostet, wie man einem Apache 2 mehrere name based virtual hosts beibiegt und dabei SSL verwenden kann. Ich habe da ja schon geschrieben, daß das eine schlechte Idee ist und nur unter ganz bestimmten Umständen sinnvoll sein kann. Nachdem ich mich natürlich nicht an meine eigene Warnung gehalten habe, begrüßt mich heute das von mit verwendete Subversion mit einem freundlichen svn: COPY of ...: 502 Bad Gateway (https://xyz). Ah ja... Etwas Recherche brachte diesen Artikel zu Tage, welcher das Problem beschreibt. Konkret fällt WebDAV unter SSL dann auf die Nase, wenn man eine URL versucht mit einem COPY-Request anzufassen, welche sich unterhalb eines mit SSL gesicherten name based virtual host befindet. Warum das so ist, weiß ich im Moment auch nicht. Es hilft in jedem Fall, den Servername des default-HTTPS-Hosts (bei Debian in der Datei /etc/apache2/sites-available/ssl-secured) auf den Namen des Repository-Hosts zu setzen und dort auch die entsprechende Location für das WebDAV zu konfigurieren. Verrückt.
(Seite 1 von 1, insgesamt 4 Einträge)
|