FAQ C++/CLI et VC++.NetConsultez toutes les FAQ

Nombre d'auteurs : 29, nombre de questions : 248, création le 22 février 2013 

 
OuvrirSommaireMixer du C++/CLI avec du code Win32 ou MFC

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 :

 
Sélectionnez

mfcPlusWinforms::mfcWinForm dlg;
dlg.ShowDialog(); // pour qu'elle se comporte comme une dialog
				

N'oubliez pas bien sur d'inclure le fichier .h correspondant au code de la winform.
Téléchargez le programme d'exemple

Créé le 9 mai 2006  par nico-pyright(c)

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 :

 
Sélectionnez

#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 :

 
Sélectionnez

Microsoft::VisualC::MFC::CWinFormsControl< System::Windows::Forms::LinkLabel >m_linkLabelDotNet;
				

Ensuite, dans le DoDataExchange :

 
Sélectionnez

DDX_ManagedControl( pDX, IDC_DOTNET, m_linkLabelDotNet );
				

Rajouter ensuite un handler d'événement :

 
Sélectionnez

void linkLabel_LinkClicked( System::Object^ sender, System::Windows::Forms::LinkLabelLinkClickedEventArgs^ e );
				

et utiliser la macro _DELEGATE_MAP pour cabler les méthodes :

 
Sélectionnez

BEGIN_DELEGATE_MAP( CMfcControlDotNetDlg )
    EVENT_DELEGATE_ENTRY( linkLabel_LinkClicked, System::Object ^, System::Windows::Forms::LinkLabelLinkClickedEventArgs^ )
END_DELEGATE_MAP()
				

On crée le délégate à l'initialisation du composant :

 
Sélectionnez

m_linkLabelDotNet->LinkClicked += MAKE_DELEGATE(System::Windows::Forms::LinkLabelLinkClickedEventHandler , linkLabel_LinkClicked);
				

Et vous pouvez ainsi désormais utiliser la fonction :

 
Sélectionnez

void CMfcControlDotNetDlg::linkLabel_LinkClicked(System::Object ^sender, System::Windows::Forms::LinkLabelLinkClickedEventArgs ^e)
{
    System::Windows::Forms::MessageBox::Show(L"Je capte l'événement click");
}
				

pour capter l'événement du click.
Téléchargez le programme d'exemple

Créé le 9 mai 2006  par nico-pyright(c)

On procède de la même façon que pour Comment utiliser un contrôle standard .Net dans une application MFC ?, à la différence près que l'on passe le type de l'usercontrol au CWinFormsControl.

 
Sélectionnez

Microsoft::VisualC::MFC::CWinFormsControl< MonUserControlNamespace::MonUserControl >m_monControle;
				

Remarque : N'oubliez pas de rajouter une référence à la DLL UserControl.

Créé le 12 juillet 2006  par nico-pyright(c)

Lien : Comment utiliser un contrôle standard .Net dans une application MFC ?

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 :

 
Sélectionnez

public ref class WinformView : public System::Windows::Forms::UserControl				
				

N'oubliez pas de rajouter l'include dans stdafx.h :

 
Sélectionnez

#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

 
Sélectionnez

class CMfcWinformAsViewView : public Microsoft::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 :

 
Sélectionnez

IMPLEMENT_DYNCREATE(CMfcWinformAsViewView, Microsoft::VisualC::MFC::CWinFormsView)				
BEGIN_MESSAGE_MAP(CMfcWinformAsViewView, Microsoft::VisualC::MFC::CWinFormsView)
				

Il ne reste plus qu'à appeler le constructeur de CWinformsView dans le constructeur de notre vue, en lui passant en paramètre le type de notre form :

 
Sélectionnez

CMfcWinformAsViewView::CMfcWinformAsViewView():Microsoft::VisualC::MFC::CWinFormsView(MfcWinformAsView::WinformView::typeid)
{
}
				

Compiler et exécuter : la winform est utilisée comme vue, les événements de la winform fonctionnant indépendamment.

Créé le 12 juillet 2006  par nico-pyright(c)

En préambule, vous devez avoir défini une Winform en tant que vue MFC : Voir Comment utiliser une Winform (UserControl) en tant que vue dans mon application MFC ?

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) :

 
Sélectionnez

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) :

 
Sélectionnez

void CMfcWinformAsViewView::OnInitialUpdate()
{
	CWinFormsView::OnInitialUpdate();
 
	MfcWinformAsView::WinformView ^ viewWinform = safe_cast<MfcWinformAsView::WinformView ^>(GetControl());
	viewWinform->pDoc = 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 :

 
Sélectionnez

public:
	CTime GetCurrentTime() { return CTime::GetCurrentTime();}
				

Je peux alors récupérer l'heure issue de mon document MFC dans ma Winform :

 
Sélectionnez

textBox2->Text =  gcnew String(pDoc->GetCurrentTime().Format("%A, %B %d, %Y / %H:%M:%S"));				
				

Téléchargez le programme d'exemple

Créé le 12 juillet 2006  par nico-pyright(c)

Lien : Comment utiliser une Winform (UserControl) en tant que vue dans mon application MFC ?

En préambule, vous devez avoir défini une Winform en tant que vue MFC : Voir Comment utiliser une Winform (UserControl) en tant que vue dans mon application MFC ?

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 :

 
Sélectionnez

public ref class WinformView : public System::Windows::Forms::UserControl, Microsoft::VisualC::MFC::IView				
				

Pour implémenter cette interface, on doit surcharger les trois fonctions virtuelles publiques OnUpdate, OnActivateView et OnInitialUpdate.

 
Sélectionnez

virtual void OnUpdate()
{
}
virtual void OnActivateView(bool activate)
{
}
virtual void OnInitialUpdate()
{
	textBox3->Text = textBox3->Text + "Passage dans OnInitialUpdate\r\n";
}				
				

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().

 
Sélectionnez

void CMfcWinformAsViewView::OnInitialUpdate()
{
	CWinFormsView::OnInitialUpdate(); // la méthode OnInitialUpdate de la winform est invoquée ici
}				
				
Créé le 12 juillet 2006  par nico-pyright(c)

Lien : Comment utiliser une Winform (UserControl) en tant que vue dans mon application MFC ?

En préambule, vous devez avoir défini une Winform en tant que vue MFC : Voir Comment utiliser une Winform (UserControl) en tant que vue dans mon application MFC ?

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 :

 
Sélectionnez

public ref class WinformView : public System::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.

 
Sélectionnez

public:
	Microsoft::VisualC::MFC::ICommandSource ^ cmdSrc;
	virtual void Initialize(Microsoft::VisualC::MFC::ICommandSource ^cmdSource)
	{
		cmdSrc = cmdSource;
		cmdSrc->AddCommandHandler(ID_APP_EXIT, gcnew Microsoft::VisualC::MFC::CommandHandler(this, &WinformView::OnExit));
	}
	void OnExit(unsigned int id)
	{
		MessageBox::Show("Click sur menu Exit");
	}		
				

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.

Téléchargez le programme d'exemple

Créé le 12 juillet 2006  par nico-pyright(c)

Lien : Comment utiliser une Winform (UserControl) en tant que vue dans mon application MFC ?

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 :

 
Sélectionnez

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 :

 
Sélectionnez

#pragma push_macro("MessageBox")
#undef MessageBox
 
...
MessageBox::Show(S"hello");
...
 
#pragma pop_macro("MessageBox")
				

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 :

 
Sélectionnez

::MessageBox(NULL, "Hello", "", MB_ICONSTOP);
				
Créé le 9 mai 2006  par nico-pyright(c)

Lorsqu'on inclut windows.h, il arrive fréquemment qu'on ait une erreur de ce style :

 
Sélectionnez

 '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 :

 
Sélectionnez

#define WIN32_LEAN_AND_MEAN					 
				 
Créé le 9 mai 2006  par nico-pyright(c)

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 :

 
Sélectionnez

#pragma unmanaged
				

et

 
Sélectionnez

#pragma managed				
				

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).

 
Sélectionnez

#pragma unmanaged
 
#include <windows.h>
#pragma comment(lib, "User32.lib")
 
class CNonManagee
{
public:
	CNonManagee(void);
	~CNonManagee(void);
	void Show()
	{
		MessageBoxW(NULL, L"Message depuis le langage machine", L"", 0);
	}
};
 
#pragma managed					
				
Créé le 12 juillet 2006  par nico-pyright(c)

Lien : Voir l'article complet

Lorsqu'on essaie de mixer du code natif et du code pure (/clr:pure) on obtient une erreur de link :

 
Sélectionnez

ijw/native module detected; cannot link with pure modules

Ceci n'est pas autorisé. Il faut alors passer le mode de compilation à mixte (/clr) pour résoudre cette erreur.

Créé le 27 mars 2007  par nico-pyright(c)

Lien : Compilation avec support mixte du CLR (/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 :

 
Sélectionnez

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 :

code natif
Sélectionnez

__declspec(dllexport) int addition(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 :

code managé
Sélectionnez

#pragma comment (lib, "testDll.lib")
__declspec(dllimport) int addition(int a,int b);

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.

 
Sélectionnez

warning C4272 : [...] is marked __declspec(dllimport); must specify native calling convention when importing a function.

Ainsi, on pourra définir ses fonctions ainsi :

code natif
Sélectionnez

__declspec(dllexport) int __stdcall addition(int a,int b)
{
	return a+b;
}
code managé
Sélectionnez

#pragma comment (lib, "testDll.lib")
__declspec(dllimport) int __stdcall addition(int a,int b);

Avec DLLImport, on utilisera les mécanismes de P/Invoke :

 
Sélectionnez

using namespace System::Runtime::InteropServices;
[DllImport("testDll")]
extern "C" int addition(int a,int b);
Créé le 27 mars 2007  par nico-pyright(c)

Lorsqu'on veut utiliser une variable native dans une classe managée, comme ci dessous :

 
Sélectionnez

#include <iostream>
ref class MaClasse
{
public:
	MaClasse() {};
private:
	std::string s;
};      

On obtient l'erreur de compilation C4368 :

 
Sélectionnez

error C4368: cannot define 's' as a member of managed 'MaClasse': mixed types are not supported

Il faut passer obligatoirement par un pointeur et procéder à son allocation.

 
Sélectionnez

#include <iostream>
ref class MaClasse
{
public:
	MaClasse() {};
private:
	std::string *s;
};      
Créé le 27 mars 2007  par nico-pyright(c)

Lorsqu'on veut utiliser un objet managé dans une classe native, comme ci dessous :

 
Sélectionnez

ref class CManagee
{
	CManagee(){};
};
 
class CNative
{
public:
	CNative();
	CManagee ^c;
};

On obtient l'erreur de compilation C3265 :

 
Sélectionnez

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).

 
Sélectionnez

#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.

Créé le 27 mars 2007  par nico-pyright(c)

gcroot est un template qui permet d'utiliser des types CLI dans une classe native comme expliqué ici. Pour l'utiliser, on inclut :

 
Sélectionnez
#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 :

 
Sélectionnez
#include <msclr\auto_gcroot.h>

Pour s'en convaincre et pour voir un exemple d'utilisation, soit le code suivant :

 
Sélectionnez
ref class CManagee
{
private:
	String ^s;
public:
	CManagee(String ^val) {s = val;};
	~CManagee()
	{
		Console::WriteLine("Destructeur : " + s);
	}
protected:
	!CManagee()
	{
		Console::WriteLine("Finalizer " + s);
	}
};
 
class CNative
{
public:
	CNative()
	{
		c = gcnew CManagee("gcroot");
		cAuto = gcnew CManagee("auto_gcroot");
	}
private:
	gcroot<CManagee^> c;
	msclr::auto_gcroot<CManagee^> cAuto;
};
 
int main(array<System::String ^> ^args)
{
	CNative *c = new CNative();
	delete c;
	Console::WriteLine("fin du programme");
	return 0;
}

Produit en sortie :

 
Sélectionnez

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.

Créé le 20 novembre 2007  par nico-pyright(c)

Il peut être utile de tester si un handle managé par gcroot ou auto_gcroot est nul.
Il n'est pas possible de faire ça :

 
Sélectionnez
class CNative
{
public:
	CNative()
	{
		if (chaine == nullptr)
			chaine = gcnew String("gcroot");
	}
private:
	gcroot<String ^> chaine;
};

On obtient pour gcroot en sortie :

 
Sélectionnez

error C2088: '==' : illegal for struct

Pour comparer à nullptr, il faut caster :

 
Sélectionnez
class CNative
{
public:
	CNative()
	{
		if (safe_cast<String ^>(chaine) == nullptr)
			chaine = gcnew String("gcroot");
	}
private:
	msclr::auto_gcroot<String ^> chaine;
};

Cependant, on peut utiliser une écriture plus simple pour tester si la variable référence un objet managé ou un nullptr :

 
Sélectionnez
class CNative
{
public:
	CNative()
	{
		if (!chaine)
			chaine = gcnew String("gcroot");
	}
private:
	msclr::auto_gcroot<String ^> chaine;
};
Créé le 20 novembre 2007  par nico-pyright(c)
  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006-2007 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.