FAQ C++/CLI et VC++.Net
FAQ C++/CLI et VC++.NetConsultez toutes les FAQ
Nombre d'auteurs : 29, nombre de questions : 248, création le 22 février 2013
- Comment ajouter une form .Net (winform) à mon application MFC ?
- Comment utiliser un contrôle standard .Net dans une application MFC ?
- Comment ajouter un contrôle utilisateur .Net (UserControl) à mon application MFC ?
- Comment utiliser une Winform (UserControl) en tant que vue dans mon application MFC ?
- Comment communiquer entre le document MFC et la vue Winform dans mon application MFC ?
- Comment intercepter les événements d'une vue Winform dans mon application MFC ?
- Comment intercepter les événements MFC dans ma vue Winform ?
- Comment gérer la cohabitation de deux fonctions ayant le même nom dans l'API Win32 et le framework.net 1.x ?
- Comment éviter la redéfinition des objets COM ?
- Comment compiler du code natif ?
- Que faire avec l'erreur d'éditions de liens LNK1313 ?
- Que faire avec l'erreur d'édition de liens LNK2031 ?
- Comment utiliser des variables natives dans une classe managée ?
- Comment utiliser des objets managés dans une classe native ?
- Quelle est la différence entre gcroot et auto_gcroot ?
- Comment savoir si un handle géré par le template gcroot est nul ?
- 4.1. Conversions
(6)
- Comment convertir une String ^ en char * ?
- Comment convertir une String ^ en wchar_t * ?
- Comment convertir un char * en String ^ ?
- Comment convertir un string de la STL en String ^ ?
- Comment convertir une String ^ en string de la STL ?
- Comment convertir un objet d'un type de base en un objet d'un autre type de base ?
- 4.2. Intéropérabilité (2)
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 :
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
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 :
Microsoft::VisualC::MFC::
CWinFormsControl<
System::Windows::Forms::
LinkLabel >
m_linkLabelDotNet;
Ensuite, dans le DoDataExchange :
DDX_ManagedControl( pDX, IDC_DOTNET, m_linkLabelDotNet );
Rajouter ensuite un handler d'événement :
void
linkLabel_LinkClicked( System::
Object^
sender, System::Windows::Forms::
LinkLabelLinkClickedEventArgs^
e );
et utiliser la macro _DELEGATE_MAP pour cabler les méthodes :
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 :
m_linkLabelDotNet->
LinkClicked +=
MAKE_DELEGATE(System::Windows::Forms::
LinkLabelLinkClickedEventHandler , linkLabel_LinkClicked);
Et vous pouvez ainsi désormais utiliser la fonction :
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
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.
Microsoft::VisualC::MFC::
CWinFormsControl<
MonUserControlNamespace::
MonUserControl >
m_monControle;
Remarque : N'oubliez pas de rajouter une référence à la DLL UserControl.
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 :
public
ref class
WinformView : public
System::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 : 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 :
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 :
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.
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) :
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) :
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 :
public
:
CTime GetCurrentTime() {
return
CTime::
GetCurrentTime();}
Je peux alors récupérer l'heure issue de mon document MFC dans ma Winform :
textBox2->
Text =
gcnew String(pDoc->
GetCurrentTime().Format("%A, %B %d, %Y / %H:%M:%S"
));
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 :
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.
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().
void
CMfcWinformAsViewView::
OnInitialUpdate()
{
CWinFormsView::
OnInitialUpdate(); // la méthode OnInitialUpdate de la winform est invoquée ici
}
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 :
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.
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.
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 :
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 :
#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 :
::
MessageBox(NULL
, "Hello"
, ""
, MB_ICONSTOP);
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 :
#define WIN32_LEAN_AND_MEAN
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 :
#pragma unmanaged
et
#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).
#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
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 :
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.
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) 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 :
#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.
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;
}
#pragma comment (lib,
"testDll.lib"
)
__declspec(dllimport
) int __stdcall addition(int a,int b);
Avec DLLImport, on utilisera les mécanismes de P/Invoke :
using
namespace
System::Runtime::
InteropServices;
[DllImport("testDll"
)]
extern
"C"
int
addition(int
a,int
b);
Lorsqu'on veut utiliser une variable native dans une classe managée, comme ci dessous :
#include
<iostream>
ref class
MaClasse
{
public
:
MaClasse() {}
;
private
:
std::
string s;
}
;
On obtient l'erreur de compilation C4368 :
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.
#include
<iostream>
ref class
MaClasse
{
public
:
MaClasse() {}
;
private
:
std::
string *
s;
}
;
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
\a
uto_gcroot.h>
Pour s'en convaincre et pour voir un exemple d'utilisation, soit le code suivant :
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 :
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.
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 :
class
CNative
{
public
:
CNative()
{
if
(chaine ==
nullptr
)
chaine =
gcnew String("gcroot"
);
}
private
:
gcroot<
String ^>
chaine;
}
;
On obtient pour gcroot en sortie :
error C2088: '==' : illegal for struct
Pour comparer à nullptr, il faut caster :
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 :
class
CNative
{
public
:
CNative()
{
if
(!
chaine)
chaine =
gcnew String("gcroot"
);
}
private
:
msclr::
auto_gcroot<
String ^>
chaine;
}
;