Toon posts:

[C++]Callback functies binnen klassen gebruiken.

Pagina: 1
Acties:
  • 143 views sinds 30-01-2008
  • Reageer

Verwijderd

Topicstarter
Hallo allemaal,

Ik stuit tegen een klein probleempje aan, ik wil namelijk een callback functie binnen een klasse gebruiken om data op het venster te tekenen. Nu heb ik een simpel voorbeeld gemaakt:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <windows.h>

class testCallback
{
public:
    testCallback();
    ~testCallback();
    LRESULT CALLBACK myProcedure(HWND hwndChild, UINT Msg, WPARAM wParam, LPARAM lParam);
private:
    WNDCLASSEX wndClassEx;
};

testCallback::testCallback()
{
    wndClassEx.lpfnWndProc = myProcedure;   // Dit is onmogelijk...
}


int main()
{
    return 0;
}


Dit kan dus niet:

wndClassEx.lpfnWndProc = myProcedure;

Door verschillende types, nu vroeg ik mij af hoe ik dit wel voor elkaar kan krijgen, ik heb wat geprobeerd maar ik kom er niet uit... Hoe moet ik dit oplossen?

Alvast bedankt! :-)

Peter.

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 11-04 03:15
Inderdaad, een C++ object method kan geen Windows callback zijn, omdat deze impliciet een pointer naar de instantie van het huidige object (de this-pointer) verwacht. Je zult de methode dus static moeten maken (zodat 'ie niet geassocieerd wordt met een instantie van de klasse) of helemaal buiten de klasse moeten halen (wat mijns inziens te prefereren is, omdat het eigenlijk een C functie is).

De vraag is dan natuurlijk hoe je het gewenste object aanspreekt vanuit de callback. Je zult zelf ergens een pointer naar dat object moeten verstoppen. Als het object een eigen window class aanmaakt, kun je 'm daar in stoppen met SetClassLongPtr, zodat je 'm in de callback weer op kunt zoeken met GetClassLongPtr.

Een kort voorbeeld:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
class Foo {
    WNDCLASSEX wndClassEx;
    Foo() {
        // TODO: initialize wndClassEx
        // TODO: register wndClassEx met sizeof(Foo*) extra bytes
        // TODO: create window
       SetClassLongPtr(hwnd, this);
    }
};
static LRESULT CALLBACK fooWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    ((Foo*)GetClassLongPtr(hwnd, 0))->wndProc(hwnd, uMsg, wParam, lParam);
}

(Niet getest, want ik werk niet onder Windows, maar het idee moge duidelijk zijn.)

Overigens ging ik er hier vanuit dat elk object z'n eigen window class heeft (zoals ik afleid uit de code in je post) - als dat niet zo is, is het logischer om de pointer in de window data (i.p.v. de class data) op te slaan en SetWindowLongPtr() en GetWindowLongPtr() te gebruiken.

[ Voor 12% gewijzigd door Soultaker op 01-02-2006 19:24 ]


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Soms mag je een void* aan een callback meegeven, die kan je ook als this pointer gebruiken (bij threading bv werkt dit prima)

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Soultaker schreef op woensdag 01 februari 2006 @ 19:20:
Inderdaad, een C++ object method kan geen Windows callback zijn, omdat deze impliciet een pointer naar de instantie van het huidige object (de this-pointer) verwacht. Je zult de methode dus static moeten maken (zodat 'ie niet geassocieerd wordt met een instantie van de klasse) of helemaal buiten de klasse moeten halen (wat mijns inziens te prefereren is, omdat het eigenlijk een C functie is).
Hoezo C functie? 't Is helemaal geen C functie. Het enige wat je nodig hebt is C linkage, maar verder werkt elke taal.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


Verwijderd

Topicstarter
Bedankt voor de snelle reacties!

Sorry dat ik een beetje onduidelijk was...
Ik probeer nu op de manier die Soultaker aangaf m'n code werkend te krijgen. Ik gebruik MVC++ 6.0, waar ik niet over de functie GetClassLongPtr beschik. Nu gebruik ik de functie GetClassLong en SetClassLong.

Ik snap hoe de techniek werkt, het lukt me alleen niet helemaal :P.
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
bool SCRIPT::InitWindow(int posX, int posY, HWND hwndParent)
{
    X = posX; Y = posY;

    if(hwndParent != NULL)
        this->hwndParent = hwndParent;
    hInstance              = (HINSTANCE)GetWindowLong(hwndParent, GWL_HINSTANCE);
    wndClass.cbSize        = sizeof(WNDCLASSEX);
    wndClass.style         = CS_HREDRAW | CS_VREDRAW;
    wndClass.lpfnWndProc   = minatuurProcedure;     
    wndClass.cbClsExtra    = sizeof(SCRIPT*);
    wndClass.cbWndExtra    = 0;
    wndClass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndClass.lpszMenuName  = NULL;
    wndClass.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
    wndClass.hInstance     = hInstance;
    wndClass.lpszClassName = CLS_SCRIPT;
    if(!RegisterClassEx(&wndClass))
        return false;

    SetClassLong(hwndWindow, sizeof(this), 0);

    return true;
}


Hier regristreer ik dus de klasse. Gebruik ik SetClassLong(...) op de juiste manier?Volgens mij doe ik het helemaal verkeerd :X .

Nu heb ik dus een callback procedure gemaakt:
code:
1
2
3
4
5
static LRESULT CALLBACK minatuurProcedure(HWND hwndChild, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    SCRIPT *instantieScript = ((SCRIPT*)GetClassLong(hwndChild, 0));
    instantieScript->setPosX(100);
... (hier komt de contorle voor de windows-messages).


setPosX veranderd enkel de positie van de window, hierdoor kijk ik of het werkt... nu krijg ik een seg-fault bij 'SCRIPT *instantieScript enz...'
Ik heb dus wat fout gedaan in het reserveren van extra geheugen en het toewijzen daarvan, ik weet alleen niet zo goed hoe ik dat wel juis moet doen O-).

Alvast bedankt!

Peter.

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 11-04 03:15
Ik zie je nog geen window aanmaken - heb je dat expres weggelaten, of...?

Wat retourneert GetClassLong? Als het 0 is, dan is de data blijkbaar niet geïnitialiseerd. Verifieer dan ook of SetClassLong niet failt (met GetLastError(), want hij retourneert altijd 0 de eerste keer).

Verder zou ik niet weten waarom je wel GetClassLong kunt gebruiken en niet GetClassLongPtr, maar ok, dat is een verhaal apart.

  • MBV
  • Registratie: Februari 2002
  • Laatst online: 12-04 17:12

MBV

Ken jij het Model-View-Controller model? Om dat te implementeren zijn er ook callbacks nodig (abonneren), en daarbij los ik het anders op. Een klasse kan 'zichzelf' achterlaten (altijd afgeleid van een interface View). In die interface wordt een functie updateDinges(waarde) gespecificeerd. In het Model wordt een map (of array) gevuld met View objecten die zichzelf aanmelden (ze roepen de void-functie model.addListener(View v) aan). Vervolgens, als er iets wijzigt, kan je die array of map aflopen, en op die objecten de functie updateDinges met een nieuwe waarde aanroepen.
Voordelen: erg flexibel, doordat je makkelijk een nieuwe weergave toe kan voegen, en je hoeft geen pointer naar een functie te hebben. Je weet immers dat de functie updateDinges er is, zodat je aan een pointer naar het object genoeg hebt :).

offtopic:
Ben ik blij dat ik nooit met Microsoft-toolkits heb hoeven programmeren, wat een lelijke code heb jij zeg :X. Ooit gehoord van Qt? Gratis, open source (ook windows) en Signal/Slot is heel makkelijk :)

Verwijderd

Topicstarter
Ik ben dom bezig, window vergeten aan te maken. En ook de functie verkeerd aangeroepen. Ga er zo mee aan de slag en dan laat ik het resultaat horen.

M'n code ziet er vies uit. Heb me daar ook nog niet veel mee bezig gehouden.
Ik zal ook eens naar jouw manier kijken MBV :).

Bedankt voor de reacties.

  • MBV
  • Registratie: Februari 2002
  • Laatst online: 12-04 17:12

MBV

offtopic:
ik bedoelde het soort variabelen, het aantal parameters en macro's dat je nodig hebt bij microsoft MVC. Ik zie dat wel vaker, en ben blij dat ik dat niet nodig heb :) DirectX heb ik ooit even naar gekeken, maar daar ben ik ook heel hard weggerend ;)

Verwijderd

Alles over function pointers, functors etc: http://www.function-pointer.org/.
In dit geval zou ik trouwens gewoon de callback een free function maken. Het probleem is wel op te lossen met wrappers e.d. maar in mijn optiek is het omslachtig en een vorm van 'shoehorning', ofwel een vierkantje door een rond gaatje proberen te krijgen. In C++ ben je niet gedwongen om alles 'OO' te doen, je zou hier bijvoorbeeld ook gewoon een namespace kunnen gebruiken.

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 12-04 09:44
Wat heeft dit met namespaces te maken als ik vragen mag? Als je de dispatcher functie lokaal wilt hebben is een static (file scope) functie toch voldoende?

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • curry684
  • Registratie: Juni 2000
  • Laatst online: 17-12-2025

curry684

left part of the evil twins

MBV schreef op woensdag 01 februari 2006 @ 20:47:
offtopic:
Ben ik blij dat ik nooit met Microsoft-toolkits heb hoeven programmeren, wat een lelijke code heb jij zeg :X. Ooit gehoord van Qt? Gratis, open source (ook windows) en Signal/Slot is heel makkelijk :)
offtopic:
Ik ben blij dat ik nooit met die megaranzige code van Qt heb hoeven werken. Wat een onoverzichtelijke spaghetticode krijg je daaruit man. En dan kun je Qt ook nog helemaal niet met Win32 vergelijken ook, omdat Qt zelf een wrapper om Win32 is en je dus helemaal niet op hetzelfde niveau kijkt en dit soort low-level API-acties dus helemaal niet meer voorkomen. Maar ik heb echt wel frameworks gezien en geschreven die Win32 op een stukken betere manier encapsuleerden dan dat gare Qt.

Tot zover onze les "alles is subjectief en kijk even wat verder dan je neus lang is voor je kortzichtig commentaar levert omdat jij product X toevallig geil vindt" :)

Professionele website nodig?


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 10:09

.oisyn

Moderator Devschuur®

Demotivational Speaker

De window long is idd een vaak gebruikte oplossing. Andere reguliere oplossingen zijn het gebruiken van een geassocieerde datastructuur (std::map<HWND, YourClass*>) en het gebruiken van zogenaamde thunks (stukjes assembly die je genereert voor elke class; de assembly code voegt gewoon een 5e parameter -de instance van je class- toe en roept vervolgens een static functie aan). Qua simpelheid is die std::map denk ik het handigst:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <map>

class MyWindow
{
public:
    MyWindow();
    LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);

private:
    LRESULT CALLBACK StaticWindowProc(HWND, UINT, WPARAM, LPARAM);

    typedef std::map<HWND, MyWindow*> WindowMap;
    static WindowMap s_windowMap;
};

MyWindow::MyWindow()
{
    // initialiseer window class e.d.

    CreateWindow(/* alle benodigde parameters */, this);
    // de this pointer is op te vragen bij een WM_NCCREATE
}

LRESULT CALLBACK MyWindow::StaticWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (msg == WM_NCCREATE) 
    {
        // we kunnen de windowMap nu vullen met de meegeleverde thispointer
        CREATESTRUCT * cs = reinterpret_cast<CREATESTRUCT*>(lParam);
        s_windowMap[hWnd] = static_cast<MyWindow*>(cs->lpParam);
    }

    // vind de juiste MyWindow instance
    WindowMap::iterator i = s_windowMap.find(hWnd); 
    if (i != s_windowMap.end())
        // en roep de non-static windowproc aan
        return i->second->WindowProc(hWnd, msg, wParam, lParam); 
    else
        // er staat nog geen entry voor deze window in de map,
        // roep de default window proc maar aan voor de zekerheid.
        return DefWindowProc(hWnd, msg, wParam, lParam); 
}


En de opmerkingen over MVC en namespaces hebben geen zak mee met je probleem te maken :)
MSalters schreef op woensdag 01 februari 2006 @ 20:25:
[...]

Hoezo C functie? 't Is helemaal geen C functie. Het enige wat je nodig hebt is C linkage, maar verder werkt elke taal.
C linkage is ook niet nodig, aangezien je een functiepointer aanlevert en hij dus net zo goed geen linkage kan hebben. Het enige wat je nodig hebt is een functie met de juiste callingconvention (ik geloof dat CALLBACK op x86 gedefinieerd is als stdcall)

[ Voor 30% gewijzigd door .oisyn op 02-02-2006 12:16 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MBV
  • Registratie: Februari 2002
  • Laatst online: 12-04 17:12

MBV

curry684 schreef op donderdag 02 februari 2006 @ 09:54:
[...]

offtopic:
Ik ben blij dat ik nooit met die megaranzige code van Qt heb hoeven werken. Wat een onoverzichtelijke spaghetticode krijg je daaruit man. En dan kun je Qt ook nog helemaal niet met Win32 vergelijken ook, omdat Qt zelf een wrapper om Win32 is en je dus helemaal niet op hetzelfde niveau kijkt en dit soort low-level API-acties dus helemaal niet meer voorkomen. Maar ik heb echt wel frameworks gezien en geschreven die Win32 op een stukken betere manier encapsuleerden dan dat gare Qt.

Tot zover onze les "alles is subjectief en kijk even wat verder dan je neus lang is voor je kortzichtig commentaar levert omdat jij product X toevallig geil vindt" :)
offtopic:
true, helemaal gelijk (over dat subjectief dan). Was meer bedoeld om een hint te geven dat er meer toolkits zijn dan Microsofts VC6 :). Zeker als je net begint is dat erg makkelijk om te weten, voor je het weet zit je vastgebakken aan een bepaalde toolkit. Gezien zijn reactie heeft hij het ook zo opgepakt :)
En spagetticode? Verklaar :P

[ Voor 3% gewijzigd door MBV op 02-02-2006 14:04 ]


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 10:09

.oisyn

Moderator Devschuur®

Demotivational Speaker

MBV schreef op donderdag 02 februari 2006 @ 14:03:
Was meer bedoeld om een hint te geven dat er meer toolkits zijn dan Microsofts VC6
VC6 is geen toolkit, datgene waar je het over hebt is gewoon de native win32 API. Zo werkt windows, en elke toolkit (Qt, WxWidgets, VCL, MFC, ...) werkt intern ook gewoon op die manier omdat dat nou eenmaal de enige manier is om met windows te interfacen. Er zitten wel 'toolkits' bij VC++, namelijk MFC en ATL (en de topicstarter gebruikt geen van beide). Toolkits tussen aanhalingstekens, omdat het eigenlijk meer lichte wrappers zijn, en niet zo'n vorm als abstractie zoals Qt, WxWidgets en Borland's VCL bewerkstelligen.

[ Voor 5% gewijzigd door .oisyn op 02-02-2006 14:21 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MBV
  • Registratie: Februari 2002
  • Laatst online: 12-04 17:12

MBV

offtopic:
Idd, je hebt gelijk. Ik zat even te denken aan MFC, da's wel een toolkit :). Ik bedoelde dus dat er handigere manieren zijn om GUI's te maken, zo goed neergezet? ;)

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
.oisyn schreef op donderdag 02 februari 2006 @ 12:11:
C linkage is ook niet nodig, aangezien je een functiepointer aanlevert en hij dus net zo goed geen linkage kan hebben. Het enige wat je nodig hebt is een functie met de juiste callingconvention (ik geloof dat CALLBACK op x86 gedefinieerd is als stdcall)
C linkage is wel nodig, om de hele simpele reden dat C linkage versus C++ linkage deel uitmaakt van het type.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Je kan, strict gezien, ook geen statische methode gebruiken; die moet je namelijk reinterpret-casten, ie. "op eigen risico".

  • MBV
  • Registratie: Februari 2002
  • Laatst online: 12-04 17:12

MBV

Ehhm, als je al objecten hebt, waarom 'spreek je dan niet af' dat ze een bepaalde functie moeten implementeren voor callback (oftewel, abstract base class/interface implementeren)? Volgens mij is dat een stuk simpeler en minder foutgevoelig :)

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 12-04 09:44
Dat moeten ze ook, je hebt alleen een functie nodig die de events dipatched naar het goede object. Daarvoor heb je een globale functie nodig, of een statische functie in een klasse.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 10:09

.oisyn

Moderator Devschuur®

Demotivational Speaker

MSalters schreef op donderdag 02 februari 2006 @ 20:03:
[...]

C linkage is wel nodig, om de hele simpele reden dat C linkage versus C++ linkage deel uitmaakt van het type.
Zoijar schreef op donderdag 02 februari 2006 @ 20:10:
Je kan, strict gezien, ook geen statische methode gebruiken; die moet je namelijk reinterpret-casten, ie. "op eigen risico".
Beetje flauw, in standard C++ heb je ook geen calling convention declarators, maar dat is wel precies wat het hele probleem oplost

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.

Pagina: 1