none
[C#] Latebinding über Reflection RRS feed

  • Frage

  • Hallo Leute,

    ich bin kurz vorm durchdrehen :(.

    Ich will euch kurz die Aussgangssituation erklären damit ihr mein Problem versteht.

    Unser Produkt basiert auf .Net 3.5 und alle Projekte (260 Stück) werden immer für die Pseude Plattform AnyDebug gebuilded.

    Jetzt musste ich eine dll einbinden die es ermöglich nativ mit dem PDF Format zu arbeiten (ohne drittanbieter). Gesagt getan. Bei dem Projekt handelt es sich um diverse C++ (ohne CLR Support) Libraries.

    Für die Interaktion mit dem .NET wurde ein C++ Projekt geschrieben (mit clr Support) das als Schnittstelle dienen soll. Diese dll linkt alle zur compilezeit alle Objektlibraries die von den anderen C++ Projekten generiert werden.

    Das C++ Schnittstellen Projekt muss trotz des CLR Support eine eindeutige ZielPlattform habe (x86 oder x64).

    Als ich dann einen verweis auf die Schnittstellenbibliothek innerhalb eines bestehendes Projektes machen wollte, sagt mir visual studio das die Zielplattformen nicht übereinstimmen und ich das Projekt nicht erstellen könnte solange das der Fall ist. (Bestehendes Projekt AnyCPU, Schnittstelle x86).

    Ich hatte mich dann dazu entschieden zwei versionen zu führen. Ich habe also die Schnittstelle für beide Plattformen (x86,x64) compiliert.

    Dann war mein Gedanke die über Reflection zur Laufzeit zu laden (Abhängig von dem aktuellen Betriebssystem).

    Die Fallunterscheidung macht er einwandfrei. Unter einem x64 System versucht er die PDFLibNet64.dll zu laden und unter 32 bit versucht er die PDFLibNet32.dll Library zu laden.

    Aber ich erhalte jedesmal so eine verdamte Formatexception (siehe screenshot).

    Screenshot der Meldung

    Ich dachte ich könnte das Problem beheben indem ich das ganze signiere aber das hat garnichts gebracht.

    Ich flip echt gleich aus. Ich meine die assemblies wurde frisch gebuilded und kopiert und er sagt mir das die nicht übereinstimmen?! Da kommt man sich schon verarscht vor xD.

    Ich bin für jeden Hinweis dankbar :).

    Lg

    Q-Tec


    Dev86 Technical Blog

    Montag, 1. Oktober 2012 10:09

Antworten

Alle Antworten

  • Hallo,

    laut der Meldung versucht .NET immer noch auf x86 (32-Bit) Assembly zuzugreifen, was nun mal prinzipiell nicht geht.

    Was u. a. die Ursache darin haben kann, dass beim DllImport der Namen 32-Bit-Assembly angegeben wurde. Beachte, dass die Prozessorarchitektur Teil eines starken Namens ist, so dass Du dort zwei unterschiedliche Assemblies bekommst, mehr siehe So sucht Common Language Runtime nach Assemblys (hier bes. Schritt 2.)

    Damit das funktionieren kann, müsste die (unmanaged) Bibliothek den gleichen Namen tragen und in unterschiedlichen Verzeichnissen liegen, siehe z. B.:
    http://stackoverflow.com/questions/108971/using-side-by-side-assemblies-to-load-the-x64-or-x32-version-of-a-dll

    Gruß Elmar
    Montag, 1. Oktober 2012 10:29
    Beantworter
  • Entschuldigung das ich das jetzt so verwirrend erklärt habe.

    Das Problem ist auf beiden System existent. Die Meldung von der ich einen Screenshot gemacht habe trat auf einem 32Bit Betriebsystem auf (also hat er sich theoretisch schonmal die richtige gekrallt).

    Das ganze Konstrukt lässt sich wie folgt abbilden:

    Abbildung der aktuellen Konstelation

    Ich habe für alle vier Bibliotheken seperat snk dateien erstellen lassen und diese den Projekte zugewiesen. Sie haben auch alle unterschiedliche Public Key Tokens. (Sind aber nicht im GAC registriert(gewollt)).

    Aufgerufen werden diese wie folgt (Wundert euch nicht über den Sinnlosen Switch Block in der LoadLibrary Funktion. Da hab ich diverse Möglichkeiten ausgprobiert (ohne Erfolg)):

    private const string MODULE32 = "Mag.Mcc.Interop.PDFLibNet32.dll"; private const string MODULE64 = "Mag.Mcc.Interop.PDFLibNet64.dll"; private const string TARGETCLASS = "Mag.Mcc.Interop.PDFLibNet.PDFController"; private const string PUP32KEY = "9ea8a38a6b339785"; private const string PUP64KEY = "84a9e417134128a0"; private void IntializeLateBinding() {

    if (OSINFO.Is64BitOperatingSystem() && File.Exists(string.Format("{0}\\{1}", enviroment, MODULE64))) { if (!LoadLibrary(string.Format("{0}\\{1}", enviroment, MODULE64), CpuContext.x64)) if (File.Exists(string.Format("{0}\\{1}", enviroment, MODULE32))) { if (!LoadLibrary(string.Format("{0}\\{1}", enviroment, MODULE32), CpuContext.x86)) throw new DllNotFoundException(string.Format("Couldn't load the library {0}", string.Format("{0}\\{1}", enviroment, MODULE32))); } else { throw new DllNotFoundException(string.Format("Couldn't load the library {0}", string.Format("{0}\\{1}", enviroment, MODULE64))); } LoadType(); CreateInstance(); viewerPanel = PanelContainer; if (viewerPanel != null) { viewerPanel.Dock = DockStyle.Fill; this.Controls.Add(viewerPanel); } } else if (File.Exists(string.Format("{0}\\{1}", enviroment, MODULE32))) { LoadLibrary(string.Format("{0}\\{1}", enviroment, MODULE32), CpuContext.x86); LoadType(); CreateInstance(); viewerPanel = PanelContainer; if (viewerPanel != null) { viewerPanel.Dock = DockStyle.Fill; this.Controls.Add(viewerPanel); } } else { throw new DllNotFoundException(string.Format("Dll not found! (64bit = {0}, 32bit = {1})", MODULE64, MODULE32)); } } private bool LoadLibrary(string dll, CpuContext context) { try { //Hier geht er jedesmal beim Load in den Catch Block (unabhängig von der Plattform) switch (context) { case CpuContext.x64 : //System.Security.Policy.Evidence evidence; //interopAssemb = Assembly.LoadFile(dll + ", Version=1.0.0.0, Culture=neutral, PublicKeyToken=84a9e417134128a0");//string.Format("{0},Version=1.0.0.0,Culture={1},PublicKeyToken={2}", dll , "neutral", PUP64KEY)); interopAssemb = Assembly.LoadFrom(dll); return true; break; case CpuContext.x86 : //System.Security.Policy.Evidence evidence; //interopAssemb = Assembly.LoadFile( dll + ", Version=1.0.0.0, Culture=neutral, PublicKeyToken=9ea8a38a6b339785");//string.Format("{0},Version=1.0.0.0,Culture={1},PublicKeyToken={2}", dll, "neutral", PUP32KEY)); interopAssemb = Assembly.LoadFrom(dll); return true; break; } } catch(Exception ex) { } return false; } private void LoadType() { baseType = interopAssemb.GetType(TARGETCLASS); } private void CreateInstance() { obj = Activator.CreateInstance(baseType, false); }

    Lg

    Q-Tec

    PS:

    Danke für die Links ich werd Sie mir mal durchlesen.


    Dev86 Technical Blog

    Montag, 1. Oktober 2012 11:29
  • Hab deine Links mal durchgelesen und bin mir leider nicht so ganz sicher ob das mit der Eventhandling bei mir umsetzbar ist.

    Was mich aber brennend Interessieren würde ist folgende Aussage:

    Zitat aus dem Msdn Beitrag:

    ---------------------------------------------------------------------------------------------------------------

    Um Ihre .NET Framework-Anwendung erfolgreich bereitstellen zu können, müssen Sie mit dem Verfahren vertraut sein, mit dem die Common Language Runtime die Assemblys sucht und bindet, aus denen Ihre Anwendung zusammengesetzt ist. Standardmäßig versucht die Common Language Runtime, die genaue Version einer Assembly einzubinden, mit der die Anwendung erstellt wurde. Dieses Standardverhalten kann durch Einstellungen in der Konfigurationsdatei überschrieben werden.

    ---------------------------------------------------------------------------------------------------------------

    Kann ich ihm in der Config Datei sagen das er die Signaturen Ignorieren soll und stattdessen einfach die Dll Laden soll die ich ihm übergebe? Ich weiß ja schließlich das die dll über alle Funktionen verfügt die benötigt werden. Das wäre ja wie Weihnachten für mich. Ginge das irgendwie?

    Lg

    Q-Tec


    Dev86 Technical Blog

    Montag, 1. Oktober 2012 11:55
  • Hallo,

    eine BadImageFormatException tritt üblicherweise auf, wenn x86 <-> x64 nicht zusammenpassen (oder die Assembly kaputt ist).

    Den Test auf 64-Bit-OS solltest Du um einen Test auf einen 64-Bit-Prozess ergänzen, siehe http://stackoverflow.com/questions/266082/how-do-i-tell-if-my-application-is-running-as-a-32-or-64-bit-application, denn dort ist beides möglich.

    Das Laden sollte so früh wie möglich geschehen, denn sobald ein Verweis verwendet wird, wird die Assembly geladen und es ist dann zu spät - hier besser nicht erst im Formular.

    Das Auflösen kann entweder über AssemblyResolve passieren oder aber über SetDllDirectory (soweit es die unmanaged Bibliothek angeht), dazu Plattform-Unterscheidung für DLL-Einbindung über "DllImport"

    Die Datei muss dabei gleich heißen - vollqualifizierte Namen helfen dabei nicht, sondern sind wieder unterschiedlich.

    Für einen Test solltest Du Dich ins AssemblyResolve einklinken, um zu sehen, was wirklich passiert.

    Gruß Elmar

    Montag, 1. Oktober 2012 14:06
    Beantworter
  • Hallo Elmar,

    ich hab das mit dem Event mal ausporbiert und musste feststellen das er da garnicht reinläuft.

    Statt des AssemblyLoad Events muss ich das Event ReflectionOnlyAssemblyResolve handlen (die dll wird ja über Reflection reingeladen und wurde nicht als Verweis referenziert).

    Allerdings geht er da leider auch nicht rein sondern steigt bei Assembly.Load direkt in den Catch Block.

    Lg

    Simon


    Dev86 Technical Blog

    Dienstag, 2. Oktober 2012 05:52
  • Hallo,

    Wirf mal den Fusion Log Viewer an, da siehst Du alle Ladevorgänge.  Etwas bunter dazu Scott Hanselmann: Back to Basics: Using Fusion Log Viewer to Debug Obscure Loader Errors

    Deine Versuche solltest Du mit einem Minimalprojekt machen (und nicht den 260 anderen). Das könntest Du dann ggf. bereitstellen.

    AssemblyResolve wird nur für abhängige Assemblies ausgeführt. Wenn Du direkt ein Assembly.Load wie oben ausführst, wird die Assembly direkt verwendet.

    ReflectionOnlyAssemblyResolve[1] ist primär für Tools (wie z. B. .NET Reflector) gedacht, dabei wird die Assembly nicht für die Ausführung geladen - ist also ungeeignet. Der Name ist hier etwas irreführend.

    [1] zu den Besonderheiten siehe Junfeng Zhang: http://blogs.msdn.com/b/junfeng/archive/2004/08/24/219691.aspx

    Dienstag, 2. Oktober 2012 06:28
    Beantworter
  • Hi,

    danke erstmal für deine tatkräftige Unterstützung und sorry das ich mich solange nicht gemeldet hatte (Urlaub :)).

    So ich habe jetzt mal beide Events implementiert. (AssemblyResolve & ReflectionOnlyAssemblyResolve)

    Das Resultat war das folgende:

    Ich bekam immernoch den Fehler (BadFormatException) ohne das er in das Event reingesprungen ist.

    Dann hatte ich testweise LoadFrom durch Load ersetzt und bekam keinen Fehler mehr.

    Er ist dann auch in das AssemblyResolve Event reingesprungen und ich habe in dem Handler manuel versucht die Assembly zu laden und zurückzugeben.

    Aber das scheint mir eine Endlosscheife zu sein. Er geht solange in den AssemblyResolveHandler bis er eine StackOverflowException wirft.

    Das ganze sieht dann wie folgt aus:

    Screenshot

    Ich habe das ganze nun als "minimal" Projekt aus der Projektstruktur gelöst um es euch bereitzustellen. Wenn Ihr möchtet werft einen Blick drauf, ich bin mit meinem Latein am Ende.

    Dabei habe ich es in drei Zip Dateien gesplitet.

    In dieser Zip Datei befindet sich das TestProjekt inklusive der (AnyDebug) PDFViewerControl Library, die das Latebinding durchführt. Beide Projekte sind als Sourcecode verfügbar nur die Plattformspezifische Interop Dll und die C++/Clr Library sind als Binaries in den Debugverzeichnissen.

    -----------------------------------------------------------------------------------------------------------------

    Den Sourcecode der 32Bit Interop und C++ Projekten könnt Ihr euch hier holen und die 64 Bit Projekte gibts hier.

    Ich hab deshalb diese Trennung gemacht, da es ziemlich tricky ist die Platformspezifischen Libraries zu compilieren.

    Es muss ein Visual Studio 2008SP1 (inkl vc 64bit compiler) sowie ein Visual Studio 2010 installiert sein. Zudem muss man das Hotfix KB976656 installiert haben ansonsten steigt der optimizer während des compilevorgangs aus und wirft den kompletten build.

    Die richtigen Solutionfiles für die beiden Ziparchive sind:

    32Bit Archiv:xpdfWin-Interop-2010x32.sln

    64Bit Archiv:xpdfWin-Interop-2010x64.sln

    -----------------------------------------------------------------------------------------------------------------

    Ich bin wie immer für jeden Vorschlag zu haben :).

    Lg und Danke

    Q-Tec


    Dev86 Technical Blog

    Sonntag, 14. Oktober 2012 00:57
  • Hallo,

    entschuldigt den Doppelpost, allerdings hat sich etwas neues ergeben.

    Auf Desktoprechner lädt er aktuell die 32Bit Version zuverlässig und fehlerfrei.

    Auf einem Windows Server 2008 R2 bekomme ich jedoch folgenden Fehler:

    Siehe Screenshot

    Im Eventlog wurde folgender Eintrag generiert:

    -------------------------------------------------------------------------------

    Fehler beim Generieren des Aktivierungskontextes für "C:\Debug\Debug\PDFLibNet32.dll".
    Die abhängige Assemblierung "Microsoft.VC90.DebugCRT,processorArchitecture="x86",publicKeyToken="1fc8b3b9a1e18e3b",type="win32",version="9.0.21022.8"" konnte nicht gefunden werden.
    Verwenden Sie für eine detaillierte Diagnose das Programm "sxstrace.exe".

    -------------------------------------------------------------------------------

    Ich hatte bereits gesucht und bin auf folgenden Eintrag von Microsoft gestoßen:

    http://support.microsoft.com/kb/2507938

    Dort gibt es folgenden Lösungsvorschlag:

    "Installieren Sie Sicherheitsupdate 2567680, um dieses Problem zu beheben. Weitere Informationen zum Sicherheitsupdate 2567680 finden Sie im folgenden Artikel der Microsoft Knowledge Base:
    2567680 MS11-063: Sicherheitsrisiko im Windows Client-/Server-Laufzeitsubsystem kann Rechteerweiterungen ermöglichen: 09. August 2011"

    Die Installation eben dieses Sucherheitsupdates hat das Problem leider nicht gelöst. Ebenfalls habe ich bereits das "Microsoft Visual C++ 2010 Redistributable Package" und das "Microsoft Visual C++ 2008 Redistributable Package" neuinstalliert.

    Auch dieser Schritt hat nichts geholfen.

    Weiß jemand darüber Bescheid und kennt einen guten Lösungsweg?

    Lg

    Q-Tec


    Dev86 Technical Blog

    Montag, 15. Oktober 2012 14:08
  • Hallo,

    ich hab das Problem lösen können. Musste lediglich die Debug Informationen entfernen und neu compilieren.


    Dev86 Technical Blog

    Dienstag, 16. Oktober 2012 17:57