Au cours du développement d’un projet j’ai été confronté à la problématique suivante: Comment utiliser une librairie dynamique (.lib) en C++ dans un projet C# ?
1. Présentation du contexte
La solution est composée des 3 projets suivants :
- MonApplication.exe [C++, MFC]
Un client lourd qui doit fonctionner de Windows XP à Seven sans prérequis (donc impossible d’utiliser le CLR). - Cryptlib [C++]
Librairie permettant de crypter et décrypter des strings. - MonService [WCF, C#]
Il permet de faire l’intermédiaire entre la base de données et le client lourd.
Le client et le service manipulent des fichiers xml cryptés par la librairie Cryptlib. Côté client, le problème ne se pose pas car j’utilise une librairie C++ native dans un client C++. En revanche, le service doit pouvoir appeler les méthodes Encrypt et Decrypt de cette même libraire.
2. Solution proposée
Reprenons le schéma actuel de la solution, à la manière du design pattern « Façade », nous créons un projet CryptlibWrapper qui va appeler les méthodes de CryptLib et renvoyer des objets de type .Net au service.
Comme présenté ci-dessus, le service ne fera pas ses appels directement sur la librairie native mais sur le Wrapper.
Nous allons créer une solution simplifiée afin de mieux présenter le concept.
Elle sera composée de 3 projets :
- Console : Ecrit en C#, on affichera le résultat de la méthode SayHello
- NativeLibrary : Ecrit en C++ natif
- WrapperLibrary : Ecrit en C++ également mais en supportant le CLR
Commençons par écrire la couche de plus bas niveau : NativeLibrary.
2.1. NativeLibrary
Nous créons une solution nommée UseCppInCsharp et un premier projet de type « Empty Project» nommé NativeLibrary.
Nous ajoutons une classe Utils avec le code suivant:
Utils.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #pragma once #include #include using namespace std; namespace NativeLibrary { class Utils { public: Utils(void); ~Utils(void); string SayHello(string name); }; } |
Utils.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include "Utils.h" namespace NativeLibrary { Utils::Utils(void) { } Utils::~Utils(void) { } string Utils::SayHello(string name) { return "Hello World " + name; } } |
Il reste à configurer les options du projet :
Configuration Properties > General > Configuration Type : Static Library (.lib)
2.2. WrapperLibrary
Nous ajoutons un projet de type « CLR Empty Project» nommé WrapperLibrary à la solution.
Nous ajoutons une classe WrapperUtils :
WrapperUtils.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #pragma once #include "Utils.h" //Use to Convert System::String to std::string #include <msclr\marshal_cppstd.h> // Allow to use System namespace with CLR using namespace System; namespace WrapperUtils { public ref class WrapperUtils { public: System::String^ SayHello(System::String^ input); }; } |
WrapperUtils.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include "stdafx.h" #include "WrapperUtils.h" namespace WrapperUtils { System::String^ WrapperUtils::SayHello(System::String^ input) { //Conversion of System::String to, std::string msclr::interop::marshal_context context; std::string stdInput = context.marshal_as(input); NativeLibrary::Utils cUtils; string tmp = cUtils.SayHello(stdInput); String^ MyString = gcnew String(tmp.c_str()); return MyString; } } |
Il reste à configurer les options du projet :
- Configuration Properties > General > Configuration Type : Dynamic Library (.dll)
- Check that the “Common Language Runtime Support” property is set to /clr
- Configuration Properties > C/C++ >General > Additional Include Directories
- Common Properties : Add new reference to NativeLibrary
2.3. Console
Dernière étape pour utiliser la librairie : le client C#.
J’ajoute un projet de type « Console Application » nommé Console à la solution.
Nous ajoutons la référence vers le projet WrapperLibrary :
Il ne reste plus qu’à appeler la fonction depuis le Main :
Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Console { class Program { static void Main(string[] args) { //Call C++ function WrapperUtils.WrapperUtils _utils = new WrapperUtils.WrapperUtils(); string str = _utils.SayHello("Olivier"); //Write result System.Console.WriteLine(str); //Wait before closing System.Console.ReadLine(); } } } |
A l’exécution, le message s’affiche correctement :
Comments
Petite erreur sur le sujet :
.lib = lib statique
.dll = lib dynamique
Très juste, merci de l’avoir signaler!