2009-08-17 10 views
0

J'ai un projet avec une grande base de code (> 200 000 lignes de code) que je gère ("Le noyau").Substitution/modification de classes C++ à l'aide de DLL

Actuellement, ce noyau dispose d'un moteur de script composé de hooks et d'une classe de gestionnaire de scripts qui appelle toutes les fonctions connectées (enregistrées via DLL) lorsqu'elles se produisent. Pour être tout à fait honnête, je ne sais pas exactement comment cela fonctionne, car le noyau est pour la plupart non documenté et s'étend sur plusieurs années et une ampleur de développeurs (qui sont, bien sûr, absents). Un exemple du moteur de script actuel est:

void OnMapLoad(uint32 MapID) 
{ 
    if (MapID == 1234) 
    { 
     printf("Map 1234 has been loaded"); 
    } 
} 

void SetupOnMapLoad(ScriptMgr *mgr) 
{ 
    mgr->register_hook(HOOK_ON_MAP_LOAD, (void*)&OnMapLoad); 
} 

Un fichier supplémentaire nommé setup.cpp appelle SetupOnMapLoad avec ScriptMgr de noyau.

Cette méthode n'est pas ce que je cherche. Pour moi, le moteur de script parfait serait celui qui me permettra de remplacer les méthodes de classe de base. Je veux être en mesure de créer des classes qui héritent des classes de base et d'étendre sur eux, comme ceci:

// In the core:  
class Map 
{ 
    uint32 m_mapid; 
    void Load(); 
    //... 
} 

// In the script: 
class ExtendedMap : Map 
{ 
    void Load() 
    { 
     if (m_mapid == 1234) 
      printf("Map 1234 has been loaded"); 

     Map::Load(); 
    } 
} 

Et je veux que chaque instance de Map à la fois le noyau et des scripts pour être effectivement une instance de ExtendedMap.

Est-ce possible? Comment?

Répondre

2

L'héritage est possible. Je ne vois pas de solution pour remplacer les instances de Map avec des instances de ExtendedMap.

Normalement, vous pouvez le faire si vous avez une classe ou une fonction usine, qui est toujours utilisée pour créer un objet Map, mais ceci est une question de conception existante (ou inexistante).

La seule solution que je vois est de rechercher dans le code des instanciations et essayer de les remplacer à la main. C'est risqué, car vous risquez de manquer certaines d'entre elles et il se peut que certaines instanciations ne figurent pas dans le code source (par exemple dans cette ancienne DLL).

Édition ultérieure Cette méthode de substitution a également un effet secondaire dans le cas d'une utilisation polymorphe.

Exemple:

Map* pMyMap = new ExtendedMap; 

pMyMap->Load(); // This will call Map::Load, and not ExtendedMap::Load. 
+0

Je ne veux pas que la source ait 'ExtendedMap' si les scripts ne fournissent pas une telle classe. L'idée est de ré-enregistrer certaines (ou toutes) les classes après les avoir étendues, dans la DLL elle-même. – MoshiBin

2

Cela ressemble à un cas d'école pour le modèle de conception "décorateur".

+0

Semble proche de ce que je cherche, mais comment puis-je faire en sorte que le noyau utilise toujours le décorateur fourni par les scripts à la classe de base (ou un décorateur de celui-ci)? Je veux que mon core utilise toujours 'MapDecorator', que _could_ peut être défini dans une DLL externe. – MoshiBin

+0

Je pense que vous devrez faire un refactoring du code de base, de sorte que toute la création d'objets Map soit faite par une usine, ce qui exposer une interface que vos hooks de script pourraient utiliser pour contrôler quelle saveur de Map décorée est créée. J'aime certaines des idées que j'ai vues dans les autres réponses; peut-être que l'un d'entre eux correspond mieux à votre situation. –

0

Bien que cela soit possible, c'est très dangereux: le système devrait être ouvert pour l'extension (c'est-à-dire les crochets), mais fermé pour le changement (c'est-à-dire redéfinition/redéfinition). Lorsque vous héritez comme ça, vous ne pouvez pas anticiper le comportement que votre code client va montrer. Comme vous le voyez dans votre exemple, le code client doit se rappeler d'appeler la méthode de la superclasse, ce qu'il ne fera pas :)

Une option serait de créer une interface non-virtuelle: une classe de base abstraite qui a quelques méthodes de modèle que appel fonctions virtuelles pures. Ceux-ci doivent être définis par des sous-classes.

Si vous ne voulez pas créer de mappage de base, le script doit donner au noyau une fabrique pour créer des descendants de Map.

0

Si mon expérience avec des systèmes similaires est applicable à votre situation, plusieurs hooks sont enregistrés.Baser ainsi une solution sur le modèle usine abstraite ne fonctionnera pas vraiment. Votre système est proche du modèle observateur, et c'est ce que j'utiliserais. Vous créez une classe de base avec tous les hooks possibles en tant que membres virtuels (ou plusieurs avec des hooks associés si les hooks sont nombreux). Au lieu d'enregistrer les hooks un par un, vous enregistrez un objet, d'un type descendant de la classe avec le remplacement nécessaire. L'objet peut avoir un état, et remplacer avantageusement les champs void* user data que de tels systèmes de rappel ont couramment.

Questions connexes