260 likes | 370 Views
Mixed code: C++/CLI. Raffaele Rialdi Visual Developer Security MVP malta@vevy.com MVP Profile: http://snipurl.com/f0cv. http://mvp.support.microsoft.com. Agenda. Perché usare C++ ... perché C++/CLI Carrellata sul linguaggio solo gli elementi del linguaggio utili per interop
E N D
Mixed code: C++/CLI Raffaele RialdiVisual Developer Security MVP malta@vevy.comMVP Profile: http://snipurl.com/f0cv http://mvp.support.microsoft.com
Agenda • Perché usare C++ ... perché C++/CLI • Carrellata sul linguaggio • solo gli elementi del linguaggio utili per interop • gestione della memoria managed/unmanaged • pinning e marshalling • mixed types • C++ future directions
Perché usare C++ • Rende semplice usare codice unmanaged • Le definizioni PInvoke non sono sempre semplici da scrivere • VC++ usa IJW (It Just Works) per usare codice nativo e codice managed allo stesso tempo • Ad esempio si semplifica l'accesso alle librerie DirectX • VC++ può creare dll/exe misti con codice C# / VB / C++ • Rende semplice usare codice managed • System.Xml semplifica la manipolazione di Xml • System.Net semplifica l'accesso ai socket e al web • System.Text.RegularExpressions semplifica le regex • Idem per altre namespace/classi del framework
Perché un nuovo linguaggio? Perché C++/CLI? • Le managed extensions erano complesse per aderire alle regole dello standard ISO (doppio underscore, etc.) • cl /clr:oldSyntax continua a compilare le managed extensions • un tool di Stan Lippman permette una migrazione quasi automatica • C++/CLI è vicino alla standardizzazione ECMA (in questi giorni) ed ISO (probabilmente chiamata ISO C++09) • C++/CLI ha una piacevole sintassi per lavorare sia con il mondo managed che unmanaged CLR Features • Garbage collector, finalizzatori • Generics • Reference e Value type • Interfacce • Verificabilità • Security • Proprietà, delegati, eventi C++ Features • Finalizzazione deterministica • Template e generics • Uso dei tipi nativi • Multiple inheritance (unmanaged) • STL, algoritmi generici • Distinzione Puntatore/Puntato • Copy construction, assignment
Novità in C++/CLIdi cui non parleremo • trivial properties: property String ^Name; • indexed properties • managed copy constructors • delegate + event • managed operator overloading • boxing/unboxing • safe_cast<> • generics vs templates • method overriding • lock(...) • jagged arrays • STL.NET • integrazione MFC / Winform • integrazione Avalon • compilazione parallela • profile guided optimization • OpenMP parallelism • CLR Delay Loading
Veloce carrellata su C++/CLI • Supporto distinto per tipi managed e unmanaged Le specifiche si trovano qui: http://msdn.microsoft.com/visualc/homepageheadlines/ecma/default.aspx
I nuovi operatori ^ e % • Introdotto nel linguaggio l' "handle" • una sorta di puntatore managed ad un oggetto nel managed heap • Il CLR tiene aggiornato il suo valore quando esegue la GC • analogo del reference di C#, ma il reference in C++ esisteva già • il simbolo è "hat" ^ • su un handle si applicano gli operatori -> e * • L'analogo di void* è Object^ • Nuovo allocatore di memoria managed gcnew • String ^s1 = gcnew String; • String s2; continua ad essere un espressione valida • Introdotto nel linguaggio il "tracking reference" • Analogo del reference & di C++ classico, cioè un alias all'oggetto • il simbolo è "%"
Distruzione deterministica • C++/CLI introduce la distruzione deterministica delle risorse • Non deve e non può riguardare la memoria, ma solo le risorse unmanaged. Questo è lo scopo del pattern Dispose. • In pratica il Pattern Dispose viene implementato dal compilatore • Implementazione completa di GC.SuppressFinalize • Quando nella classe esiste il distruttore: • In sostanza il distruttore della classe viene mappato su Dispose • La classe implementa automaticamente IDisposable • L'uscita dallo scope o una delete esplicita provoca la chiamata a Dispose (analogo dello statement using di C#, ma più semplice) • Introdotto anche la sintassi per il finalizzatore • La sintassi è analoga al distruttore !NomeClasse() {...} • Nel finalizzatore si mette la distruzione delle risorse • Nella Dispose si mette la chiamata al finalizzatore
Istruire il precompilatore • Il compilatore VC++ accetta di mixare codice managed e unmanaged anche nello stesso listato • Alcune volte potrebbe esserci ambiguità su come compilare il codice • Si può informare il compilatore con due #pragma • #pragma managed • #pragma unmanaged #pragma managed class Managed {...} ; #pragma unmanaged class Native {...} ; #pragma managed ...
Memoria: Interior Pointers • Al contrario dell'handle, permette l'aritmetica dei puntatori • Utile per la veloce manipolazione di array e buffer • Trasparente: è usabile anche per tipi unmanaged (restituisce un puntatore classico) interior_ptr<type> name = &value; Esempio 1 array<int>^a = {1,2,3,4,5}; interior_ptr<int> ip = &a[0]; for(int i = 0; i<a->Length; i++) Console::WriteLine(++ip[i]); // output: 2, 3, 4, 5, 6 Esempio 2 String ^str1 = "Hello, world"; String ^str2 = str1; interior_ptr<String^> ip = &str1; *ip = "Ciao"; Console::WriteLine(str1 + " - " + str2); // output: Ciao – Hello, world
Memoria: Pinning Pointers pin_ptr<type> name = &value; void F(int* p); // Func unmanaged array<int>^ arr = …;pin_ptr<int> pi = &arr[0];F(pi); // ptr unmanaged Interior Pointerinterior_ptr<T> String ^str1 = "Hello, world"; // interior pointer al buffer della stringa (non è una copia) interior_ptr<const wchar_t> ip = PtrToStringChars(str1); // interior pointer senza 'const' interior_ptr<wchar_t> ip2 = const_cast<interior_ptr<wchar_t> >(ip); // pinning pointer Il GC non può muovere il buffer pin_ptr<wchar_t> pp = ip2; // modifico il buffer for(int i=0; i<str1->Length; i++) ++pp[i]; // caratteri ascii incrementati Console::WriteLine(str1); // out Ifmmp-!xpsme Pinning Pointerpin_ptr<T> Unmanaged PointerT*
memoria classica sempre ferma Unmanaged heap MyClass1 *pc = new MyClass1(0x30); // pc è un "puntatore" gg.hh.jj.kk MyClass1 &rc = *pc; 30.00.00.00 sizeof(MyClass1) // rc è un "reference" alias MyClass1 *pc2 = pc; gg.hh.jj.kk Stack locale GCaggiorna i valori quando muove la memoria MyClass2 ^hc = gcnew MyClass2(); xx.yy.zz.tt // hc è un handle. ^ si pronuncia hat interior_ptr<MyClass2 ^> ip = &hc; pp.qq.rr.ss // ip è un "interior pointer" pin_ptr<MyClass2 ^> pp = &hc; pin_ptr<MyClass2 ^> pp2 = pp; ll.mm.nn.oo // pp e pp2 sono "pinning pointers" Managed heap GCmuove i blocchi di memoria MyRefType %tr = *hc; 30.00.00.00 // tr è un "tracking reference" size unknown pinned Sguardo molto semplicistico in memoria
È tutto così semplice?... quasi • Fin ad ora abbiamo visto che: • Creare immagini miste managed/unmanaged è semplice • Eseguire il marshalling dei parametri è semplice • Ci sono semplici strumenti per accedere alla memoriamanaged e unmanaged • L'interoperabilità è possibile in due modi: • P/Invoke esplicito (come in C#) • IJW (=It Just Works) eseguendo il marshalling dei parametri • E allora dov'è il problema?
Mixed types are not supported public ref class RefClass { public: POINT pt; // unmanaged struct }; public class Native { public: System::String ^str; }; error C4368: cannot define 'pt' as a member of managed 'ManagedClass': mixed types are not supported error C3265: cannot declare a managed 'str' in an unmanaged 'Native'
Tipi misti: tipi managed dentro tipi unmanaged • GCHandle • gcroot<> • necessita #include<vcclr.h> • non chiama automaticamente Dispose! • msclr::auto_gcroot<> • necessita #include <msclr\auto_gcroot.h> • chiama automaticamente la IDisposable::Dispose se esiste #include <vcclr.h> using namespace System; public class Native1 { gcroot<String ^> str; }; #include <msclr\auto_gcroot.h> using namespace msclr; using namespace System; public class Native2 { auto_gcroot<String ^> str; };
Tipi misti: tipi unmanaged dentro tipi managed • Brutta notizia: fin'ora nessun supporto ufficiale ma la soluzione è molto semplice .... • In una classe managed si può avere un puntatore unmanaged • ma è poi necessario gestire la sua distruzione (ciclo di vita) • Molto meglio scrivere una classe con template che gestisce il ciclo di vita del puntatore • Brandon Bray (uno degli ideatori della nuova sintassi) ne ha pubblicata una chiamata "Embedded" sul suo blog #include <windows.h> #include "Embedded.h" public ref class RefClass { Embedded<POINT> np; };
Cosa sono le calling convention? • Una sorta di contratto alla compilazione che prevede: • come passare gli argomenti delle funzionied il valore di ritorno • quali registri della CPU devono essere salvati • Le quattro convenzioni più usate oggi sono: • __cdecl usato dalle librerie C e numerose API • __stdcall conosciuta anche come "pascal", usata dalle Win32 API • __fastcall usa i registri per passare gli argomenti • __thiscall default per le chiamate a funzioni membro in C++
Cos'è il "double thunking"? • Quando si compila codice con metadati ogni funzione ha due entry-point: • uno con la calling-convention assegnata • uno con la calling-convention CLR • Quale viene usato? • se il codice è compilato con /clr, l'entry-point di base è un thunk alla chiamata CLR • se il codice è compilato senza /clr, l'entry point CLR è un thunk alla chiamata x86 • Come viene scelto l'entry-point da usarsi? • il compilatore è normalmente in grado di scegliere ma ... • non può scegliere se la chiamata è un puntatore a funzione • non può scegliere anche per le funzioni virtuali perché queste sono puntatori a funzioni
Cos'è il "double thunking"? • Dove si presenta il problema? • Le funzioni virtuali compilate in IL avranno sempre un thunk da unmanaged a managed. Questo è inevitabile. • Se poi la chiamata viene fatta da codice managed,c'è un thunk supplementare:managed unmanaged managedQuesto doppio passaggio si chiama "double thunking" • Esiste una soluzione? • La soluzione esiste solo se quella chiamata virtuale verrà solo chiamata dal mondo managed • In questo caso è sufficiente marcare la funzione con laconvenzione __clrcall • forzando __clrcall si evita il double thunking virtual return-type__clrcallfunction-name(arguments);
Un assembly, mixed language • Task complesso, nessun supporto di VS2005 • Più semplice se si disabilitano i precompiled headers in tutti i progetti VC++ (ma è comunque usarli) • La novità consiste nei .netmodule • Il .netmodule è identico ad un assembly ma senza metadati • per esempio non ha versione • Il netmodule viene ri-compilato al link time • Solo il linker di C++ ha questa capacità di ricompilazione a.cpp C++ Compiler a.obj EXE C++ Code C++ Linker D:\>cl /c /clr a.cpp C# Code D:\>csc /t:module c.cs c.cs C# Compiler c.netmodule
Un assembly, mixed language • Esempio di una Winform C# che usa una business logic in C++/CLI • Progetto 1: CppLogicClassLibrary • Per semplicità precompiled headers disabilitati • Si compila con VS.net EXE CppLogicClassLibrary CsFormClassLibrary CppStartWinform
Un assembly, mixed language • Progetto 2: CsFormClassLibrary • Eliminato Program.cs, l'entry point sarà in C++/CLI • Si referenzia CppLogicClassLibrary e si usano le classi • Si compila in VS.NET solo per il controllo sintattico • Necessario compilare a mano (ma si può lanciare make.bat come post-build action) Compilatore C# vogliamo un .netmodule dipendenza dal progetto C++/CLI csc /t:module /addmodule:..\CppLogicClassLibrary\debug\CppLogicClassLibrary.obj /resource:obj\Debug\CsFormClassLibrary.Form1.resources *.cs compilo tutti i sorgenti aggiungole risorse (form)
Un assembly, mixed language • Progetto 3: CppStartWinform • Progetto C++/CLI Winform a cui si toglie la form • Cambiare le opzioni da /clr:safe a /clr • Aggiungere nel Linker – Input – Additional il .netmodule di C# e l'obj di C++ • Aggiungere alla command line del linker l'opzione /LTCG • Funge solo da entry point per l'applicazione managed • Si può fare la build da VS.NET • Risultato: 1 Assembly EXE con dentro tre immagini miste native/managed • Ovviamente la dipendenza dal framework rimane
Qual'è il futuro di ISO C++? • Ci sono problemi da risolvere per il cambio nell'evoluzione della crescita hardware • niente più grossi aumenti di velocità nelle CPU • aumento del numero di 'core' nelle CPU • L'accesso diretto alla memoria impedisce una gestione efficiente nel determinare i problemi di concorrenza • Work in progress su: • gestione automatica della concorrenza ("concurs") • gestione asincrona ("Futures") • type inference • lambda functions • Linq