18 décembre 2023
Les clients apprécient les produits Aspose, qui permettent de manipuler des protocoles et des fichiers de formats populaires. La plupart de ces produits ont été initialement développés pour .NET. Parallèlement, les applications métier pour les formats de fichiers s'exécutent dans différents environnements. Cet article décrira comment nous avons réussi à mettre en place les versions d'Aspose pour C++, en construisant un cadre de traduction de code depuis le C#. Le maintien de la fonctionnalité des versions .NET de ces produits était techniquement complexe.
Nous avons développé l'infrastructure nécessaire nous-mêmes, permettant la traduction de code entre les langages et l'émulation des fonctions de la bibliothèque .NET. Ce faisant, nous avons résolu un problème qui est généralement considéré comme académique. Cela nous a permis de commencer à publier mensuellement des produits .NET pour le langage C++, en obtenant le code de chaque version à partir du code C# correspondant. De plus, les tests qui couvraient le code C# original sont traduits en parallèle, garantissant que la fonctionnalité de la solution résultante est surveillée, au même titre que les tests spécialement écrits en C++.
Le succès du traducteur de code C# vers C++ repose sur l'expérience réussie de l'équipe CodePorting lors de la mise en place de la traduction automatisée du code C# vers Java. Le cadre créé transformait les classes C# en classes Java tout en remplaçant correctement les appels de bibliothèque système.
Différentes approches ont été envisagées pour le cadre. Le développement de versions Java pures à partir de zéro aurait nécessité trop de ressources. Une option consistait à effectuer un appel de méthodes depuis le code Java vers l'environnement .NET, mais cela aurait limité les plates-formes de programmation que nous pouvions prendre en charge à l'avenir. À l'époque, .NET était présent uniquement sur Windows. Les appels de méthodes sont pratiques pour les appels peu fréquents utilisant des types de données largement utilisés. Cependant, cela devient complexe lorsqu'il s'agit de travailler avec de nombreux objets et types de données personnalisés.
Au lieu de cela, nous nous sommes demandé comment traduire entièrement le code existant vers une nouvelle plate-forme. C'était un problème d'actualité car la migration du code devait être effectuée mensuellement et pour tous les produits, produisant un flux synchronisé de versions présentant des fonctionnalités similaires.
La solution a été divisée en deux parties :
Les arguments suivants ont confirmé que le plan était techniquement viable :
System.Net
et System.Drawing
.Nous n'entrerons pas dans les détails du traducteur de C# vers Java, cela nécessiterait des articles dédiés. En résumé, la conversion des produits C# en Java est devenue une pratique courante de l'entreprise, grâce au traducteur de code créé. Le traducteur est passé d'un simple transformateur de texte basé sur des règles à un générateur de code complexe qui fonctionne avec la représentation AST du code source.
Le succès du traducteur de C# vers Java nous a permis de pénétrer le marché Java, et le sujet a été soulevé pour commencer à publier des produits pour C++ en utilisant le même scénario.
Pour rendre possible la sortie de la version C++ de nos produits, il était nécessaire de créer un cadre qui nous permettrait de traduire le code C# en C++, de le compiler, de le tester et de l'envoyer au client. Le code se composait de bibliothèques, chacune comportant jusqu'à quelques millions de lignes de code. Le composant Bibliothèque du traducteur de code devait couvrir les points suivants :
De nombreux lecteurs se demanderont probablement pourquoi nous n'avons pas envisagé d'utiliser des solutions existantes, telles que le projet Mono. Plusieurs raisons expliquent notre choix :
Théoriquement, nous pourrions utiliser notre traducteur pour convertir une solution existante en C++. Cependant, cela nécessiterait d'avoir un traducteur pleinement fonctionnel dès le départ, car il est impossible de déboguer un code traduit sans bibliothèque système. De plus, les problèmes d'optimisation deviendraient encore plus essentiels que pour le code des produits traduits, car les appels de bibliothèque système tendent à devenir des goulots d'étranglement.
Revenons à nos exigences pour le traducteur de code. En raison de l'incapacité de faire correspondre les types .NET aux types STL, nous avons décidé d'utiliser des types personnalisés de la bibliothèque comme substitutions. La bibliothèque a été développée sous forme d'adaptateurs permettant l'utilisation des fonctionnalités de bibliothèques tierces via une API similaire à celle de .NET (comme en Java).
Pendant que nous traduisions les bibliothèques avec l'API existante, une exigence importante pour le code traduit était qu'il devait s'exécuter dans n'importe quelle application cliente. Par conséquent, nous ne pouvions pas utiliser la collecte des déchets (garbage collection) pour le code traduit, car cela couvrirait l'ensemble de l'application. À la place, notre modèle de gestion de la mémoire devait être clair pour les développeurs C++. L'utilisation de pointeurs intelligents (smart pointers) a été choisie comme compromis. Nous décrirons comment nous avons réussi à modifier le modèle de mémoire dans un article séparé.
CodePorting a une forte culture de couverture de tests, et la possibilité d'appliquer les tests écrits pour le code C# aux produits C++ simplifierait considérablement le dépannage. Le traducteur de code devait également être capable de traduire les tests.
Initialement, la correction manuelle du code Java traduit a permis d'accélérer le développement et les sorties de produits. Cependant, à long terme, cela a considérablement augmenté les dépenses nécessaires pour préparer chaque version avant la sortie, car chaque erreur de traduction devait être corrigée à chaque apparition. Cela aurait pu être géré en alimentant le code Java résultant avec les correctifs calculés comme la différence entre les sorties du traducteur générées pour deux révisions de code C# consécutives au lieu de le convertir à partir de zéro à chaque fois. Néanmoins, il a été décidé de donner la priorité à la correction du framework C++ plutôt qu'à la correction du code résultant, afin de ne corriger chaque erreur de traduction qu'une seule fois.