.NET-Interop IntPtr vs. ref
Wahrscheinlich eine noob Frage, aber interop ist einer meiner starken Punkte noch.
Abgesehen von der Beschränkung der Anzahl der überladungen ist es aus irgendeinem Grund sollte ich erklären, meine DllImports wie:
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);
Und verwenden Sie wie folgt:
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);
Anstatt eine gezielte überlastung:
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, ref FORMATRANGE lParam);
Und es zu benutzen, wie:
FORMATRANGE lParam = new FORMATRANGE();
int returnValue = User32.SendMessage(_RichTextBox.Handle, ApiConstants.EM_FORMATRANGE, wParam, ref lParam);
Den von ref überlast endet als einfacher zu bedienen, aber ich Frage mich, ob es ein Nachteil ist, ich bin mir nicht bewusst.
Edit:
Viele tolle info, so weit Jungs.
@P Papi: haben Sie ein Beispiel davon, die struct-Klasse aus einer abstrakten (oder einer anderen) Klasse? Ich änderte meine Signatur:
[DllImport("user32.dll", SetLastError = true)]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, [In, Out, MarshalAs(UnmanagedType.LPStruct)] CHARFORMAT2 lParam);
Ohne die In
, Out
, und MarshalAs
SendMessage (EM_GETCHARFORMAT in meinem test) fehl. Das obige Beispiel funktioniert gut, aber wenn ich es zu ändern:
[DllImport("user32.dll", SetLastError = true)]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, [In, Out, MarshalAs(UnmanagedType.LPStruct)] NativeStruct lParam);
Bekomme ich eine System.TypeLoadException, dass das CHARFORMAT2 format nicht gültig ist (ich werde versuchen, es einzufangen für hier).
Ausnahme:
Konnte nicht geladen werden Typ 'CC.Utilities.WindowsApi.CHARFORMAT2' aus assembly 'CC.Utilities, Version=1.0.9.1212, Culture=neutral, PublicKeyToken=111aac7a42f7965e", denn das format ist ungültig.
Den NativeStruct Klasse:
public class NativeStruct
{
}
Ich habe versucht abstract
hinzufügen der StructLayout
Attribut, etc. und bekomme ich die gleiche exception.
[StructLayout(LayoutKind.Sequential)]
public class CHARFORMAT2: NativeStruct
{
...
}
Edit:
Ich nicht zu Folgen, die FAQ und ich stellte eine Frage, die diskutiert werden kann, aber nicht positiv beantwortet. Abgesehen davon, dass es hat viele aufschlussreiche Informationen in diesem thread. Also ich überlasse es den Lesern zu Stimmen, eine Antwort. Erste, über 10 bis-Stimmen wird die Antwort sein. Wenn keine Antwort trifft dies in zwei Tagen (12/17 PST), werde ich meine eigene Antwort, dass fasst alle lecker Kenntnisse im thread 🙂
Bearbeiten Wieder:
Ich habe gelogen, die Annahme P Daddy ' s Antwort, weil er der Mann ist und war eine große Hilfe (er hat einen niedlichen kleinen Affen zu :-P)
- Die NativeStruct-Klasse muss die
StructLayout
Attribut zu. Ich werde nach meiner Arbeit Beispielcode.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Wenn das struct ist marshalable ohne benutzerdefinierte Verarbeitung ich sehr, den zweiten Weg bevorzugen, wo Sie erklären, die p/invoke Funktion wie ein
ref
(Zeiger auf) Ihrem Typ. Alternativ können Sie erklären, Ihre Typen als Klassen statt structs, und dann können Sie passierennull
sowie.Bearbeiten
Die Sie erwähnen, sind Sie immer eine TypeLoadException bei der Verwendung einer Klasse statt einer Struktur, und Fragen Sie nach einer Probe. Ich habe einen schnellen test mit
CHARFORMAT2
, da es aussieht wie, was Sie versuchen, zu verwenden.Zuerst die ABC1:
Den
StructLayout
Attribut erforderlich ist, oder Sie wird bekommen eine TypeLoadException.Nun die
CHARFORMAT2
Klasse:Ich habe
using
Aussagen zu aliasSystem.UInt32
alsDWORD
,LCID
, undCOLORREF
und aliasSystem.UInt16
alsWORD
. Ich versuche meine P/Invoke-Definitionen als wahr zu SDK-Skillung, wie ich kann.CFM
undCFE
sindenums
enthalten, die flag-Werte für diese Felder. Ich habe Ihre Definitionen für die Kürze, aber hinzufügen können, wenn nötig.Habe ich erklärt
SendMessage
als:HWND
ist ein alias fürSystem.IntPtr
,MSG
istSystem.UInt32
, undWPARAM
istSystem.UIntPtr
.[In, Out]
Attribut auflParam
ist erforderlich für diese zu arbeiten, ansonsten funktioniert es nicht scheinen, um gemarshallt beide Richtungen (vor und nach dem Aufruf von native code).Ich rufe es mit:
EM
undSCF
sindenum
s, die ich habe, wieder, Links für (relativen) Kürze.Überprüfe ich Erfolg mit:
und ich bekomme:
Funktioniert wie ein Charme!
Ähm, oder auch nicht, je nachdem, wie viel Schlaf Sie gehabt haben und wie viele Dinge, die Sie versuchen, auf einmal zu tun, nehme ich an.
Diese würde funktionieren, wenn
CHARFORMAT2
waren für blitvorgänge geeignet geben. (Eine für blitvorgänge geeignet Typ ist ein Typ, der hat die gleiche Darstellung im verwalteten Speicher wie in den nicht verwalteten Speicher.) Zum Beispiel, dieMINMAXINFO
Typ hat arbeiten, wie beschrieben.Dies ist, weil für blitvorgänge geeignet sind die Typen nicht wirklich gemarshallt. Sie sind nur angeheftet im Speicher—das hält die GC von Sie zu bewegen—und die Adresse von Ihrem Standort im verwalteten Speicher ist an die native Funktion übergeben.
Nicht für blitvorgänge geeignet ist Arten haben gemarshallt werden. Die CLR reserviert nicht verwalteten Speicher zu und kopiert die Daten zwischen dem verwalteten Objekt und nicht verwalteten Darstellung, so dass die notwendigen Konvertierungen zwischen den Formaten, wie es geht.
Den
CHARFORMAT2
Struktur nicht für blitvorgänge geeignet ist, weil derstring
Mitglied. Die CLR kann nicht einfach übergeben Sie einen Zeiger auf ein .NETstring
Objekt, wo ein fixed-length character-array erwartet wird. Also dieCHARFORMAT2
Struktur gemarshallt werden müssen.Wie es scheint, für die korrekte Marshalling auftreten, die interop-Funktion muss deklariert werden, mit der Art gemarshallt werden. In anderen Worten, angesichts der oben genannten definition, die CLR muss, irgendeine Art von Bestimmung, basierend auf dem statischen Typ
NativeStruct
. Ich würde vermuten, dass es korrekt erkennen, dass das Objekt gemarshallt werden, aber dann nur "Marshalling" eine null-byte-Objekt, die Größe derNativeStruct
selbst.So, in Reihenfolge zu get Ihr code arbeiten für
CHARFORMAT2
(und jeder andere der nicht für blitvorgänge geeignet-Typen, die Sie verwenden könnten), müssen Sie gehen zurück zu deklarierenSendMessage
wie einCHARFORMAT2
Objekt. Sorry, dass ich Euch in die Irre geführt auf diese.Captcha für die Vorherige Bearbeitung:
Yeah, Peitschen Sie es gut!
Cory,
Dies ist off-topic, aber ich merke, ein potenzielles problem für Sie in der app sieht es aus wie du Sie machst.
Das rich textbox-Steuerelement verwendet standard-GDI-text-mess-und text-Funktionen zum zeichnen. Warum ist das ein problem? Denn trotz behauptet, dass eine TrueType-schriftart sieht das gleiche auf dem Bildschirm als auf Papier, GDI ist nicht genau zu platzieren-Zeichen. Das problem ist die Rundung.
GDI verwendet all-integer-Routinen zu Messen, text-und Platz-Zeichen. Die Breite der einzelnen Zeichen (und die Höhe jeder Zeile, für diese Angelegenheit) ist, gerundet auf die nächste ganze Zahl der Pixel, die keine Fehlerkorrektur.
Den Fehler können leicht gesehen werden in der test-app. Legen Sie die schriftart Courier New in 12 Punkt. Dieser Zeichensatz mit fester Breite sollten Leerzeichen genau 10 pro Zoll, oder von 0,1 Zoll pro Charakter. Dies sollte bedeuten, dass Sie angesichts Ihrer Start-Linienbreite von 5.5 Zoll, sollten Sie in der Lage sein, um fit 55 Zeichen in der ersten Zeile vor dem wickeln tritt.
Aber wenn Sie versuchen, werden Sie sehen, dass wickeln tritt nach nur 54 Zeichen. Was mehr ist die 54th "Zeichen" und ein Teil der 53rd überhang der scheinbare Rand angezeigt auf dem Lineal bar.
Dies setzt Voraus, Sie haben Ihre Einstellungen auf standard 96 DPI (normale Schrift). Wenn Sie 120 DPI (große Schriften), werden Sie nicht sehen, dieses problem, obwohl es scheint, dass Sie die Größe, die Ihre Steuern nicht korrekt in diesem Fall. Du auch nicht, wahrscheinlich finden Sie diese auf der gedruckten Seite.
Was ist denn hier Los? Das problem ist, dass 0,1 Zoll (die Breite eines Zeichens) 9.6 Pixel (wieder mit 96 DPI). GDI-nicht space-Zeichen mit floating-point-zahlen, so ist es Runden dies bis zu 10 Pixel. So 55 Zeichen dauert bis 55 * 10 = 550 Pixel /96 DPI = 5.7291666... Zoll, während das, was wir erwartet hatten, war die 5,5 Zoll.
Obwohl dies wahrscheinlich weniger ausgeprägt in den normalen Anwendungsfall für ein Textverarbeitungsprogramm, gibt es eine Wahrscheinlichkeit von Fällen, in denen der Zeilenumbruch tritt an verschiedenen Orten auf dem Bildschirm statt auf der Seite, oder, dass die Dinge nicht die gleiche, wenn gedruckt, wie Sie auf dem Bildschirm. Das kann ein problem sein für Sie, wenn das ist eine kommerzielle Anwendung, die Sie gerade arbeiten.
Leider die Lösung für dieses problem ist nicht einfach. Es bedeutet, Sie müssen verzichten auf das rich textbox-Steuerelement, das bedeutet ein riesiger Aufwand der Implementierung sich alles, was es für Sie tut, was ziemlich viel ist. Es bedeutet auch, dass der text der Zeichnung code müssen Sie implementieren, wird ziemlich kompliziert. Ich habe code, der es tut, aber es ist zu Komplex, um Sie hier zu posten. Sie könnten jedoch finden dieses Beispiel oder diese eine hilfreich.
Glück!
1 Abstrakte Basisklasse
SendMessage
. Sie können ableiten, alle deine nativen Typ-Definitionen von einer einzigen abstrakten Klasse und erklärenSendMessage
zu nehmen, die Klasse.ref
route. In jedem Fall bin ich sehr interessiert an deiner Lösung die Verwendung einer abstrakten Basisklasse, bin aber laufen in einige Probleme (siehe mein edit). Ich werde weiter versuchen, um es herauszufinden.StructLayout
- Attribut für meine base-Klasse verursacht das TypeLoadException dann aber das problem war, dass ich mit[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32]
fürszFaceName
was verursacht die Nachricht zu scheitern. Wechsel zuUnmanagedType.ByValTStr
(das ist VIEL mehr Benutzer-freundlich) löste die Meldung Fehler. Wieder ein dickes Lob an Sie und vielen Dank für die tolle Beratung!if (flag)
AbschnittCC.Controls.RichTextBoxEx.GetCharformat()
die Nachricht schlägt fehl und ich bekomme keine Daten in meinem CHARFORMAT2 Objekt. Wenn ich den CC.Utilities.User32.SendMessage(..., NativeStruct) Aufruf von CC.Utilities.User32.SendMessage(..., CHARFORMAT2) die Botschaft arbeitet. Sie haben dazu beigetragen, viel mehr als ich jemals erwartet habe für eine solche generische Frage und dankbar für die Hilfe!Ich hatte schon einige lustige Fälle, in denen ein parameter ist so etwas wie
ref Guid parent
und die entsprechende Dokumentation sagt:Wenn
null
(oderIntPtr.Zero
fürIntPtr
Parameter) ist wirklich ein Ungültiger parameter, dann sind Sie gut mit einemref
parameter - vielleicht sogar noch besser aus, da es extra klar, genau das, was Sie brauchen, um passieren.Wenn
null
ist ein Gültiger parameter, die Sie übergeben könnenClassType
stattref StructType
. Objekte, die von einem Referenz-Typ (class
) übergeben werden, wie ein Zeiger, und Sie erlaubennull
.Nein, Sie können nicht überladen SendMessage und stellen Sie die Parameter wparam argument ein int. Das wird Ihr Programm nicht auf einem 64-bit-version des Betriebssystems. Es muß sich um einen Zeiger, der entweder IntPtr, eine für blitvorgänge geeignet Verweis oder ein out-oder ref-Wert-Typ. Überlastung der out - /ref-type ist ansonsten gut.
EDIT: Wie der OP darauf hingewiesen, dies ist nicht wirklich ein problem. Die 64-bit-Funktion Aufruf-Konvention übergibt die ersten 4 Argumente über Register, nicht den Stapel. Es besteht somit keine Gefahr des stack-mis-alignment für wparam und lparam Argumente.
RichTextBox
(unter vielen anderen, die Kontrolle ich bin auf der Suche an mit Reflektor) sollte das nicht funktionieren in einer 64-bit-Betriebssystem und ich weiß, dass das nicht der Fall ist.Sehe ich keine Nachteile.
By-ref ist oft genug für den einfachen Typ und einfache Struktur.
IntPtr sollten bevorzugt werden, wenn die Struktur hat eine variable Größe, oder wenn Sie wollen, um benutzerdefinierte Verarbeitung.
Mit
ref
ist einfacher und weniger fehleranfällig als die Manipulation von Zeigern manuell, also ich sehe keinen guten Grund für Sie es nicht... ein Weiterer Vorteil der Verwendungref
ist, dass Sie nicht haben, um über sorgen zu befreien, nicht verwalteten Speicher reserviert