Il s'agit tout d'abord de transformer son application MFC en application managée, pour ceci, il faut ajouter le support du CLR.
Bouton droit sur le projet -> Common properties -> general -> Common language runtime support ; Mettre à /clr (common language runtime support).
Ensuite ajoutez simplement une nouvelle winform, rajoutez des composants dessus, etc ...
Instanciez votre nouvelle form :
Il s'agit tout d'abord de transformer son application MFC en application managée, pour ceci, il faut ajouter le support du CLR.
Bouton droit sur le projet -> Common properties -> general -> Common language runtime support ; Mettre à /clr (common language runtime support).
Ensuite, pour ajouter un contrôle .Net, il vous faut d'abord ajouter un static MFC classique où vous voudrez positionner votre contrôle .Net
Ajouter ensuite l'include dans stdafx.h :
#include<afxWinForms.h>
On utilise ensuite l'objet CWinFormsControl pour instancier une donnée membre de notre contrôle.
Par exemple, si je veux utiliser le contrôle LinkLabel :
Il s'agit tout d'abord de transformer son application MFC en application managée, pour ceci, il faut ajouter le support du CLR.
Bouton droit sur le projet -> Common properties -> general -> Common language runtime support ; Mettre à /clr (common language runtime support).
Ajouter ensuite une Winform à votre projet. La form, pour pouvoir fonctionner en tant que vue, doit hériter de UserControl. Changer alors la définition de la classe, pour qu'elle ressemble à la ligne suivante :
public ref class WinformView : publicSystem::Windows::Forms::UserControl
N'oubliez pas de rajouter l'include dans stdafx.h :
#include<afxWinForms.h>
Modifier le fichier .h de la vue pour faire hériter la classe, non plus de CView, mais de Microsoft::VisualC::MFC::CWinFormsView
class CMfcWinformAsViewView : publicMicrosoft::VisualC::MFC::CWinFormsView
Il faut ensuite modifier le .cpp pour rester cohérent avec ce nouvel héritage. Changer la définition de l'IMPLEMENT_DYNCREATE ainsi que du BEGIN_MESSAGE_MAP :
Il s'agit ici simplement de faire passer le pointeur de document à la Winform.
Rajoutez un membre public à votre classe Winform du type de votre Document (n'oubliez pas d'inclure le .h correspondant) :
public:
CMfcWinformAsViewDoc *pDoc;
Il faut maintenant initialiser ce pointeur à partir du document courant MFC, on peut le faire par exemple dans le OnInitialUpdate de la vue.
Pour ajouter cette méthode, positionnez vous sur le .h de la vue, cliquez sur le bouton "overrides" de la fenêtre de propriétés et générer la méthode OnInitialUpdate.
On utilisera alors la méthode GetControl pour récupérer un handle sur la Winform, puis on initialise le membre public de la Winform avec le document courant (méthode GetDocument) :
Vous pouvez désormais manipuler les données du document depuis votre winform.
Pour l'exemple, j'ai rajouté une méthode dans ma classe document qui renvoit l'heure courante :
Il peut être intéressant de capter les événements classiques d'une vue dans notre winform CWinFormsView.
Pour ceci, il faut implémenter l'interface Microsoft::VisualC::MFC::IView au niveau de la classe de la Winform. Ceci donne :
public ref class WinformView : publicSystem::Windows::Forms::UserControl, Microsoft::VisualC::MFC::IView
Pour implémenter cette interface, on doit surcharger les trois fonctions virtuelles publiques OnUpdate, OnActivateView et OnInitialUpdate.
Notez ici que j'ai simplement surchargé une seule de ces méthodes, mais les 3 fonctions doivent être obligatoirement définies.
Voilà, c'est fini.
Remarque : Notez dans mon exemple, que c'est la méthode OnInitialUpdate de la vue MFC qui est appelée avant la méthode OnInitialUpdate de la vue Winform. En effet, cette dernière est appelée avec CWinFormsView::OnInitialUpdate().
Il peut être intéressant de capter les événements MFC dans notre winform CWinFormsView, par exemple un clic sur un élément du menu principal.
Pour ceci, il faut implémenter l'interface Microsoft::VisualC::MFC::ICommandTarget au niveau de la classe de la Winform, qui va nous permettre de mapper les événements à l'intérieur de notre Winform. Ceci donne :
public ref class WinformView : publicSystem::Windows::Forms::UserControl, Microsoft::VisualC::MFC::ICommandTarget
Pour implémenter cette interface, il y a une seule méthodes virtuelle publique à surcharger : il s'agit de la méthode Initialize.
On définit la méthode Initialize qui prend un ICommandSource en paramètre. Ce ICommandSource est stocké en tant que membre de la classe.
Il faut maintenant effectuer le mapping à proprement dit. On utilise la méthode AddCommandHandler. On lui passe en paramètre l'ID de la ressource (du menu dans l'exemple) à intercepter.
On lui passe aussi un pointeur sur la méthode qui va être appelée lors du click sur l'élément du menu.
Et ici, on affiche un MessageBox lors du clic sur le menu.
Voilà, c'est fini.
Remarque : Notez dans mon exemple, qu'on ne peut plus quitter par le menu, il faudra quitter par la croix ; ceci montre que l'interception du message est remplacée, on ne pourra pas traiter ce message dans la classe d'application.
Ceci est valable pour un projet C++ utilisant les extensions managées du framework .Net 1.x :
Lorsqu'on migre petit à petit son projet en C++.Net ou simplement lorsqu'on veut faire cohabiter l'Api Win32 (ou les MFC) et le framework.Net, on peut rencontrer des problèmes de compilation du style :
error C2039: 'GetObjectA' : is not a member of 'System::Resources::ResourceManager'
error C2653: 'MessageBoxA' : is not a class or namespace name
Le problème vient du faire que lorsqu'on souhaite utiliser la fonction MessageBox par exemple, le compilateur ne sait pas s'il doit utiliser la fonction messageBox définie dans Windows.h ou bien dans le namespace System::Windows::Forms.
Il faut donc à ce moment dire au compilateur quelle fonction on veut utiliser.
On enlève alors la définition du messageBox comme ci-dessous :
Ainsi le compilateur saura qu'il faut utiliser le MessageBox de .Net.
Remarque : Avec IJW et le compilateur Visual C++ 2005, une simple utilisation de l'opérateur de résolution de portée suffit :
Lorsqu'on inclut windows.h, il arrive fréquemment qu'on ait une erreur de ce style :
'IDataObject' : ambiguous symbol error
Ce problème vient de cet include, qui définit lui aussi un IDataObject, qui est une interface COM.
Il faut définir WIN32_LEAN_AND_MEAN, qui a pour but d'exclure tous les entêtes Win32 qui ne sont pas utiles.
On rajoute donc avant notre premier include :
Lorsque l'on développe une application "managée", le compilateur génère du MSIL et du langage machine lorsqu'il ne peut pas générer de MSIL (j'entends par langage machine du code x86/x64/IA64).
Il peut être intéressant de forcer le compilateur à générer du langage machine pour certaines portions de code. Cela est très utilisé lorsque vous souhaitez porter votre application Win32 petit à petit en .Net ou lorsque vous souhaitez profiter de la couche la plus basse possible, pour des portions de code critiques par exemple.
Dans ces portions de code, il sera bien sur impossible d'accéder au CLR, car le compilateur ne génère pas de MSIL.
On utilise alors les pragmas :
#pragmaunmanaged
et
#pragmamanaged
Il suffit alors d'entourer du code par ces deux pragmas pour qu'il soit généré en langage machine. Cela implique donc bien sur d'utiliser le mode de compilation mixte (/clr).
Cette erreur apparait dans certains cas précis, lorsqu'on essaie d'utiliser des fonctions dont la convention d'appel n'a pas été précisée explicitement.
On obtient l'erreur de link LNK2031 :
error LNK2031: unable to generate p/invoke for "extern "C" int __clrcall ...
Une première solution est de faire l'édition de lien en mode mixte, donc de ne pas utiliser le mode /clr:pure, mais le mode de compilation /clr.
Une deuxième solution est de définir précisément les prototypes pour faire concorder les conventions d'appels (ce qui n'est pas toujours évident lors de l'utilisation d'une bibliothèque tierce). En effet, les conventions implicites ne sont pas les mêmes pour le monde natif et le monde managé.
Enfin, une troisième solution est d'utiliser DLLImport au lieu d'une édition de liens mixte.
Exemple, une bibliothèque définie ainsi :
__declspec(dllexport) intaddition(int a,int b)
{return a+b;
}
a une convention d'appel implicite. Par défaut, à la création d'un projet, il s'agit de __cdecl.
Ainsi, lorsqu'on prototype la fonction dans une application managée, si on fait :
on utilisera la convention d'appel implicite __clrcall. D'où l'erreur.
En mode de compilation /clr, la convention d'appel implicite n'est pas la même qu'en compilation pure. En cas d'oubli de spécification précise de la convention d'appel, le compilateur nous met le warning C4272.
warning C4272 : [...] is marked __declspec(dllimport); must specify native calling convention when importing a function.
Ainsi, on pourra définir ses fonctions ainsi :
__declspec(dllexport) int __stdcall addition(int a,int b)
{return a+b;
}
#pragmacomment(lib,"testDll.lib")__declspec(dllimport) int __stdcall addition(int a,int b);
Avec DLLImport, on utilisera les mécanismes de P/Invoke :
Lorsqu'on veut utiliser un objet managé dans une classe native, comme ci dessous :
ref class CManagee
{CManagee(){};
};
class CNative
{public:
CNative();
CManagee ^c;
};
On obtient l'erreur de compilation C3265 :
error C3265: cannot declare a managed 'c' in an unmanaged 'CNative'
may not declare a global or static variable, or a member of a native type that refers to objects in the gc heap
Une classe native ne sait pas gérer, allouer, etc ... les objets managés en tant que tel. Il faut utiliser les mécanismes du CLR qui sont factorisés dans le template gcroot. (utilisation nottament de GCHandle::Alloc).
#include<vcclr.h>
ref class CManagee
{CManagee() {};
};
class CNative
{public:
CNative();
gcroot<CManagee ^> c;
};
On n'oubliera pas bien sur d'inclure le fichier vcclr.h.
gcroot est un template qui permet d'utiliser des types CLI dans une classe native comme expliqué ici. Pour l'utiliser, on inclut :
#include<vcclr.h>
auto_gcroot est aussi un template qui permet d'utiliser des types CLI dans une classe native, à la différence près qu'en utilisant ce template, on est assuré de la libération des ressources quand il est supprimé ou quand il sort de son scope (comme auto_ptr en C++).
Il se situe dans le namespace msclr. Pour l'utiliser, on inclut :
#include<msclr\auto_gcroot.h>
Pour s'en convaincre et pour voir un exemple d'utilisation, soit le code suivant :
Destructeur : auto_gcroot
fin du programme
Finalizer gcroot
On a donc bien, lors de la destruction explicite de l'objet natif, la libération du handle cAuto grâce au template auto_gcroot.
On a ensuite la fin du programme.
Puis la libération du handle c par le garbage collector lorsque l'objet n'est plus utilisé, par l'appel du finalizer.
Pour rejoindre la rédaction, émettre une suggestion ou proposer un tutoriel : contactez nous via le forum ou directement sur l'adresse mail de la rubrique.