Comment concevoir un programme C ++ pour permettre l'importation de fonctions au moment de l'exécution?

10

aujourd'hui, j'aime vous poser une question sur les capacités de C ++ à réaliser une architecture logicielle spécifique.

Bien sûr, j'ai utilisé la recherche mais je n'ai trouvé aucune réponse directement liée.

Fondamentalement, mon objectif est de construire un programme qui permet à l'utilisateur de modéliser et de simuler des systèmes physiques composés arbitrairement, par exemple une voiture qui conduit. Je suppose avoir une bibliothèque de modèles physiques (fonctions au sein des classes). Chaque fonction peut avoir certaines entrées et renvoyer certaines sorties en fonction de la description physique sous-jacente, par exemple un modèle de moteur à combustion, un modèle de traînée aérodynamique, un modèle de roue, etc.

Désormais, l'idée est de fournir à l'utilisateur un cadre lui permettant de composer toutes les fonctions selon ses besoins, c'est-à-dire de cartographier tout comportement physique. Le cadre devrait fournir des fonctionnalités pour connecter les sorties et les entrées de différentes fonctions. Par conséquent, le framework fournit une classe de conteneur. Je l'appelle COMPONENT, qui peut contenir un ou plusieurs objets de modèle (FUNCTION). Ces conteneurs peuvent également contenir d'autres composants (cf. modèle composite) ainsi que les connexions (CONNECTOR) entre les paramètres de la fonction. De plus, la classe de composants fournit des fonctionnalités numériques générales telles que le solveur mathématique, etc.

La composition des fonctions doit être effectuée pendant l'exécution. Dans le premier cas, l'utilisateur doit pouvoir configurer une composition en important un XML qui définit la structure de la composition. Plus tard, on pourrait penser à ajouter une interface graphique.

Pour vous donner une meilleure compréhension, voici un exemple très simplifié:

<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>

Il n'est pas nécessaire d'approfondir les capacités du framework car mon problème est beaucoup plus général. Lorsque le code / programme cadre est compilé, la description du problème physique, ainsi que les fonctions définies par l'utilisateur, ne sont pas connues. Lorsque l'utilisateur sélectionne (via XML ou ultérieurement via une interface graphique) une structure, le framework doit lire les informations de la fonction, c'est-à-dire doit obtenir les informations des paramètres d'entrée et de sortie, afin d'offrir à l'utilisateur la possibilité d'interconnecter les fonctions.

Je connais les principes de la réflexion et je suis conscient que C ++ ne fournit pas cette fonctionnalité. Cependant, je suis sûr que le concept de "construction d'objets pendant l'exécution" est très souvent requis. Comment dois-je configurer mon architecture logicielle en C ++ pour atteindre mon objectif? C ++ est-il le bon langage? Qu'est-ce que j'oublie?

Merci d'avance!

À la vôtre, Oliver

Oliver
la source
C ++ possède des pointeurs de fonction et des objets fonction. Toutes les fonctions sont-elles compilées dans l'exécutable, ou sont-elles dans des bibliothèques dynamiques (sur quelle plateforme)?
Caleth
1
La question est trop large en ce sens qu'elle requiert généralement un diplôme universitaire en génie électrique / [automatisation de la conception électronique (EDA)] ( en.wikipedia.org/wiki/Electronic_design_automation ) ou en génie mécanique / conception assistée par ordinateur (CAO) . Comparativement parlant, appeler la bibliothèque dynamique C / C ++ est très facile, voir Conventions d'appel C pour x86 . Cela peut toutefois nécessiter la manipulation de la pile (via le pointeur de pile CPU) et des valeurs de registre CPU.
rwong
1
Le chargement dynamique des fonctions n'est pas pris en charge par le langage C ++. Vous devrez examiner quelque chose de spécifique à la plate-forme. Par exemple, un compilateur C ++ sous Windows devrait prendre en charge les DLL Windows, qui prennent en charge une forme de réflexion.
Simon B
En C ++, il est vraiment difficile d'appeler une fonction dont la signature (types d'argument et de retour) n'est pas connue au moment de la compilation. Pour ce faire, vous devez savoir comment fonctionnent les appels de fonction au niveau de l'assemblage de la plateforme choisie.
Bart van Ingen Schenau,
2
La façon dont je résoudrais cela est de compiler du code c ++ qui crée un interpréteur pour n'importe quelle langue qui prend en charge une commande eval. Problème de Bang résolu en utilisant c ++. : P Réfléchissez à la raison pour laquelle cela ne suffit pas et mettez à jour la question. Cela aide lorsque les exigences réelles sont claires.
candied_orange

Réponses:

13

En C ++ standard pur, vous ne pouvez pas "autoriser l'importation de fonctions au moment de l'exécution"; selon la norme, l'ensemble des fonctions C ++ est connu statiquement au moment de la construction (en pratique, au moment de la liaison) car il est fixé à partir de l'union de toutes les unités de traduction composant votre programme.

En pratique, la plupart du temps (à l'exclusion des systèmes embarqués), votre programme C ++ s'exécute au-dessus d'un système d'exploitation . Lisez Systèmes d'exploitation: trois pièces faciles pour un bon aperçu.

Plusieurs systèmes d'exploitation modernes permettent le chargement dynamique des plugins . POSIX précise notamment dlopen& dlsym. Windows a quelque chose de différent LoadLibrary(et un modèle de liaison inférieur; vous devez annoter explicitement les fonctions concernées, fournies ou utilisées par les plugins). BTW sous Linux, vous pouvez pratiquement dlopenun grand nombre de plugins (voir mon manydl.cprogramme , avec assez de patience il peut générer puis charger près d'un million de plugins). Donc, votre truc XML pourrait conduire au chargement des plugins. Votre description multi-composant / multi-connecteur me rappelle les signaux et les emplacements Qt (qui nécessitent un mocpréprocesseur ; vous pourriez aussi avoir besoin de quelque chose comme ça).

La plupart des implémentations C ++ utilisent le changement de nom . À cause de cela, vous feriez mieux de déclarer que extern "C"les fonctions liées aux plugins (et définies dans eux, et accessibles par dlsymdepuis le programme principal). Lisez le mini HowTo C ++ dlopen (pour Linux au moins).

BTW, Qt et POCO sont des frameworks C ++ fournissant une approche portable et de niveau supérieur aux plugins. Et libffi vous permet d'appeler des fonctions dont la signature n'est connue qu'au moment de l'exécution.

Une autre possibilité est d'incorporer un interprète, comme Lua ou Guile , dans votre programme (ou d'écrire le vôtre, comme Emacs l'a fait). Il s'agit d'une décision de conception architecturale forte. Vous pouvez lire Lisp In Small Pieces et Programming Language Pragmatics pour plus d'informations.

Il existe des variantes ou des mélanges de ces approches. Vous pouvez utiliser une bibliothèque de compilation JIT (comme libgccjit ou asmjit). Vous pouvez générer au moment de l'exécution du code C et C ++ dans un fichier temporaire, le compiler en tant que plugin temporaire et charger dynamiquement ce plugin (j'ai utilisé une telle approche dans GCC MELT ).

Dans toutes ces approches, la gestion de la mémoire est une préoccupation importante (c'est une propriété de "programme entier", et ce qui est réellement "l'enveloppe" de votre programme "change"). Vous aurez besoin d'au moins un peu de culture sur la collecte des ordures . Lisez le manuel du GC pour la terminologie. Dans de nombreux cas ( références circulaires arbitraires où les pointeurs faibles ne sont pas prévisibles), le schéma de comptage des références cher aux pointeurs intelligents C ++ peut ne pas être suffisant. Voir aussi ceci .

Lisez également la mise à jour dynamique du logiciel .

Notez que certains langages de programmation, notamment Common Lisp (et Smalltalk ), sont plus conviviaux à l'idée d'importer des fonctions d'exécution. SBCL est une implémentation logicielle gratuite de Common Lisp, et compile le code machine à chaque interaction REPL (et est même capable de récupérer le code machine, et peut enregistrer un fichier image de base entier qui peut être facilement redémarré plus tard).

Basile Starynkevitch
la source
3

De toute évidence, vous essayez de rouler votre propre style de logiciel de type Simulink ou LabVIEW, mais avec un composant XML impie.

À la base, vous recherchez une structure de données orientée graphique. Vos modèles physiques sont constitués de nœuds (vous les appelez composants) et d'arêtes (connecteurs dans votre dénomination).

Il n'y a pas de mécanisme imposé par le langage pour le faire, même pas avec réflexion, donc à la place, vous devrez créer une API et tout composant qui veut jouer devra implémenter plusieurs fonctions et respecter les règles définies par votre API.

Chaque composant devra implémenter un ensemble de fonctions pour faire des choses comme:

  • Obtenez le nom du composant ou d'autres détails à ce sujet
  • Obtenez le nombre d'entrées ou de sorties exposées par le composant
  • Interroger un composant sur une entrée particulière de notre sortie
  • Connectez les entrées et les sorties ensemble
  • et d'autres

Et c'est juste pour configurer votre graphique. Vous aurez besoin de fonctions supplémentaires définies pour organiser la façon dont votre modèle est réellement exécuté. Chaque fonction aura un nom spécifique et tous les composants doivent avoir ces fonctions. Tout ce qui est spécifique à un composant doit être accessible via cette API, de manière identique d'un composant à l'autre.

Votre programme ne devrait pas essayer d'appeler ces «fonctions définies par l'utilisateur». Au lieu de cela, il devrait appeler une fonction de calcul à usage général ou une fonction similaire sur chaque composant, et le composant lui-même prend soin d'appeler cette fonction et de transformer son entrée en sa sortie. Les connexions d'entrée et de sortie sont les abstractions de cette fonction, c'est la seule chose que le programme devrait voir.

En bref, peu de ces informations sont spécifiques au C ++, mais vous devrez implémenter une sorte d'informations de type à l'exécution, adaptées à votre domaine de problème particulier. Avec chaque fonction définie par l'API, vous saurez quels noms de fonctions appeler au moment de l'exécution, et vous connaîtrez les types de données de chacun de ces appels, et vous utilisez simplement l'ancien chargement de bibliothèque dynamique régulier pour le faire. Cela viendra avec une bonne quantité de passe-partout, mais cela fait juste partie de la vie.

Le seul aspect spécifique au C ++ que vous voudrez garder à l'esprit est cependant qu'il est préférable que votre API soit une API C afin que vous puissiez utiliser différents compilateurs pour différents modules, si les utilisateurs fournissent leurs propres modules.

DirectShow est une API qui fait tout ce que j'ai décrit et peut être un bon exemple à regarder.

comment s'appelle-t-il
la source