net uy meetup 6 - integrando con c/c++ por medio de p/invoke by juan ramirez

49
Platform Invocation Services P/Invoke Juan Ramírez (@ichramm) @NETUYMeetup

Upload: net-uy-meetup

Post on 05-Dec-2014

580 views

Category:

Technology


0 download

DESCRIPTION

 

TRANSCRIPT

Page 1: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Platform Invocation Services

P/Invoke

Juan Ramírez (@ichramm)@NETUYMeetup

Page 2: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

● Intro con dibujito● Qué es? Qué hace? Para qué sirve?● Analizando el problema

○ P/Invoke vs C++/CLI● DllImport y sus allegados● Data Marshalling

○ Data Types○ Estructuras○ Callbacks

Agenda

Page 3: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Qué es? Que hace? Para qué sirve?● Es una funcionalidad del CLR ...

… que permite invocar código nativo (unmanaged) desde .Net (managed).

● Es muy flexible, y con defaults razonables.

Page 4: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

DLL

Common Language Runtime

Managedsourcecode

DLL Function

Unmanaged Managed

Assembly

Metadata

MSIL code

DLL Function

DLL Function

P/Invoke Compiler

“Permite ejecutar native code desde managed code”

Page 5: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

PARA QUÉSIRVE?

y...

Page 6: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Problema● Se desea utilizar cierta API nativa, que no

tiene bindings para .Net.

● Debe ser Portable.

● No se dispone del código fuente de la librería.

Page 7: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Ejemplos (poco concretos)

● API de Windows

● Librería que implementa determinado protocolo.

● Interoperabilidad con distintos frameworks

Page 8: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Y qué pasa con C++/CLI?● Crear un wrapper sería más sencillo. ✓

● Buena performance, mejor que P/Invoke en algunos casos. ✓

● Estáticamente type-safe. ✓

● Transiciones entre managed y unmanaged son sencillas y seguras. ✓

Page 9: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Y qué pasa con C++/CLI?● No es portable (solo Windows). ✘

● Se necesita el código fuente de la librería. ✘

● Hay que tocar otro lenguaje, una especie de C++ maquillado. ✘

Page 10: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Y con P/Invoke?● Solo necesito la DLL. ✓

● Es portable (mono project). ✓

● Solo tengo que tocar C#. ✓

Page 11: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Peeeero...● No es type-safe. ✘

● Más lento en ciertos casos. ✘

● Se puede volver muy complicado. ✘

Page 12: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

En resumen

● Es la solución a un problema

● Permite utilizar funcionalidades que de otra manera no se podrían utilizar.

Page 13: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

GETTING BUSINESSTO

DOWN

Basta de cháchara...

Page 14: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Ejemplo

// Usage: bool copied = CopyFile(textBox1.Text, textBox2.Text, true);

using System.Runtime.InteropServices;

[DllImport("kernel32.dll")]

private static extern bool CopyFile (

string lpExistingFileName,

string lpNewFileName,

bool bFailIfExists

);

Page 15: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

● Indica que el método es expuesto por una DLL nativa (class DllImportAttribute).

/- DllImport

● El keyword extern indica que el método está implementado en otro lado.

● En nuestro ejemplo:○ Dll Nativa: kernel32.dll○ Función : CopyFile

Page 16: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Ejemplo[DllImport("kernel32.dll",

CharSet = CharSet.Unicode)]

private static extern bool CopyFile (

[MarshalAs(UnmanagedType.LPWStr)] string lpExistingFileName,

[MarshalAs(UnmanagedType.LPWStr)] string lpNewFileName,

bool bFailIfExists

);

// Usage: bool copied = CopyFile(textBox1.Text, textBox2.Text, true);

Page 17: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

/- CharSet● Marshalling de strings

○ Charset.Ansi -> char*○ Charset.Unicode -> wchar_t*

● Name matching○ Charset.Ansi -> CopyFileA○ Charset.Unicode -> CopyFileW

Page 18: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Ejemplo[DllImport("kernel32.dll",

CharSet = CharSet.Unicode,

ExactSpelling = true)]

private static extern bool CopyFile (

[MarshalAs(UnmanagedType.LPWStr)] string lpExistingFileName,

[MarshalAs(UnmanagedType.LPWStr)] string lpNewFileName,

bool bFailIfExists

);

Page 19: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

/- CharSet vs ExactSpelling ● ExactSpelling deshabilita name matching.

● La función CopyFile no existe en kernel32.dll.

● Sí existen CopyFileA y CopyFileW

Page 20: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

[DllImport("kernel32.dll",

CharSet = CharSet.Unicode,

ExactSpelling = true)]

private static extern bool CopyFileW (

[MarshalAs(UnmanagedType.LPWStr)] string lpExistingFileName,

[MarshalAs(UnmanagedType.LPWStr)] string lpNewFileName,

bool bFailIfExists

);

/- CharSet vs ExactSpelling Posible solución

Page 21: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Ejemplo[DllImport("kernel32.dll",

CharSet = CharSet.Unicode,

SetLastError = true, // This function sets last error

CallingConvention = CallingConvention.Cdecl)]

[return: MarshalAs(UnmanagedType.Bool)]

private static extern bool CopyFile (

[MarshalAs(UnmanagedType.LPWStr)] string lpExistingFileName,

[MarshalAs(UnmanagedType.LPWStr)] string lpNewFileName,

[MarshalAs(UnmanagedType.Bool)] bool bFailIfExists

);

Page 22: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Ejemplo[DllImport("kernel32.dll",

CharSet = CharSet.Unicode,

SetLastError = true, // This function sets last error

CallingConvention = CallingConvention.StdCall)]

[return: MarshalAs(UnmanagedType.Bool)]

private static extern bool CopyFile (

[MarshalAs(UnmanagedType.LPWStr)] string lpExistingFileName,

[MarshalAs(UnmanagedType.LPWStr)] string lpNewFileName,

[MarshalAs(UnmanagedType.Bool)] bool bFailIfExists

);

Page 23: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

/- CallingConvention● Esquema que establece como una función recibe

parámetros y devuelve un resultado.

● El código necesario lo genera el propio compilador (C/C++).

● El default en .Net es Winapi (StdCall).

● “... quien limpia el stack”.

Page 24: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

/- CallingConvention

StdCall● El callee limpia el

stack.

● Default en el Windows API.

Cdecl● El caller limpia el

stack.

● Default en Visual C++.

Page 25: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Ejemplo[DllImport("kernel32.dll",

CharSet = CharSet.Unicode,

SetLastError = true,

CallingConvention = CallingConvention.StdCall,

EntryPoint = "CopyFile")]

[return: MarshalAs(UnmanagedType.Bool)]

private static extern bool NativeCopyFile (

[MarshalAs(UnmanagedType.LPWStr)] string lpExistingFileName,

[MarshalAs(UnmanagedType.LPWStr)] string lpNewFileName,

[MarshalAs(UnmanagedType.Bool)] bool bFailIfExists

);

Page 26: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

/- EntryPoint● Especifica el nombre con el que se va a buscar

la función.

● O el índice de la función dentro de la DLL .○ Ej: EntryPoint = "#167"

● El default es el nombre del método marcado con el DllImport.

Page 27: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Ejemplo

void CopyFile(string from, string to)

{

bool copied = NativeCopyFile(from, to, true);

if (copied == false)

{

throw new Win32Exception(Marshal.GetLastWin32Error());

}

}

Page 28: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

LOCOSEJEMPLITOSDOS

> git checkout example1

> git checkout example2

Page 29: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

● Se trata de una especie de transformación de datos.

● Desde el mundo managed al mundo nativo.

● Desde el mundo nativo al mundo managed.

Data Marshaling

Page 30: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Para cada data type de .net existe un data type unmanaged por defecto.

● int -> int● bool -> BOOL /ojo! son 4 bytes● string -> char * (o TCHAR)● etc

Data Marshaling / Data Types

Page 31: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Data Marshaling / Data Types / System.IntPtr

● Se utiliza para representar punteros (o handles).

● IntPtr es ideal para tipos opacos.

● Es platform-specific (x86, x64).

Page 32: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Ejemplo

[DllImport("kernel32.dll")]

public static extern IntPtr OpenMutex(

uint dwDesiredAccess, bool bInheritHandle, string lpName

);

// typedef void * HANDLE; -> winnt.h

HANDLE WINAPI OpenMutex(

DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName

);

Page 33: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Data Marshaling / Data Types / System.String

● Ya vimos como pasar strings desde el mundo managed al mundo unmanaged.

● Vamos a ver como se hace al revés.

Page 34: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

DWORD WINAPI GetModuleFileName(

HMODULE hModule, LPTSTR lpFilename, DWORD nSize

);

[DllImport("kernel32.dll")]

public static extern uint GetModuleFileName(

IntPtr hModule,

// System.String is not mutable StringBuilder lpFilename,

[MarshalAs(UnmanagedType.U4)] int nSize

);

Data Marshaling / Data Types / System.String

Page 35: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Ejemplo

public string StartupPath {

get {

StringBuilder sb = new StringBuilder(260);

GetModuleFileName(IntPtr.Zero, sb, sb.Capacity);

// TODO: Handle error

return sb.ToString();

}

}

Page 36: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

EJEMPLITOOTRO

> git checkout example3

Page 37: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

● Se debe especificar el layout● Members pueden llevar atributos

Data Marshaling / Structs

[StructLayout(LayoutKind.Sequential)]public class LOGFONT {

// members...

[MarshalAs(UnmanagedType.ByValTStr,

SizeConst = LF_FACESIZE)]

public string lfFaceName;

Page 38: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

● El layout se puede configurar explícitamente [StructLayout(LayoutKind.Explicit)]public class LOGFONT {

[FieldOffset(0)] public int lfHeight;

// more members…

}

Data Marshaling / Structs

Page 39: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

● Cual es el tamaño de este struct?public struct Test { public byte a;

public int b;

public short c;

public byte d;

}

Data Marshaling / Structs● Ojo con el padding!!

12

Page 40: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

JUMP!

> git checkout example4

Page 41: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

● Mapean directo con delegates

Data Marshaling / Callbacks

● Mismas reglas que con las demas funciones.

● Una forma de llamar funciones de .Net desde el mundo unmanaged.

Page 42: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Ejemplo

ESME_HANDLE SMPP_API libSMPP_ClientCreate (

OnIncomingMessageCallback onNewMessage

);

Page 43: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Ejemplo

typedef void (*OnIncomingMessageCallback)(

ESME_HANDLE hClient,

const char* from,

const char* to,

const char* content, // UTF-8

unsigned int size

);

Page 44: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Ejemplo

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]

delegate void IncomingMessageHandler(

IntPtr hClient,

string from,

string to,

[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)]

byte[] content,

[MarshalAs(UnmanagedType.U4)] int bufferSize

);

Page 45: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Ejemplo

[DllImport("inconcertsmpp.dll"

, CharSet = CharSet.Ansi

, CallingConvention = CallingConvention.Cdecl)]

static extern IntPtr libSMPP_ClientCreate(

[MarshalAs(UnmanagedType.FunctionPtr)]

IncomingMessageHandler onNewMessage

);

Page 46: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

hClient = libSMPP_ClientCreate(

new IncomingMessageHandler( delegate(

IntPtr hClient, string from, string to,

byte[] content, int bufferSize)

{

string text = Encoding.UTF8.GetString(content);

// ...

});

Ejemplo

Page 47: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Pero...

● Ese código vuela por los aires

● El garbage collector se lleva el delegate

● Hay que mantener vivas las referencias a esos elementos

Page 48: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

m_handler = new IncomingMessageHandler( delegate(

IntPtr hClient, string from, string to,

byte[] content, int bufferSize)

{

string text = Encoding.UTF8.GetString(content);

// ...

};

hClient = libSMPP_ClientCreate(m_handler);

Ejemplo

Page 49: NET UY Meetup 6 - Integrando con C/C++ por medio de P/Invoke by Juan Ramirez

Gracias! Didn’t make it:● Keywords: unsafe, fixed● Custom Marshallers● Punteros