Deadline der Woche: MapServer

February 18, 2008 at 6:43 PMAndre Loker

wmsHeute war Abgabetermin für das MapServer Uni Projekt. Und wie das bei Softwareenwicklern so ist, bedeutet das, früh aufstehen, durcharbeiten... Kurz vor fünf habe ich jedenfalls die Dokumentation und die Anwendung abgeliefert. Hoffen wir mal, dass alles auch so funktioniert, wie ich es vorhatte.

Hier eine kleine Feature-Liste:

  • Kompatibel zum OGC Standard WMS 1.1.1
  • Drei verschiedene Point-Features (ScaledDot, PieChart, BarChart)
  • Komplexe Konfiguration via "Query Dateien" möglich
  • "Permutation" von Query Dateien: eine Query Datei wird zu mehreren Layern
  • Anbindung an beliebige Datenquellen via OleDB
  • Verarbeitung verschiedenstartiger Ergebnisse aus SQL Queries
  • Liest intern Shapefiles und DBase Tabellen

Posted in: Projects

Tags: , , , , ,

Familientreffen

February 18, 2008 at 12:04 AMAndre Loker

Nicht, dass es mich betreffen würde - ich bin seit fast sieben Jahren liiert - aber mir ist am Wochenende eines klar geworden: Familientreffen sind ein gänzlich ungeeigneter Ort, um als Single auf Partnersuche zu gehen. Alle anwesenden Personen sind entweder deine Verwandten oder bereits der Partner/die Partnerin eines Verwandten.

Posted in: Off topic

Tags:

Definition von __doPostBack erzwingen

February 15, 2008 at 2:13 PMAndre Loker

Das Problem

Zwei aspx-Seiten, beide rufen den identischen JavaScript Code auf:

   1: function postBackHiddenField(hiddenFieldID) {
   2:     var hiddenField = $get(hiddenFieldID);
   3:     if (hiddenField) {
   4:         hiddenField.value = (new Date()).getTime();
   5:         __doPostBack(hiddenFieldID,'');
   6:     }
   7: }

Auf Seite A funktioniert der JS Code, auf Seite B nicht. Bei letzterem sagt der JavaScript-Debugger:

__doPostBack not defined

(Hoffentlich) verständlicherweise verursachte das bei mir ziemliches Kopfkratzen. __doPostBack sollte ja eigentlich auf einer ASPX Seite nicht fremd sein. Bis mir auf einmal siedendheiß einfiel, dass ASP.NET vielleicht so schlau ist, und nur dann die enstprechenden Script-Dateien inkludiert, wenn tatsächlich ein Control auch ein Postback via JavaScript ausführen kann. Auf Seite A war so ein Control vorhanden, auf Seite B jedoch nicht. Demzufolge wurde für Seite B der Code für __doPostBack nicht in die Seite eingepflegt.

Der Quick-Fix

Um das Problem zu lösen, reicht es schon aus, einfach ein Control einzufügen, dass einen Postback mittels JavaScript auslöst, wie etwa ein LinkButton. Dieser muss auf der Client Seite nicht zwingend sichtbar sein, d.h. man kann ihn clientseitig ausblenden:

   1: <asp:LinkButton runat="server" Style='display: none;' ID="enablesJSPostBack" />

Achtung: es funktioniert nicht, wenn man den LinkButton schon serverseitig ausblendet mit Visible="false"! In dem Fall erzeugt ASP.NET ebenfalls den nötigen Code für __doPostBack nicht.

Technische Details und eine elegantere Lösung

Ich habe ein wenig mit Hilfe von Reflector im Gedärm von ASP.NET gewühlt um nachzusehen, unter welchen Umständen der __doPostBack-Code gerendert wird. Womöglich gibt es eine Methode, mit der man eben dieses erreichen kann, ohne ein Control in der Seite erstellen zu müssen. Entscheidend ist die Methode RegisterPostBackScript der Page-Klasse. Wird sie angerufen erzeugt die Seite den __doPostBack Code. Leider ist die Methode internal. Allerdings wird die Methode unter anderem aufgerufen, wenn man ClientScript.GetPostBackEventReference anruft. Mit dieser Methode kann man den __doPostBack Script-Code erzeugen, der einen Script-seitiges Postback ausführt. Ich habe meiner Page-Klasse folgende Methode hinzugefügt, um (ohne zusätzliche Controls) sicher zu stellen, dass das __doPostBack-Script erzeugt wird:

   1: protected void EnsureDoPostBackDefined(){
   2:     ClientScript.GetPostBackEventReference(this, string.Empty);
   3: }

Die Methode EnsureDoPostBackDefined wird einfach z.B. im Load Ereignis aufgerufen und schon klappt es mit __doPostBack.

Posted in: ASP.NET | Snippets

Tags: ,

Passage - Das Leben in 5 Minuten

February 14, 2008 at 4:17 PMAndre Loker

Mal ganz weit weg von .NET, aber meiner Meinung nach bemerkenswert. Durch Zufall bin ich über das Spiel "Passage" gestolpert. Auf dem ersten Blick scheint es extrem minimalistisch zu sein: gröbstpixelige Grafik, piepsiger Sound, simple Steurung (hoch, runter, links rechts). Aber wenn man es ein oder zwei Male gespielt hat, wird einem schnell klar, dass dieses "Spiel" unglaublich viel Tiefgang hat.

Passage - Das Leben in 5 Minuten

Worum geht es?

In Passage wird ein ganzes Menschenleben simuliert - und zwar reduziert auf 5 Minuten. Der Spieler steuert einen Mann durch die Jahre - von der Geburt bis zum Tod - hindurch, indem er von links nach rechts läuft. Dabei kann er zunächst eine extrem wichtige Entscheidung treffen: entweder er findet eine Frau und bindet sich fürs Leben, oder er bleibt solo. Je nachdem wie er sich entscheidet, hat das Konsequenzen für den Verlauf seines weiteren Lebens. Ich möchte hier noch nicht zu viel verraten, weshalb ich jedem empfehle, das Spiel zunächst zu spielen und danach die Diskussion mitzulesen, die zu dem Spiel und seinen vielen Metaphern entstanden sind.

Kokoromi Gamma256

Entwickelt wurde es von Jason Rohrer für den Kokoromi Gamma256 Game Design Contest. Das Interessante an diesem Wettbewerb ist, dass die Beiträge maximal 256x256 Pixel groß sein durften. Herr Rohrer hat es noch etwas krasser getrieben und das ganze auf 100x16 Pixel heruntergedrückt - ein Seitenverhältnis von 25:4...

Also, wie gesagt: herunterladen, spielen, mitinterpretieren!

Zur Homepage des Spiels

Posted in: Off topic

Tags:

K.I.S.S. mit dem Holzhammer

February 12, 2008 at 6:09 PMAndre Loker

Manchmal ist es echt so, dass der leichteste Weg doch noch am besten ist. Das musste ich heute auf einigermaßen schmerzliche Art erfahren.

Das Problem

Ich brauchte in einer Anwendung die Möglichkeit, das System herunter zu fahren. Genauer: nach Ablauf eines Timers sollte das Programm einen kompletten Shutdown des Systems einleiten.

Die gut gemeinte Lösung

Mir fiel recht schnell shutdown.exe ein, ein Dienstprogramm, dass ab Windows 2000 mit an Bord ist. Aber mal ehrlich - ich kann ja nicht einfach so ein Programm aufrufen, das dann die Arbeit erledigt. Wofür gibt es die WinAPI? Also, frisch gegoogelt, und das Stichwort war ExitWindowsEx. So soll es sein! P/Invoke an die Macht:

   1: [DllImport( "user32.dll" )]
   2: private static extern bool ExitWindowsEx( uint mode, uint reason );

Eigentlich ja ganz schön, wenn da nicht bei de, für mich interessanten Flag EWX_POWEROFF die lapidare Bemerkung stünde:

The calling process must have the SE_SHUTDOWN_NAME privilege. For more information, see the following Remarks section.

Offenbar konnte ich diese Flags nicht verwenden, ohne das entsprechende Privileg zu setzen. Das allerdings erforderte wiederum eine beachtliche Menge Code, nicht zu letzt wegen der verschiedenen Interop-Konstrukte. Meine Lösung sah schließlich so aus:

   1: /// <summary>
   2: /// Defines the shutdown mode
   3: /// </summary>
   4: public enum ShutDownMode {
   5:     /// <summary>
   6:     /// Log off current user.
   7:     /// </summary>
   8:     LogOff = 0,
   9:     /// <summary>
  10:     /// Shut down computer without turning power off (some OS's still power off though)
  11:     /// </summary>
  12:     ShutDown = 0x1,
  13:     /// <summary>
  14:     /// Shut down computer and turn power off if supported.
  15:     /// </summary>
  16:     ShutDownAndPowerOff = 0x8,
  17:     /// <summary>
  18:     /// Reboot system.
  19:     /// </summary>
  20:     Reboot = 0x2,
  21:     /// <summary>
  22:     /// Reboot system and restart all applications that used RegisterApplicationRestart 
  23:     /// to register for re-start.s
  24:     /// </summary>
  25:     RebootAndRestartApps = 0x40
  26: }
  27:  
  28: /// <summary>
  29: /// 
  30: /// </summary>
  31: public enum ForceType {
  32:     /// <summary>
  33:     /// Applications get the chance to notify the user of the upcoming shutdown.
  34:     /// This way the user may save changes.
  35:     /// </summary>
  36:     None = 0,
  37:     /// <summary>
  38:     /// Applications get the chance to let the user save changes. After a time-out
  39:     /// the applications are still forced to shutdown.
  40:     /// </summary>
  41:     ForceIfHung = 0x10,
  42:     /// <summary>
  43:     /// The user does not get a chance to save changes.
  44:     /// </summary>
  45:     Force = 0x4
  46: }
  47:  
  48: /// <summary>
  49: /// Utility class to exit windows or the current interactive logon session.
  50: /// </summary>
  51: public static class ShutdownUtil {
  52:     #region Error messages
  53:     private const string ErrorLookupPrivilegeValue = "Konnte die Privilegdaten nicht anfordern.";
  54:     private const string ErrorOpenProcessToken = "Konnte token nicht öffnen";
  55:     private const string ErrorAdjustTokenPrivileges = "Du hast nicht die Rechte, um den Computer herunterzufahren.";
  56:     #endregion
  57:  
  58:     /// <summary>
  59:     /// Exits windows or logs off the current user
  60:     /// </summary>
  61:     /// <param name="mode">The shutdown mode.</param>
  62:     /// <param name="forceType">Defines whether and how a shutdown is forced.</param>
  63:     /// <returns><c>true</c> if shutdown was initiated; otherwise, <c>false</c></returns>
  64:     public static bool Shutdown( ShutDownMode mode, ForceType forceType ) {
  65:         IntPtr privileges = SetPrivileges();
  66:  
  67:         try {
  68:             uint flags = (uint)mode | (uint)forceType;
  69:             // Reason for shutdown (for simplicity)
  70:             // "Other (Planned)"
  71:             // SHTDN_REASON_MAJOR_OTHER | 
  72:             // SHTDN_REASON_MINOR_OTHER | 
  73:             // SHTDN_REASON_FLAG_PLANNED:
  74:             const uint PlannedShutdown = 0x80000000;
  75:  
  76:             if ( !ExitWindowsEx( flags, PlannedShutdown ) ) {
  77:                 Console.WriteLine( Marshal.GetLastWin32Error() );
  78:                 return false;
  79:             }
  80:             return true;
  81:         } finally {
  82:             CloseHandle( privileges );
  83:         }
  84:     }
  85:  
  86:     private static IntPtr SetPrivileges() {
  87:         TokenPrivileges priv = new TokenPrivileges();
  88:         priv.PrivilegesCount = 1;
  89:         priv.Privileges = new LuidAndAttributes[priv.PrivilegesCount];
  90:         // Get
  91:         if ( !LookupPrivilegeValue( "", "SeShutdownPrivilege", out priv.Privileges[ 0 ] ) ) {
  92:             throw new Exception( ErrorLookupPrivilegeValue );
  93:         }
  94:         priv.Privileges[ 0 ].Attributes = SE_PRIVILEGE_ENABLED;
  95:  
  96:         TokenPrivileges rec = new TokenPrivileges();
  97:  
  98:         IntPtr token;
  99:         IntPtr procHandle = Process.GetCurrentProcess().Handle;
 100:         if ( !OpenProcessToken( procHandle, TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, out token ) ) {
 101:             throw new Exception( ErrorOpenProcessToken );
 102:         }
 103:         try {
 104:             int recSize;
 105:             if ( !AdjustTokenPrivileges( token, false, ref priv,
 106:                       Marshal.SizeOf( rec ), ref rec, out recSize ) ||
 107:                  Marshal.GetLastWin32Error() == 1300 /*ERROR_NOT_ALL_ASSIGNED*/ ) {
 108:                 throw new Exception( ErrorAdjustTokenPrivileges );
 109:             }
 110:             return token;
 111:         } catch {
 112:             CloseHandle( token );
 113:             throw;
 114:         }
 115:     }
 116:  
 117:     #region Interop
 118:     [DllImport( "user32.dll" )]
 119:     private static extern bool ExitWindowsEx( uint mode, uint reason );
 120:     
 121:     // enable privilege on token 
 122:     private const uint SE_PRIVILEGE_ENABLED = 2;
 123:     // used by OpenProcessToken
 124:     private const uint TOKEN_QUERY = 0x0008;
 125:     // used by OpenProcessToken
 126:     internal const uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
 127:  
 128:  
 129:     [DllImport( "Advapi32.dll" )]
 130:     private static extern bool LookupPrivilegeValue( string system,
 131:                                                      string privilege,
 132:                                                      out LuidAndAttributes result );
 133:  
 134:     [DllImport( "advapi32.dll" )]
 135:     [return : MarshalAs( UnmanagedType.Bool )]
 136:     private static extern bool OpenProcessToken( IntPtr ProcessHandle,
 137:                                                  UInt32 DesiredAccess,
 138:                                                  out IntPtr TokenHandle );
 139:  
 140:     [DllImport( "Advapi32.dll" )]
 141:     private static extern bool AdjustTokenPrivileges( IntPtr handle,
 142:                                                       bool disableAll,
 143:                                                       ref TokenPrivileges newState,
 144:                                                       int bufferLength,
 145:                                                       ref TokenPrivileges realState,
 146:                                                       out int foo );
 147:  
 148:     [DllImport( "Kernel32.dll" )]
 149:     private static extern bool CloseHandle( IntPtr handle );
 150:  
 151:     #region Nested type: LuidAndAttributes
 152:     [StructLayout( LayoutKind.Sequential )]
 153:     public struct LuidAndAttributes {
 154:         public long LUID;
 155:         public uint Attributes;
 156:     }
 157:     #endregion
 158:  
 159:     #region Nested type: TokenPrivileges
 160:     [StructLayout( LayoutKind.Sequential )]
 161:     private struct TokenPrivileges {
 162:         public uint PrivilegesCount;
 163:         [MarshalAs( UnmanagedType.ByValArray, SizeConst = 1 )] 
 164:         public LuidAndAttributes[] Privileges;
 165:     }
 166:     #endregion
 167:  
 168:     #endregion
 169: }

Spektakulär viel Code, um ein System herunter zu fahren.  Und das Beste: es funktioniert nicht mal! Zumindest unter meinem eingeschränkten Vista Account schien es so, als ob der Account die entsprechenden Privilegien nicht erhalten konnte. Ich bin zugegebenerweise beileibe kein Token- und Privlegien-Fachmann, aber trotzdem war das irgendwie ernüchternd. Fazit: viel Code, nichts dahinter.

Die offensichtliche Lösung

Schande über mein Haupt. Da hab ich es so gut gemeint mit meiner "Profi-Lösung" und dann funktioniert sie nicht. Mir blieb also nicht viel übrig, als auf die schnöde Variante mittels shutdown.exe auszuweichen. Hier also der vollständige Code der Lösung:

   1: /// <summary>
   2: /// Defines the shutdown mode
   3: /// </summary>
   4: public enum ShutDownMode {
   5:     /// <summary>
   6:     /// Log off current user.
   7:     /// </summary>
   8:     LogOff = 0,
   9:     /// <summary>
  10:     /// Shut down computer without turning power off (some OS's still power off though)
  11:     /// </summary>
  12:     ShutDown = 1,
  13:     /// <summary>
  14:     /// Reboot system.
  15:     /// </summary>
  16:     Reboot = 2,
  17: }
  18:  
  19: /// <summary>
  20: /// Utility class to exit windows or the current interactive logon session.
  21: /// </summary>
  22: public static class ShutdownUtil {
  23:     /// <summary>
  24:     /// Exits windows or logs off the current user
  25:     /// </summary>
  26:     /// <param name="mode">The shutdown mode.</param>
  27:     /// <returns>The exit code of the shutdown application.
  28:     /// </returns>
  29:     public static int Shutdown( ShutDownMode mode ) {
  30:         
  31:         string switches;
  32:         switch ( mode ) {
  33:             case ShutDownMode.LogOff:
  34:                 switches = "-l -f";
  35:                 break;
  36:             case ShutDownMode.ShutDown:
  37:                 switches = "-s -t 10 -f";
  38:                 break;
  39:             case ShutDownMode.Reboot:
  40:                 switches = "-r -t 10 -f";
  41:                 break;
  42:             default:
  43:                 throw new ArgumentOutOfRangeException( "mode" );
  44:         }
  45:         ProcessStartInfo info = new ProcessStartInfo( "shutdown", switches );
  46:         info.CreateNoWindow = true;
  47:         info.UseShellExecute = false;
  48:         
  49:         Process p = Process.Start( info );
  50:         p.WaitForExit();
  51:         return p.ExitCode;
  52:     }
  53: }

Deutlich weniger Code, wie man sieht. Und das Schöne - oder Gemeine, je nach Blickwinkel - ist, dass diese Lösung auch mit einem eingeschränkten Account funktioniert.

Was lernen wir daraus?

K.I.S.S. - Keep it simple, stupid! Die offensichtlichsten und leichtesten Lösungen sind oft doch die richtigen. 

Update 06/09/2008

Eine Alternatieve zum Herunterfahren des Computers hat mir heute ein Kollege zugetragen. Mittels WMI lässt sich der Shutdown elegant und ohne das Starten eines zusätzlichen Prozesses einleiten:

   1: var wmi = new System.Management.ManagementClass("Win32_OperatingSystem");
   2: wmi.Scope.Options.EnablePrivileges = true;
   3: foreach (System.Management.ManagementObject o in wmi.GetInstances()) {
   4:   o.InvokeMethod("Shutdown", null);
   5: }

Vielen Dank, Hennie!

Posted in: Snippets

Tags: , , ,