Nel post precedente, abbiamo visto come realizzare una
dll con tecnologia
.NET, in grado di consentire ad una qualunque applicazione (sviluppata con tecnologia
.NET) di usufruire di un generico servizio web. Tuttavia il problema di partenza era quello di creare un ponte tra il framework .NET ed un’applicazione
Win 32.
Il wrapper C++/CLI
Passiamo quindi a descrivere i passi per lo sviluppo del nostro
wrapper, ovvero della
dll che renderà accessibili i servizi esportati dalla libreria dinamica implementata negli step precedenti.
● Il primo passo è quello di creare un progetto C++ per la creazione di una dll, ed abilitare il
Supporto Common Language Runtime (/clr).
● Aggiungiamo un riferimento alla
dll prodotta dal progetto
GoogleMapsAPI● Fatto ciò aggiungeremo due classi
CGoogleMapsClientIntefacePrivate e
CGoogleMapsClientInteface.
#pragma once
# include <string>
using namespace std;
class CGoogleMapsClientIntefacePrivate;
class CGoogleMapsClientInteface
{
public:
CGoogleMapsClientInteface();
~CGoogleMapsClientInteface();
#pragma region Attributes
private:
CGoogleMapsClientIntefacePrivate* _private;
#pragma endregion
#pragma region Properties
public:
__declspec(property(get = GetLatitude)) double Latitude;
double GetLatitude(void);
__declspec(property(get = GetLongitude)) double Longitude;
double GetLongitude(void);
__declspec(property(put = SetAddress, get = GetAddress)) const char* Address;
void SetAddress(const char*);
const char* GetAddress(void) const;
#pragma endregion
};
● La cui implementazione è la seguente:
#include "stdafx.h"
#include "GoogleMapsClientInteface.h"
# include <msclr\auto_gcroot.h>
using namespace System::Runtime::InteropServices;
using namespace GoogleMapsAPI;
class CGoogleMapsClientIntefacePrivate
{
public:
msclr::auto_gcroot<GoogleMaps^> googleMapsAPI;
};
CGoogleMapsClientInteface::CGoogleMapsClientInteface()
{
this->_private = new CGoogleMapsClientIntefacePrivate();
this->_private->googleMapsAPI = gcnew GoogleMaps();
}
CGoogleMapsClientInteface::~CGoogleMapsClientInteface()
{
if (this->_private)
delete this->_private;
}
double CGoogleMapsClientInteface::GetLatitude()
{
return this->_private->googleMapsAPI->Latitude;
}
double CGoogleMapsClientInteface::GetLongitude()
{
return this->_private->googleMapsAPI->Longitude;
}
void CGoogleMapsClientInteface::SetAddress(const char* value)
{
this->_private->googleMapsAPI->Address = gcnew System::String(value);
this->_private->googleMapsAPI->GetCoordinates();
}
const char* CGoogleMapsClientInteface::GetAddress(void) const
{
return (const char*)Marshal::StringToHGlobalAnsi(this->_private->googleMapsAPI->Address).ToPointer();
}
Il punto focale dell’implementazione è dato dalla classe
auto_gcroot, che, citando l’help online di Microsoft, “può essere utilizzato per incapsulare un handle virtuale in un tipo nativo”. In altre parole questa classe ci fornirà il punto d’accesso al mondo .NET, e, ad essa delegheremo gli oneri legati alle differenti modalità di gestione della memoria tra il mondo nativo ed il mondo gestito. Tuttavia, così com’è, la classe
auto_gcroot, parte dell’armamentario
C++/CLI, non è utilizzabile in una dll
C++ nativa. In primo luogo perchè non verrebbe riconosciuta in fase di compilazione, ed anche perchè se venisse compilata altererebbe irrimediabilmente il livello di astrazione che stiamo cercando di dare all’intero progetto seguendo una delle tecniche care allo sviluppo C++ nativo descritte nel
PIMPL Idiom. A tale scopo abbiamo
nascosto il nostro handle nella classe
CGoogleMapsClientIntefacePrivate ottenendo un’interfaccia compatibile con il mondo nativo.
Funzioni esportate
Ottenuto il punto d’accesso, o il wrapper che dir si voglia, non ci resta che implementare la nostra
dll nativa. Dando per scontata la parte legata allo sviluppo della dll in sé per sé, focalizzerei l’attenzione sul fatto che non abbiamo esportato direttamente la classe
CGoogleMapsClientInteface, cosa del tutto lecita e possibile, ma abbiamo creato un
handle verso essa mediante il metodo
CGoogleMapsClientInteface_Create(). Il motivo di tale scelta è dato dal fatto che il nostro
client non verrà sviluppato in C++ nativo (in tal caso ci saremmo fermati al passo precedente), ma in
Delphi 7, linguaggio che ha una gestione della memoria totalmente differente da quella del C++, e che difficilmente riuscirebbe ad utilizzare una classe esportata direttamente dal nostro wrapper.
typedef void* THandle;
extern "C"
{
inline CLIBRIDGE_API THandle CGoogleMapsClientInteface_Create()
{
return (THandle) new CGoogleMapsClientInteface();
}
inline CLIBRIDGE_API void CGoogleMapsClientInteface_SetAddress(THandle handle, const char* value)
{
if (handle)
{
((CGoogleMapsClientInteface*)handle)->Address = value;
((CGoogleMapsClientInteface*)handle)->GetAddress();
}
}
inline CLIBRIDGE_API const char* CGoogleMapsClientInteface_GetAddress(THandle handle)
{
return (handle)
? ((CGoogleMapsClientInteface*)handle)->Address
: "";
}
inline CLIBRIDGE_API double CGoogleMapsClientInteface_GetLatitude(THandle handle)
{
return (handle)
? ((CGoogleMapsClientInteface*)handle)->Latitude
: numeric_limits<double>::signaling_NaN();
}
inline CLIBRIDGE_API double CGoogleMapsClientInteface_GetLongitude(THandle handle)
{
return (handle)
? ((CGoogleMapsClientInteface*)handle)->Longitude
: numeric_limits<double>::signaling_NaN();
}
CLIBRIDGE_API int fnCLIBridge(void);
}
L’applicazione nativa
A questo punto non ci resta che mettere insieme i blocchi sviluppati finora per creare un programma di test scritto in
idioma nativo. Si tratta di una semplice applicazione
Console, scritta in
C++ e non in
Delphi 7 (a tutto c’è un limite ;-)).
#include "stdafx.h"
# include <iostream>
# include <conio.h>
# include "CLIBridge.h"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
cout << " ------------------------ " << endl;
cout << " ---------START---------- " << endl;
cout << " ------------------------ " << endl;
cout << endl;
THandle handle = CGoogleMapsClientInteface_Create();
const char* address = "Google Building 44, Mountain View, California, Stati Uniti";
CGoogleMapsClientInteface_SetAddress(handle, address);
const char* value = CGoogleMapsClientInteface_GetAddress(handle);
cout << "Address:\t" << value << endl;
cout << endl;
cout << "Latitude:\t" << CGoogleMapsClientInteface_GetLatitude(handle) << endl;
cout << endl;
cout << "Longitude:\t" << CGoogleMapsClientInteface_GetLongitude(handle) << endl;
cout << endl;
cout << " ------------------------ " << endl;
cout << " ----------END----------- " << endl;
cout << " ------------------------ " << endl;
std::cin.get();
cout << " ------------------------ " << endl;
return 0;
}
Il risultato finale è illustrato nell’immagine seguente, nella quale si mette a confronto l’output dell’applicazione
console con quella
WinForm sviluppata nel post precedente.
Conclusione
Lavorando nel mondo della tecnologia, in qualsiasi ambito, l’eterogeneità degli strumenti da utilizzare è la regola e non l’eccezione, e quindi può capitare che una tecnologia
datata, ma ancora efficiente e funzionale, debba poter accedere a nuovi strumenti. Quello illustrato è uno dei casi che ci si può trovare a dover affrontare, ed uno dei possibili metodi per risolverlo.
Il link ai sorgenti dell’intero progetto raggiungibili dal link seguente
GitEnjoy