vectores solo seguro punteros puede declaracion con compila codigo aparecer c# c pinvoke marshalling unsafe

solo - Rendimiento de C#: uso de punteros inseguros en lugar de IntPtr y Marshal



punteros en c# pdf (6)

Pregunta

Estoy portando una aplicación C en C #. La aplicación C llama a muchas funciones desde una DLL de terceros, así que escribí envoltorios de P / Invoke para estas funciones en C #. Algunas de estas funciones C asignan datos que tengo que usar en la aplicación C #, así que utilicé IntPtr , Marshal.PtrToStructure y Marshal.Copy para copiar los datos nativos (matrices y estructuras) en variables administradas.

Desafortunadamente, la aplicación C # demostró ser mucho más lenta que la versión C. Un análisis de rendimiento rápido mostró que la copia de datos basada en el cálculo de referencias mencionado anteriormente es el cuello de botella. Estoy pensando en acelerar el código C # reescribiéndolo para usar punteros en su lugar. Como no tengo experiencia con códigos inseguros y punteros en C #, necesito la opinión de un experto con respecto a las siguientes preguntas :

  1. ¿Cuáles son los inconvenientes de utilizar códigos y punteros unsafe lugar de IntPtr y Marshal ing? Por ejemplo, ¿es más inseguro (juego de palabras) de alguna manera? La gente parece preferir la clasificación, pero no sé por qué.
  2. ¿Usar punteros para P / Invocar es realmente más rápido que usar el cálculo de referencias? ¿Cuánta aceleración se puede esperar aproximadamente? No pude encontrar ninguna prueba de referencia para esto.

Código de ejemplo

Para hacer la situación más clara, pirateé un pequeño código de ejemplo (el código real es mucho más complejo). Espero que este ejemplo muestre a qué me refiero cuando estoy hablando de "códigos y punteros inseguros" contra "IntPtr y Marshal".

Biblioteca C (DLL)

MyLib.h

#ifndef _MY_LIB_H_ #define _MY_LIB_H_ struct MyData { int length; unsigned char* bytes; }; __declspec(dllexport) void CreateMyData(struct MyData** myData, int length); __declspec(dllexport) void DestroyMyData(struct MyData* myData); #endif // _MY_LIB_H_

MyLib.c

#include <stdlib.h> #include "MyLib.h" void CreateMyData(struct MyData** myData, int length) { int i; *myData = (struct MyData*)malloc(sizeof(struct MyData)); if (*myData != NULL) { (*myData)->length = length; (*myData)->bytes = (unsigned char*)malloc(length * sizeof(char)); if ((*myData)->bytes != NULL) for (i = 0; i < length; ++i) (*myData)->bytes[i] = (unsigned char)(i % 256); } } void DestroyMyData(struct MyData* myData) { if (myData != NULL) { if (myData->bytes != NULL) free(myData->bytes); free(myData); } }

Aplicación C

C Principal

#include <stdio.h> #include "MyLib.h" void main() { struct MyData* myData = NULL; int length = 100 * 1024 * 1024; printf("=== C++ test ===/n"); CreateMyData(&myData, length); if (myData != NULL) { printf("Length: %d/n", myData->length); if (myData->bytes != NULL) printf("First: %d, last: %d/n", myData->bytes[0], myData->bytes[myData->length - 1]); else printf("myData->bytes is NULL"); } else printf("myData is NULL/n"); DestroyMyData(myData); getchar(); }

Aplicación C #, que usa IntPtr y Marshal

Program.cs

using System; using System.Runtime.InteropServices; public static class Program { [StructLayout(LayoutKind.Sequential)] private struct MyData { public int Length; public IntPtr Bytes; } [DllImport("MyLib.dll")] private static extern void CreateMyData(out IntPtr myData, int length); [DllImport("MyLib.dll")] private static extern void DestroyMyData(IntPtr myData); public static void Main() { Console.WriteLine("=== C# test, using IntPtr and Marshal ==="); int length = 100 * 1024 * 1024; IntPtr myData1; CreateMyData(out myData1, length); if (myData1 != IntPtr.Zero) { MyData myData2 = (MyData)Marshal.PtrToStructure(myData1, typeof(MyData)); Console.WriteLine("Length: {0}", myData2.Length); if (myData2.Bytes != IntPtr.Zero) { byte[] bytes = new byte[myData2.Length]; Marshal.Copy(myData2.Bytes, bytes, 0, myData2.Length); Console.WriteLine("First: {0}, last: {1}", bytes[0], bytes[myData2.Length - 1]); } else Console.WriteLine("myData.Bytes is IntPtr.Zero"); } else Console.WriteLine("myData is IntPtr.Zero"); DestroyMyData(myData1); Console.ReadKey(true); } }

Aplicación C #, que usa códigos y punteros unsafe

Program.cs

using System; using System.Runtime.InteropServices; public static class Program { [StructLayout(LayoutKind.Sequential)] private unsafe struct MyData { public int Length; public byte* Bytes; } [DllImport("MyLib.dll")] private unsafe static extern void CreateMyData(out MyData* myData, int length); [DllImport("MyLib.dll")] private unsafe static extern void DestroyMyData(MyData* myData); public unsafe static void Main() { Console.WriteLine("=== C# test, using unsafe code ==="); int length = 100 * 1024 * 1024; MyData* myData; CreateMyData(out myData, length); if (myData != null) { Console.WriteLine("Length: {0}", myData->Length); if (myData->Bytes != null) Console.WriteLine("First: {0}, last: {1}", myData->Bytes[0], myData->Bytes[myData->Length - 1]); else Console.WriteLine("myData.Bytes is null"); } else Console.WriteLine("myData is null"); DestroyMyData(myData); Console.ReadKey(true); } }


Como indicó que su código llama a DLL de terceros, creo que el código inseguro es más adecuado para su caso. Te encontraste con una situación particular de dispersión de una matriz de longitud variable en una struct ; Lo sé, sé que este tipo de uso ocurre todo el tiempo, pero no siempre es el caso, después de todo. Es posible que desee ver algunas preguntas sobre esto, por ejemplo:

¿Cómo clasifico una estructura que contiene una matriz de tamaño variable a C #?

Si ... digo que ... puede modificar un poco las bibliotecas de terceros para este caso particular, entonces podría considerar el siguiente uso:

using System.Runtime.InteropServices; public static class Program { /* [StructLayout(LayoutKind.Sequential)] private struct MyData { public int Length; public byte[] Bytes; } */ [DllImport("MyLib.dll")] // __declspec(dllexport) void WINAPI CreateMyDataAlt(BYTE bytes[], int length); private static extern void CreateMyDataAlt(byte[] myData, ref int length); /* [DllImport("MyLib.dll")] private static extern void DestroyMyData(byte[] myData); */ public static void Main() { Console.WriteLine("=== C# test, using IntPtr and Marshal ==="); int length = 100*1024*1024; var myData1 = new byte[length]; CreateMyDataAlt(myData1, ref length); if(0!=length) { // MyData myData2 = (MyData)Marshal.PtrToStructure(myData1, typeof(MyData)); Console.WriteLine("Length: {0}", length); /* if(myData2.Bytes!=IntPtr.Zero) { byte[] bytes = new byte[myData2.Length]; Marshal.Copy(myData2.Bytes, bytes, 0, myData2.Length); */ Console.WriteLine("First: {0}, last: {1}", myData1[0], myData1[length-1]); /* } else { Console.WriteLine("myData.Bytes is IntPtr.Zero"); } */ } else { Console.WriteLine("myData is empty"); } // DestroyMyData(myData1); Console.ReadKey(true); } }

Como puede ver, gran parte de su código de clasificación original está comentado y declarado como CreateMyDataAlt(byte[], ref int) para una función no gestionada externa CreateMyDataAlt(BYTE [], int) . Parte de la copia de datos y la comprobación del puntero se vuelven innecesarios, eso dice, el código puede ser incluso más simple y probablemente se ejecute más rápido.

Entonces, ¿qué es tan diferente con la modificación? La matriz de bytes ahora se organiza directamente sin warpping en una struct y se pasa al lado no administrado. No asigna la memoria dentro del código no administrado, sino que simplemente le llena los datos (se omiten los detalles de implementación); y después de la llamada, los datos necesarios se proporcionan al lado administrado. Si desea presentar que los datos no están llenos y no deben utilizarse, simplemente puede establecer la length en cero para indicarle al lado administrado. Debido a que la matriz de bytes se asigna dentro del lado administrado, se recopilará en algún momento, no es necesario que se ocupe de eso.


Dos respuestas,

  1. El código no seguro significa que no es administrado por el CLR. Debe cuidar los recursos que utiliza.

  2. No puede escalar el rendimiento porque hay tantos factores que lo afectan. Pero definitivamente el uso de punteros será mucho más rápido.


Es un hilo un poco viejo, pero recientemente realicé pruebas de rendimiento excesivas con referencias en C #. Necesito desempacar muchos datos de un puerto serie durante muchos días. Era importante para mí no tener pérdidas de memoria (porque la fuga más pequeña se volvería significativa después de un par de millones de llamadas) y también hice muchas pruebas de rendimiento estadístico (tiempo utilizado) con estructuras muy grandes (> 10 kb) solo para el por eso (un no, nunca deberías tener una estructura de 10kb :-))

Probé las siguientes tres estrategias de desemparejamiento (también probé la clasificación). En casi todos los casos, el primero (Marshal Matters) superó a los otros dos. Marshal.Copy siempre fue el más lento por el momento, los otros dos estaban muy juntos en la carrera.

El uso de un código inseguro puede representar un riesgo de seguridad significativo.

Primero:

public class MarshalMatters { public static T ReadUsingMarshalUnsafe<T>(byte[] data) where T : struct { unsafe { fixed (byte* p = &data[0]) { return (T)Marshal.PtrToStructure(new IntPtr(p), typeof(T)); } } } public unsafe static byte[] WriteUsingMarshalUnsafe<selectedT>(selectedT structure) where selectedT : struct { byte[] byteArray = new byte[Marshal.SizeOf(structure)]; fixed (byte* byteArrayPtr = byteArray) { Marshal.StructureToPtr(structure, (IntPtr)byteArrayPtr, true); } return byteArray; } }

Segundo:

public class Adam_Robinson { private static T BytesToStruct<T>(byte[] rawData) where T : struct { T result = default(T); GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned); try { IntPtr rawDataPtr = handle.AddrOfPinnedObject(); result = (T)Marshal.PtrToStructure(rawDataPtr, typeof(T)); } finally { handle.Free(); } return result; } /// <summary> /// no Copy. no unsafe. Gets a GCHandle to the memory via Alloc /// </summary> /// <typeparam name="selectedT"></typeparam> /// <param name="structure"></param> /// <returns></returns> public static byte[] StructToBytes<T>(T structure) where T : struct { int size = Marshal.SizeOf(structure); byte[] rawData = new byte[size]; GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned); try { IntPtr rawDataPtr = handle.AddrOfPinnedObject(); Marshal.StructureToPtr(structure, rawDataPtr, false); } finally { handle.Free(); } return rawData; } }

Tercero:

/// <summary> /// http://.com/questions/2623761/marshal-ptrtostructure-and-back-again-and-generic-solution-for-endianness-swap /// </summary> public class DanB { /// <summary> /// uses Marshal.Copy! Not run in unsafe. Uses AllocHGlobal to get new memory and copies. /// </summary> public static byte[] GetBytes<T>(T structure) where T : struct { var size = Marshal.SizeOf(structure); //or Marshal.SizeOf<selectedT>(); in .net 4.5.1 byte[] rawData = new byte[size]; IntPtr ptr = Marshal.AllocHGlobal(size); Marshal.StructureToPtr(structure, ptr, true); Marshal.Copy(ptr, rawData, 0, size); Marshal.FreeHGlobal(ptr); return rawData; } public static T FromBytes<T>(byte[] bytes) where T : struct { var structure = new T(); int size = Marshal.SizeOf(structure); //or Marshal.SizeOf<selectedT>(); in .net 4.5.1 IntPtr ptr = Marshal.AllocHGlobal(size); Marshal.Copy(bytes, 0, ptr, size); structure = (T)Marshal.PtrToStructure(ptr, structure.GetType()); Marshal.FreeHGlobal(ptr); return structure; } }


Para cualquiera que siga leyendo,

Algo que creo que no vi en ninguna de las respuestas, el código inseguro presenta un riesgo de seguridad. No es un gran riesgo, sería algo bastante difícil de explotar. Sin embargo, si como yo trabajas en una organización compatible con PCI, la política no permite el código inseguro por este motivo.

El código administrado es normalmente muy seguro porque el CLR se encarga de la ubicación y asignación de la memoria, lo que le impide acceder o escribir cualquier memoria que se supone que no debe.

Cuando usa la palabra clave insegura y compila con ''/ inseguro'' y usa punteros, omite estos controles y crea la posibilidad de que alguien use su aplicación para obtener cierto nivel de acceso no autorizado a la máquina en la que se está ejecutando. Utilizando algo así como un ataque de desbordamiento de búfer, su código podría ser engañado en instrucciones de escritura en un área de memoria que luego podría ser accedida por el contador del programa (es decir, inyección de código), o simplemente bloquear la máquina.

Hace muchos años, el servidor SQL en realidad cayó presa del código malicioso entregado en un paquete TDS que era mucho más largo de lo que se suponía que era. El método de lectura del paquete no verificó la longitud y siguió escribiendo el contenido más allá del espacio de direcciones reservado. La longitud y el contenido extra se diseñaron cuidadosamente de modo que escribieran un programa completo en la memoria, en la dirección del siguiente método. Luego, el atacante tenía su propio código ejecutado por el servidor SQL dentro de un contexto que tenía el más alto nivel de acceso. Ni siquiera necesitó romper el cifrado ya que la vulnerabilidad estaba por debajo de este punto en la pila de la capa de transporte.


Solo quería agregar mi experiencia a este viejo hilo: utilizamos Marshaling en el software de grabación de sonido: recibimos datos de sonido en tiempo real del mezclador en los búferes nativos y los distribuimos en byte []. Eso fue un verdadero asesino de rendimiento. Nos obligaron a movernos a estructuras inseguras como la única forma de completar la tarea.

En caso de que no tenga grandes estructuras nativas y no le importe que todos los datos se llenen dos veces, Marshaling es más elegante y mucho más seguro.


Consideraciones en Interoperabilidad explica por qué y cuándo se requiere Marshaling y a qué costo. Citar:

  1. El emparejamiento se produce cuando un llamante y un destinatario no pueden operar en la misma instancia de datos.
  2. las referencias repetidas pueden afectar negativamente el rendimiento de su aplicación.

Por lo tanto, respondiendo tu pregunta si

... usando punteros para P / Invocar realmente más rápido que usar referencias ...

primero hágase una pregunta si el código administrado puede operar en la instancia de valor de retorno del método no administrado. Si la respuesta es sí, entonces Marshaling y el costo de rendimiento asociado no son necesarios. El ahorro de tiempo aproximado sería la función O (n) donde n del tamaño de la instancia ordenada. Además, no mantener los bloques de datos administrados y no administrados en la memoria al mismo tiempo durante la duración del método (en el ejemplo de "IntPtr y Marshal") elimina la sobrecarga adicional y la presión de la memoria.

¿Cuáles son los inconvenientes de utilizar códigos y punteros inseguros ...?

El inconveniente es el riesgo asociado al acceso a la memoria directamente a través de punteros. No hay nada menos seguro que usar punteros en C o C ++. Úselo si es necesario y tiene sentido. Más detalles están here .

Hay una preocupación de "seguridad" con los ejemplos presentados: la liberación de la memoria no administrada asignada no está garantizada después de los errores del código administrado. La mejor práctica es

CreateMyData(out myData1, length); if(myData1!=IntPtr.Zero) { try { // -> use myData1 ... // <- } finally { DestroyMyData(myData1); } }