MVC implémenté en C pur

Est-ce que quelqu’un connaît des ressources qui fournissent un exemple simple d’essayer de créer un modèle de conception de contrôleur de vue de modèle dans un contexte C? Et en particulier un système embarqué?

Pour clarifier, je ne suis pas intéressé par les exemples de langage C #, C ++, Objective-C, Java, PHP ou autre. Je veux savoir ce que les gens pensent de la façon d’aborder ce modèle avec Ansi C99 ou même C89. Peut-être que cela n’a même pas de sens en C à cause du manque de construction de langage POO formel?

Un peu de contexte: mes collègues et moi travaillons sur des systèmes intégrés alimentés par des puces PSoC basées sur Arm. Nous contrôlons la conception matérielle et les circuits imprimés et devons développer des logiciels pour améliorer les fonctionnalités de notre produit. Notre modèle consiste généralement en une acquisition de données à partir de convertisseurs analogiques-numériques du produit. Les vues peuvent être une page Web alimentée par un serveur Web intégré ou un écran LCD avec contrôle tactile capacitif. Nos contrôleurs seraient plus ou moins la logique collante qui gère la relation entre ces deux zones de code. Nous avons un grand nombre de produits et de variantes à prendre en charge. La réutilisation du code est donc souhaitable.

Pas à la recherche de frameworks très détaillés ou de niveau entreprise. Mais des exemples assez simples illustrent de bonnes stratégies pour séparer les problèmes de programmation, mais avec un biais pour les idiomes trouvés dans le niveau C inférieur, tels que les structures, les fonctions, la logique événementielle et une sorte de transmission de message abstrait ayant un sens en C.

En raison de la nature du matériel, nous devons utiliser C et amorcer beaucoup de choses nous-mêmes. Et dans certains cas, nous avons access à un système d’exploitation et dans d’autres, nous nous contentons de comstackr directement sur le processeur et de commencer avec une fonction principale. Tout cela est très primitif, mais nous recherchons des approches qui permettent la réutilisation du code et, espérons-le, accélèrent le processus d’ingénierie logicielle.

Pseudo … cela pourrait être une longue réponse … mais voilà …

Commençons par cette déclaration:

Maybe this doesn't even make sense in C because of the lack of formal OOP language constructs?

Ne pouvait pas être plus en désaccord avec cette déclaration. Comme je le montrerai plus tard; Ce n’est pas parce que C n’a pas des mots-clés astucieux comme “classe” que vous ne pouvez pas accomplir les mêmes choses.

Je vais essayer de passer au travers de cette étape du mieux que je peux, en suivant l’évolution de votre question.

OOP en C

D’après le libellé de votre question, je soupçonne que vous maîsortingsez assez bien les concepts de la programmation orientée object (vous pensez même en termes de modèles et vous avez même une bonne idée de la façon dont ces modèles joueront dans votre scénario particulier) – alors laissez-moi faire un tutoriel “POO en C” dans “30 secondes ou moins”.

Une fois que vous maîsortingserez les choses, vous réaliserez que vous pouvez faire beaucoup plus que ce que je vais montrer ici – mais je veux simplement vous donner un avant-goût.

101

Premièrement, nous allons commencer par un “cours” de base (allez avec moi à ce sujet):

Foo.h:

 typedef struct Foo Foo; Foo * FooCreate(int age, int something); void FooSetAge(Foo * this, int age); void FooFree(Foo * this); 

Foo_Internal.h: (vous verrez pourquoi je l’ai cassé en une seconde)

 #include "Foo.h" struct Foo { int age; int something; }; void FooInitialize(Foo * this, int age, int something); 

Foo.c:

 #include "Foo_Internal.h" // Constructor: Foo * FooCreate(int age, int something) { Foo * newFoo = malloc(sizeof(Foo)); FooInitialize(newFoo); return newFoo; } void FooInitialize(Foo * this, int age, int something) { this->age = age; this->something = something; } // "Property" setter: void FooSetAge(Foo * this, int age) { this->age = age; } void FooFree(Foo * this) { // Do any other freeing required here. free(this); } 

Quelques choses à remarquer:

  • Nous avons caché les détails d’implémentation de Foo derrière un pointeur opaque. D’autres personnes ne savent pas ce qu’il y a dans un Foo car ce détail d’implémentation se trouve dans le fichier d’en-tête “interne”, pas dans l’en-tête “public”.
  • Nous implémentons des “méthodes d’instance”, comme le ferait un langage POO (à l’exception du fait que nous devons passer manuellement le pointeur “this” – les autres langages le font pour vous), mais ce n’est pas grave.
  • Nous avons des “propriétés”. Encore une fois, d’autres langues encapsuleront les getters et les parameters de propriétés dans une syntaxe plus élégante, mais ce qu’ils font en coulisse est de créer une méthode de lecture / paramétrage pour vous et de traduire les appels aux “propriétés” en appels de méthodes.

Héritage

Et si nous voulions une “sous-classe” de Foo – qui n’ajoute que des fonctionnalités supplémentaires – mais peut être remplacée par un Foo ? Simple:

FooSubclass.h:

 typedef struct FooSubclass FooSubclass; FooSubclass * FooSubclassCreate(int age, int something, int somethingElse); void FooSubclassSetSomethingElse(FooSubclass * this, int somethingElse); void FooSubclassFree(FooSubclass * this); 

FooSubclass_Internal.h:

 #include "FooSubclass.h" #include "Foo_Internal.h" struct FooSubclass { Foo base; int something; }; void FooSubclassInitialize(FooSubclass * this, int age, int something, int somethingElse); 

FooSubclass.c

 #include "FooSubclass_Internal.h" // Constructor: Foo * FooSubclassCreate(int age, int something, int somethingElse) { FooSubclass * newFooSubclass = malloc(sizeof(FooSubclass)); FooSubclassInitialize(newFooSubclass, age, something, somethingElse); return newFooSubclass; } void FooSubclassInitialize(FooSubclass * this, int age, int something, int somethingElse) { FooInitialize(this, age, something); this->somethingElse = somethingElse; } void FooSubclassSetSomethingElse(Foo * this, int somethingElse) { this->somethingElse = somethingElse; } void FooSubclassFree(FooSubclass * this) { // Do any other freeing required here. free(this); } 

Maintenant, je devrais mentionner, tout comme nous avons créé des “initialiseurs” qui n’appellent pas réellement malloc , mais qui sont responsables de l’initialisation des variables membres – nous avons également besoin de désallocateurs – qui ne libèrent pas réellement la structure – mais qui libèrent / libèrent toutes les références “possédant”, etc. Cependant … je vais en fait mentionner quelque chose dans la section ci-dessous qui pourrait expliquer pourquoi je ne me suis pas encore préoccupé de cela.

Vous devriez remarquer maintenant que, puisque le premier membre de notre FooSubclass est en fait une structure Foo , toute référence à une FooSubclass est également une référence valide à un Foo , ce qui signifie qu’elle peut être utilisée telle quelle pratiquement.

Cependant, il y a quelques petits problèmes avec ceci – comme je l’ai mentionné dans le paragraphe précédent – cette technique ne vous permet pas réellement de changer le comportement de la classe de base. (Quelque chose que nous aimerions faire pour désallouer notre instance, par exemple).

Polymorphisme

Disons que nous avons une méthode – nous allons trouver un exemple aléatoire de BS – appelée calculate .

Nous voulons que l’appel calculate sur un Foo retourne une valeur, mais une valeur différente s’il a été appelé sur une FooSubclass .

C’est simple en C: il s’agit simplement de créer une méthode de wrapper qui appelle une fonction référencée par un pointeur de fonction. Les langages POO font cela pour vous dans les coulisses et cela est généralement implémenté via ce qu’on appelle une VTable .

Voici un exemple (je vais arrêter de donner des exemples complets pour plutôt me concentrer sur les parties pertinentes):

Nous définissons d’abord la signature de la méthode. Nous disons ici que “CalculateMethod” est: un pointeur sur une méthode qui prend un paramètre (un pointeur) et renvoie un int.

 typedef int (*calculateMethod)(void *); 

Ensuite, nous ajoutons une variable membre dans notre classe de base qui pointera sur une fonction:

 struct Foo { // ... calculateMethod calc; // ... } 

Nous initialisons cela avec une valeur initiale dans la méthode FooInitialize (pour notre implémentation de base):

 int FooCalculate(Foo * this) { this->calc(this); } int FooCalculateImplementation(void * this) { Foo * thisFoo = (Foo *)this; return thisFoo->age + thisFoo->something; } void FooInitialize(Foo * this, ...) { // ... this->calc = &FooCalculateImplementation; // ... } 

Maintenant, nous Foo_Internal.h aux sous-classes de remplacer cette méthode, par exemple une méthode déclarée dans le fichier Foo_Internal.h appelée void FooSetCalculateMethod(Foo * this, calculateMethod value); – et le tour est joué! Méthodes pouvant être remplacées dans des sous-classes.

Modèle

Our model would typically consist of data acquisition from Analog to Digital converters in the product.

OK – alors Model est probablement la chose la plus facile à mettre en œuvre – de simples “classes” utilisées comme mécanismes de stockage de données.

Vous devrez trouver quelque chose pour votre scénario particulier (étant un système embarqué, je ne sais pas exactement quelles seront vos ressortingctions – et si vous êtes inquiet à propos de la RAM / persistance / etc.), mais je pense que ce n’est pas le cas. veux que je plonge de toute façon dans cela.

Vue

The views might be a web page powered by an embedded web server, or else an LCD screen with capacitive touch control.

Pour les choses physiques, votre “vue” peut être constituée de boutons fixes sur un panneau de commande – ou, comme vous l’avez dit, d’un écran LCD ou HTML.

En résumé, vous avez simplement besoin de classes capables de présenter au rest de votre système une interface “simple” permettant d’afficher / de modifier des éléments dans la vue – et d’encapsuler les détails d’IO à l’utilisateur.

En général, la partie “I” de “IO” nécessite au moins un petit coin de code dans la vue.

Je ne pense pas que ce soit l’idéal – mais, la plupart du temps, il n’y a pas de bonne façon de faire en sorte que votre utilisateur de “visualisation” renvoie à vos contrôleurs. Peut-être qu’avec votre système, il existe un bon moyen de contourner cela – étant donné que vous avez un contrôle total.

J’espère que vous pouvez maintenant voir comment vous pouvez facilement créer des classes de vues correspondant à vos besoins.

Manette

Our controllers would more or less be the glue logic that manages the relationship between these two areas of code.

C’est généralement le courage de l’application. Vous aurez probablement besoin de plus d’un contrôleur à la fois – un pour l’entrée / le traitement des données du capteur, un ou plusieurs pour l’interface utilisateur active, et éventuellement d’autres.

Quoi qu’il en soit, j’espère que cela aidera … J’ai l’impression d’écrire un livre maintenant, alors je vais m’arrêter.

Faites-moi savoir si vous voulez plus, ou si cela aide du tout.

mon framework MVC!

 typedef struct { int x; } x_model; typedef void (*f_void_x)(x_model*); void console_display_x(x_model* x) { printf("%d\r\n",x->x); } typedef struct { f_void_x display; } x_view; typedef struct { x_model* model; x_view* view; } x_controller; void create_console_view(x_view* this) { this->display = console_display_x; } void controller_update_data(x_controller* this, int x) { this->model->x = x; this->view->display(this->model); } void x_controler_init(x_controller* this, x_model* model, x_view* view) { this->model = model; this->view = view; } int main(int argc, char* argv[]) { x_model model; x_view view; x_controller controller; create_console_view(&view); x_controler_init(&controller, &model, &view); controller_update_data(&controller, 24); } 

Vous auriez probablement un peu plus raffiné que cela cependant. Si vous avez plusieurs vues sur un contrôleur, vous souhaiterez utiliser un modèle d’observateur pour gérer les vues. Mais avec cela, vous avez des vues connectables. Je serais probablement un peu plus ssortingct en réalité et ne laisserais le modèle être modifié que par une fonction et le pointeur de fonction d’affichage des vues ne serait également appelable que par l’intermédiaire d’une fonction (je les appelle directement). Cela permet divers crochets (pour commencer, vérifiez si le modèle ou le pointeur de vue / fonction est nul). J’ai laissé de côté la gestion de la mémoire car il n’est pas trop difficile de l’append, mais rend les choses plus compliquées.

Je suis intéressé par les suggestions que les gens pourraient avoir mais je pense que vous avez frappé le clou – Cela n’a probablement aucun sens en raison du manque de constructions formelles pour la programmation orientée object.

Toutefois; il est possible d’introduire des concepts de POO à ANSI-C; J’ai un lien vers ce PDF depuis un moment et bien que je ne l’ait jamais vraiment absorbé (en raison de mon absence d’exposition au C dans mon travail quotidien), il semble certainement fructueux:

http://www.planetpdf.com/codecuts/pdfs/ooc.pdf

C’est une tâche ardue, mais vous pourriez en fin de compte créer une sorte de travail sur les modèles / bâtis qui faciliterait l’écriture de nouveaux développements de style MVC; mais je suppose que le compromis est – pouvez-vous vous permettre le temps? Les limitations des plates-formes intégrées sont-elles telles que les avantages de la clarté apscope par MVC sont contrebalancés par le manque de performances / protection de la mémoire / récupération des ordures et bien sûr par le simple effort de réinventer la roue?

Je vous souhaite bonne chance et je serai très intéressé de voir ce que vous proposez!

Modifier:

Après reflection, le simple fait de disposer de certaines techniques de POO sans aller jusqu’à une implémentation complète de MVC pourrait aider à résoudre vos problèmes. chemin vers l’objective de réutilisation du code.

Cet autre stackoverflow traite de la mise en œuvre de la POO dans Ansi C, c’est une lecture intéressante mais des liens vers le même pdf: pouvez-vous écrire du code orienté object en C?