Frage zu Doppelzeiger (IO-Funktion + GetQueuedCompletionStatus)
-
hustbaer schrieb:
Wenn du z.B. von "Context" zu "void*" castest, und dann von "void*" zu "OVERLAPPED*", dann wird das nix werden.
Genau so wenn du über void* zurückcastest.Und wie sonst? Und warum geht das nicht?
-
Du musst direkt von Context* zu OVERLAPPED* casten, damit der Zeiger entsprechend angepasst wird.
Wenn du von Context* zu void* castest, dann darfst du den void* den du bekommst nur wieder zurück zu Context* casten. Den so erhaltenen void* zu einer Basisklasse von Context* zu casten *kann* gehen, ist aber laut Standard undefiniert, und daher einfach falsch.
(Und es geht eben auch oft genug wirklich nicht.)Alles C++ Grundlagen.
ps.:
BTW: der Fehler ist - wenn dein Beispielcode deinem Programm entspricht - eh ein anderer (sehr ähnlich, aber nix mit void*):
Context* context GetQueuedCompletionStatus(..., (OVERLAPPED**)&context); // das ist HumbugRichtig geht das so:
OVERLAPPED* overlapped; GetQueuedCompletionStatus(..., &overlapped); // Kein Cast nötig Context* context = static_cast<Context*>(overlapped); // Jetzt casten wir den Zeiger (was OK ist), und nicht einen Zeiger auf einen Zeiger (was Humbug wäre)Der Grund warum es nicht funktioniert, ist dass "Zeiger auf Context" und "Zeiger auf OVERLAPPED" zwei unverwandte Typen sind. Anders gesagt: du kannst nicht T** zu U** casten, egal wie T und U verwandt sind, da T* und U* eben nicht verwandt sind.
Sorry, kann es grad auf die Schnelle nicht besser erklären, bzw. hab keine Zeit es besser/näher zu erklären.
-
hustbaer schrieb:
Du musst direkt von Context* zu OVERLAPPED* casten, damit der Zeiger entsprechend angepasst wird.
Wenn du von Context* zu void* castest, dann darfst du den void* den du bekommst nur wieder zurück zu Context* casten. Den so erhaltenen void* zu einer Basisklasse von Context* zu casten *kann* gehen, ist aber laut Standard undefiniert, und daher einfach falsch.
(Und es geht eben auch oft genug wirklich nicht.)Alles C++ Grundlagen.
Jo,
wenn man eine Inheritclass zu einer baseclass über einen void direkt
konvertiert, muss man darauf achten, dass es die erste Basisklasse ist.
Ansonsten muss der this ptr adjustiert werden,..Mal grob pseudomäßig:
class Inherit: BaseA, BaseB {} //... LPVOID blub =(LPVOID) new Inerhit; BaseA* basA = (BaseA) blub; //geht, da nach msdn spec für MSVC BaseA erste addy ist BaseB* basB = (BaseB) (blub +sizeof(BaseA)); //Adjustiere zeiger // BaseB liegt hinter BaseA, sizeof(BaseA) sollte auch die die größe der vftbl beinhaltenDie adjustage sollte man aber compiler/architektur-spezifisch explecit nochmal prüfen, bevor man sich darauf verlässt das es "einfach" funktioniert,...
grüßli
-
Jo,
wenn man eine Inheritclass zu einer baseclass über einen void direkt
konvertiert, muss man darauf achten, dass es die erste Basisklasse ist.Bist du sicher dass das dann definiert ist (laut Standard)?
Ich müsste da erst nachsehen...
-
Hi,.
100% sicher bin ich jetzt gerade net,..wo du fragst.
Aber ich schaue mal morgen nach.gn8
-
Ouch, Grundlagenlücke!
Aber: Was, wenn man es statt mit Vererbung so macht:struct Context { OVERLAPPED overlapped; // ... };Ist "GetQueuedCompletionStatus(..., (OVERLAPPED**)&context);" dann OK? Ich denke schon, denn so, ganz "roh", dürfte ja "Context" und "OVERLAPPED" bis hin zu sizeof(OVERLAPPED) Bytes "speichergleich" sein...
-
Hi schrieb:
Ouch, Grundlagenlücke!
Aber: Was, wenn man es statt mit Vererbung so macht:struct Context { OVERLAPPED overlapped; // ... };Ist "GetQueuedCompletionStatus(..., (OVERLAPPED**)&context);" dann OK? Ich denke schon, denn so, ganz "roh", dürfte ja "Context" und "OVERLAPPED" bis hin zu sizeof(OVERLAPPED) Bytes "speichergleich" sein...
Nein!
Das hat doch nicht mit Vererbung zu tun.Außerdem: Versuche casts zu Vermeiden. Gerade wen es hier in keiner Weise nötig ist.
1. Würde ich dennoch den Member in der Context Struktur direkt adressieren.
2. Würde ich nicht darauf bauen, dass overlapped der ertse Parameter der Strutktur bleibt.
3. Wenn überhaupt würde ich von OVERLAPPED ableiten:struct Context : public OVERLAPPED { // ... };Aber selbst das ist IMHO mieses Design.
4. Ein Zeiger auf einen Zeiger Overlapped Struktur ist das:struct Context { OVERLAPPED overlapped; // ... }; //... Context myContext; LPOVERLAPPED p = &myContext.overlapped; GetQueuedCompletionStatus(..., &p, ...);
-
Geht auch ohne Vererbung mit dem Makro CONTAINING_RECORD()

-
Jodocus schrieb:
Geht auch ohne Vererbung mit dem Makro CONTAINING_RECORD()

Ich habe doch geschrieben, dass es ohne Vererbung geht und auch besser wäre...
Ich verstehe Deinen Einwurf nicht.
Zudem löst es nicht das Grundproblem, dass der OP nämlich einen Zeiger auf einen Zeiger benötigt!
-
> dass der OP nämlich einen Zeiger auf einen Zeiger benötigt!
Hä? Den braucht er doch überhaupt nicht.
Mein Post war kein Einwand, sondern eine gängige Möglichkeit, aus einem Member das Container-Objekt zu ermitteln (was auch funktioniert, wenn OVERLAPPED nicht der erste Member der Struktur ist).
-
Jodocus schrieb:
Hä? Den braucht er doch überhaupt nicht.
Nicht?
Dann schau Dir mal die Definition von GetQueuedCompletionStatus an.
http://msdn.microsoft.com/en-us/library/aa364986(VS.85).aspxNach meinem Dafürhalten ist "LPOVERLAPPED *lpOverlapped" ein Zeiger auf einen Zeiger, und damit hat der OP anscheinend Probleme...
Just my 2 cents!
-
Ich glaube, ich verwende lieber den completion key (unsigned int*)

Sollte eigentlich genau so klappen. Man hat dann halt keine verschiedenen Kontexte für zB. AcceptEx und WSARecv, aber dann packt man halt alles nötige in eine einzige ClientContext-Struktur.
Oder warum sollte man das nicht machen?
-
Huch, da hab ich das LP überlesen, sorry Martin.

Anyway, das Problem ist ja nicht der Doppelzeiger, mit dem hat er ja eigentlich fast nichts zu tun, bisauf das Übergeben der Adresse.@ TS: Das ist doch ganz einfach:
enum operation_type { accept, send, recv, ... }; // POD struct container { operation_type type; WSAOVERLAPPED overlapped; }; container c = new container; std::memset(c, sizeof *c, 0); c->type = accept; AcceptEx(..., c, ...); ... WSAOVERLAPPED* ol_ptr = 0; GetQueuedCompletionStatus(..., &ol_ptr, ...); container* con_ptr = CONTAINING_RECORD(ol_ptr, container, overlapped); event_workers[con_ptr->type](..., con_ptr, ...);Mit dem CONTAINING_RECORD-Makro erhälst du den Container, der das OVERLAPPED-Objekt enthält (intern natürlich per Casts). In einem Array von Funktoren kannst du dann entsprechende Funktionen sammeln, die über eine Operation-ID dispatcht werden.
> Ich glaube, ich verwende lieber den completion key (unsigned int*)

Den solltest du benutzen, aber nicht für Per-I/O-Daten. Der Completion-Key ist für gewöhnlich der Socket-Deskriptor, auf dem die Operation stattgefunden hat.

Edit: Hoffentlich bist du auch über den korrekten Umgang mit AcceptEx() aufgeklärt.
-
WSAOVERLAPPED overlapped; <- müsste dann aber die erste Membervariable sein, damit es funktioniert
-
Nö, der Offset wird intern abgezogen.

Wird bei mir jedenfalls so definiert:#define CONTAINING_RECORD(address,type,field) ((type* )( (PCHAR)(address) - (ULONG_PTR)(&((type )0)->field)))
-
Dann darfst du aber bei AcceptEx nicht direkt c übergeben sondern &c.overlapped
-
Hmm, guck ich mir mal an.
Jodocus schrieb:
Den solltest du benutzen, aber nicht für Per-I/O-Daten.
Warum nicht? Kann auch gleich eine ganze Struktur übergeben.
Und ich brauche ja pro Client nicht mehr als einen Kontext und einen WSARecv-Aufruf.
Dann bei GetQueuedCompletionStatus() habe ich den Clientkontext verfügbar. Ist doch genau so wie mit dem OVERLAPPED *.Jodocus schrieb:
Edit: Hoffentlich bist du auch über den korrekten Umgang mit AcceptEx() aufgeklärt.
Vielleicht kannst du dazu noch kurz was erwähnen?
(Außer natürlich, dass man sich unbedingt den Funktionszeiger holen sollte. Und vllt. noch setsockopt mit SO_UPDATE_ACCEPT_CONTEXT. Und natürlich immer schön null-initialisierte OVERLAPPED-Strukturen zu verwenden
).
-
> Dann darfst du aber bei AcceptEx nicht direkt c übergeben sondern &c.overlapped
Logo.

> Warum nicht? Kann auch gleich eine ganze Struktur übergeben.
Weil eine Verbindung nicht viel mit einer Operation zu tun hat. Per-Handle-Data und Per-I/O-Data sind doch in diesem Kontext verschieden.
Für Per-Handle-Data speichert man so Sachen wie Socket-Deskriptor, gesendete/empfangene Bytes, Adresse etc., hingegen benutzt die Per-I/O-Data für Sachen wie Fehlercode, Puffer, Operationstyp etc. Wenn du das alles in eine einzige monolithische Struktur presst, hast du oft Informationen an manchen Stellen, die du gar nicht brauchst.
Du kannst es natürlich machen, technisch spricht auch nichts dagegen. Es ist eben üblich, I/O-Completionports auf diese Weise zu handhaben. (jaja, ich weiß, Dogmatismus :D)> Vielleicht kannst du dazu noch kurz was erwähnen?
Benutzt du den übergebenen Puffer, um gleich auch Daten vom Client zu empfangen?
-
Alles klar, werd ich bedenken. Danke!
Jodocus schrieb:
Benutzt du den übergebenen Puffer, um gleich auch Daten vom Client zu empfangen?
Nope. Hab ich lieber gelassen, wegen stale clients.
Macht für mich auch irgendwie überhaupt keinen Sinn, bei AcceptEx schon was zu empfangen. Wobei... soll das etwa für "Autorisierungen" sein?
-
> Wobei... soll das etwa für "Autorisierungen" sein?
Nö, für Performance. Du kannst im Kernelmode gleich 2 Operationen ausführen, anstatt explicit WSARecv() zu benutzten (du sparst dir einen switch in den Usermode). Aber schon AcceptEx() brauchst du nur bei äußerster Performance. Was programmierst du überhaupt?
> Nope. Hab ich lieber gelassen, wegen stale clients.
Dafür gibt es ja Lösungsmöglichkeiten.
