Close
Duke shfaqur rezultatin -19 deri 0 prej 10
  1. #1
    i/e regjistruar
    Anëtarësuar
    17-11-2006
    Postime
    81

    C++ - Katër mënyra për të shoqëruar një objekt me një dritare

    Këto ditë, ndërsa jam duke i shtuar aftësinë "Control Container" programit që po krijoj, m'u desh të eksploroj disa nga file-t e ATL-së, dhe ndër të tjera "zbulova" një teknikë mjaft interesante që e justifikon plotësisht emrin "arti i programimit", ndaj dhe vendosa ta ndaj me të tjerët "zbulimin" tim.

    Thuajse të gjithë programet që krijohen sot strukturohen në formën e objekteve. Nga ana tjetër pjesa dërrmuese e programeve për Windows janë të tipit GUI, pra komunikojnë me përdoruesit përmes "dritareve". E gjitha kjo do të thotë se thuajse në ç'do program lind nevoja për të shoqëruar një objekt me një dritare.

    Natyrisht të gjithë ata që përdorin Delphi-n, C++ Builder, MFC, Visual Basic apo ndonjë platformë tjetër për të krijuar programet e tyre, nuk e perceptojnë fare nevojën për të shoqëruar objektet me dritaret, pasi të gjitha platformat e mësipërme (dhe të tjera) e realizojnë automatikisht këtë.

    Sidoqoftë, ndonëse njohja e teknikës që do përshkruaj nuk është e domosdoshme (për më tepër ATL-ja e ofron të gatshëm implementimin e saj), njohja e saj mund të rezultojë e dobishme në situata të tjera, pasi kjo teknikë në vetvete ka të bëjë me gjenerimin e instruksioneve "fluturimthi" (on the fly) dhe shpeshherë njihet me emrin "thunk".

    Por fillimisht po përshkruaj 3 teknikat "standarte" për të shoqëruar objektet me dritaret, pasi në këtë mënyrë ka për të dalë më mirë në pah se pse ATL-ja nuk ka "preferuar" asnjë prej tyre, por ka zgjedhur këtë mënyrë "të pazakontë" të të bërit të gjërave.

    Mënyra I ka të bëjë me rezervinin e hapësirës së nevojshme në WNDCLASS : : cbWndExtra, dhe përdorimin e kësaj hapësire për të ruajtur pointer-in në objektin që duam të shoqërojmë me një dritare të caktuar. Gjithësesi, nëse dritaren nuk e kemi krijuar ne, nuk kemi asnjë kontroll mbi cbWndExtra, prandaj dhe kjo mënyrë ka përdorim të kufizuar - e rendita thjeshtë për të thënë se ekziston edhe kjo mënyrë.

    Mënyra II

    Kodi:
    //    Shoqërojmë objektin me dritaren:
     
    //    hwnd është Handle i një dritare
    //    pObj është një pointer në objektin që duam ta shoqërojmë me këtë dritare
    SetWindowLongPtr (
                        hwnd,
                        GWLP_USERDATA,    // indeksi
                        (LONG_PTR)pObj
                        );
    
    
    //    Në implementimin e WindowProc ne mund ta "marrim" pointerin në objektin tonë në këtë mënyrë:
    
     
    
    LRESULT   CALLBACK  WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        //    CObjekt përfaqëson klasën e këtij objekti:
        CObjekt *pObj = (CObjekt*)GetWindowLongPtr (hwnd, GWLP_USERDATA);
        if (pObj)
        {
            //    përdorim objektin:
            pObj->Funksion();
            pObj->Data = ...
        };
    };
    Problemi kryesor me këtë teknikë ka të bëjë me faktin se ç'do dritare ka vetëm një "slot" (hapësirë) GWLP_USERDATA, dhe ndërsa ky në vetvete mund të mos përbëjë një problem, problemi lind kur dritares tonë mund t'i bëjë "subclass" dikush tjetër i cili gjithashtu vendos të përdorë GWLP_USERDATA duke mbishkruar pointerin tonë, ose anasjelltas, kur ne i bëjmë "subclass" dritares së dikujt tjetër duke mbishkruar informacionin që programi tjetër ka shoqëruar me këtë dritare (duke përdorur GWLP_USERDATA).



    Mënyra III

    Për shkak të problematikës që ekziston në mënyrën II, Windows-i vë në dispozicion një mekanizëm tjetër: "Window Properties"


    Kodi:
    //    Shoqërojmë objektin me dritaren:
    SetProp (
            hwnd,
            "Pointeri_Im",    // emri i properti-së
            (HANDLE)pObj
            );
     
    
    //    Në implementimin e WindowProc ne mund ta "marrim" pointerin në objektin tonë në këtë mënyrë:
    
    LRESULT   CALLBACK  WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        //    CObjekt përfaqëson klasën e këtij objekti:
        CObjekt *pObj = (CObjekt*)GetProp (hwnd, "Pointeri_Im");
        if (pObj)
        {
            //    përdorim objektin:
            pObj->Funksion();
            pObj->Data = ...
        };
    };

    Kjo mënyrë na lejon të shoqërojmë "informacion" me një dritare pa pasur frikë nga ndonjë "konflikt" i mundshëm (për aq kohë sa emri që zgjedhim si property (psh. "Pointeri_Im") nuk koincidon të përdoret edhe nga dikush tjetër).


    Sidoqoftë edhe kjo mënyrë me sa duket e ka një problem: "nuk është aq e shpejtë sa do t'ja donte zemra ATL-së".








    Mënyra IV (the ATL way)

    Të gjitha dritaret e një klase (WNDCLASS(EX)) kanë të përbashkët një funksion të vetëm për procesimin e mesazheve (WndProc). Ndonëse implementimin e këtij funksioni e ofrojmë ne, ky funksion ka për t'u thirrur për të procesuar mesazhet e të gjitha dritareve që kemi krijuar, për pasojë nga brendësia e tij ne s'mund ta dimë nëse filan mesazhi i takon filan dritare (që në programin tonë përfaqësohet nga një objekt i caktuar C++).

    Në implementimin e ATL-së të WindowProc më bëri përshtypje type-cast i çuditshëm:

    LRESULT CALLBACK WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
    // CObjekt përfaqëson klasën e këtij objekti:
    CObjekt *pObj = (CObjekt*)hwnd;


    Me një fjalë ATL-ja "s'e vriste fare mendjen" as për GetWindowLongPtr(hwnd, ...) dhe as për GetProp(hwnd, ...) por thjeshtë i bënte një type-cast hwnd-së në (CObjekt*).

    Të gjitha interpretimet hipotetike që munda t'i bëj këtij instruksioni rezultonin absurde, mbasi si mundet vallë një "handle in a window" (objekt që krijohet dhe menaxhohet nga sistemi i operimit) të mund të trajtohet (përmes type-casting) si një pointer në një objekt që e kam krijuar unë (adresën e të cilit OS-ja s'ka nga ta dijë)?

    Pasi u enda për njëfarë kohe mes konstrukeve të ATL-së shpjegimi u bë i qartë:
    ATL-ja krijon "flutirimthi" një funksion unik për ç'do dritare, dhe ky funksion bën vetëm 2 gjëra: mbishkruan argumentin hwnd me pObj dhe thërret funksionin origjinal WindowProc. Kështuqë në brendësi të WindowProc hwnd përfaqëson realisht pObj ndaj dhe konstrukti i mësipërm është krejt i rregullt.

    Kodi:
    /////////////////////////////////////////////////////////////////////////////
    // Thunks for __stdcall member functions
    #if defined(_M_IX86)
    #pragma pack(push,1)
    struct _stdcallthunk
    {
        DWORD m_mov; // mov dword ptr [esp+0x4], pThis (esp+0x4 is hWnd)
        DWORD m_this; //
        BYTE m_jmp; // jmp WndProc
        DWORD m_relproc; // relative jmp
        void Init(DWORD_PTR proc, void* pThis)
        {
            m_mov = 0x042444C7; //C7 44 24 0C
            m_this = PtrToUlong(pThis);
            m_jmp = 0xe9;
            m_relproc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(_stdcallthunk)));
            // write block from data cache and
            // flush from instruction cache
            FlushInstructionCache(GetCurrentProcess(), this, sizeof(_stdcallthunk));
        }
        //some thunks will dynamically allocate the memory for the code
        void* GetCodeAddress()
        {
            return this;
        }
    };
    #pragma pack(pop)
    #elif defined (_M_ALPHA)
    // For ALPHA we will stick the this pointer into a0, which is where
    // the HWND is. However, we don't actually need the HWND so this is OK.
    Fragmenti mësipër është kopjuar direkt nga implementimi i ATL-së. Siç vihet re ekzistojnë implementime për të gjitha familjet e proçesorëve (unë kam kopjuar vetëm implementimin për x86 (#if defined (_M_IX86)) si dhe fillimin e implementimit për alpha (#elif definde (_M_ALPHA))).

    Normalisht ne i përdorim strukturat për të rezervuar një "bllok memorje" për disa variabla që kanë lidhje me njëri-tjetrin, pra për të ruajtur "data" në këtë memorje.

    ATL-ja nga ana tjetër, me anë të strukturës _stdcallthunk rezervon memorje për të ruajtur një "funksion".

    Konkrethisht 4 "fushat" (variablat) e vetëm të kësaj strukture përfaqësojnë (dhe kanë për t'u inicjuar me) numra që më pas kanë për t'ju dhënë procesorit t'i ekzekutojë (duke i konsideruar si instruksione makine)

    8 byte-t e parë të kësaj strukture (2 DWORD-et: m_mov, m_this) përfaqësojnë instruksionin mov (në këtë rast instruksioni mov është 8 byte, por ka raste kur ky instruksion është vetëm 1 byte) i cili mbishkruan memorjen në [esp + 0x4] (në [esp] gjendet adresa e instruksionit që e thirri këtë funksion, ndërsa në [esp + 4] ndodhet argumenti i parë, që i bie të jetë hwnd) me vlerën e pointerit në objekt (vlerë që është ruajtur në fushën m_this).

    Mbas instruksionit të parë (8 byte-t e mov) vjen instruksioni i dytë 5 byte-sh jmp (1 byte për m_jmp + 4 byte për m_relproc). Ky instruksion bën një jump (goto) m_relproc byte nga pozicioni aktual, "kapërcim" që ka për ta "degdisur" procesorin ekzaktësisht në fillim të funksionit WindowProc().

    Meqënëse alternativa e ATL-së angazhon ekzekutimin e vetëm 2 instruksioneve ekstra (mov + jmp) kjo është në absolut mënyra më e shpejtë mes mënyrave të paraqitura mësipër, të cilat angazhonin thirrjen e një funksioni (GetWindowLongPtr(), GetProp()) që në ç'do rast përfshin ekzekutimin e shumë më tepër instruksioneve.

    Vlerat e fushave të kësaj strukture (që në fakt përfaqëson code) iniciohen në funksionin Init () i cili merr 2 argumenta: adresën e WindowProc origjinal, si dhe pointerin në objekt. Mbasi inicion strukturën (gjeneron code) ky funksion thërret FlushInstrucionCache() për t'i bërë update cache të procesorit.

  2. #2
    Programues Softueresh Maska e edspace
    Anëtarësuar
    04-04-2002
    Vendndodhja
    Filadelfia, SHBA
    Postime
    2,573

    Post

    Faleminderit për këto shpjegime që janë po aq të bukura sa dhe të vështira. Kjo është bukuria e C/C++ që mungon në Java dhe gjuhët e tjera të nivelit më të lartë.

    Thjesht dua të di nëse kam arritur t'i kuptoj siç duhet sqarimet më lart. Me sa kuptova, kodi i _stdcallthunk injektohet në kodin e funksionit WindowsProc si dy instruksione të vetme për çdo arkitekturë. Ky kod mbishkruan dritaren me objektin dhe pastaj fshin (flush) vetveten nga cache i instruksioneve, duke lejuar të vazhdojë ekzekutimi i WindowsProc me CObjekt *pObj = (CObjekt*)hwnd;.

    Pra, në kapice (stack) kemi diçka të tillë:

    Kodi:
    0 WindowProc (
    1    HWND hwnd, 
    2    UINT uMsg, 
    3   WPARAM wParam, 
    4    LPARAM lParam)
    5   CObjekt *pObj = (CObjekt*)hwnd;
    por ATL e kthen në:

    Kodi:
    0:esp WindowProc (
    1  HWND hwnd, 
    2  UINT uMsg, 
    3  WPARAM wParam, 
    4  LPARAM lParam)
    5  mov [esp+1] pThis => mov 1 pThis
    6  jmp esp - (4 + 2) => jmp 0 - 6 => jmp -6
    7   CObjekt *pObj = (CObjekt*)hwnd;
    dhe pastaj bën flush 4 2 që heq instruksionet e injektuara:

    Kodi:
    0:esp WindowProc (
    1  pthis, 
    2  UINT uMsg, 
    3  WPARAM wParam, 
    4  LPARAM lParam)
    -------------------------
    -------------------------
    5   CObjekt *pObj = (CObjekt*)hwnd;

    I vetmi disavantazh që shoh me këtë mënyrë është se programuesi duhet të shkruajë kod në asembli për çdo arkitekturë, por ia vlen të shkruhet ky kod se vetëm njëherë do shkruhet.
    Edi

  3. #3
    mos e luaj; I DEBUAR! Maska e qoska
    Anëtarësuar
    17-05-2004
    Vendndodhja
    tirane
    Postime
    837
    FlushInstrucionCache() (per ta shtuar si informacion) nuk eshte fort performante ne arkitekturat e sotme.

    Por me te vertete qe ATL ka disa optimizime te zgjuara pasi ne ate fushe perdoret.

    Gjithsesi me korigjo nese ekam kuptuar gabim!
    Ato kodet asm eliminojne problemin e perdorimit te "FarPointer" per te perfituar performance dhe ndonje problem sigurie me perdorimin e tyre!?

  4. #4
    i/e regjistruar
    Anëtarësuar
    12-06-2006
    Vendndodhja
    Redmond, WA, USA
    Postime
    181
    Po ta bëja unë Code review metodën IV ajo kurr nuk futej në ATL.
    Së pari, metoda _stdcallthunk:: Init nuk validiton parametrat.
    Së dyti, si do ta bëjsh debug këtë pjesë të programit kur edhe debuger përdorë të njejtin FlushInstructionCache për të ndëhyrë me edit&compile gjatë egzkutimit step-by-step.

    Metoda të tilla janë mirë vetëm për tu mësuar dhe më vjen mirë që Neritani e solli në forum, por jo edhe të praktikohen veçanërisht në prodhime komerciale.

    Fatkeqësisht prodhimet e MS janë përplot me metoda të tilla, për të cilat MS sot paguan miliona dollarë për ti pëmirësuar këto "përmirësime performancash".
    Shikoni këtë libër "Writing Secure Code" të cilin Bill Gates e ka bërë të detyrueshëm për të gjithë ne që punojmë në Microsoft:
    http://www.microsoft.com/mspress/books/5957.aspx

    //Agroni

  5. #5
    i/e regjistruar
    Anëtarësuar
    16-04-2004
    Postime
    684
    Une ATL e perdori shume rralle, dhe ate ne rastet kur e krijoj ndonje komponente ne server zakonisht pa kurfar "interface" vizual, i cili pastaj automatizohet nga klienti. Besoj se kjo edhe eshte detyra primare e ATL??

  6. #6
    i/e regjistruar
    Anëtarësuar
    17-11-2006
    Postime
    81
    Ka disa komente interesante ne mesazhet e meparshem, te cileve kam deshire tu pergjigjem

    Se pari nuk besoj se ka ndonje disavantazh kjo menyre e te gjeneruarit kod krahasuar me menyren tradicionale (source code + kompilator = kod makine), ne raport me portabilitetin. Bie fjala, kur une dua te krijoj nje file, une perdor funksionin CreateFile (te windows-it) dhe jo versionet portable te C/C++. Kjo sepse ky funksion, ndersa nuk eshte portabel ne platforma te tjera, ne Windows ofron gjithe sherbimet qe implementon windows-i (psh. mund ta shoqeroj file-n me nje Security Descriptor). Ne fund te fundit, te pakten per mua, nese ve ne kandar nga njera ane portabilitetin, dhe nga ana tjeter nje oferte me te pasur ne sherbime per perdoruesit, une anoj nga kjo e dyta.

    Se dyti, ne rastin konkret, as nuk na duhet te shkruajme instruksione makine, pasi ky implementim ofrohet i gatshem (ne fakt autoret e ATL-se e kane marre "mundimin" te ofrojne implementimin per c'do platforme, por ndersa Microsoft-i ka treg te "larmishem", ne te tjeret s'e kemi kete fat/problem).

    Nuk ka asnje problem performance me FlushInstructionCache(), per me teper ky funksion thirret vetem kur krijohet nje dritare e re (apo kur i behet subclass nje ekzistuese), proces ky qe "konsumon" shume me teper "procesor cycles" se sa FlushInstructionCache.

    Gjithesesi, te gjitha platformat (MFC, Delphi, C++ Builder, Visual Basic, .NET, etj) shoqerojne dritaret (nje primitiv e sistemit te operimit) me objkete (keto platforma jane esencialisht OOP Object Orientet Programing), dhe implementimi i ATL-se eshte ne absolut me efikasi (ndoshta edhe platformat e tjera bejne te njenjten gje - ATL-ja vjen si source-code, e per pasoje munda ta shikoja).
    Kur ju krijoni programe duke perdorur platformat e mesiperme, edhe pse ju mund te shkruani vetem “portable code”, keto platforma kane per te “injektuar” ne programin tuaj (te kompiluar) “non portable code” si ky i mesipermi.

    Ekzistojne disa versione si per instruksionin mov ashtu edhe per instruksionin jmp. ATL-ja ka perdorur versionet me te shkurtra dhe qe ekzekutohen me shpejte (i gjithe "thunk-u" eshte vetem 13 Byte).

    Nuk ka ndonje problem FAR pointer ketu, pasi te gjithe pointerat jane te tille (FAR). FAR dhe NEAR pointers ekzistonin ne DOS apo windows 3.x (ne sistemet 16 bit)

    Nuk ka nevoje qe metoda Init() te validoje parametrat, pasi ky funksion thirret vetem nga nje funksion tjeter i ATL-se, i shkruar nga te njejtet autore. Pra nuk ka pse funksioni Init() te "mos i besoje" kodit qe e thirri.
    Gjithashtu funksioni FlushInstructionCache() nuk perben asnje problem per debugerin. Natyrisht, ndersa ben debug nuk e ke fatin te shohesh "source code", pasi ai nuk ekziston (fragmenti eshte shkruar ne instruksione makine), por nese nuk e ke te veshtire t’i besh debug nje programi per te cilin nuk disponon source code, nuk e ke te veshtire t’i besh debug as kodit te gjeneruar ne kete menyre (_stdcallthunk).
    Gjithesesi fragmente te tilla jane shume te shkurtra, perdoren vetem ne raste speciale, dhe ne raport me kontekstin jane te justifikuar.

    Pergjithesisht termi “thunk” lidhet me fragmente te tilla te shkurtra instruksione makine, qe “impostojne” (pretendojne sikur jane) nje funksion i caktuar, modifikojne stack-un, dhe me pas transferojne kontrollin tek funksioni origjinal.

    Versionet Windows 9x perdornin te tilla teknika kur funksionet ne librarite 32 bit duhej te therrisnin funksionet ne librarite 16 bit (ka te ngjare qe edhe Windows Vista te perdore te tilla teknika per te implementuar komunikimin midis moduleve 64 bit dhe atyre 32 bit).

    Edhe implementimet e COM apo NET perdorin teknika te tilla, dhe meqenese pjesa derrmuese e programeve te sotem perdorin (ne nje mase apo nje tjeter) keto teknologji, i bie qe pjesa derrmuese e programeve ekzekutojne kod te krijuar ne kete menyre, edhe pse autoret e ketyre programeve mund te jene “natyre” “puritane” ne raport me portabilitetin..

    Natyrisht kur "viganeve" u shkrepet ti bejne marketing nje platforme (sic eshte NET-i psh.) apo nje metodologjie (rregullat per "safe code"), efekti mund te jete i tille qe shume njerez mund t'i "perceptojne" ato si bibel apo kuran.

    Megjithate, midis idhtareve "die hard" te asemblerit qe evokojne shkrimin e gjithe programit ne asm nga njera ane, dhe marketingut per NET-in (qe e quan gjithcka "unmanaged" - si te thuash "jo normale") duke synuar qe c'do program te "futet" brenda nje .dll-je nga ana tjeter, besoj se midis ketyre dy “filozofive ekstremiste” ka boll hapesire ne mes per t'u ndier I LIRE dhe KRIJUES (ne fund te fundit programimi eshte krijimtari para se gjithash).

    Tani dua te shtjelloj me mire kodin dhe interpretimin e edspace.

    ATL-ja nuk shoqeron asnje pointer (apo c'faredolloj informacioni) me dritaren. Ne fakt ATL-ja krijon nje funksion unik per c'do dritare (funksion qe perfaqesohet nga struktura _stdcallthunk) dhe i ben subclass dritares duke zevendesuar WindowProc origjinal me adresen e kesaj strukture/funksioni. Keshtuqe windows-i therret kete funksion (_stdcallthunk) per te procesuar te gjitha mesazhet e nje dritare te caktuar.
    Pra windows-i vendos ne stack vetem 4 argumenta HWND hwnd, UINT uMsg, WPARAM wParam dhe LPARAM lParam, dhe transferon fokusin e procesorit ne fillim te struktures _stdcallthunk.
    Instruksioni i pare (mov) i kesaj strukture mbishkruan argumentin hwnd ne stack me DWORD-in e dyte te kesaj strukture (m_this) qe u inicjua ne Init().
    Me pas instruksioni jmp e transferon procesorin ne fillim te WindowProc origjinal, ku tashme argumenti hwnd ka vleren e pointerit ne objekt, keshtuqe type-casti eshte krejt i rregullt.
    Nese do perpiqeshim ta kthenim kodin e makines ne C, do kishim dicka te tille:

    hwnd = m_this;
    goto WindowProc;

    Vetem se m_this nuk eshte nje variabel por vlere numerike, pra me sakte kodi do ishte:

    hwnd = 0x005234ed; // vlere e trilluar (funksioni Init() e "krijon" kete konstante nga argumenti pThis i tij)
    goto WindowProc;

    Pra per ta thene ndryshe, kodi _stdcallthunk nuk injektohet ne WindowProc, ai perfaqeson nje funksion me vete (me sakte prologun e nje funksioni) i cili perdoret per t'i bere subclass dritares duke zevendesuar funksionin origjinal WindowProc (te cilin me pas funksioni “_stdcallthunk” e therret pasi ka mbishkruar hwnd ne stack).

    Eshte e rendesishme te theksohet se vendosjen e argumentave ne stack e ben kodi qe therret nje funksion (ne kete rast Windows-i). Pra ne momentin kur therritet _stdcallthunk ne stack jane hwnd, uMsg, wParam, lParam ndersa ne momentin kur thirret WindowProc ne stack kemi pObj, uMsg, wParam, lParam - ne te dy momentet ne stack jane vetem 4 argumenta.

    m_relproc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(_stdcallthunk)));

    variabli m_relproc iniciohet me diferencen midis adreses se WindowProc origjinal dhe adreses se byte-it te pare mbas _stdcallthunk (ky byte ndodhet ne adresen = adres e _stdcallthunk + sizeof (_stdcallthunk)).

    (INT_PTR)this e kthen (trajton) adresen e _stdcallthunk ne nje numer (pra arithmetika e metejshme nuk eshte arithmetike pointerash por numrash).
    (INT_PTR)this + sizeof(_stdcallthunk) do te thote “adresa e _stdcallthunk + sizeof(_stdcallthunk) = adresa e byte-it te pare mbas fundit te _stdcallthunk”
    Me gjuhen e shifrave, nese 0x00100000 do ishte adresa e _stdcallthunk, (INT_PTR)this + sizeof(_stdcallthunk) do ishte 0x00100000 + 13 = 0x0010000d.

    Se fundmi diferenca midis (INT_PTR)proc dhe adreses se fundit te _stdcallthunk, eshte ne fakt offseti nga fundi i _stdcallthunk per tek WindowProc origjinal.
    Serisht me gjuhen e shifrave, nese adresa e WindowProc origjinal do ishte 0x00100100, atehere m_relproc do ishte 0x00100100 – 0x0010000d = 0x000000f3 (ne sistemin decimal 243)
    Tani per c’do dritare te krijuar ekziston nje _stdcallthunk me vete (ne nje adrese te caktuar te memorjes), por qe te gjitha dritaret kane nje WindowProc te perbashket. Pra per te gjitha dritaret 0x00100100 (ne shembullin tim) ka per te qene i njejte, ndersa 0x0010000d do jete i ndryshem per _stdcallthunk te ndryshem, keshtuqe edhe offset-i (243-shi) ka per te qene i ndryshem.

    Meqenese instruksioni jmp i perdorur ketu ben nje jump x-byte relativ nga pozicioni aktual (procesori konsideron si pozicion aktual adresen e instruksionit pasardhes – aktualisht eshte duke u ekzekutuar instruksioni jmp, dhe instruksioni pasardhes duhej te ndodhej ne byte-n e pare mbas _stdcallthunk), ky ofset na con pikerisht ne fillim te WindowProc origjinal. Pra me gjuhen e shifrave instruksion “aktual” konsiderohet adresa 0x0010000d, dhe offseti prej 243 byte-sh na con ne adresen 0x00100100 ku ndodhet WindowProc origjinal.

    Regjistri esp nuk ka ndonje rol ne arithmetiken e mesiperme, dhe as ne instruksionin jmp.

    Gjithashtu duhet sqaruar edhe kronologjia e gjerave.
    Arithmetika e mesiperme ndodh vetem 1 here gjate funksionit Init() i cili therritet gjate krijimit apo sbuclass-it te nje dritare.
    Ndersa _stdcallthunk ekzekutohet sa here therritet WindowProc (pra per c'do mesazh).
    Per sa eshte Windows-i ne dijeni, adresa e _stdcallthunk eshte adresa e WindowProc.

    esp ((Extended) Stack Pointer) mban adresen e vleres se fundit qe eshte futur ne stack. Ne rastin e funksioneve __stdcall argumentat futen ne stack nga e djathta ne ne majte.
    Stack-u s'eshte vecse nje bllok memorje, dhe esp shenon diku ne te.
    Futja e nje argumenti ne stack ndodh ne kete menyre:

    DWORD *pEsp;

    *--pEsp = lParam; // argumenti me i djathte futet i pari ne stack
    *--pEsp = wParam;
    *--pEsp = uMsg;
    *--pEsp = hwnd;
    *--pEsp = instruksioni_pasardhes;
    goto WindowProc;
    instruksioni_pasardhes: // label-i ne C perfaqeson adresen e instruksionit qe e pason ate, pra adresen e instruksionit qe vjen mbas instruksionit goto

    Mesiper kam paraqitur ne gjuhen C ate c'ka ndodh ne asembler kur ne shkruajme dicka te tille:

    WindowProc (hwnd, uMsg, wParam, lParam); // therrasim funksionin WindowProc

    Pra esp-ja eshte pak a shume si nje pointer DWORD.
    Pointeri fillimisht zvogelohet (me 4 byte) dhe me pas ne kete adrese “futet” (shkruhet) argumenti i fundit (lParam).
    Pointeri zvogelohet serisht (pra shenon 4 byte me perpara) dhe ne kete adrese vendoset argumenti i parafundit (wParam), e keshtu me rradhe.

    Ne fund, pointer-i zvogelohet serisht (4 byte), dhe ne kete adrese vendoset adresa e instruksionit qe pason instruksionin goto (thirrjen e funksionit). Ne kete menyre, nga brendesia e funksionit qe u therrit (ne kete rast WindowProc) ne mund te kthehemi serisht (me ane te instruksionit return) tek instruksioni qe pason instruksionin qe na therriti (qe une e kam etiketuar me label-in instruksioni_pasardhes)

    Pra permbajtja e stack-ut ne momentin e therritjes se nje funksioni (ne kete rast ekzekutimi i goto-se), e shprehur me ane te pointerit pEsp do ishte:
    *pEsp == *(pEsp + 0) == adresa e instruksionit qe pason instruksionin goto, pra adresa ku do kthehet procesori mbasi te ekzekutoje funksionin (goto WindowProc)
    *(pEsp + 1) == hwnd;
    *(pEsp + 2) == uMsg;
    *(pEsp + 3) == wParam;
    *(pEsp + 4) == lParam;

    Shprehja *(pEsp + n) ne C, do perkthehej ne [esp + 4 * n] ne asembler (procesori nuk ofron “arithmetiken e pointerave” sic ben C-ja, pra meqenese pointeri eshte DWORD (4 byte), duhet ta zhumezojme n-ne me 4).

    Keshtuqe ne brendesi te funksionit te therritur (psh. WindowProc) [esp + 0] perfaqeson adresen e instruksionit ku do na ktheje ekzekutimi i instruksionit return (apo arritja ne fundit te funksionit), [esp + 4] (ne C do ishte *(pEsp + 1)) perfaqeson argumentin e pare (hwnd).

    Funksioni FlushInstructionCache() nuk ka ndonje rol esencial ne kete konstrukt, dhe mund te mos perdorej fare. Por meqenese kur procesori ekzekuton instruksionet, ai i ben nje kopje ketyre instruksioneve ne memorjen e tij te brendshme (cache) ne menyre qe kur t'i bjere rasti per t'i ekzekutuar serisht, te mos "lodhet" se lexuari nga memorja, por t'i lexoje nga cache i brendshem qe eshte shume me i shpejte.
    Keshtuqe ekziston rreziku qe ndersa ne iniciojme strukturen _stdcallthunk, procesori te mos i "shohe" keto ndryshime, por te perdore nje version te kesaj strukture qe ne e kemi inicjuar me pare (gjate krijimit te nje dritare tjeter). Pra FlushInstructionCache s'ben gje tjeter po i thote procesorit "shiko se versioni qe ke ne cache mund te mos jete koherent, prandaj rifreskoje (sinkronizoje) cache-n me versionin ne memorje".
    Mund te duket sikur FlushInstructionCache() “saboton” performancen e procesorit (qe konsiston ne berjen “cache” te instruksioneve), por ne kete rast procesori “e ka tepruar” me performancen e tij.

    Ndoshta nuk arrita ta shpjegoj sic do doja, por gjithesesi eshte nje perpjekje.

  7. #7
    Programues Softueresh Maska e edspace
    Anëtarësuar
    04-04-2002
    Vendndodhja
    Filadelfia, SHBA
    Postime
    2,573
    Faleminderit për sqarimet Neritan. Duke kërkuar në google për FlushInstructionCache gjeta dhe një faqe tjetër që trajton pikërisht të njëjtin kod.

    Gjithashtu gjeta faqen e Mikrosoft-it të titulluar How to Modify Executable Code in memory. Aty shpjegohet se arsyeja e përdorimit të FlushInstructionCache është e nevojshme jo vetëm për rastin që ke cekur ti, por edhe për rastin kur procesori ka populluar cache me instruksione nga RAM para se t'u ketë ardhur radha për ekzekutim (prefetching). Për shembull, nëse procesori po ekzekuton rreshtin e parë të një funksioni, mund të ketë gati në cache edhe 10 rreshtat e tjerë. Kjo bëhet për të shfrytëzuar procesorin në maksimum duke patur gjithnjë instruksione për të ekzekutuar, jo të ekzekutojë një instruksion dhe të presë sa instruksioni tjetër të tërhiqet nga RAM.

    Konkretisht, nëse nuk thirret FlushInstructionCache, kodi i _stdcallthunk mund të mos ekzekutohet fare nëse procesori ka populluar cache nga RAM para se RAM-i të jetë azhurnuar me instruksionet mov dhe jmp.


    Citim Postuar më parë nga Microsoft
    The reason for calling FlushInstructionCache() is to make sure that your changes are executed. As processors get faster, the instruction caches on the chips get larger. This allows more out of order prefetching to be done. If you modify your code, but do not call FlushInstructionCache(), the previous instructions may already be in the cache and your changes will not be executed.
    Edi

  8. #8
    mos e luaj; I DEBUAR! Maska e qoska
    Anëtarësuar
    17-05-2004
    Vendndodhja
    tirane
    Postime
    837
    Neritan sqarim jashtezakonisht i mire dhe pergezime nga ana ime pasi jane te pakte ata qe arrijne te interpretojne ne nje nivel kaq te ulet kodin!

    FarPointer qe kam permendur aty eshte e vertete qe ka qene me i perdorshem ne arkitektura 16bit por jo se ka humbur konceptin ne 32bit. Kjo vjen pasi windows(tm) perdor PASCAL conventions ne stack.
    Konteksti qe une kam perdorur ate term ka te beje vetem me stackun ne kete rast pasi nese do te duhej ta benim nepermjet "high level" code kete gje do te nenkuptonte nje stack switch+clone dhe modifikim qe normalisht do te conte ne nje page te ri ne virtual memory(ne shumicen e rasteve) qe do te perbente nje exeption ...... e me radhe. E pranoj qe mund te mos e shpjegoj shume mire ate qe kam shprehur me siper si term por kur mesohesh me disa term ndonjehere i perdor pa u menduar sidomos ne rastin kur te tjere do ta lexojne .

    FlushInstructionCache() konsiderohet si joperformant ne arkitekturat e sotme pasi rrit shume "instruction cycle" duke patur parasysh dhe L2 cache ne arktitekturat e sotme.
    Pra, situata do te ishte e tille ti therret kete funksion dhe procesori boshatis L1 dhe L2 cache(apo duhet te themi instruksioni), nga kjo procesori shkon ne "stall" dhe merret vetem me ripopullimin e cache per nje kohe te caktuar qe mund te jete mjaft e konsiderueshme sikur gjate ketij procesi do te kishim dhe update ne memorie fizike(jo RAM) sepse do te kishim dhe invalidim te te dhenave ne RAM.

    Per siguri ka pak per te folur ketu pasi kemi nje ekspozim te stackut direkte ne program qe mund te shfrytezohet(supozim) pasi varet nga cfare kontrolli i behet variablave nga funksionet paraardhese qe mund te mos perfshijne validim te madhesise se stackut .

    Edhe njehere komplimente per njohurite.

    Sa per shtese mbi implementimin e "trampolinave" dmth kalimin nga 32bit ne 64bit ose menyra te tjera qe ofron procesori ka dy variante ose "emulim" ne software i sistemit ose krijimi i trampolinave ne procesor ne adresa te caktuara(statike) qe per 64bit kane pak veshtiresi pasi duhet implementuar nje minikernel. Kete te fundit e kam nga eksperienca e diskutimit te implementimit te dickaje te tille ne nje sistem open-source i cili do te perdore disa funksione BIOS-i qe per me teper kerkon kalim nga 64bit(protected mode) ne 16bit(real-mode).
    Ndryshuar për herë të fundit nga qoska : 21-04-2007 më 07:11

  9. #9
    i/e regjistruar
    Anëtarësuar
    17-11-2006
    Postime
    81
    Interesant artikulli qe kishe gjetur edspace (WNDPROC Thunks). Do doja ta kisha gjetur perpara se te me duhej te eksploroja atlwin.h.

    Tani kam edhe une nje pyetje. Per here te pare m'u desh te fusja edhe figura ne postimet e mia. Tani, une u detyrova t'i ngarkoja figurat ne sitin tim, dhe me pas te specifikoja url-ne e tyre ne mesazh. Pyetja eshte, a ka ndonje menyre me te thjeshte?

  10. #10
    i/e regjistruar
    Anëtarësuar
    12-06-2006
    Vendndodhja
    Redmond, WA, USA
    Postime
    181
    Për të postuar një porosi kliko në "Mënyra e avancuar"
    Emri:  pi1.jpg

Shikime: 394

Madhësia:  4.8 KB

    Pastaj kliko në shenjën e bashkangjitjes.
    Emri:  p2.jpg

Shikime: 387

Madhësia:  10.0 KB

Tema të Ngjashme

  1. Një shtet paralel që po kërcënon shoqërinë shqiptare
    Nga DYDRINAS në forumin Problematika shqiptare
    Përgjigje: 11
    Postimi i Fundit: 18-01-2010, 17:22
  2. Dy mënyra për ta njohur Krijuesin
    Nga abdurrahman_tir në forumin Komuniteti musliman
    Përgjigje: 0
    Postimi i Fundit: 27-01-2009, 09:31
  3. Dy mënyra për ta njohur Krijuesin
    Nga llokumi në forumin Komuniteti musliman
    Përgjigje: 0
    Postimi i Fundit: 25-03-2006, 18:49
  4. "Nano Ik", protesta sërrisht në Bruksel
    Nga Asteroid në forumin Tema e shtypit të ditës
    Përgjigje: 500
    Postimi i Fundit: 02-05-2004, 12:07

Regullat e Postimit

  • Ju nuk mund të hapni tema të reja.
  • Ju nuk mund të postoni në tema.
  • Ju nuk mund të bashkëngjitni skedarë.
  • Ju nuk mund të ndryshoni postimet tuaja.
  •