Se la struct è marshalable senza elaborazione personalizzata, preferisco di gran lunga il secondo approccio, in cui dichiari la funzione p/invoke come prendendo un ref
(puntatore a) del tuo tipo. In alternativa, puoi dichiarare i tuoi tipi come classi anziché come strutture, quindi puoi passare anche a null
.
[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>
Tra l'altro, nel tuo esempio passando un puntatore come IntPtr
, hai utilizzato il torto Alloc
. SendMessage
non è una funzione COM, quindi non si dovrebbe utilizzare l'allocatore COM. Utilizzare Marshal.AllocHGlobal
e Marshal.FreeHGlobal
. Sono scarsamente nominati; i nomi hanno senso solo se hai fatto la programmazione dell'API di Windows, e forse neanche allora. AllocHGlobal
chiama GlobalAlloc
in kernel32.dll, che restituisce un HGLOBAL
. Questo utilizzato da è diverso da uno HLOCAL
, restituito da LocalAlloc
indietro nei giorni di 16 bit, ma in Windows a 32 bit sono gli stessi.
L'uso del termine HGLOBAL
per riferirsi a un blocco di memoria (nativo) user-space solo tipo di bloccato, immagino, e le persone che progettano la classe Marshal
non devono aver avuto il tempo di pensare a come poco intuitivo che sarebbe per la maggior parte degli sviluppatori .NET. D'altra parte, la maggior parte degli sviluppatori .NET non hanno bisogno di allocare memoria non gestita, così ....
</pedantry>
Modifica
Lei parla che stai ricevendo un TypeLoadException quando si utilizza una classe anziché una struct e si richiede un campione. Ho fatto un rapido test usando CHARFORMAT2
, poiché sembra che sia quello che stai cercando di usare.
First ABC :
[StructLayout(LayoutKind.Sequential)]
abstract class NativeStruct{} // simple enough
è richiesto l'attributo StructLayout
, oppure si sarà ottenere un TypeLoadException.
Ora la CHARFORMAT2
classe:
[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;
}
ho usato using
dichiarazioni di alias System.UInt32
come DWORD
, LCID
e COLORREF
, e l'alias System.UInt16
come WORD
. Cerco di mantenere le mie definizioni P/Invoke fedele alle specifiche SDK come posso. CFM
e CFE
sono enums
che contengono i valori di flag per questi campi. Ho lasciato le loro definizioni per brevità, ma posso aggiungerle se necessario.
ho dichiarato SendMessage
come:
[DllImport("user32.dll", CharSet=CharSet.Auto)]
static extern IntPtr SendMessage(
HWND hWnd, MSG msg, WPARAM wParam, [In, Out] NativeStruct lParam);
HWND
è un alias per System.IntPtr
, MSG
è System.UInt32
, e WPARAM
è System.UIntPtr
.
l'attributo [In, Out]
su lParam
è necessario per questo funzionamento, in caso contrario, non sembra avere il marshalling in entrambe le direzioni (prima e dopo la chiamata al codice nativo).
mi chiamano con:
CHARFORMAT2 cf = new CHARFORMAT2();
SendMessage(rtfControl.Handle, (MSG)EM.GETCHARFORMAT, (WPARAM)SCF.DEFAULT, cf);
EM
e SCF
sono enum
s che ho, ancora una volta, lasciato fuori per (relativa) brevità.
verifico con successo:
Console.WriteLine(cf.szFaceName);
e ottengo:
Microsoft Sans Serif
funziona come un fascino!
Um, o meno, a seconda di quanto sonno hai avuto e quante cose si sta cercando di fare in una sola volta, suppongo.
Questo funzionerebbe se CHARFORMAT2
fosse un tipo blittable. (Un tipo blittable è un tipo che ha la stessa rappresentazione nella memoria gestita come nella memoria non gestita.) Ad esempio, il tipo MINMAXINFO
fa come descritto.
[StructLayout(LayoutKind.Sequential)]
class MINMAXINFO : NativeStruct{
public Point ptReserved;
public Point ptMaxSize;
public Point ptMaxPosition;
public Point ptMinTrackSize;
public Point ptMaxTrackSize;
}
Ciò è dovuto al fatto che i tipi blittable non vengono realmente sottoposti a marshalling. Sono solo bloccati in memoria, questo impedisce al GC di spostarli e l'indirizzo della loro posizione nella memoria gestita viene passato alla funzione nativa.
È necessario eseguire il marshalling dei tipi non blittibili. Il CLR assegna la memoria non gestita e copia i dati tra l'oggetto gestito e la sua rappresentazione non gestita, apportando le necessarie conversioni tra i formati man mano che procede.
La struttura CHARFORMAT2
non è blitta a causa del membro string
. Il CLR non può semplicemente passare un puntatore a un oggetto .NET string
in cui è previsto un array di caratteri a lunghezza fissa. Quindi la struttura CHARFORMAT2
deve essere eseguita il marshalling.
Come sembrerebbe, per eseguire correttamente il marshalling, la funzione di interoperabilità deve essere dichiarata con il tipo da eseguire il marshalling. In altre parole, data la definizione di cui sopra, il CLR deve effettuare una sorta di determinazione basata sul tipo statico di NativeStruct
. Direi che sta rilevando correttamente che l'oggetto deve essere eseguito il marshalling, ma solo "esegue il marshalling" di un oggetto a byte zero, la dimensione di NativeStruct
stesso.
Così, al fine di ottenere il vostro codice di lavoro per CHARFORMAT2
(e qualsiasi altro tipo non copiabili si potrebbe utilizzare), si dovrà tornare a dichiarare SendMessage
come prendere un oggetto CHARFORMAT2
. Mi dispiace ti ho portato fuori strada su questo.
Captcha per la modifica precedente:
il whippet
Sì, frusta bene!
Cory,
Questo è fuori tema, ma ho notato un potenziale problema per voi in app sembra che si sta facendo.
Il controllo rich textbox utilizza le funzioni standard di misurazione del testo e di disegno del testo GDI. Perché questo è un problema? Perché, nonostante affermi che un carattere TrueType è uguale sullo schermo come su carta, GDI non posiziona correttamente i caratteri. Il problema è l'arrotondamento.
GDI utilizza routine all-inte per misurare il testo e posizionare caratteri. La larghezza di ciascun carattere (e l'altezza di ogni riga, per quella materia) è arrotondata al numero intero più vicino di pixel, senza correzione degli errori.
L'errore può essere facilmente visualizzato nell'app di prova. Imposta il carattere su Courier New a 12 punti.Questo carattere a larghezza fissa deve contenere caratteri esattamente 10 per pollice o 0,1 pollici per carattere. Questo dovrebbe significare che, data la larghezza della tua linea di partenza di 5,5 pollici, dovresti essere in grado di inserire 55 caratteri sulla prima riga prima che avvenga l'avvolgimento.
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123
Ma se ci provate, vedrete che il ritorno si verifica dopo soli 54 caratteri. Inoltre, il numero 54 th e una parte del numero 53 rd sporgono dal margine apparente mostrato sulla barra del righello.
Questo presuppone che le impostazioni siano a 96 DPI standard (tipi di carattere normali). Se utilizzi 120 DPI (caratteri grandi), non visualizzerai questo problema, anche se in questo caso sembra che il controllo della dimensione non sia corretto. Probabilmente non lo vedrai sulla pagina stampata.
Cosa sta succedendo qui? Il problema è che 0.1 pollici (la larghezza di un carattere) è 9.6 pixel (di nuovo, usando 96 DPI). GDI non spazio caratteri utilizzando numeri in virgola mobile, quindi arrotonda fino a 10 pixel. Quindi 55 caratteri occupano 55 * 10 = 550 pixel/96 DPI = 5.7291666 ... pollici, mentre quello che ci aspettavamo era 5,5 pollici.
Mentre questo sarà probabilmente meno evidente nel caso di utilizzo normale per un programma di elaborazione testi, esiste una probabilità di casi in cui il ritorno a capo avviene in diversi punti sullo schermo rispetto a pagina, o che le cose non allineano il stesso una volta stampato come hanno fatto sullo schermo. Questo potrebbe rivelarsi un problema per te se questa è un'applicazione commerciale su cui stai lavorando.
Sfortunatamente, la soluzione per questo problema non è semplice. Significa che dovrai rinunciare al ricco controllo della casella di testo, il che significa un enorme problema di implementare te stesso tutto ciò che fa per te, il che è molto. Significa anche che il codice di disegno del testo che dovrai implementare diventa piuttosto complicato. Ho il codice che lo fa, ma è troppo complesso per postare qui. Potrebbe, tuttavia, trovare this example o this one utile.
Buona fortuna!
classe astratta di base
La classe NativeStruct richiede anche l'attributo 'StructLayout'. Pubblicherò il mio codice di esempio funzionante. –