Wie entwerfe ich ein C ++ - Programm, um den Laufzeitimport von Funktionen zu ermöglichen?

10

Heute möchte ich Ihnen eine Frage zu den Fähigkeiten von C ++ stellen, eine bestimmte Softwarearchitektur zu realisieren.

Natürlich habe ich die Suche verwendet, aber keine direkt verknüpfte Antwort gefunden.

Grundsätzlich ist es mein Ziel, ein Programm zu erstellen, mit dem der Benutzer beliebig zusammengesetzte physikalische Systeme, z. B. ein fahrendes Auto, modellieren und simulieren kann. Ich gehe davon aus, eine Bibliothek physikalischer Modelle (Funktionen innerhalb von Klassen) zu haben. Jede Funktion kann abhängig von der zugrunde liegenden physikalischen Beschreibung einige Eingaben haben und einige Ausgaben zurückgeben, z. B. ein Verbrennungsmotormodell, ein Luftwiderstandsmodell, ein Radmodell usw.

Die Idee ist nun, dem Benutzer ein Framework bereitzustellen, mit dem er alle Funktionen gemäß seinen Anforderungen zusammenstellen kann, dh jedes physische Verhalten abbilden kann. Das Framework sollte Funktionen zum Verbinden der Ausgänge und Eingänge verschiedener Funktionen bereitstellen. Daher stellt das Framework eine Containerklasse bereit. Ich nenne es KOMPONENTE, die ein oder mehrere Modellobjekte aufnehmen kann (FUNCTION). Diese Container können auch andere Komponenten (vgl. Composite Pattern) sowie die Verbindungen (CONNECTOR) zwischen den Funktionsparametern enthalten. Darüber hinaus bietet die Komponentenklasse einige allgemeine numerische Funktionen wie den mathematischen Löser usw.

Die Zusammensetzung der Funktionen sollte zur Laufzeit erfolgen. In erster Linie sollte der Benutzer in der Lage sein, eine Komposition durch Importieren einer XML-Datei einzurichten, die die Kompositionsstruktur definiert. Später könnte man sich vorstellen, eine GUI hinzuzufügen.

Zum besseren Verständnis hier ein sehr vereinfachtes Beispiel:

<COMPONENT name="Main">
  <COMPONENT name="A">
    <FUNCTION name="A1" path="lib/functionA1" />
  </COMPONENT>
  <COMPONENT name="B">
    <FUNCTION name="B1" path="lib/functionB1" />
    <FUNCTION name="B2" path="lib/functionB2" />
  </COMPONENT>
  <CONNECTIONS>
    <CONNECTOR source="A1" target="B1" />
    <CONNECTOR source="B1" target="B2" />
  </CONNECTIONS>        
</COMPONENT>

Es ist nicht notwendig, tiefer in die Fähigkeiten des Frameworks einzutauchen, da mein Problem viel allgemeiner ist. Wenn der Framework-Code / das Framework-Programm kompiliert wird, sind die Beschreibung des physischen Problems sowie die benutzerdefinierten Funktionen nicht bekannt. Wenn der Benutzer (über XML oder später über eine GUI) eine Funktion auswählt, sollte das Framework die Funktionsinformationen lesen, dh die Informationen der Eingabe- und Ausgabeparameter abrufen, um dem Benutzer die Möglichkeit zu bieten, die Funktionen miteinander zu verbinden.

Ich kenne die Prinzipien der Reflexion und bin mir bewusst, dass C ++ diese Funktion nicht bietet. Ich bin mir jedoch sicher, dass das Konzept "Objekte zur Laufzeit erstellen" sehr häufig erforderlich ist. Wie soll ich meine Softwarearchitektur in C ++ einrichten, um mein Ziel zu erreichen? Ist C ++ die richtige Sprache? Was übersehen ich?

Danke im Voraus!

Prost, Oliver

Oliver
quelle
C ++ verfügt über Funktionszeiger und Funktionsobjekte. Sind alle Funktionen in der ausführbaren Datei kompiliert oder befinden sie sich in dynamischen Bibliotheken (auf welcher Plattform)?
Caleth
1
Die Frage ist zu weit gefasst in dem Sinne, dass normalerweise ein Universitätsabschluss in Elektrotechnik / [Electronic Design Automation (EDA)] ( en.wikipedia.org/wiki/Electronic_design_automation ) oder Maschinenbau / Computer Aided Design (CAD) erforderlich ist . Vergleichsweise ist das Aufrufen der dynamischen C / C ++ - Bibliothek sehr einfach, siehe C-Aufrufkonventionen für x86 . Möglicherweise müssen jedoch die Werte für den Stapel (über den CPU-Stapelzeiger) und die CPU-Register geändert werden.
Rwong
1
Das dynamische Laden von Funktionen wird von der C ++ - Sprache nicht unterstützt. Sie müssen sich etwas Plattformspezifisches ansehen. Beispielsweise sollte ein C ++ - Compiler unter Windows Windows-DLLs unterstützen, die eine Form der Reflexion unterstützen.
Simon B
In C ++ ist es sehr schwierig, eine Funktion aufzurufen, deren Signatur (Argument- und Rückgabetypen) zur Kompilierungszeit nicht bekannt ist. Dazu müssen Sie wissen, wie Funktionsaufrufe auf Assembly-Ebene der von Ihnen ausgewählten Plattform funktionieren.
Bart van Ingen Schenau
2
Ich würde dies lösen, indem ich C ++ - Code kompiliere, der einen Interpreter für jede Sprache erstellt, die einen eval-Befehl unterstützt. Bang Problem mit c ++ gelöst. : P Bitte überlegen Sie, warum das nicht gut genug ist und aktualisieren Sie die Frage. Es hilft, wenn die tatsächlichen Anforderungen klar sind.
candied_orange

Antworten:

13

In reinem Standard-C ++ können Sie "den Laufzeitimport von Funktionen nicht zulassen". Gemäß dem Standard ist der Satz von C ++ - Funktionen zur Erstellungszeit (in der Praxis zur Verbindungszeit) statisch bekannt , da er aus der Vereinigung aller Übersetzungseinheiten , aus denen Ihr Programm besteht, festgelegt wurde.

In der Praxis läuft Ihr C ++ - Programm die meiste Zeit (mit Ausnahme eingebetteter Systeme) über einem bestimmten Betriebssystem . Lesen Sie Betriebssysteme: Drei einfache Teile für einen guten Überblick.

Mehrere moderne Betriebssysteme ermöglichen das dynamische Laden von Plugins . POSIX spezifiziert insbesondere dlopen& dlsym. Windows hat etwas anderes LoadLibrary(und ein minderwertiges Verknüpfungsmodell; Sie müssen die betroffenen Funktionen, die von Plugins bereitgestellt oder verwendet werden, explizit mit Anmerkungen versehen). Übrigens können Sie unter Linux praktisch dlopenviele Plugins (siehe mein manydl.cProgramm , mit genügend Geduld kann es dann fast eine Million Plugins laden). Ihr XML-Ding könnte also das Laden von Plugins fördern. Ihre Beschreibung für mehrere Komponenten / mehrere Anschlüsse erinnert mich an Qt-Signale und Steckplätze (für die ein mocPräprozessor erforderlich ist ; möglicherweise benötigen Sie auch so etwas).

Die meisten C ++ - Implementierungen verwenden Name Mangling . Aus diesem Grund sollten Sie extern "C"die Funktionen, die sich auf Plugins beziehen (und in diesen definiert sind und auf die über dlsymdas Hauptprogramm zugegriffen wird ) , besser deklarieren . Lesen Sie das C ++ dlopen mini HowTo (zumindest für Linux).

BTW, Qt und POCO sind C ++ - Frameworks, die einen portablen und übergeordneten Ansatz für Plugins bieten. Mit libffi können Sie Funktionen aufrufen, deren Signatur nur zur Laufzeit bekannt ist.

Eine andere Möglichkeit besteht darin, einen Interpreter wie Lua oder Guile in Ihr Programm einzubetten (oder einen eigenen zu schreiben, wie es Emacs getan hat). Dies ist eine starke architektonische Entscheidung. Vielleicht möchten Sie Lisp In Small Pieces und Programmiersprache Pragmatik lesen, um mehr zu erfahren .

Es gibt Varianten oder Mischungen dieser Ansätze. Sie könnten eine JIT-Kompilierungsbibliothek verwenden (wie libgccjit oder asmjit). Sie können zur Laufzeit C- und C ++ - Code in einer temporären Datei generieren , als temporäres Plugin kompilieren und dieses Plugin dynamisch laden (ich habe einen solchen Ansatz in GCC MELT verwendet ).

Bei all diesen Ansätzen ist die Speicherverwaltung ein wichtiges Anliegen (es handelt sich um eine "Ganzprogramm" -Eigenschaft, und was eigentlich die "Hüllkurve" Ihres Programms ist, ändert sich "). Sie brauchen zumindest etwas Kultur über die Müllabfuhr . Lesen Sie das GC-Handbuch für die Terminologie. In vielen Fällen (willkürliche Zirkelverweise, bei denen schwache Zeiger nicht vorhersehbar sind) reicht das Referenzzählschema für C ++ - Smart-Zeiger möglicherweise nicht aus. Siehe auch dies .

Lesen Sie auch über dynamische Software-Updates .

Beachten Sie, dass einige Programmiersprachen, insbesondere Common Lisp (und Smalltalk ), die Idee des Laufzeitimportierens besser verstehen. SBCL ist eine kostenlose Software - Implementierung von Common Lisp und compiliert in Maschinencode auf jeder REPL Interaktion (und ist sogar in der Lage zu garbage collect Maschinencode und kann spart eine ganze Core - Image - Datei , die später leicht wieder gestartet werden kann).

Basile Starynkevitch
quelle
3

Natürlich versuchen Sie, Ihren eigenen Stil von Simulink- oder LabVIEW-Software zu entwickeln, jedoch mit einer unheiligen XML-Komponente.

Im einfachsten Fall suchen Sie nach einer graphorientierten Datenstruktur. Ihre physischen Modelle bestehen aus Knoten (Sie nennen sie Komponenten) und Kanten (Konnektoren in Ihrer Benennung).

Es gibt keinen sprachgesteuerten Mechanismus, um dies zu tun, auch nicht mit Reflexion. Stattdessen müssen Sie eine API erstellen und jede Komponente, die spielen möchte, muss mehrere Funktionen implementieren und die von Ihrer API festgelegten Regeln einhalten.

Jede Komponente muss eine Reihe von Funktionen implementieren, um Folgendes zu tun:

  • Holen Sie sich den Namen der Komponente oder andere Details dazu
  • Ermitteln Sie die Anzahl der Ein- oder Ausgänge, die die Komponente verfügbar macht
  • Fragen Sie eine Komponente nach einer bestimmten Eingabe unserer Ausgabe ab
  • Verbinden Sie Ein- und Ausgänge miteinander
  • und andere

Und das ist nur zum Einrichten Ihres Diagramms. Sie benötigen zusätzliche Funktionen, um zu definieren, wie Ihr Modell tatsächlich ausgeführt wird. Jede Funktion hat einen bestimmten Namen und alle Komponenten müssen diese Funktionen haben. Alles, was für eine Komponente spezifisch ist, muss über diese API auf identische Weise von Komponente zu Komponente erreichbar sein.

Ihr Programm sollte nicht versuchen, diese "benutzerdefinierten Funktionen" aufzurufen. Stattdessen sollte für jede Komponente eine Allzweck-Rechenfunktion oder eine ähnliche Funktion aufgerufen werden, und die Komponente selbst kümmert sich darum, diese Funktion aufzurufen und ihre Eingabe in ihre Ausgabe umzuwandeln. Die Eingabe- und Ausgabeverbindungen sind die Abstraktionen zu dieser Funktion, das ist das einzige, was das Programm sehen sollte.

Kurz gesagt, wenig davon ist tatsächlich spezifisch für C ++, aber Sie müssen eine Art Laufzeitinformationen implementieren, die auf Ihre spezielle Problemdomäne zugeschnitten sind. Mit jeder von der API definierten Funktion wissen Sie, welche Funktionsnamen zur Laufzeit aufgerufen werden müssen, und Sie kennen die Datentypen jedes dieser Aufrufe. Sie verwenden lediglich das normale Laden der alten dynamischen Bibliothek, um dies zu erledigen. Dies wird mit einer angemessenen Menge an Boilerplate kommen, aber das ist nur ein Teil des Lebens.

Der einzige C ++ - spezifische Aspekt, den Sie berücksichtigen sollten, ist jedoch, dass Ihre API eine C-API ist, damit Sie verschiedene Compiler für verschiedene Module verwenden können, wenn Benutzer ihre eigenen Module bereitstellen.

DirectShow ist eine API, die alles tut, was ich beschrieben habe, und ein gutes Beispiel dafür sein kann.

Whatsisname
quelle