[C++] Van __cdecl naar __thiscall ?

Pagina: 1
Acties:

  • DieterVDW
  • Registratie: Juli 2002
  • Laatst online: 12-02-2017
Ok dit is mijn probleem:

Ik gebruik een 3rd party dll (waaraan ik dus niks kan veranderen) die een zekere
functie bevat waaraan ik een pointer naar een andere functie met de __cdecl calling conventie moet geven.
Ik wil echter meerdere instanties van deze dll in m'n programma gebruiken
(het zijn eigenlijk niet dezelfde dll's, maar wel met dezelfde api, vandaar),
en daarom ben ik nu een object aan het schrijven dat een abstractie vormt
van 1 instantie van zo'n dll.
Ik wil nu aan die functie uit de dll een pointer naar een member functie van m'n object geven, zodat elke dll de functie oproept uit z'n eigen bijhorend object.
Dit kan echter niet aangezien een memberfunctie van een object de calling conventie __thiscall heeft.

Nu vroeg ik mij af hoe ik dit het beste kan oplossen/omzeilen/... ?

Ik heb eraan gedacht om al die dll's dezelfde gewone (__cdecl) functie te laten oproepen en dan die functie de memberfunctie van het juiste object te laten oproepen, maar dit is echter niet mogelijk aangezien ik met geen enkel middel kan
achterhalen uit welke dll die functie oproep kwam, en ik dus niet kan achterhalen welk object ik zou moeten oproepen...

Ik veronderstel dat ik toch in ieder geval een pointer naar een gewone (__cdecl) functie zal moeten geven aan die dll, maar hoe kan ik dan te weten komen bij welk object de dll hoort vanwaar de functieoproep kwam?

Tnx for the help!

  • Shadowman
  • Registratie: Januari 2002
  • Niet online
Ik geloof dat je dat op de volgende manier goed kunt oplossen:
C++:
1
(returnwaarde) (*__thiscall) (argumenten)=__cdecl;

  • DieterVDW
  • Registratie: Juli 2002
  • Laatst online: 12-02-2017
Hmm ik moet toegeven dat ik die regel code niet echt kan ontcijferen hoor.
Waar gebruik je die dan?

Maar ff een bedenking:
is het niet sowieso onmogelijk om die dll een memberfunctie van een object te laten oproepen?
Als een memberfunctie opgeroepen wordt verwacht die namelijk dat er zich een pointer naar z'n object in ECX bevindt, iets waar de dll zeker niet zal voor zorgen...
Maw: de opgeroepen memberfunctie kan niet weten bij welk object hij behoort...

Ik denk dus dat ik zeker een __cdecl functie pointer zal moeten geven aan die dll.
Maar dan moet ik in die functie wel kunnen achterhalen bij welk object de dll hoort van waaruit de functiecall kwam...
Is het soms mogelijk om op te vragen in welke memory range een dll geladen is?
Dan zou ik kunnen proberen aan het return adress te geraken en kijken in welke memory range dit valt? (Beetje (te) vergezocht...?)

  • MisterData
  • Registratie: September 2001
  • Laatst online: 20:35
Misschien een beetje te ingewikkeld, maar het werkt wel. Je kunt via een Runtime Assembler zoals SoftWire at-runtime een functie genereren (kan dus ook een __cdecl zijn) en die gecompileerd wegschrijven naar het geheugen. Als je in die functie een call doet naar jouw member-functie waarvoor de functie is gemaakt, en je de dll verteld dat hij naar die functie moet callen, dan heb je het opgelost :) Vereist wel een hoop kennis van Assembly (afgezien van dat het alleen op x86 systemen werkt en dus niet portable is).

edit:
nog wat...


Als je een HMODULE hebt van je DLL dan kun je toch met functies a la GetModuleName enzo erachter komen welke DLL er bezig is? Volgens mij bestaat er ook zoiets als GetModuleFileName (of returned GetModuleName zelf al de naam van het bestand?) Zoek dat dus even in de MSDN.

[ Voor 22% gewijzigd door MisterData op 06-09-2003 17:16 ]


  • DieterVDW
  • Registratie: Juli 2002
  • Laatst online: 12-02-2017
Phew die SoftWire oplossing ziet er wel ingewikkeld uit...

Mjah 't probleem is dat ik dus wel geen HMODULE heb van die dll. De dll geeft die niet mee aan de functie, en ik kan de dll niet aanpassen. Er kunnen trouwens ook twee instanties van dezelfde dll actief zijn, dus aan de filename heb ik ook niet zoveel.

Verwijderd

Ik neem aan dat je een soort callback mechanisme hebt in die 3rd party DLL? Zonder de precieze parameters/beschrijving van de routines in die DLL kun je hier weinig over zeggen.

ALS je aan de registratie routine in die 3rd pary DLL een extra parameter mee kan geven die door de DLL weer aan jouw callback functie doorgegeven word, kun je het volgende doen:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class YourClass
{
public:
   void Setup()
   {
      SomeDLL_RegisterCallback(
         Callback, this /* = pUserData in callback */);
   }

private:
   static void Callback(..., void *pUserData)
   {
      YourClass *pThis = (YourClass *) pUserData;
      pThis->RealCallback(...);
   }

   void RealCallback(...)
   {
      // ...callback code...
   }
}

[ Voor 2% gewijzigd door Verwijderd op 06-09-2003 17:23 . Reden: code edit ]


  • DieterVDW
  • Registratie: Juli 2002
  • Laatst online: 12-02-2017
Ook geen callback mechanisme jammer genoeg.
Het enigste wat de dll krijgt is een pointer naar een functie.
(de dll stamt nog uit het C tijdperk vrees ik :( )

  • MisterData
  • Registratie: September 2001
  • Laatst online: 20:35
DieterVDW schreef op 06 September 2003 @ 17:21:
Phew die SoftWire oplossing ziet er wel ingewikkeld uit...

Mjah 't probleem is dat ik dus wel geen HMODULE heb van die dll. De dll geeft die niet mee aan de functie, en ik kan de dll niet aanpassen. Er kunnen trouwens ook twee instanties van dezelfde dll actief zijn, dus aan de filename heb ik ook niet zoveel.
Je gebruikt LoadLibrary niet dan?

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 21-08 17:14
Een mogelijke truc is Thread Local Storage en een aparte thread voor elke DLL. In de TLS stamp je dan de object pointer voor die thread c.q. DLL

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


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 21-08 18:33
Schrijf een global wrapper die de callback forward naar je object

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.


  • madwizard
  • Registratie: Juli 2002
  • Laatst online: 26-10-2024

madwizard

Missionary to the word of ska

Heb je maar 1 zo'n object per instantie van de DLL? Dan kan je toch gewoon een globale variabele gebruiken met het object erin?
Zoniet, dan 1 per thread? Dat kan waarschijnlijk wel met TLS zoals MSalters zei.. Bij meer per thread wordt het lastig, je zal toch iets moeten kunnen identificeren.

www.madwizard.org


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 04:06

.oisyn

Moderator Devschuur®

Demotivational Speaker

afgezien van runtime asm generation, wat hier overigens een beetje overkill is (de asm die je wilt genereren is altijd hetzelfde, alleen het adres van het object veranderd), kun je ook een kleine thunk genereren voor ieder object. Zo'n thunk is eigenlijk niets meer dan een structure waar asm code in staat die het adres van het object op de stack pushed en dan een bepaalde functie aanroept. Het adres van deze structure converteer je gewoon naar een functiepointer.
Deze techniek wordt ook in de ATL/WTL onder win32 gebruikt om windowprocedures te binden aan objecten.

Ok even een voorbeeld. Stel je functie waarvan je de pointer wilt hebben ziet er zo uit:

C++:
1
typedef void (*funcptr_t) (int x, int y);


Goed, nou heb je dus een object, die een dergelijke functie als member heeft:

C++:
1
2
3
4
5
6
class Object
{
    // ...
public:
    virtual void functie (int x, int y);
};


De thunk die we gaan maken pushed het adres van het object extra op de stack, en roept daarna een functie aan, die op zijn beurt de juiste memberfunctie aanroept op het object, wat eveneens als parameter meegegeven wordt. Die functie definieren we hier:

C++:
1
2
3
4
void __cdecl call_Object_functie (void * retAddress, Object * p, int x, int y)
{
    p->functie (x, y);
}


Dat met retAddress leg ik zo uit

Dan wat asm. Bij een cdecl functie worden alle parameters in omgekeerde volgorde op de stack gepushed, en vervolgens wordt de functie aangeroepen. De aanroepende code moet vervolgens weer zelf de stack opruimen. Aangezien we een vervangende functie aanleveren, staat op dat moment het returnadres op de stack, en daarvoor de parameters van de functie. Daartussen moet echter nog het adres van het object. Alleen de aanroepende functie, die ook de stack op gaat ruimen, rekent natuurlijk maar op 2 parameters, terwijl er met het adres van het object erbij 3 op staan. Dus die extra parameters moeten we zelf ook opruimen.

Dit wordt de uiteindelijke asm:

code:
1
2
3
4
5
pop eax                   ; oude returnaddress van de stack halen
push object_addr          ; adres van het object erop
push eax                  ; returnaddress weer op de stack
call call_Object_functie  ; roep de functie aan
ret 4                     ; retourneer en ruim de extra 4 bytes op


die laatste ret 4 springt terug naar het adres wat bovenop de stack staat, en haalt er vervolgens 4 extra bytes af. Dit zijn natuurlijk die 4 bytes die gebruikt werden om het adres van het object in op te slaan.

Gooi je dit door een assembler, dan krijg je de volgende output:

code:
1
2
3
4
5
58               pop         eax  
68 78 56 34 12   push        12345678h 
50               push        eax  
E8 DB EE FF FF   call        dummy (41A117h) 
C2 04 00         ret         4


Dus we maken een struct zodat we de bytes makkelijk in kunnen vullen:
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
#pragma pack(push, 1)
struct ObjectThunk
{
    char pop_eax;
    char push_object_addr;
    Object * object_addr;
    char push_eax;
    char call;
    int Object_functie_addr;
    char ret;
    short ret_stack_cleaup;

    void init (Obejct * o)
    {
        pop_eax = 0x58;
        push_object_addr = 0x68;
        object_addr = o;
        push_eax = 0x50;
        call = 0xe8;
        Object_functie_addr = reinterpret_cast<int> (&call_Object_functie) - reinterpret_cast<int> (&ret);
        ret = 0xc2;
        ret_stack_cleanup = 4;
    }
};
#pragma pack(pop)


(die pragma is MSVC++, je moet even voor jouw compiler uitvinden hoe je een structure kunt packen op 1 byte)
Nu maak je simpelweg een ObjectThunk aan voor het object dat je wilt gebruiken, je initialiseert hem met de initfunctie en het adres van het Object waar hij op moet werken, en je geeft als functiepointer voor de DLL gewoon het adres van de ObjectThunk mee.

Et Voila, het werkt ;)

PS. de bovenstaande code is uit mijn hoofd en kan dus fouten bevatten ;)

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.


  • DieterVDW
  • Registratie: Juli 2002
  • Laatst online: 12-02-2017
Wow niet slecht...
Morgen ff proberen.
Pagina: 1