...someplace, where there isn't any trouble? Do you suppose there is such a place, Toto?

Good bye "Segoe UI"?

Link: http://www.heise.de/newsticker/meldung/71604

Hhm, zuerst der Delay von Vista selber, nun gilt auch noch die neue Schrifttype "Segoe UI" auch ganz offiziell als nicht so ganz koscher. Mal sehen was nun aus der vollmundigen Ankündigung wird, "Segoe UI" werde es als redistributable Download für ISVs geben, die natürlich jetzt schon anfangen sollen, ihr UI und ihre Applikationen auf "Segoe UI" hinzutrimmen. Vielleicht sollte man dann doch einfach erstmal besser bei "MS Shell Dlg" bleiben bis klar ist ob Segoe UI wirklich einfach so mit Vista mitkommen darf...

What's wrong with this code?

Über dieses Stückchen Code mußte ich gestern mit einem Kollegen drüberschauen und ich konnte meinen Augen kaum trauen:

BOOL FileCreateEmptyFile( LPCTSTR lpszFilename )
{
  HANDLE hEmptyFile = 
    CreateFile(
      lpszFilename,
      GENERIC_WRITE,
      FILE_SHARE_READ | FILE_SHARE_WRITE,
      NULL,
      CREATE_ALWAYS,
      FILE_ATTRIBUTE_HIDDEN,
      NULL
    );

  ASSERT( hEmptyFile );
  CloseHandle( hEmptyFile );
  return (hEmptyFile != 0);
}

Was ist hier alles falsch? Oder anders formuliert: Ist hier eigentlich überhaupt irgendwas richtig an diesem Stück Code?

Riffing on Dirkie, Part II

Link: http://geekswithblogs.net/dirksblog/archive/2006/03/05/71471.aspx

In unserer andauernden Diskussion, wie man selbst mit P/Invoke .NET-Assemblies auf einem x64-Rechner so starten kann, dass sie als x64-Prozesse laufen, war mein Vorschlag, forwarder DLLs zu verwenden, die auf die plattformspezifischen DLLs verweisen, die native für die jeweilige Architektur gebuildet wurden. Das .NET-Assembly wird also nur eine Datei foofwd.dll laden, die ihrerseits je nach Plattform gegen eine foo.dll für x86 oder eine foox64.dll für x64 linkt. Der einzige Knackpunkt (und Dirkies Hauptkritikpunkt) ist dabei, daß man die x64-Variante der foofwd.dll ins Verzeichnis %systemroot%\system32 kopieren muß. Eine Variante, wie man dies umgehen kann und alle DLLs im Installationsverzeichnis halten kann, will ich im Folgenden zeigen:

Ich gehe im Folgenden davon aus, dass unser Installatioonsverzeichnis unter

c:\program files\ourapp

liegt. Ich gehe weiterhin davon aus, daß alle unsere Programme auch dort liegen, inklusive der native DLLs foo.dll und foox64.dll. Für alle Forwarder DLLs legen wir aber nun ein Unterverzeichnis forward.net an und darunter die Verzeichnisse x86 und x64 in welche wir nun alle unsere forwarder DLLs reinkopieren:

c:\-
   |
   -program files-
                 |
                 -ourapp-
                        |
                        -foo.dll (native x86)
                        |
                        -foox64.dll (native x64)  
                        |
                        -forward.net-
                                    |
                                    -x86-
                                    |   |
                                    |   -foofwd.dll (native x86)
                                    |
                                    -x64-
                                        |
                                        -foofwd.dll (native x64)  

Das einzige Problem besteht nun darin, daß ein .NET-basiertes Programm zur Laufzeit ermitteln kann, ob es als 32-bittiger oder als 64-bittiger Prozeß läuft. Und es muß darauf basierend dann in der Lage sein, die forwarder DLLs aus dem richtigen Verzeichnis zu laden. Sehr einfach geht, das, indem der Prozeß gleich zu Beginn seine an ihn vererbte Environmentvariable %PATH% entsprechend ergänzt, dann geht das alles kinderleicht. Folgendes Beispiel zeigt wie's gehen kann:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Reflection;

namespace DoIlooklikeIgivaShit
{
  enum PROCESSOR_ARCH
  {
    PROCESSOR_ARCHITECTURE_INTEL = 0,
    PROCESSOR_ARCHITECTURE_MIPS = 1,
    PROCESSOR_ARCHITECTURE_ALPHA = 2,
    PROCESSOR_ARCHITECTURE_PPC = 3,
    PROCESSOR_ARCHITECTURE_SHX = 4,
    PROCESSOR_ARCHITECTURE_ARM = 5,
    PROCESSOR_ARCHITECTURE_IA64 = 6,
    PROCESSOR_ARCHITECTURE_ALPHA64 = 7,
    PROCESSOR_ARCHITECTURE_MSIL = 8,
    PROCESSOR_ARCHITECTURE_AMD64 = 9,
    PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 = 10,
    PROCESSOR_ARCHITECTURE_UNKNOWN = 0xFFFF
  }


  [StructLayout(LayoutKind.Sequential)]
  class SYSTEM_INFO
  {
      public ushort wProcessorArchitecture;
      public ushort wReserved;
      public uint dwPageSize;
      public IntPtr lpMinimumApplicationAddress;
      public IntPtr lpMaximumApplicationAddress;
      public IntPtr dwActiveProcessorMask;
      public uint dwNumberOfProcessors;
      public uint dwProcessorType;
      public uint dwAllocationGranularity;
      public ushort wProcessorLevel;
      public ushort wProcessorRevision;
  }


  class Program
  {
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode,
           CallingConvention = CallingConvention.Winapi,
           EntryPoint = "GetSystemInfo")]
    private static extern void GetSystemInfo([In, 
                MarshalAs(UnmanagedType.LPStruct)]
		SYSTEM_INFO pSysInfo);

    public static ushort GetProcessorArchitecture()
    {
        SYSTEM_INFO si = new SYSTEM_INFO();
        GetSystemInfo(si);
        return si.wProcessorArchitecture;
    }    

    static void Main(string[] args)
    {
      string strPath = Environment.GetEnvironmentVariable("PATH");
      Assembly ass = Assembly.GetExecutingAssembly();
      string str = ass.Location;
      int iLastBSlash = str.LastIndexOf('\\');
      ushort uPlatform = (ushort)GetProcessorArchitecture();
      if (-1 != iLastBSlash && 
        (
        (ushort)PROCESSOR_ARCH.PROCESSOR_ARCHITECTURE_INTEL==
                uPlatform
        ||
        (ushort)PROCESSOR_ARCH.PROCESSOR_ARCHITECTURE_AMD64==
                uPlatform
        ))
      {
        str = str.Substring(0, iLastBSlash) + "\\\\forward.net\\\\";
        switch (uPlatform)
        {
          case (ushort)
               PROCESSOR_ARCH.PROCESSOR_ARCHITECTURE_INTEL:
            str += "x86";
            break;
          case (ushort)
               PROCESSOR_ARCH.PROCESSOR_ARCHITECTURE_AMD64:
            str += "x64";
            break;
        }
        strPath += ";" + str;
       
        Environment.SetEnvironmentVariable("PATH", strPath);
      }

      /// 
      /// und ab jetzt machen wir Aufrufe mit P/Invoke
      /// 
    }
  }
}

Natürlich sollte man das Ganze in ein separates Assembly in Form einer DLL packen, so daß der Code nicht für jede Exe dupliziert werden muß. Das hübsche an dieser Variante ist, daß mit diesem Code nur noch jede neue forwarder-DLL in das richtige Verzeichnis geworfen werden muß und am managed Wrapper-Code nichts angepaßt werden muss. Und das Ganze funktioniert automatisch richtig, wenn ein .NET-Assambly nicht mit "Any CPU" übersetzt ist, sondern für eine spezifische Target-CPU.

Riffing on Dirkie

In einem seiner letzten Blogs hat Dirkie Code vorgestellt, der zur Laufzeit prüft, ob managed Code unter x64 Windows als 64-bit Prozeß zur Ausführung kommt oder aber als 32-bit Prozeß unter x86 Windows oder x64 Windows läuft. Der einfache Größenvergleich von IntPtr mit 8 hat mich stutzig gemacht und ich habe mir überlegt, ob man das mit P/Invoke irgendwie besser hinkriegen kann, denn der Ansatz liefert dasselbe Ergebnis zurück für alle 64 bit Plattformen, inklusive IA64 oder zukünftigen Plattformen (Can you say 64 bit Pocket PC?). Dabei bin ich auf das traditionelle GetSystemInfo zurückgekommen und so funktioniert's damit dann:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;


namespace platformdetect
{
    enum PROCESSOR_ARCH
    {
        PROCESSOR_ARCHITECTURE_INTEL            = 0,
        PROCESSOR_ARCHITECTURE_MIPS             = 1,
        PROCESSOR_ARCHITECTURE_ALPHA            = 2,
        PROCESSOR_ARCHITECTURE_PPC              = 3,
        PROCESSOR_ARCHITECTURE_SHX              = 4,
        PROCESSOR_ARCHITECTURE_ARM              = 5,
        PROCESSOR_ARCHITECTURE_IA64             = 6,
        PROCESSOR_ARCHITECTURE_ALPHA64          = 7,
        PROCESSOR_ARCHITECTURE_MSIL             = 8,
        PROCESSOR_ARCHITECTURE_AMD64            = 9,
        PROCESSOR_ARCHITECTURE_IA32_ON_WIN64    = 10,
        PROCESSOR_ARCHITECTURE_UNKNOWN          = 0xFFFF
    }


    [StructLayout(LayoutKind.Sequential)]
    class SYSTEM_INFO
    {
        public ushort wProcessorArchitecture;
        public ushort wReserved;
        public uint dwPageSize;
        public IntPtr lpMinimumApplicationAddress;
        public IntPtr lpMaximumApplicationAddress;
        public IntPtr dwActiveProcessorMask;
        public uint dwNumberOfProcessors;
        public uint dwProcessorType;
        public uint dwAllocationGranularity;
        public ushort wProcessorLevel;
        public ushort wProcessorRevision;
    }

    class Program
    {

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, 
                CallingConvention = CallingConvention.Winapi,
                EntryPoint="GetSystemInfo")]
        private static extern void GetSystemInfo([In, 
                    MarshalAs(UnmanagedType.LPStruct)]
		    SYSTEM_INFO pSysInfo);

        public static ushort GetProcessorArchitecture()
		{
		    SYSTEM_INFO si = new SYSTEM_INFO();
		    GetSystemInfo(si);
		    return si.wProcessorArchitecture;
		}    
        
        static void Main(string[] args)
        {
            ushort uPA = GetProcessorArchitecture();
            string[] platforms = {"x86", "MIPS", "Alpha", 
              "Power PC", "SHX", "ARM", "Itanium", 
              "Alpha 64", "MS intermediate Language", 
              "AMD64", "x86 on Itanium"};
            string strPlatform = "";

            try
            {
                strPlatform = platforms[uPA];
            }
            catch (System.IndexOutOfRangeException e)
            {
                strPlatform = "Unknown";
            }    
            Console.WriteLine("Platform is {0}", strPlatform);
            SYSTEM_INFO si = new SYSTEM_INFO();
            Console.WriteLine("SYSTEM_INFO size is {0} bytes", 
              System.Runtime.InteropServices.Marshal.SizeOf(si));
            Console.ReadKey();
        }
    }
}

Und beim nachschauen, was die Trackback URL von Dirkies Beitrag war, muss ich feststellen, daß Dirkie mit einem Link hierher
das Ganze als "wie es nicht gemacht werden sollte" abtut. Ich finde, damit tut man dieser zugegebenermassen etwas aufwendigeren Variante Unrecht, aber ich denke auch, daß man diese Lösung hier keinesfalls in einer innermost loop verwenden sollte. Aber einmal am Anfang des Programms tut das sicherlich nicht weh...

(Edited 03/11/2006: Oops, Testcode für die IndexOutOfRangeException entfernt)

64 bit Windows - Teil 9

Heute geht es mal um was ganz anderes, nämlich um Managed Code unter x64. Jawoll, ich habe die Stirn, was über Managed Code zu schreiben, obwohl ich bis vor zwei Wochen noch einen weeeiiiten Bogen drumrum gemacht habe. Genauer gesagt will ich was über Managed Code auf x64 schreiben, der P/Invoke macht aus C#-Code, denn dabei kann so einiges schiefgehen. Mit P/Invoke kann man aus Managed Code traditionellen unmanaged Code aufrufen, indem man bei der Deklaration einer Methode angibt, wie die DLL heißt, aus der der unmanaged Code aufgerufen wird, wie die Funktion heißt, die man daraus aufrufen will und wie die Funktionsparameter gemarshalt (gemarshaled?) werden sollen.

Beispielsweise könnte ich schon seit Jahren eine 32-bittige unmanaged DLL namens foo.dll haben, die eine Funktion Bar mit folgendem Prototyp exportiert:

  BOOL __stdcall Bar(DWORD dwLevel, LPVOID pSrc, DWORD dwFlags, 
      LPCWSTR szString, LPCWSTR szMessage);

Ich komme nun auf die Idee, in einem funkelnagelneuen .NET-Assembly diese DLL verwenden zu wollen weil in dieser uralten DLL extrem viel Gehirnschmalz drinsteckt, oder weil das Biest extrem performant und deswegen unmanaged sein muß oder was auch immer.

Dann könnte eine passende P/Invoke-Implementation in C# beispielsweise so aussehen:

namespace MyCompany.Whatever.DoIlooklikeIgivaShit
{
    public class FooDll
    {

       [DllImport("foo.dll", CharSet = CharSet.Unicode, 
                             CallingConvention = 
                             CallingConvention.Winapi, 
                             SetLastError=true,
                             EntryPoint="Bar")]
       public static extern bool FooBar(uint dwLevel, IntPtr pSrc, 
             uint dwFlags, 
             [MarshalAs(UnmanagedType.LPWStr)] string strString,            
             [MarshalAs(UnmanagedType.LPWStr)] string strMsg);
    }
}

Wenn man das nun so macht und mit der Einstellung "Any CPU" für das .NET-Assembly buildet, dann funktioniert das wunderbar auf einer herkömmlichen x86-Maschine. Die Welt ist schön. Wenn man aber nun dieselben Files auf eine x64-Maschine mit dem .NET-Framework 2.0 kopiert und denkt, das funktioniere da genauso, stellt man plötzlich fest, daß der Prozeß nach kürzester Zeit mit folgender Fehlermeldung kollabiert:

Unhandled Exception: System.BadImageFormatException: 
An attempt was made to load a program with an incorrect format. 
(Exception from HRESULT: ....

Das Problem hierbei ist, daß das .NET-Assembly mit "Any CPU" übersetzt worden ist. Daher wird versucht, den Prozeß als nativen Prozeß (für die CPU) auszuführen, also als x64-Prozeß. Wenn aber leider die DLL, die aus diesem 64-bittigen Prozeß aufgerufen werden soll, eine 32-bittige ist, wie in unserem Fall die Datei foo.dll, dann geht das nicht. Tyischer Fall von "Isso".

Man hat nun zwei Optionen:

  • Das .NET-Assembly nur für x86 übersetzen, wobei die schöne Prozessorunabhängigkeit von .NET verloren geht (sounds like Java, eh?)
  • Die Datei foo.dll native auf x64 portieren

Wer mit Option 1 zufrieden ist, ist fein raus, wird aber niemals die Goodies von x64 in Anspruch nehmen können und kann jetzt mit weiterlesen aufhören.

Im folgenden nehme ich an, daß wir aber unser tolles .NET-Assembly unter x86 als native x86-Prozeß laufen lassen wollen und unter x64 als native x64-Prozeß. Wir lassen also die Einstellung für das Assembly auf "Any CPU" und fangen an, die Datei foo.dll nach x64 zu portieren, schließlich haben wir auch ein paar native x64 Binaries aus unmanaged Code, die einen x64-Port von foo.dll auch ganz gut benutzen könnten.

Haben wir jetzt ein reinrassiges x86-Softwareprodukt und ein reinrassiges x64-Softwareprodukt, wo es keine Übereinstimmungen im Auslieferungsumfang zwischen beiden Varianten geben soll, dann haben wir jetzt kein Problem mehr. Besteht unser Softwareprodukt jedoch aus gemischten Komponenten, also teils aus x86-Binaries und x64-Binaries und installieren wir alles in ein und dasselbe Installationsverzeichnis, dann haben wir jetzt ein Problem: Wie soll denn die native x64-Version von foo.dll heissen? foo.dll? Geht nicht, so heisst schon die x86-Version. foox64.dll? Geht nicht, so kann unser tolles .NET-Assembly sie nicht laden, weil es im DllImport-Attribut was von "foo.dll" stehen hat und nicht "foox64.dll". Sollen wir dann zwei Versionen von unserem Assembly machen, die sich jeweils nur im Dll-Namen im DllImport-Attribut unterscheiden? Can you say maintenance nightmare?

Was wir brauchen, ist eigentlich ein "Mediator", der automatisch die Aufrufe aus unserem tollen .NET-Assembly unter x86 in Aufrufe in die x86-Variante foo.dll übersetzt und unter x64 in die x64-Variante foox64.dll. Und an der Stelle kommt uns eine nette Eigenschaft von native DLLs entgegen, nämlich die Möglichkeit, Aufrufe in andere DLLs zu forwarden. Wir erzeugen also eine neue DLL namens foofwd.dll, die keinerlei Code enthält, sondern alle Exporte von foo per def-file identisch exportiert. Diese foofwd.dll builden wir einmal für x86 und einmal für x64 und nennen sie beidesmal gleich, nämlich foofwd.dll. Die x86-Version wird alle ihre Exporte nach foo.dll forwarden und die x64-Version alle ihre Exporte nach foox64.dll. Mit zwei getrennten def files, die man evtl auch noch als Teil des Builds skriptgesteuert erzeugen läßt, ist das absolut Kinderfasching.

Was ist aber nun mit unserem tollen .NET-Assembly? Das .NET-Assembly schreiben wir nun, da wir um den "Mediator" in Form von foofwd.dll wissen, in vorauseilendem Gehorsam so, daß er ausschließlich die Datei "foofwd.dll" in seinen DllImport-Attributen referenziert, also nicht foo.dll oder foox64.dll.

Und was liefern wir nun auf welcher Plattform wie aus?

Auf einem 32-bit Windows XP macht es gar keinen Sinn, die x64-Binaries mitauszuliefern. Unser Installationsprozeß wird also die x64-Binaries auf diesem Zielsystem komplett übergehen. Die Datei foofwd.dll landet hier zusammen mit foo.dll in unserem Installationsverzeichnis und alles ist gut, denn die .NET-basierten Prozesse können hier nur als 32-bit-Prozesse laufen, finden zur Laufzeit die 32-bittige foofwd.dll im Installationsverzeichnis, welche beim Laden automatisch die 32-bittige foo.dll nachzieht.

Auf einem 64-bittigen Windows XP machen wir erstmal die Installation exakt wie unter dem 32-bittigen XP, kopieren also die 32-bittige foofwd.dll und die 32-bittige foo.dll ins Installationsverzeichnis. Dann kopieren wir die 64-bittige foox64.dll ins Installationsverzichnis. Damit aber ein .NET-basierter Prozess nun seine 64-bittige forwarder DLL findet, müsssen wir diese an einen Ort kopieren, wo ein beliebiger 32-bittiger Prozeß (also beispielweise auch ein .NET-Assembly das nicht mit "Any CPU" übersetzt wurde sondern mit "x86") sie auf keinen Fall aus Versehen laden kann (was nämlich fehlschlagen würde) aber ein 64-bittiger Prozeß sie auf jeden Fall findet.

Genau dieses Problem hat MS ja auch, denn es gibt unter Windows XP x64 Edition beispielsweise einen 32-bittigen regedit.exe und einen 64-bittigen regedit.exe. Oder einen 32-bittigen cmd.exe und einen 64-bittigen cmd.exe. Beide linken dynamisch gegen kernel32.dll aber der 32-bittige regedit gegen den 32-bittigen kernel32.dll und der 64-bittige regedit gegen den 64-bittigen kernel32.dll, und beide Versionen von kernel32.dll befinden sich in verschiedenen Unterverzeichnissen, die durch file system redirection voneinander abgeschottet sind. So ist die 32-bittige Version von kernel32.dll unter %systemroot%\syswow64 zu finden und die 64-bittige Version von kernel32.dll unter %systemroot%\system32. Einem 32-bittigen Prozess wird, so er irgendetwas aus %systemroot%\system32 laden will, automatisch immer in Wahrheit %systemroot%\syswow64 durch diesen Mechanismus namens "file system redirection" untergeschoben.
Und diesen Mechanismus können wir uns bei unsrem Problem ganz einfach zunutze machen, und zwar indem wir die Datei foofwd.dll in ihrer 64-bittigen Variante bei der Installation auf einem x64-Betriebssystem schlicht und einfach nach %systemroot%\system32 kopieren. Dann finden alle "Any-CPU"-.NET-basierten Prozesse die foofwd.dll über %systemroot%\system32 (also über die Umgebungsvariable %PATH%) und alle "x86"-.NET-basierten Prozesse über das lokale Installationsverzeichnis.

<< 1 ... 30 31 32 33 34 35 36 37 38 39 40 ... 47 >>