c# .net winapi interop intptr

c# - .NET Interop IntPtr vs. ref



winapi (5)

No veo inconvenientes.

Por ref es a menudo suficiente para el tipo simple y la estructura simple.

IntPtr debe ser favorecido si la estructura tiene un tamaño variable o si desea hacer un procesamiento personalizado.

Probablemente sea una pregunta novato, pero la interoperabilidad no es uno de mis puntos fuertes todavía.

Además de limitar el número de sobrecargas, hay alguna razón por la que debería declarar mis DllImports como:

[DllImport("user32.dll")] public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);

Y úsalos así:

IntPtr lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(formatrange)); Marshal.StructureToPtr(formatrange, lParam, false); int returnValue = User32.SendMessage(_RichTextBox.Handle, ApiConstants.EM_FORMATRANGE, wParam, lParam); Marshal.FreeCoTaskMem(lParam);

En lugar de crear una sobrecarga específica:

[DllImport("user32.dll")] public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, ref FORMATRANGE lParam);

Y usándolo como:

FORMATRANGE lParam = new FORMATRANGE(); int returnValue = User32.SendMessage(_RichTextBox.Handle, ApiConstants.EM_FORMATRANGE, wParam, ref lParam);

La sobrecarga por ref resulta ser más fácil de usar, pero me pregunto si hay un inconveniente del que no tengo conocimiento.

Editar:

Mucha mucha información hasta ahora chicos.

@P Daddy: ¿Tiene un ejemplo de basar la clase struct de una clase abstracta (o cualquiera)? Cambié mi firma a:

[DllImport("user32.dll", SetLastError = true)] public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, [In, Out, MarshalAs(UnmanagedType.LPStruct)] CHARFORMAT2 lParam);

Sin In , Out y MarshalAs al MarshalAs SendMessage (EM_GETCHARFORMAT en mi prueba) falla. El ejemplo anterior funciona bien, pero si lo cambio a:

[DllImport("user32.dll", SetLastError = true)] public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, [In, Out, MarshalAs(UnmanagedType.LPStruct)] NativeStruct lParam);

Obtengo una System.TypeLoadException que dice que el formato CHARFORMAT2 no es válido (intentaré capturarlo aquí).

La excepción:

No se pudo cargar el tipo ''CC.Utilities.WindowsApi.CHARFORMAT2'' del ensamblado ''CC.Utilities, Version = 1.0.9.1212, Culture = neutral, PublicKeyToken = 111aac7a42f7965e'' porque el formato no es válido.

La clase NativeStruct:

public class NativeStruct { }

Intenté abstract , StructLayout atributo StructLayout , etc. y obtuve la misma excepción.

[StructLayout(LayoutKind.Sequential)] public class CHARFORMAT2: NativeStruct { ... }

Editar:

No seguí las preguntas frecuentes y formulé una pregunta que puede ser discutida pero no respondida positivamente. Aparte de eso, ha habido mucha información perspicaz en este hilo. Así que dejaré que los lectores voten por una respuesta. La respuesta será de 1 a más de 10 votos. Si ninguna respuesta cumple esto en dos días (12/17 PST), agregaré mi propia respuesta que resume todos los sabrosos conocimientos en el hilo :-)

Editar nuevamente:

Mentí, aceptando la respuesta de P Daddy porque él es el hombre y me ha sido de gran ayuda (también tiene un lindo mono pequeño :-P)


Usar ref es más simple y menos propenso a errores que manipular punteros manualmente, así que no veo una buena razón para no usarlo ... Otro beneficio de usar ref es que no tiene que preocuparse por liberar memoria asignada no administrada


No, no puede sobrecargar SendMessage y hacer que el argumento wparam sea int. Eso hará que su programa falle en una versión de 64 bits del sistema operativo. Tiene que ser un puntero, ya sea IntPtr, una referencia blittable o un tipo de valor de salida o ref. Sobrecargar el tipo de salida / ref es por lo demás correcto.

EDITAR: Como señaló el OP, esto no es realmente un problema. La convención de llamada de función de 64 bits pasa los primeros 4 argumentos a través de registros, no la pila. Por lo tanto, no hay peligro de que la pila se desalinee para los argumentos wparam y lparam.


Si la estructura es ordenable sin procesamiento personalizado, prefiero mucho el último enfoque, donde declaras que la función p / invoke toma un ref (puntero) a tu tipo. Alternativamente, puede declarar sus tipos como clases en lugar de estructuras, y luego puede pasar null , también.

[StructLayout(LayoutKind.Sequential)] struct NativeType{ ... } [DllImport("...")] static extern bool NativeFunction(ref NativeType foo); // can''t pass null to NativeFunction // unless you also include an overload that takes IntPtr [DllImport("...")] static extern bool NativeFunction(IntPtr foo); // but declaring NativeType as a class works, too [StructLayout(LayoutKind.Sequential)] class NativeType2{ ... } [DllImport("...")] static extern bool NativeFunction(NativeType2 foo); // and now you can pass null

<pedantry>

Por cierto, en su ejemplo al pasar un puntero como IntPtr , ha utilizado el Alloc incorrecto. SendMessage no es una función COM, por lo que no debe utilizar el asignador COM. Usa Marshal.AllocHGlobal y Marshal.FreeHGlobal . Están mal nombrados; los nombres solo tienen sentido si has hecho la programación de la API de Windows, y tal vez ni siquiera entonces. AllocHGlobal llama a GlobalAlloc en kernel32.dll, que devuelve un HGLOBAL . Esto solía ser diferente de HLOCAL , devuelto por LocalAlloc en los días de 16 bits, pero en Windows de 32 bits son los mismos.

El uso del término HGLOBAL para referirse a un bloque de memoria de espacio de usuario (nativo) simplemente se atascó, supongo, y las personas que diseñaron la clase Marshal no debieron haberse tomado el tiempo de pensar en lo poco intuitivo que sería para la mayoría Desarrolladores .NET. Por otro lado, la mayoría de los desarrolladores .NET no necesitan asignar memoria no administrada, así que ...

</pedantry>

Editar

Menciona que obtiene una TypeLoadException cuando usa una clase en lugar de una estructura, y solicita una muestra. Hice una prueba rápida usando CHARFORMAT2 , ya que parece que eso es lo que estás tratando de usar.

Primero el ABC 1 :

[StructLayout(LayoutKind.Sequential)] abstract class NativeStruct{} // simple enough

Se requiere el atributo StructLayout , o obtendrá una TypeLoadException.

Ahora la clase CHARFORMAT2 :

[StructLayout(LayoutKind.Sequential, Pack=4, CharSet=CharSet.Auto)] class CHARFORMAT2 : NativeStruct{ public DWORD cbSize = (DWORD)Marshal.SizeOf(typeof(CHARFORMAT2)); public CFM dwMask; public CFE dwEffects; public int yHeight; public int yOffset; public COLORREF crTextColor; public byte bCharSet; public byte bPitchAndFamily; [MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)] public string szFaceName; public WORD wWeight; public short sSpacing; public COLORREF crBackColor; public LCID lcid; public DWORD dwReserved; public short sStyle; public WORD wKerning; public byte bUnderlineType; public byte bAnimation; public byte bRevAuthor; public byte bReserved1; }

He usado el using sentencias para alias System.UInt32 como DWORD , LCID y COLORREF , y alias System.UInt16 como WORD . Intento mantener mis definiciones de P / Invoke como verdaderas a la especificación del SDK como puedo. CFM y CFE son enums que contienen los valores de bandera para estos campos. Dejé sus definiciones por brevedad, pero puedo agregarlas si es necesario.

He declarado SendMessage como:

[DllImport("user32.dll", CharSet=CharSet.Auto)] static extern IntPtr SendMessage( HWND hWnd, MSG msg, WPARAM wParam, [In, Out] NativeStruct lParam);

HWND es un alias para System.IntPtr , MSG es System.UInt32 y WPARAM es System.UIntPtr .

[In, Out] atributo [In, Out] en lParam es necesario para que esto funcione; de ​​lo contrario, no parece que se haya clasificado en ambas direcciones (antes y después de la llamada al código nativo).

Lo llamo con:

CHARFORMAT2 cf = new CHARFORMAT2(); SendMessage(rtfControl.Handle, (MSG)EM.GETCHARFORMAT, (WPARAM)SCF.DEFAULT, cf);

EM y SCF son enum s He, de nuevo, dejado de lado por (relativa) brevedad.

Verifico el éxito con:

Console.WriteLine(cf.szFaceName);

y obtengo:

Microsoft Sans Serif

¡Funciona de maravilla!

Um, o no, dependiendo de cuánto dormiste y cuántas cosas intentas hacer a la vez, supongo.

Esto funcionaría si CHARFORMAT2 fuera un tipo blittable . (Un tipo blittable es un tipo que tiene la misma representación en la memoria administrada que en la memoria no administrada). Por ejemplo, el tipo MINMAXINFO funciona como se describe.

[StructLayout(LayoutKind.Sequential)] class MINMAXINFO : NativeStruct{ public Point ptReserved; public Point ptMaxSize; public Point ptMaxPosition; public Point ptMinTrackSize; public Point ptMaxTrackSize; }

Esto se debe a que los tipos blittables no están realmente calculados. Simplemente están fijados en la memoria, esto impide que el GC los mueva, y la dirección de su ubicación en la memoria administrada se transfiere a la función nativa.

Los tipos no blittable tienen que ser calculados. El CLR asigna memoria no administrada y copia los datos entre el objeto administrado y su representación no administrada, realizando las conversiones necesarias entre formatos a medida que avanza.

La estructura CHARFORMAT2 no es blittable debido al miembro de string . El CLR no puede simplemente pasar un puntero a un objeto de string .NET donde se espera que esté una matriz de caracteres de longitud fija. Entonces la estructura CHARFORMAT2 debe ser ordenada.

Como parece, para que se produzca la clasificación correcta, la función de interoperabilidad debe declararse con el tipo que se debe calcular. En otras palabras, dada la definición anterior, el CLR debe realizar algún tipo de determinación en función del tipo estático de NativeStruct . Supongo que está detectando correctamente que el objeto necesita ser ordenado, pero solo "ordenando" un objeto de cero bytes, el tamaño de NativeStruct mismo.

Entonces, para que su código funcione para CHARFORMAT2 (y para cualquier otro tipo no blittable que pueda usar), tendrá que volver a declarar que SendMessage toma un objeto CHARFORMAT2 . Perdón por haberte descarriado en este.

Captcha para la edición anterior:

el whippet

Sí, ¡látigo bien!

Cory,

Esto está fuera de tema, pero veo un problema potencial para ti en la aplicación que parece que estás haciendo.

El control de cuadro de texto enriquecido usa funciones estándar de GDI para medir texto y dibujar texto. ¿Por qué es esto un problema? Porque, a pesar de las afirmaciones de que una fuente TrueType tiene el mismo aspecto en la pantalla que en papel, GDI no coloca caracteres con precisión. El problema es el redondeo.

GDI usa rutinas enteras para medir texto y colocar caracteres. El ancho de cada carácter (y la altura de cada línea, para el caso) se redondea al número entero más cercano de píxeles, sin corrección de errores.

El error se puede ver fácilmente en su aplicación de prueba. Establezca la fuente en Courier New en 12 puntos. Esta fuente de ancho fijo debe espaciar caracteres exactamente 10 por pulgada o 0.1 pulgadas por carácter. Esto debería significar que, dado el ancho de la línea de inicio de 5.5 pulgadas, debería poder caber 55 caracteres en la primera línea antes de que se produzca el ajuste.

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123

Pero si lo intentas, verás que el ajuste se produce después de solo 54 caracteres. Además, el 54º personaje y parte del 53º voladizo sobresalen del margen aparente que se muestra en la barra de la regla.

Esto supone que tiene su configuración en 96 DPI estándar (fuentes normales). Si usa 120 ppp (fuentes grandes), no verá este problema, aunque parece que en este caso dimensiona incorrectamente su control. Tampoco es probable que vea esto en la página impresa.

¿Que está pasando aqui? El problema es que 0.1 pulgadas (el ancho de un caracter) es 9.6 pixeles (nuevamente, usando 96 DPI). GDI no espacia los caracteres que usan números de coma flotante, por lo que redondea esto hasta 10 píxeles. Así que 55 caracteres ocupan 55 * 10 = 550 píxeles / 96 DPI = 5.7291666 ... pulgadas, mientras que lo que esperábamos era 5.5 pulgadas.

Si bien esto probablemente será menos notorio en el caso de uso normal de un procesador de textos, existe la posibilidad de que el ajuste de palabras ocurra en diferentes lugares en la pantalla o en la página, o que las cosas no coincidan una vez impresas. lo hicieron en la pantalla. Esto podría ser un problema para usted si se trata de una aplicación comercial en la que está trabajando.

Desafortunadamente, la solución para este problema no es fácil. Significa que tendrá que prescindir del rico control de cuadro de texto, lo que significa una gran molestia de implementar todo lo que hace por usted, que es bastante. También significa que el código de dibujo de texto que deberá implementar se vuelve bastante complicado. Tengo un código que lo hace, pero es demasiado complejo para publicarlo aquí. Sin embargo, puede encontrar útil este ejemplo o este .

¡Buena suerte!

1 clase base abstracta


He tenido algunos casos divertidos donde un parámetro es algo así como ref Guid parent y la documentación correspondiente dice:

"Puntero a un GUID que especifica el elemento principal. Pase un puntero nulo para usar [insertar algún elemento definido por el sistema] ".

Si null (o IntPtr.Zero for IntPtr parameters) realmente es un parámetro inválido, entonces está bien usar un parámetro ref , quizás incluso mejor, ya que es más claro exactamente lo que necesita pasar.

Si null es un parámetro válido, puede pasar ClassType lugar de ref StructType . Los objetos de un tipo de referencia ( class ) se pasan como un puntero, y permiten null .