2010-03-16 36 views
2

J'ai une base de code C++ très grande et mature que j'essaie d'utiliser SWIG pour générer une interface C# pour. Je ne peux pas changer le code C++ lui-même, mais nous pouvons utiliser toutes les offres de SWIG pour l'étendre/le mettre à jour. Je suis confronté à un problème où une fonction C++ écrite comme ci-dessous provoque des problèmes en C#.Comment redescendre correctement en C# avec une interface générée par SWIG?

A* SomeClass::next(A*) 

L'appelant peut faire quelque chose comme:

A* acurr = 0; 
while((acurr = sc->next(acurr)) != 0){ 
    if(acurr isoftype B){ 
     B* b = (B*)a; 
     ...do some stuff with b.. 
    } 
    elseif(acurr isoftype C) 
    ... 
} 

Essentiellement, itérer à travers un récipient d'éléments qui, selon leur vrai type, fait quelque chose de différent. La SWIG générée couche C# pour la fonction « suivant », malheureusement, ne les éléments suivants:

return new A(); 

Ainsi, le code d'appel en C# ne peut pas déterminer si l'objet retourné est en fait une classe dérivée ou non, il semble en fait toujours la classe de base (ce qui a un sens). Je suis tombé sur plusieurs solutions:

  1. Utilisez le mot-clé SWIG% extend pour ajouter une méthode sur un objet et finalement appeler dynamic_cast. L'inconvénient de cette approche, comme je le vois, est que cela nécessite de connaître la hiérarchie de l'héritage. Dans mon cas, c'est plutôt énorme et je vois que c'est une question de maintenance.
  2. Utilisez le mot-clé% factory pour fournir la méthode et les types dérivés et demandez à SWIG de générer automatiquement le code dynamic_cast. Cela semble être une meilleure solution que la première, mais sur un regard plus profond, il vous faut encore traquer toutes les méthodes et tous les types dérivés possibles, il pourrait revenir. Encore une fois, un énorme problème de maintenance. J'aurais aimé avoir un lien vers un document pour cela mais je ne peux pas en trouver un. J'ai découvert cette fonctionnalité en consultant l'exemple de code fourni avec SWIG.
  3. Créez une méthode C# pour créer une instance de l'objet dérivé et transférer le cPtr vers la nouvelle instance. Bien que je considère cela comme maladroit, cela fonctionne. Voir un exemple ci-dessous.
 
    public static object castTo(object fromObj, Type toType) 
    { 
     object retval = null; 

     BaseClass fromObj2 = fromObj as BaseClass; 
     HandleRef hr = BaseClass.getCPtr(fromObj2); 
     IntPtr cPtr = hr.Handle; 
     object toObj = Activator.CreateInstance(toType, cPtr, false); 

     // make sure it actually is what we think it is 
     if (fromObj.GetType().IsInstanceOfType(toObj)) 
     { 
      return toObj; 
     } 

     return retval; 
    } 

Sont-ils vraiment les options? Et si je ne suis pas prêt à explorer toutes les fonctions existantes et les dérivations de classes, alors je reste aveC# 3? Toute aide serait appréciée.

+0

Quelle solution avez-vous avec, si je peux demander? Je suis confronté à un problème très similaire dans une autre langue cible ... – klickverbot

Répondre

1

Nous avons ajouté des fonctions à l'interface pour obtenir le type spécifique nécessaire:

// .cpp 
class foo : public bar { 
} 

///////////// part of swig 
// .i (swig) 
%extend foo { 
    static foo* GetFoo(bar* iObj) { 
     return (foo*)iObj; 
    } 
} 

Il est un peu fastidieux, beause il fallait faire pour toutes les classes, mais là encore, il pourrait être transformé en SWIG macro.

3

Par défaut SWIG generates C# and Java code that does not support downcast pour les types de retour polymorphes. J'ai trouvé une manière simple de résoudre ceci, à condition que votre code C++ ait un moyen d'identifier la classe concrète des instances C++ qui sont retournées. Autrement dit, ma technique ne fonctionnera que si l'API C++ que vous enveloppez avec SWIG a quelque chose de similaire à C# object.GetType() ou Java Object.getClass().

La solution consiste à ajouter une méthode de classe intermédiaire C# pour instancier la classe concrète que le C++ dit être. Ensuite, utilisez% typemap (out) pour indiquer à SWIG d'utiliser cette méthode de classe intermédiaire lors du retour des classes abstraites.

C'est une explication terriblement laconique, alors référez-vous à mon article de blog qui montre how to generate polymorphic C# and Java that you can downcast. Il a tous les détails.

2

La troisième solution dans le message original ne fonctionne pas dans Swig 2.0 où les constructeurs sont privés (donc l'Activator ne peut pas trouver le constructeur). Par conséquent, différents BindingFlags doivent être utilisés. Voici une variante générique de la troisième solution mentionnée dans le message original:

public class SwigHelper 
{ 
    public static T CastTo<T>(object from, bool cMemoryOwn) 
    { 
     System.Reflection.MethodInfo CPtrGetter = from.GetType().GetMethod("getCPtr", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); 
     return CPtrGetter == null ? default(T) : (T) System.Activator.CreateInstance 
     (
      typeof(T), 
      System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, 
      null, 
      new object[] { ((HandleRef) CPtrGetter.Invoke(null, new object[] { from })).Handle, cMemoryOwn }, 
      null 
     ); 
    } 
} 

Étant donné deux emballages SWIG Foo et Bar where Bar : Foo, vous pouvez maintenant essayer de downcaster Foo à Bar comme dans l'exemple suivant:

Foo foo = new Bar(); 
Bar bar = SwigHelper.CastTo<Bar>(foo, false);