...

Mixed code: C++/CLI

by user

on
Category: Documents
17

views

Report

Comments

Transcript

Mixed code: C++/CLI
Mixed code: C++/CLI
Raffaele Rialdi
Visual Developer Security MVP
[email protected]
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
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
C++ Features
•Finalizzazione deterministica
•Template e generics
•Uso dei tipi nativi
•Multiple inheritance (unmanaged)
•STL, algoritmi generici
•Distinzione Puntatore/Puntato
•Copy construction, assignment
CLR Features
•Garbage collector, finalizzatori
•Generics
•Reference e Value type
•Interfacce
•Verificabilità
•Security
•Proprietà, delegati, eventi
Novità in C++/CLI
di 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
C++/CLI
C#
public ref class MyClass { ... } ;
public ref struct MyClass { ... } ;
MyClass ^c = gcnew MyClass();
public class MyClass { ... }
public value class MyValueType { ... } ;
public value struct MyValueType { ... } ;
MyValueType v;
public struct MyValueType { ... }
public interface class MyInterface { ... } ;
public interface struct MyInterface { ... } ;
public interface MyInterface { ... }
public enum class MyEnum : char { ... } ;
public enum struct MyEnum : char { ... } ;
public enum MyEnum : char { ... }
array<int> ^nums = {1,2,3} ;
int[] nums = new int[] {1,2,3}
MyClass c = new MyClass();
MyValueType v;
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
interior_ptr<type> name = &value;
• 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)
Esempio 1
Esempio 2
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]);
String ^str1 = "Hello, world";
String ^str2 = str1;
interior_ptr<String^> ip = &str1;
*ip = "Ciao";
Console::WriteLine(str1 + " - " + str2);
// output: 2, 3, 4, 5, 6
// 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 Pointer
interior_ptr<T>
String ^str1 = "Hello, world";
// interior pointer al buffer della stringa (non è una copia)
interior_ptr<const wchar_t> ip = PtrToStringChars(str1);
Pinning Pointer
pin_ptr<T>
// 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;
Unmanaged Pointer
T*
// modifico il buffer
for(int i=0; i<str1->Length; i++)
++pp[i];
// caratteri ascii incrementati
Console::WriteLine(str1); // out Ifmmp-!xpsme
Sguardo molto semplicistico in
memoria
Unmanaged heap
MyClass1 *pc = new MyClass1(0x30);
// pc è un "puntatore"
MyClass1 &rc = *pc;
gg.hh.jj.kk
// rc è un "reference"  alias
30.00.00.00
MyClass1 *pc2 = pc;
gg.hh.jj.kk
MyClass2 ^hc = gcnew MyClass2();
// hc è un handle. ^ si pronuncia hat
interior_ptr<MyClass2 ^> ip = &hc;
xx.yy.zz.tt
sizeof(MyClass1)
Stack locale
pp.qq.rr.ss
// ip è un "interior pointer"
pin_ptr<MyClass2 ^> pp = &hc;
pin_ptr<MyClass2 ^> pp2 = pp;
GC
aggiorna i
valori
quando
muove la
memoria
ll.mm.nn.oo
// pp e pp2 sono "pinning pointers"
Managed heap
MyRefType %tr = *hc;
// tr è un "tracking reference"
memoria
classica
sempre
ferma
30.00.00.00
size unknown
pinned
GC
muove i
blocchi di
memoria
Marshalling di stringhe
Stringhe Ansi
Managed  Unmanaged
Unmanaged  Managed
Marshal::StringToHGlobalAnsi Marshal::PtrToStringAnsi
Stringhe Unicode
Managed  Unmanaged
Unmanaged  Managed
PtrToStringChars
Marshal::PtrToStringUni
(#include <vcclr.h>)
Stringhe COM (BSTR)
Managed  Unmanaged
Unmanaged  Managed
Marshal::StringToBSTR
Marshal::PtrToStringBSTR
È 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 memoria
managed 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;
#include <msclr\auto_gcroot.h>
using namespace msclr;
using namespace System;
public class Native1
{
gcroot<String ^> str;
};
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 funzioni
ed 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  managed
Questo 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 la
convenzione __clrcall
– forzando __clrcall si evita il double thunking
virtual return-type __clrcall function-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
D:\>cl /c /clr a.cpp
C++
Linker
D:\>csc /t:module c.cs
c.cs
C#
Compiler
c.netmodule
C++ Code
C# Code
Un assembly, mixed language
• Esempio di una Winform C# che usa una business logic in
C++/CLI
EXE
CppLogicClassLibrary
CsFormClassLibrary
CppStartWinform
– Progetto 1: CppLogicClassLibrary
• Per semplicità precompiled headers disabilitati
• Si compila con VS.net
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
aggiungo
le 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
Domande?
Fly UP