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

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.

Trackback address for this post

This is a captcha-picture. It is used to prevent mass-access by robots.
Please enter the characters from the image above. (case insensitive)

2 comments

Comment from: dirk [Visitor]
dirkUnd der zweite Kritikpunkt war das zahlemäßige Anwachsen der DLLs, weil für jede native DLL eine zweite Forwarder DLL geschrieben werden muss. Aus 3 native DLLs(3 x86 udn 3x64) werden nochmal 6 forwarder DLLs benötigt. Kommt IA64 hinzu wird nochmal der volle Satz benötigt. Da ist mir ein Assembly und 3(3 x86 und 3 x64) native DLLs lieber ;-).
03/14/06 @ 07:21
Comment from: Stefan [Visitor]
StefanDirkie,
was sind schon zwei DLLs unter Freunden? Der Punkt ist: So wird kein Code dupliziert und die Forwarder DLLs sind trivial erzeugbar, denn sie haben keinen Code, sie bestehen wirklich nur aus einem def file und linken gegen die Import-Library der geforwardeten DLL.
Plattenplatz? Die forwarder DLLs sind im Bereich 20~50k anzusiedeln.
Mehr Aufwand bei der Patcherstellung? Auch nicht, weil Forwarder DLLs keinen Code beinhalten, sie müssen also nur neu deployed werden, wenn die geforwardete DLL ergänzt wird.
03/14/06 @ 10:38

Comments are closed for this post.