septembre
2008
on me demande souvent comment créer un wrapper C++/CLI …
Je vais, dans ce post, vous guider dans la création d’un tel wrapper.
Pour l’exemple, je vais utiliser la très bonne bibliothèque de compression en .Cab, créée par Elmue sur CodeProject.
Cette bibliothèque possède une version C++ et une version .Net, cette dernière ayant été créé avec le Managed C++ de Visual Studio 2002, je vais vous montrer comment on peut encapsuler la version C++ pure dans un wrapper C++/CLI afin de l’utiliser avec vos projets .Net.
Pour cet exemple, je vais encapsuler uniquement la compression, donc je vais soulager le projet C++ de quelques fichiers et je vais les modifier pour retirer les références à ces fichiers inutiles.
J’ai donc cette liste de fichier qui vont me servir :
– Blowfish.h
– Compress.h
– Error.h
– File.h
– Map.h
– String.h
(notez que j’ai renommé les fichiers .hpp en .h parce que j’aime mieux …)
Je créé ensuite un nouveau projet CLR : Class Library que j’appelle CabUtil (il est donc compilé avec l’option /clr).
J’y ajoute les fichiers précédemment cités, je compile … et c’est presque terminé !
La fonctionnalité nommée C++ Interop a fait tout le boulot pour nous. Elle a compilé du code natif dans notre assembly .Net.
Passons désormais à la création du wrapper C++/CLI, qui fera office de wrapper et de façade en même temps.
Je créé une nouvelle classe, que j’appelle CabUtil.
Ici, je n’ai pas grand chose à wrapper, mais nous allons le faire quand même. On remarque qu’on a besoin d’un char * qui représente la clé de cryptage du .Cab.
Créons donc un membre privé de type char *
Je vais lui affecter une valeur par défaut dans mon constructeur :
{
s8_EncryptionKey = new char[73];
strcpy(s8_EncryptionKey, "");
}
Maintenant, on va créer une propriété qui va exposer une String ^ .Net et qui se chargera de convertir en char * :
{
void set(String ^value)
{
if (value->Length > 72)
value = value->Substring(0, 72);
char* temp = static_cast<char *>(Marshal::StringToHGlobalAnsi(value).ToPointer());
strcpy(s8_EncryptionKey, temp);
Marshal::FreeHGlobal(safe_cast<IntPtr>(temp));
}
}
Note : la clé ne peut pas dépasser 72 caractères.
Bien sur, il ne faudra pas oublier de désallouer le char * dans les destructeurs et finalizers :
{
delete s8_EncryptionKey;
s8_EncryptionKey = NULL;
}
protected:
!CabCompress()
{
delete s8_EncryptionKey;
s8_EncryptionKey = NULL;
}
Je vais maintenant passer à l’écriture d’une méthode qui va compresser une liste de fichier, cette méthode étant une façade pour un algorithme un peu plus complexe.
Il s’agit de la méthode :
void CompressFileList(IDictionary<String^, String^>^ fileList, String ^compressFile)
Elle prend en paramètre un dictionnaire qui va contenir le nom du fichier à compresser et sa destination dans l’arborescence du .Cab. Le deuxième paramètre est le fichier archive de destination.
Il y a dans cette méthode quelques conversions de String ^ en char *, mais nous n’allons pas nous attarder, vu qu’on a déjà vu le principe plus haut.
L’autre élément intéressant est le mapping d’une callback native en delegate .Net. Nous allons créer un événement pour être informé de la mise à jour du status de la compression.
On avait en C++, la définition suivante pour gérer la callback :
k_ComprCallbacks.f_UpdateStatus = &CMain::OnUpdateStatus;
avec la méthode statique associée :
{
...
}
pour wrapper ceci, nous allons créer un delegate, dans les membres privés :
delegate int WrapperUpdateStatusHandler(UINT typeStatus, Cabinet::CCompress::kCurStatus *pk_CurStatus, void *p_Param);
WrapperUpdateStatusHandler ^UpdateStatusDelegate;
Notez que le delegate a bien sur la même signature que la méthode OnUpdateStatus.
Dans notre méthode, on pourra instancier notre delegate ainsi :
UpdateStatusDelegate = gcnew WrapperUpdateStatusHandler(this, &CabCompress::UpdateStatus);
qui est raccroché à la méthode
static int UpdateStatus(UINT typeStatus, Cabinet::CCompress::kCurStatus *pk_CurStatus, void *p_Param)
(les observateurs auront noté que j’ai changé le nom de la méthode C++, qui au passage, n’est plus statique)
Ensuite on utilise GetFunctionPointerForDelegate pour raccrocher ce delegate à la callback en C++
k_ComprCallbacks.f_UpdateStatus = (Cabinet::CCompress::kCallbacks::t_UpdateStatus)p.ToPointer();
i_Compress.SetCallbacks(&k_ComprCallbacks);
Maintenant, la méthode UpdateStatus pourra lever l’événement suivant (qu’on aura défini comme membre public de la classe) :
event UpdateStatusHandler ^OnUpdateStatus;
int UpdateStatus(UINT typeStatus, Cabinet::CCompress::kCurStatus *pk_CurStatus, void *p_Param)
{
if (pk_CurStatus->FolderPercent > 0)
OnUpdateStatus(pk_CurStatus->FolderPercent);
return 0;
}
Et voilà, il ne nous reste plus qu’à compiler et notre assembly est prête.
Reste à l’utiliser dans un programme C#. Pour ce faire, on référence cette assembly dans notre projet, et on peut appeler la méthode de compression ainsi :
{
CabCompress c = new CabCompress();
c.OnUpdateStatus += c_OnUpdateStatus;
Dictionary<string, string> dictionary = new Dictionary<string, string>
{
{"C:\\test.txt", "C\\test.txt"},
{"C:\\temp\\fichier.mp3", "C\\temp\\fichier.mp3"}
};
c.CompressFileList(dictionary, "C:\\OutPut_%d.cab");
}
static void c_OnUpdateStatus(int PercentComplete)
{
Console.WriteLine("Compression : {0} %", PercentComplete);
}
Vous pouvez télécharger les sources de cet exemple : version zip ou version rar
Commentaires récents
- [Tests] Arrange Act Assert, une traduction ? dans
- [UnitTest][C#] Tester une méthode privée dans
- Récupérer une valeur d’un contrôle depuis une autre Form / inclusions croisées et déclaration anticipée dans
- Tutoriel : Utiliser la ListBox et l’Isolated Storage dans vos applications Windows Phone 7 avec Silverlight dans
- Tutoriel : Utiliser la ListBox et l’Isolated Storage dans vos applications Windows Phone 7 avec Silverlight dans
Archives
- janvier 2013
- avril 2012
- janvier 2012
- juin 2011
- janvier 2011
- décembre 2010
- novembre 2010
- septembre 2010
- juin 2010
- mars 2010
- février 2010
- janvier 2010
- décembre 2009
- novembre 2009
- octobre 2009
- septembre 2009
- août 2009
- juillet 2009
- mai 2009
- avril 2009
- mars 2009
- janvier 2009
- décembre 2008
- novembre 2008
- octobre 2008
- septembre 2008
- août 2008
- juillet 2008
- juin 2008
- mai 2008
- avril 2008
- mars 2008
- février 2008
- janvier 2008
- décembre 2007
- novembre 2007
- octobre 2007
- septembre 2007
- août 2007
- juillet 2007
- juin 2007
- mai 2007