none
DLL vernünftig einbinden RRS feed

  • Frage

  • Hallo, ich bin kurz vor der Fertigstellung einer plattformunabhängigen Library. Natürlich lege ich großen Wert darauf, dass sie auch von Windows-Nutzern bequem verwendet werden kann. Daher bin ich dabei, ein kleines Beispielprojekt zu erstellen um die Kompatibilität zu testen und Windows-Nutzern ein funktionierendes Beispiel zur Verfügung stellen zu können.

    Es ist mir bereits gelungen, die von mir erstellte dll erfolgreich zu testen, in dem ich die einzelnen Funktionen über folgenden Aufruf eingebunden habe.

    [DllImport("myproject.dll", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
    private static extern void myfunction();

    Diese Lösung ist meiner Meinung nach eine Zumutung. Es ist unmöglich, die Abhängigkeit der erstellten Binärdatei von meiner Library nachzuweisen und ich habe den Verdacht, dass die einzelnen Importe womöglich die Performance beeinträchtigen. Außerdem ist es beim Testen echt furchtbar, wenn man für jede Änderung die Importe anpassen muss.

    Wie kann ich denn einfach meine header-Datei einbinden oder die DLL im Projekt verknüpfen???

    Dienstag, 12. Februar 2013 17:52

Alle Antworten

  • Hallo Jacob,

    Das .NET Framework ist eine eigene Umgebung. Was Du dort einbindest, ist für die CLR unverwalteter Legacy - Code, über den die Laufzeitumgebung keine Kontrolle hat.

    Und ja, unmanaged Code hat einen Overhead, was den Aufruf angeht; Deine Bibliothek sollte einen entsprechenden Gegenwert dafür liefern.

    Der Weg für das Einbinden solcher - im übrigen platformabhängigen => x86, x64, Itanium - Bibliotheken erfolgt entweder über DllImport oder alternativ über eine COM-Schnittstelle, siehe
    Interoperation mit nicht verwaltetem Code

    Zu der gezeigten Signatur sei gesagt:

    Standard-Aufruf Konvention für Windows allgemein ist StdCall,  siehe Argument Passing and Naming Conventions

    SuppressUnmanagedCodeSecurity ist ein "No Go" für externe Bibliotheken; damit werden die Schutzmechanismen von .NET ausgehebelt (und ein erfahrenerer .NET Programmierer lässt von solchen Bibliotheken i. a. die Finger).

    Gruß Elmar

    Dienstag, 12. Februar 2013 19:03
  • Hallo Elmar,

    vielen Dank für die schnelle Antwort. Du hast natürlich Recht. Mein Quelltext ist plattformunabhängig aber die Binaries sind jeweils plattformgebunden. Die Aufrufkonvention könnte ich problemlos über eine Präprozessoranweisung in StdCalls umwandeln, wenn ich für Windows kompiliere und die SuppressUnmanagedCodeSecurity-Anweisung ist mir ja auch ein Dorn im Auge.

    Vielleicht hätte ich gar nicht so weit ausholen sollen. Mein Anliegen ist ganz generell die Frage, wie ich eine dll, die mit dem GNU-Compiler aus ANSI-C Quelltext gebaut wurde, für die Nutzergemeinde von VisualStudio möglichst komfortabel zur Verfügung stellen kann.

    Ich habe mich dabei bisher an die Beschreibung unter "Verwenden nicht verwalteter DLL-Funktionen" gehalten und eine Klasse erstellt, die die einzelnen Plattformaufrufe enthält und die Parameterübergabe anpasst. Da ich zum Teil Pointer auf Strukturen übergebe werde ich da auch nicht dran vorbeikommen. Ich habe eh schon einen wrapper, der für c++ Programmierer ein Klassensystem zur Verfügung stellt.

    Gruß

    Dienstag, 12. Februar 2013 20:29
  • Hallo Jacob,

    schon eine sauberere managed Schnittstelle macht einiges an Arbeit... Und wenn Du nicht "nur" das .NET Framework, sondern Windows Entwickler allgemein bedienen willst, so sind das unterschiedliche Bereiche. Das ist traditionell gewachsen und leider nie so richtig zusammengekommen - und wie an Windows 8 RT zu sehen, wird es wohl auch nicht so bald einheitlich.

    Was .NET angeht, so sollltest Du zunächst eine Trennung der unmanaged Aufrufe in interne Klassen vornehmen, siehe dazu u. a.:
    CA1060: Move P/Invokes to NativeMethods class

    FAQ: How do I fix a violation of MovePInvokesToNativeMethodsClass?
    (weiteres findet man über Google)

    Darüber würden die öffentlichen managed Klassen auf Deine Bibliothek zugreifen.

    "Pointer auf Strukturen" sind in .NET tabu, denn .NET typische Sprachen (C#/VB) kennen keine Pointer (unsafe aussen vorgelassen). Deine Klassen sollte nach außen als .NET Klassen definiert werden, siehe  Design Guidelines for Developing Class Libraries

    Wenn Pointer als Übergabe notwendig sind, sollte die Klasse es intern verwalten und abgewickeln. Das kann u. U. heissen, dass manche Elemente zweifach implementiert werden. Als Beispiel: System.Drawing.Rectangle entspricht in der Datenstruktur einem RECT. Die Platformaufrufe verwenden ein RECT, in .NET selbst arbeitet man mit Rectangle.

    Für die nativen (C++) Programmierer unter Windows wiederum würde man ein Standard-API bereit stellen. Ist Deine Bibliothek auch für Office, Windows Shell und Co.  interessant könnte auch eine COM-Schnittstelle in Betracht kommen. Letztere kann auch in .NET verwendet werden und könnte insofern eine Alternative zu mehreren Schnittstellen-Implementationen sein.

    Gruß Elmar

    Dienstag, 12. Februar 2013 22:32
  • Hallo Elmar,

    das sind genau die Informationen, die ich haben wollte. Der internal-Schlüssel sorgt also dafür, dass die unmanaged Aufrufe für die öffentlichen Klassen nicht zugänglich sind. Ich nehme an, internal arbeitet auf Dateiebene und ist daher sicherer als mein private-Schlüssel. Bei mir war auch noch alles in einer einzigen Klasse. Habe jetzt mal mit folgendem Test angefangen:

    using System;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Security.Permissions;

    public static class myclass
    {
        public static List<double> myPublicFunction
        {
            get
            {
                new UIPermission(UIPermissionWindow.AllWindows).Demand();
                return UnsafeNativeMethods.myPrivateFunction();
            }
        }
    }

    [SuppressUnmanagedCodeSecurityAttribute]
    internal static class UnsafeNativeMethods
    {
        [DllImport("mydll.dll", CharSet = CharSet.Auto, ExactSpelling = true)]       
        internal static extern double* myFunction();

        internal static unsafe List<double> myPrivateFunction()
        {
            double* value;
            List<double> result = new List<double>();
            value = myFunction();
            while (value != null)
            {
                result.Add(*value);
                value++;
            }
            return result;
        }
    }

    Ich habe nur etwas noch nicht ganz verstanden: teile ich die Funktionen nach eigenem Ermessen in Safe und Unsafe ein oder gibt es da feste Richtlinien und Kriterien? Sollte ich ggF. zwei native Klassen anlegen eine für Safe und eine für Unsafe?

    Es hört sich tatsächlich nach sehr viel Arbeit an. Ich werde das wahrscheinlich Schritt für Schritt umsetzen, da es noch nicht eilig ist. Die DLL wird aber immer noch zur Laufzeit eingebunden - das muss ich wohl in Kauf nehmen.

    Mittwoch, 13. Februar 2013 13:39
  • Die Nutzung in Office ist erstmal uninteressant und für die Shell-Ebene habe ich auch ein build-target als Konsolenanwendung. Es geht zunächst um das .NET Framework.


    Danke und Gruß
    Mittwoch, 13. Februar 2013 13:56
  • Hallo Jacob,

    fangen wir am Ende an: Die .NET CLR bindet alles zur Laufzeit ein, wie die verwalteten Assemblies, ein statisches Binden kennt es in dem Sinne nicht.

    Mit C++ / CLR kann man zwar Mixed Mode Assemblies erzeugen, das bringt aber oft mehr Komplexität als Nutzen. Und würde in Deinem Falle einer Portabilität vermutlich im Wege stehen.

    Zu Safe vs. Unsafe-NativeMethods:

    Das liegt letztendlich in Deinem Ermessen. Wie bei den Links auch nachzulesen, sollte Unsafe für solche Methoden verwendet werden, die intensiv geprüft werden, weil sie die Sicherheit beeinträchtigen können. Was wiederum davon abhängt welche Funktionen die nativen Methoden ausführen.

    Um es Einzuordnen muss man sich wiederum mit der .NET Sicherheit beschäftigen - Warnung vorab: ein ziemlich komplexes Thema.

    Lies Dir dazu mal durch: Security Changes in the .NET Framework 4
    (und die dortigen Themen nach Bedarf - es ist einiges)

    Dabei zu beachten: Zwischen .NET 2.0/3.5 und .NET 4.x hat ein Paradigmenwechsel stattgefunden. Mit .NET 4.0 die Code Access Security (CAS) deutlich einfacher geworden.

    Da Du oben UIPermission (aus den Beispielen) verwendet hast, prüfe bitte, ob das in Deinem Falle wirklich zutrifft - gedacht ist er für Code, der auf die Benutzeroberfläche zugreift, siehe Code Access Permissions.

    Gruß Elmar

    Mittwoch, 13. Februar 2013 19:02
  • Hallo Jacob,

    Da Du nichts zur Funktionalität Deiner Bibliothek gesagt hast, belasse ich es bei: Die Windows Shell[1] (aka Windows Explorer) exportiert ihre Funktionalität im wesentlichen über COM.

    Gruß Elmar

    [1] BTW mögen die Entwickler .NET dort nicht besonders, siehe das Fette in Guidance for Implementing In-Process Extensions (Windows).
    Und haben für Windows RT .NET neu erfunden - IMHO ein NIH Syndrom ;)

    Mittwoch, 13. Februar 2013 19:12
  • Hallo Elmar,

    vielen Dank für Deine kompetente Hilfe. Ich hätte gar nicht erwartet hier so schnell so gute Antworten zu bekommen. Meine Bibliothek ist über die letzten Jahre entstanden und soll Programmierern höherer Sprachen die einfache Darstellung von 3D-Objekten bieten. Ich weiss, das ist nichts Neues aber sie ist sehr schlank und konnte bisher zwei Forschungsprojekten eine einfache Darstellung geben und ein kommerzielles Projekt erheblich beschleunigen.

    Außerdem ist sie mir über die Zeit sehr ans Herz gewachsen und bietet nicht nur Darstellungs- sondern auch Bearbeitungsfunktionen, die aber bisher in keiner Anwendung zur Geltung kommen. Ich habe vor sie (hoffentlich) 2014 als uneingeschränkte OpenSource-Version unter LGPL zu veröffentlichen. Bei den Forschungsprojekten wird bereits c# mit .NET-Bedienelementen verwendet. Jedoch mit der für mich unbefriedigenden Aufrufprozedur, die ich oben beschrieben habe.

    Mich würde sehr interessieren, welche Anbindung Du für VisualStudio-Benutzer interessant fändest. Zunächst gibt es die Anwendung als reiner Viewer von beliebigen geometrischen Modellen oder geometrisch dargestellten Rechenergebnissen. In diesem Fall hält sich die Interprozesskommunikation eher in Grenzen (übergabe des Modells und dann Bewegung, Transformation und Rotation von Objekten, Gesamtmodell oder Kamera). Für diese Aufgaben bietet der Viewer auch einen geschützten Modus in dem nur intern mit Pointern gearbeitet wird und der externe Programmierer mit absturzsicheren IDs arbeitet. In diesem Modus könnte man darüber hinaus auch begrenzt editieren und csg-Operationen durchführen sowie Objekte laden und speichern.

    Wenn aber in hohem Maße interaktiv auf das Modell zugegriffen werden soll, bieten sich sehr viele weitere (unsicherere) Möglichkeiten, bei denen ich nicht noch sicher bin, ob ich sie in eine VisualStudio-Schnittstelle implementieren kann. Der Viewer bietet z.B. eine Ereignisbehandlung der Funktions-Pointer zugewiesen werden können. In C++ (bisher nur GNU) kann man die einzelnen Nodes des Szenegraphs (von denen es verschiedene Arten gibt) natürlich als Basisklassen verwenden und um eigene Datenstrukturen und Funktionalität erweitern. Diese kann man dann nach Belieben in den Szenegraphen einhängen.

    Es besteht auch eine Konfiguration, die als server läuft und eine socket-Verbindung zuläßt. Das war mal ein Enwurf für eine Schnittstelle mit einem anderen Projekt, die wir dann aber verworfen haben.

    Vielen Dank für Deine Geduld und Gruß

    Donnerstag, 14. Februar 2013 23:37