sizeof(char) vs SizeOf(char)

May 6, 2008 at 10:59 PMAndre Loker

This is weird.

   1: using System;
   2: using System.Runtime.InteropServices;
   3:  
   4: public class Program {
   5:     public static void Main() {
   6:         Console.Out.WriteLine("sizeof(char) = {0}", sizeof (char));
   7:         Console.Out.WriteLine("Marshal.SizeOf(typeof(char)) = {0}", Marshal.SizeOf(typeof(char)));
   8:     }
   9: }

 

Output:

   1: sizeof(char) = 2
   2: Marshal.SizeOf(typeof(char)) = 1

And here is why: Of course Marshal.SizeOf handles different character sets when strings fields in structures are marshalled. An interesting point is that when no character set is explicitly given, the CLR assumes CharSet.Ansi. As System.Char has no CharSet defined, SizeOf calculates the marshalled size assuming Ansi encoding. When the char is within a struct, the StructLayoutAttribute can be used and is honoured by SizeOf:

   1: using System;
   2: using System.Runtime.InteropServices;
   3:  
   4: [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
   5: public struct Ex1 {
   6:     private char x;
   7: }
   8:  
   9: [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
  10: public struct Ex2 {
  11:     private char x;
  12: }
  13:  
  14: public class Program {
  15:  
  16:     public static unsafe void Main() {
  17:         Console.Out.WriteLine("sizeof(Ex1) = {0}", sizeof (Ex1));
  18:         Console.Out.WriteLine("Marshal.SizeOf(typeof(Ex1)) = {0}", Marshal.SizeOf(typeof (Ex1)));
  19:         Console.Out.WriteLine("sizeof(Ex2) = {0}", sizeof (Ex2));
  20:         Console.Out.WriteLine("Marshal.SizeOf(typeof(Ex2)) = {0}", Marshal.SizeOf(typeof (Ex2)));
  21:     }
  22: }
   1: sizeof(Ex1) = 2
   2: Marshal.SizeOf(typeof(Ex1)) = 1
   3: sizeof(Ex2) = 2
   4: Marshal.SizeOf(typeof(Ex2)) = 2

This looks more realistic again. sizeof(Ex1) both return the same size the structs occupy in memory (which is 2 bytes because of the unicode character). Marshal.SizeOf(typeof(Ex1)) return the marshalled size, which is 1 because I chose Ansi as the character set. Similarly, the marshalled size of Ex2 is 2 because of the unicode character set.

Posted in: C#

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: , , ,