Struct senden per WM_COPYData
-
Hallo
Ich versuche eine struct per WM_COPYDATA an eine MFC Applikation zu senden.
Dazu habe ich schon viele Beispiele gefunden. Leider aber immer nur wie man einen String versendet oder einen Integer.Doch ich möchte eine Struktur versenden. Und alle versuche sind bisher gescheitert:
Hier mal Code:
// für WM_COPYDATA public struct COPYDATASTRUCT { public IntPtr dwData; public int cbData; public object lpData; } // meine Struktur mal stark vereinfacht. // es fehlen noch enums und weitere Strukturen und ein Timestamp // aber nicht mal die einfach Struktur bekomme ich übertragen [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct IpcArgs { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2048)] public string message; public int code; } public void SendData(int hwnd, IpcArgs args) { var cpd = new COPYDATASTRUCT(); cpd.dwData = (IntPtr)0; cpd.lpData = args; // Wird diese Zeile ausgeführt empfängt meine MFC Applikation gar nichts mehr // andernfalls kommt nur Müll an. //cpd.cbData = Marshal.SizeOf(args); SendMessage(hwnd, WM_COPYDATA, 0, ref cpd); }
Hoffe mir kann man noch helfen
-
Sag uns am besten, wie die Struktur in C aussieht, dann können wir dir am besten helfen. Du musst schliesslich die C Struktur in C# nachbauen.
Informationen über P/Invoke findest du hier:
http://msdn.microsoft.com/en-us/library/fzhhdwae.aspx
(aber ist ein knietiefer Sumpf ;))Viele Beispiele und bereits fertige Lösungen für die WinAPI findest du hier:
http://pinvoke.net/Grüssli
-
hier ist meine struct mit ihren benötigten Komponenten.
//----------------------------------------------------------------------------- /// Event Komponente //----------------------------------------------------------------------------- struct ComponentIdent { /// Zeile int line; /// Datei char file[2048]; /// Funktion char proc[256]; /// Sektion char section[256]; }; //----------------------------------------------------------------------------- /// Event Stufe //----------------------------------------------------------------------------- enum Severity { /// Information SEV_INFO, /// Warnung SEV_WARNING, /// Fehler SEV_ERROR }; //----------------------------------------------------------------------------- /// Debug Level //----------------------------------------------------------------------------- enum DebugLevel { /// Bediener DBL_USER, /// Debug Level 1 DBL_1, /// Debug Level 2 DBL_2, }; //----------------------------------------------------------------------------- /// Event Argumente //----------------------------------------------------------------------------- struct IPCArgs { /// Indentifiaktion der Komponente die das Event ausgelöst hat ComponentIdent reporter; /// Indentifiaktion der Komponente in der das Event entstanden ist ComponentIdent source; /// Zeitstempel CTime timestamp; /// Nachricht char message[2048]; /// Code int code; /// Stufe Severity severity; /// DebugLevel DebugLevel debugLevel; };
Mein Problem besteht aber wohl eher darin wie ich die Instanz von der IPCArgs Struct in die COYDATASTRUCT bekomme.
Denn ich schaffe es nicht mal die Struktur die ich bis auf einen integer heruntergebrochen habe zu übertragen.
-
Das
CTime
in derIPCArgs
Struktur kannst du vergessen. Eine C++ Klasse kannst du nicht per P/Invoke übertragen. Ich habe es hier mal durch einenInt64
ersetzt, heisst du kannst z.B. einen Unix-Timestamp nehmen.[StructLayout(LayoutKind.Sequential)] struct COPYDATASTRUCT { public IntPtr dwData; public Int32 cbData; public IntPtr lpData; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] struct ComponentIdent { public Int32 line; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2048)] public string file; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string proc; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string section; } enum Severity : Int32 { SEV_INFO, SEV_WARNING, SEV_ERROR, } enum DebugLevel : Int32 { DBL_USER, DBL_1, DBL_2, } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] struct IPCArgs { public ComponentIdent reporter; public ComponentIdent source; public Int64 timestamp; // CTime ist eine Klasse und kann nicht übertragen werden. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2048)] public string message; public Int32 code; public Severity severity; public DebugLevel debugLevel; } // ... [DllImport("coredll.dll")] private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam); // ... var args = new IPCArgs(); // Fülle die Daten var cd = new COPYDATASTRUCT(); cd.dwData = IntPtr.Zero; cd.cbData = Marshal.SizeOf(args); var argsHandle = GCHandle.Alloc(args, GCHandleType.Pinned); cd.lpData = argsHandle.ToIntPtr(); var cdHandle = GCHandle.Alloc(cd, GCHandleType.Pinned); SendMessage(hwnd, WM_COPYDATA, IntPtr.Zero, cdHandle.ToIntPtr()); cdHandle.Free(); argsHandle.Free();
Es ist alles ungetestet und einfach mal so schnell während der Vorlesung eingetippt. Aber ich hoffe es hilft dir schonmal weiter
Wichtig wäre wohl noch zu sagen, dass du bei den
GCHandle.Alloc
nicht die Objekte selbst pinst sondern eine Kopie von diesen, daGCHandle.Alloc
einobject
erwartet und die Strukturen daher vorher ein Boxing durchlaufen. Heisst du musst sie zuvor füllen und danach pinnen. Wenn du es umgekehrt machst, werden deine Daten nicht übertragen.Es ist zudem zu empfehlen, das Ganze exceptionsicher zu machen, heisst dass die Handles immer freigegeben werden, sonst hast du hier ein Memory-Leak. Freigeben solltest du die Objekte natürlich erst, wenn sie nicht mehr verwendet werden.
Grüssli
-
Hallo Dravere.
Danke dir schon mal.
Ein paar Dinge dazu:
Du hast folgende Funktion verwendet:
[DllImport("coredll.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);Ich hatte diese verwendet.
[DllImport("User32.dll", EntryPoint = "SendMessage")]
public static extern int SendMessage(int hWnd, int Msg, int wParam, ref COPYDATASTRUCT lParam);Dachte hier wird schon direkt COPYDATASTRUCT verwendet.
Aber im Grunde eigentlich egal. Hauptsache es funktioniert. Tut es leider aber in beiden Fällen noch nicht.
Folgende Probleme.
1. Bei der von mir verwendeten Funktion kann man für das hwnd ein int übergeben. Die habe ich mir bisher über FindWindow() geholt. Bei deiner verwendeten ein IntPtr. Ich weiß es gibt noch eine FindWindoxEx. Dass ist aber um einiges komplizierter. Mit Foreach über Process.GetProcessesByName("Prozessname");
2. Die Deklaration der enums mit "public enum Severity : Int32" geht nicht. Hier gibts ein Fehler bei Int32: Typ "byte", "sbyte", "short", "ushort", "int", "uint", "long" oder "ulong" erwartet.
3. Bei den 2 aufrufen von ToIntPtr wird ein Argeument erwartet. Bin hier immer noch am überlegen was man da übergeben muss.
Gruß
-
Marshall schrieb:
1. Bei der von mir verwendeten Funktion kann man für das hwnd ein int übergeben. Die habe ich mir bisher über FindWindow() geholt. Bei deiner verwendeten ein IntPtr. Ich weiß es gibt noch eine FindWindoxEx. Dass ist aber um einiges komplizierter. Mit Foreach über Process.GetProcessesByName("Prozessname");
FindWindow
gibt dir einHWND
zurück und das ist schlussendlich einvoid*
. Daher ist wohl die Deklaration deinerFindWindow
Funktion falsch. Siehe:
http://pinvoke.net/default.aspx/user32/FindWindow.html[DllImport("user32.dll", SetLastError = true)] static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
Marshall schrieb:
2. Die Deklaration der enums mit "public enum Severity : Int32" geht nicht. Hier gibts ein Fehler bei Int32: Typ "byte", "sbyte", "short", "ushort", "int", "uint", "long" oder "ulong" erwartet.
int
entspricht in C# einemInt32
. Ersetze einfach denInt32
durch einint
. Ich mag es, genau zu sein, bei P/Invoke, da es dort sehr wichtig ist, ob nun etwas ein Int32 oder Int64 oder sonst was ist. Grundsätzlich kannst du aber aber den Teil auch ganz weglassen, da standardmässig einEnum
sowieso einemint
entspricht und somit einemInt32
.Marshall schrieb:
3. Bei den 2 aufrufen von ToIntPtr wird ein Argeument erwartet. Bin hier immer noch am überlegen was man da übergeben muss.
Wups.
ToIntPtr
ist ja eine statische Funktion...GCHandle.ToIntPtr(cdHandle) // bzw. GCHandle.ToIntPtr(argsHandle)
Siehe dazu auch die Dokumentation.
Grüssli
-
So, jetzt konnte ich noch kurz selber das durchtesten, noch ein paar Korrekturen sind notwendig. Hab zwei Dinge vergessen:
1. Beim ersten Objekt darf man nichtGCHandle
verwenden, da du darin Strings hast, welche durch den Marshaler umgewandelt werden müssen. Wir können somit nicht einfach das C# Objekt pinnen und dieses verwenden, sondern müssen Speicher reservieren und die Struktur dort reinkopieren. Danach den Zeiger auf diesen Speicherbereich übergeben.
2. NichtToIntPtr
verwenden sondernAddrOfPinnedObject
, blöder Anfängerfehlervar args = new IPCArgs(); args.reporter.line = 1; args.reporter.file = "filereporter"; args.reporter.proc = "procreporter"; args.reporter.section = "sectionreporter"; args.source.line = 2; args.source.file = "filesource"; args.source.proc = "procsource"; args.source.section = "sectionsource"; args.timestamp = 1234; args.message = "this is a test"; args.code = -1; args.severity = Severity.SEV_WARNING; args.debugLevel = DebugLevel.DBL_2; var cd = new COPYDATASTRUCT(); cd.dwData = IntPtr.Zero; cd.cbData = Marshal.SizeOf(args); var argsBuffer = Marshal.AllocHGlobal(cd.cbData); Marshal.StructureToPtr(args, argsBuffer, false); cd.lpData = argsBuffer; var cdHandle = GCHandle.Alloc(cd, GCHandleType.Pinned); SendMessage(hwnd, WM_COPYDATA, IntPtr.Zero, cdHandle.AddrOfPinnedObject()); cdHandle.Free(); Marshal.FreeHGlobal(argsBuffer);
Das hat bei mir in einem kleinen Testprogramm funktioniert.
Falls weitere Fragen bestehen, einfach fragen
Grüssli
-
Hallo.
Nochmals vielen Danke für deine Hilfe.
FindWindow gibt dir ein HWND zurück und das ist schlussendlich ein void*. Daher ist wohl die Deklaration deiner FindWindow Funktion falsch.
Ok. Hatte ich von dieser Seite hier:
http://boycook.wordpress.com/2008/07/29/c-win32-messaging-with-sendmessage-and-wm_copydata/
Hat aber auch Funktioniert. Seltsam.
Aber nun zu deiner Lösung. Beim Ausführen der Funktion SendMessage erhalte ich den Fehler dass das Modul coredll.dll nicht geladen werden kann.
Habe nun die SendMessage Funktion aus der user32.dll verwendet. Das funktioniert. Und auch der Rest. Supi.
Noch ne Frage:
Hätte es eigentlich mit der Funktion die ich verwendet habe, bei der man direkt die CopyDataStruct übergeben kann auch funktioniert?Und noch eine:
Wo eignet man sich so wissen über das P/Invoke Zeugs an. Gibts da ne Schulung dafür, oder alles selber beibringen?
-
Und noch eine Frage. Ich hoffe es werden nicht zuviel.
Und zwar zu der CTime Geschichte. Welcher Datentyp wäre hier denn am besten geeignet für beide Seiten bzw. für alle 3 Seiten.
3 Seiten weil: Momentan sende ich von einem MFC Fenster an ein anderes MFC Fenster und hier hat CTime natürlcih funktioniert. Nun kommt das WPF Fenster hinzu das zusätlich an das MFC Fenster sendet.
Gibt es einen Datentyp den man in c++ sowie auch in c# verwenden kann um die Zeit + Datum zu verwalten
-
Marshall schrieb:
Ok. Hatte ich von dieser Seite hier:
http://boycook.wordpress.com/2008/07/29/c-win32-messaging-with-sendmessage-and-wm_copydata/
Hat aber auch Funktioniert. Seltsam.
Nicht wirklich seltsam. Unter einem 32-Bit System ist ein
int
gleich gross wie einvoid*
. Und ein Zeiger ist schliesslich nicht viel anderes als eine Zahl. Unter 64-Bit würdest du aber unter Umständen in Probleme reinkommen.Marshall schrieb:
Aber nun zu deiner Lösung. Beim Ausführen der Funktion SendMessage erhalte ich den Fehler dass das Modul coredll.dll nicht geladen werden kann.
Habe nun die SendMessage Funktion aus der user32.dll verwendet.
Das ist auch absolut richtig. Das war ein Copy&Paste Fehler von meiner Seite.
Marshall schrieb:
Hätte es eigentlich mit der Funktion die ich verwendet habe, bei der man direkt die CopyDataStruct übergeben kann auch funktioniert?
Ja, wahrscheinlich schon. ref hätte dafür gesorgt, dass ein Zeiger draus wird und dass das
InAttribute
undOutAttribute
angewendet werden. Dadurch würden die Daten korrekt übergeben und es würde sogar dafür gesorgt, dass sie bei Änderungen korrekt zurückgeschrieben werden. Da die Struktur zudem Blittable ist (heisst man kann die Struktur einfach Bit für Bit kopieren und erhält das gleiche, sie besteht ausschliesslich aus primitiven Typen) dürfte der Marshaler dann das gleiche machen, dass er das Objekt einfach im Speicher für den Aufruf pinnt.Marshall schrieb:
Wo eignet man sich so wissen über das P/Invoke Zeugs an. Gibts da ne Schulung dafür, oder alles selber beibringen?
Ich habe mir alles selber beigebracht über die MSDN Seiten und ein paar wenigen anderen Seiten. Habe dann sogar, um mich selber weiterzubilden, ein Tutorial dazu geschrieben. Aber nie fertiggestellt ... wurde auch viel zu gross. Und aktuell sowieso keine Zeit mehr dazu.
Marshall schrieb:
Und noch eine Frage. Ich hoffe es werden nicht zuviel.
Naja, muss bald auf den Zug
Marshall schrieb:
Und zwar zu der CTime Geschichte. Welcher Datentyp wäre hier denn am besten geeignet für beide Seiten bzw. für alle 3 Seiten. ...
Das Problem, welches du hier hast, ist die Schnittstelle per P/Invoke. Diese kann nur C Datentypen durchreichen. Daher dürfte es wohl am einfachsten sein, wenn du mit einem Unix-Timestamp fährst:
http://de.wikipedia.org/wiki/Unix-TimestampDen Unix-Timestamp kannst du dann in C# in ein
DateTimeOffset
umwandeln, bzw. in C++ und MFC dann in einCTime
.CTime
aktzeptiert z.B. direkt über den Konstruktur einen Unix-Timestamp:
http://msdn.microsoft.com/en-US/library/b6989cds.aspxÜber
CTime::GetTime
erhälst du den Unix-Timestamp.Bei
DateTimeOffset
musst du TheEpoch (1.1.1970 00:00:00) erstellen und die Sekunden dazurechnen oder die Differenz bilden und dann das Total der Sekunden nehmen.Grüssli
-
Hallo Dravere
Es hat jetzt ziemlich lange gedauert. Aber ich musste das zunächst mal hinten anstellen.
Ich mache es nun wie folgt:
DateTime timeStamp = DateTime.Now Int64 timestamp = (long) (timeStamp - new DateTime(1970, 1, 1).ToLocalTime()).TotalSeconds
Auf meiner c++ Seite habe ich gar nichts geändert. Muss ich ja nicht hast ja geschrieben dass der Konstruktor von CTime einen UnixTimestamp akzeptiert.
Es sieht nun gar nicht schlecht aus. Bis auf die Tatsache das die angezeigt Zeit nun um eine Stunde verschoben ist. Also wenn ich 9.00 sende kommt 10.00 an.
Vieleicht hast du mir ja nochmal eine Idee woran das liegen kann.
-
So nochmal ich.
Habe es gelöst:
timestamp = (long)(reportServiceEventArgs.TimeStamp.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds
muss meinen lokalen Timestamp erst mal in UTC wandeln.