Come progettare un programma C ++ per consentire l'importazione in runtime di funzioni?


10

oggi, mi piace fare una domanda sulle capacità del C ++ di realizzare una specifica architettura software.

Certo, ho usato la ricerca ma non ho trovato alcuna risposta direttamente collegata.

Fondamentalmente, il mio obiettivo è quello di costruire un programma che consenta all'utente di modellare e simulare sistemi fisici composti in modo arbitrario, ad esempio un'auto alla guida. Presumo di avere una libreria di modelli fisici (funzioni all'interno delle classi). Ogni funzione può avere alcuni input e restituire alcuni output a seconda della descrizione fisica sottostante, ad esempio un modello di motore a combustione, un modello di resistenza aerodinamica, un modello di ruota, ecc.

Ora, l'idea è quella di fornire all'utente un framework che gli consenta di comporre qualsiasi funzione in base alle sue esigenze, cioè di mappare qualsiasi comportamento fisico. Il framework dovrebbe fornire funzionalità per collegare gli output e gli input di diverse funzioni. Pertanto, il framework fornisce una classe container. Lo chiamo COMPONENTE, che è in grado di contenere uno o più oggetti modello (FUNZIONE). Questi contenitori possono contenere anche altri componenti (cfr. Modello composito) nonché le connessioni (CONNECTOR) tra i parametri della funzione. Inoltre, la classe componente fornisce alcune funzionalità numeriche generali come il solutore matematico e così via.

La composizione delle funzioni deve essere eseguita durante il runtime. In primo luogo, l'utente dovrebbe essere in grado di impostare una composizione importando un XML che definisce la struttura della composizione. Più tardi, si potrebbe pensare di aggiungere una GUI.

Per darti una migliore comprensione qui è un esempio molto semplificato:

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

Non è necessario approfondire le capacità del framework perché il mio problema è molto più generale. Quando viene compilato il codice / programma del framework, la descrizione del problema fisico e le funzioni definite dall'utente non sono note. Quando l'utente seleziona (tramite XML o successivamente tramite una GUI) una funzione, il framework dovrebbe leggere le informazioni sulla funzione, ovvero dovrebbe ottenere le informazioni sui parametri di input e output, al fine di offrire all'utente la possibilità di interconnettere le funzioni.

Conosco i principi di riflessione e sono consapevole che C ++ non fornisce questa funzionalità. Tuttavia, sono sicuro che il concetto di "costruzione di oggetti durante il runtime" è molto spesso richiesto. Come devo impostare la mia architettura software in C ++ per raggiungere il mio obiettivo? C ++ è la lingua giusta? Cosa trascuro?

Grazie in anticipo!

Saluti, Oliver


C ++ ha puntatori e oggetti funzione. Tutte le funzioni sono compilate nell'eseguibile o si trovano in librerie dinamiche (su quale piattaforma)?
Caleth,

1
La domanda è troppo ampia nel senso che richiede in genere un diploma universitario in ingegneria elettrica / [progettazione elettronica automazione (EDA)] ( en.wikipedia.org/wiki/Electronic_design_automation ) o ingegneria meccanica / progettazione assistita da computer (CAD) . Comparativamente parlando, chiamare la libreria dinamica C / C ++ è molto semplice, vedi convenzioni di chiamata C per x86 . Tuttavia, potrebbe essere necessario manipolare lo stack (tramite il puntatore dello stack CPU) e i valori del registro CPU.
dal

1
Il caricamento dinamico delle funzioni non è supportato dal linguaggio C ++. Dovrai guardare qualcosa di specifico per la piattaforma. Ad esempio un compilatore C ++ su Windows dovrebbe supportare le DLL di Windows, che supportano una forma di riflessione.
Simon B,

In C ++ è davvero difficile chiamare una funzione la cui firma (tipi di argomento e di ritorno) non è nota al momento della compilazione. Per fare ciò, è necessario sapere come funzionano le chiamate di funzione a livello di assembly della piattaforma prescelta.
Bart van Ingen Schenau,

2
Il modo in cui lo risolverei è quello di compilare il codice c ++ che crea un interprete per qualsiasi linguaggio che supporti un comando eval. Problema di Bang risolto usando c ++. : P Pensa al perché non è abbastanza buono e aggiorna la domanda. Aiuta quando i requisiti reali sono chiari.
candied_orange

Risposte:


13

Nel C ++ standard puro, non è possibile "consentire l'importazione in runtime di funzioni"; secondo lo standard, l'insieme di funzioni C ++ è staticamente noto al momento della compilazione (in pratica, link-time) poiché fissato dall'unione di tutte le unità di traduzione che compongono il programma.

In pratica, la maggior parte delle volte (esclusi i sistemi integrati) il tuo programma C ++ funziona al di sopra di alcuni sistemi operativi . Leggi i sistemi operativi: tre semplici pezzi per una buona panoramica.

Diversi sistemi operativi moderni consentono il caricamento dinamico dei plug-in . POSIX specifica in particolare dlopen& dlsym. Windows ha qualcosa di diverso LoadLibrary(e un modello di collegamento inferiore; è necessario annotare esplicitamente le funzioni interessate, fornite o utilizzate dai plugin). A proposito su Linux puoi praticamente dlopenun sacco di plugin (vedi il mio manydl.cprogramma , con abbastanza pazienza che può generare e caricare quasi un milione di plugin). Quindi la tua cosa XML potrebbe guidare il caricamento dei plugin. La tua descrizione multi-componente / multi-connettore mi ricorda i segnali e gli slot Qt (che richiedono un mocpreprocessore ; potresti aver bisogno anche di qualcosa del genere).

La maggior parte delle implementazioni in C ++ usa la modifica del nome . Per questo motivo, è meglio dichiarare come extern "C"le funzioni relative ai plug-in (e definite in essi, e accessibili dlsymdal programma principale). Leggi il C ++ dlopen mini HowTo (almeno per Linux).

BTW, Qt e POCO sono framework C ++ che forniscono un approccio portatile e di livello superiore ai plugin. E libffi ti consente di chiamare funzioni la cui firma è nota solo in fase di esecuzione.

Un'altra possibilità è quella di incorporare qualche interprete, come Lua o Guile , nel tuo programma (o scrivere il tuo, come ha fatto Emacs). Questa è una forte decisione di progettazione architettonica. Potresti voler leggere Lisp in piccoli pezzi e pragmatica del linguaggio di programmazione per ulteriori informazioni.

Esistono varianti o miscele di tali approcci. È possibile utilizzare alcune librerie di compilation JIT (come libgccjit o asmjit). Potresti generare in fase di esecuzione del codice C e C ++ in un file temporaneo, compilarlo come plugin temporaneo e caricare dinamicamente quel plugin (ho usato un tale approccio in GCC MELT ).

In tutti questi approcci, la gestione della memoria è una preoccupazione significativa (è una proprietà "intero programma", e ciò che in realtà è la "busta" del programma sta "cambiando"). Avrai bisogno di almeno un po 'di cultura sulla raccolta dei rifiuti . Leggi il manuale GC per la terminologia. In molti casi ( riferimenti circolari arbitrari in cui i puntatori deboli non sono prevedibili), lo schema di conteggio dei riferimenti caro ai puntatori intelligenti C ++ potrebbe non essere sufficiente. Si veda anche questo .

Leggi anche sull'aggiornamento dinamico del software .

Si noti che alcuni linguaggi di programmazione, in particolare Common Lisp (e Smalltalk ), sono più amichevoli all'idea delle funzioni di importazione in runtime. SBCL è un'implementazione software gratuita di Common Lisp e si compila in codice macchina ad ogni interazione REPL (ed è anche in grado di raccogliere immondizia il codice macchina e può salvare un intero file immagine core che può essere successivamente riavviato facilmente).


3

Chiaramente stai cercando di creare il tuo stile di software di tipo Simulink o LabVIEW, ma con un componente XML malvagio.

Nella sua forma più semplice, stai cercando una struttura di dati orientata al grafico. I tuoi modelli fisici sono costituiti da nodi (li chiami componenti) e bordi (connettori nella tua denominazione).

Non esiste un meccanismo imposto dal linguaggio per farlo, nemmeno con la riflessione, quindi dovrai creare un'API e qualsiasi componente che desideri giocare dovrà implementare diverse funzioni e rispettare le regole stabilite dalla tua API.

Ogni componente dovrà implementare un set di funzioni per fare cose come:

  • Ottieni il nome del componente o altri dettagli al riguardo
  • Ottieni il numero di input o output esposti dal componente
  • Interroga un componente su un particolare input del nostro output
  • Collegare ingressi e uscite insieme
  • e altri

E questo è solo per impostare il tuo grafico. Avrai bisogno di ulteriori funzioni definite per organizzare il modo in cui il tuo modello viene effettivamente eseguito. Ogni funzione avrà un nome specifico e tutti i componenti devono avere tali funzioni. Tutto ciò che è specifico di un componente deve essere raggiungibile attraverso quell'API, in modo identico da componente a componente.

Il tuo programma non dovrebbe tentare di chiamare queste "funzioni definite dall'utente". Al contrario, dovrebbe chiamare una funzione di "calcolo" per scopi generici o una parte del genere su ciascun componente e il componente stesso si occupa di chiamare quella funzione e di trasformare il suo input in output. Le connessioni di input e output sono le astrazioni per quella funzione, questa è l'unica cosa che il programma dovrebbe vedere.

In breve, poco di questo è in realtà specifico del C ++, ma dovrai implementare una sorta di informazioni sul tipo di runtime, su misura per il tuo particolare dominio problematico. Con ogni funzione definita dall'API, saprai quali nomi di funzione chiamare in fase di esecuzione e conoscerai i tipi di dati di ciascuna di quelle chiamate e dovrai semplicemente utilizzare il normale caricamento della libreria dinamica precedente per farlo. Questo arriverà con una buona quantità di boilerplate, ma questo è solo una parte della vita.

L'unico aspetto specifico di C ++ che dovresti tenere a mente è che è meglio avere la tua API come API C in modo da poter usare compilatori diversi per moduli diversi, se gli utenti forniscono i propri moduli.

DirectShow è un'API che fa tutto ciò che ho descritto e può essere un buon esempio da guardare.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.