[C++, WinApi] Konwencje

0

Mam sobie 2 pliki dll wykorzystywane w programie.
W pierwszym jest exportowana funkcja ustawMenu(DWORD addr);
Drugi dll importuje ta funkcje i wywoluje ja w taki sposob:

UINT CALLBACK menuProc(UINT status)
{
    if(status == 0)
        return 1;

    return 0;
}

// wywolanie
ustawMenu((DWORD)menuProc);

W pierwszym dll funkcja ustawMenu wyglada tak:

// plik .h
extern "C" __declspec(dllexport) void ustawMenu(DWORD addr);
// plik .cpp
typedef UINT (*fmenuProc)(UINT);
fmenuProc menuProc;

void ustawMenu(DWORD addr)
{
    menuProc = (fmenuProc)addr;
    menuProc(1);  // <- Blad.
}

Blad jest taki iz wyrzuca komunikat Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call....
Blad oznacza nic innego jak roznice w konwencji funkcji.
Problem w tym, ze po dosc dlugiej dlubaninie w kodzie, po googlach i msdn nie mam pojecia jak rozwiazac ten problem.

Dziekuje z gory za łaskawe oświecenie. Ja mam dosc juz kombinacji na dzis :)

0

A gdzie się podziało

CALLBACK

w typedef UINT (*fmenuProc)(UINT);

?
0

A wlasnie :)

Nie ma, bo nawet jesli jest, to nadal jest dokladnie ten sam blad.

0
Malcolm napisał(a)

A wlasnie :)

Nie ma, bo nawet jesli jest, to nadal jest dokladnie ten sam blad.

No ale co to za argument? Ten

CALLBACK

w końcu coś oznacza, nie? Tu masz fragment wyrwany z windef.h:

#ifdef _MAC
#define CALLBACK    PASCAL
...
...
#elif (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED)
#define CALLBACK    __stdcall
...
...
#else
#define CALLBACK
...
...

no, czyli jednak coś oznacza ten

CALLBACK

. Definiując wskaźnik na funkcję bez jasnego określenia typu konwencji wywołania kompilator przyjmie domyślny typ - na ogół __cdecl

 <-- tu wywołujący sciąga ze stosu parametry (w przeciwieństwie do <code class="cpp">__stdcall

).

0

Wiem co CALLBACK oznacza i jakie konwencje kompilator przyjmuje jednak nadal to nie zmienia tego, ze nadal mam ten sam blad w lini, ktora w pierwszym poscie zaznaczylem.

Kompilator u mnie przyjmuje __cdecl, ustawione mam CALLBACK przy tej funkcji i mimo to sie sypie, a w sumie nie powinno.

No chyba, ze jest opcja, ze to w tej lini jest cos nie tak, choc mam wrazenie, ze dobrze robie.

menuProc = (fmenuProc)addr;
0
Malcolm napisał(a)

No chyba, ze jest opcja, ze to w tej lini jest cos nie tak, choc mam wrazenie, ze dobrze robie.

menuProc = (fmenuProc)addr;

No tu jest akurat dobrze. Dalej obstaje przy tym, że wskaźnik powinien być zdefiniowany tak:

typedef UINT CALLBACK (*fmenuProc)(UINT);

A najprościej, to użyć debuggera i zobaczyć, która funkcja ściąga ze stosu parametry. ;)

0

Hm raczej tak powinno byc

typedef UINT (CALLBACK *fmenuProc)(UINT);

Inaczej to errorami sypie.

A z debuggerem to wlasnie kombinuje caly czas. Moze wkoncu ta bzdure rozgryze.

0
Malcolm napisał(a)

Hm raczej tak powinno byc

typedef UINT (CALLBACK *fmenuProc)(UINT);

Inaczej to errorami sypie.

U mnie obie formy przechodzą (BCB).

A z debuggerem to wlasnie kombinuje caly czas. Moze wkoncu ta bzdure rozgryze.

No przecież to proste i od razu widać co jest nie tak (mowa o sposobie wywowływania funkcji).

0

U mnie pod VC 7.1 tylko jedna opcja dziala.

0x666 napisał(a)

No przecież to proste i od razu widać co jest nie tak (mowa o sposobie wywowływania funkcji).

Gdyby uzyskanie odpowiedzi bylo takie proste bym na forum nie pytal :)

Hm moze MarcinEc moglby cos tutaj poradzic.
Ide o zaklad, ze gdzies strasznie glupi blad robie. Zreszta sam message bledu o tym swiadczy ;) a szukanie strasznie glupich bledow jest strasznie nie fajne :(

0

No dobra, a wchodzi w ogóle w

menuProc

? czy od razu wywala błąd? Spróbuj zamiast CALLBACK użyć __stdcall

. Dodaj, jeżeli nie ma <code class="cpp">#include <windows.h>

do projektów obu dll'ek.

0

Wchodzi do menuProc i wykonuje to co tam jest. Po zmianie na __stdcall w ogole nie wykonuje funkcji, juz wczesniej sprawdzalem. <windows.h> jest wszedzie dodane - nie tedy droga.

Jedna opcje odkrylem. Ogolnie mam wiecej funkcji z CALLBACK'iem (funkcje wykonuja proste rzeczy, wiekszosc to przyjmowanie i zwrot wartosci UINT). Dll'ki byly ladowane przez glowny proces wykorzystujacy funkcje LoadLibraryW, po zmianie na LoadLibraryA, jedna funkcja zaczela dzialac idealnie ale tylko po kompilacji release. 8-| Ta akurat przyjmuje wartosc char* a zwraca void
Przy debug nadal sie wszystkie sypia jak opisalem.

Skrocilem tez calosc do jednej funkcji ktora jest identyczna jak w pierwszym poscie i na niej kombinuje, jednak cokolwiek zmienie blad jest nadal ten sam.

Ja sie zastanawiam czy moze w tym miejscu cos nie namieszalem. Tak wyglada u mnie importowanie funkcji ustawMenu:

// .h
typedef void (WINAPI *fustawMenu)(DWORD addr);
extern fustawMenu ustawMenu;
// .cpp
#include "powyzszy.h"
fustawMenu ustawMenu;
HMODULE hMod = GetModuleHandle("pierwszy.dll");
ustawMenu = (fustawMenu)GetProcAddress(hMod, "ustawMenu");

Moze w tym miejscu cos nie tak jest dlatego pozniej sie sypie?
Nie wiem, po prostu inwencja tworcza zaczyna mi sie konczyc :(

0

Zrób tak:

menuProc = (fmenuProc)addr;
menuProc(1); //<--- tu ustaw breakpoint'a
addr=0; //<--- tymczasowo

teraz jak program zatrzyma się przejdź do widoku assembler'a i:

(mniej więcej coś takiego powinno być)

...
...
push 0x1      <--- ściągnij stąd wartość rejestru ESP 
call dword ptr[tu adres funkcji]
mov addr,0             <--- ściągnij stąd wartość rejestru ESP 
...

jeżeli obie wartości są równe to oznacza, że funkcja po sobie sprząta ściągając wartości ze stosu - więc nie jest

__cdecl

. Problem będzie gdy:

...
push 0x1
call dword ptr[tu adres funkcji]
pop ecx lub add esp,4  <--- ponowne ściągnięcie ze stosu!!!!!!!!!!!!
mov addr,0
...

Podobnie będzie w przypadku gdy po wyjściu z funkcji wartości ESP będą inne i nie będzie po niej instrukcji

pop reg

lub add esp,4

. Wniosek będzie taki, że kompilator źle interpretuje wskaźnik na funkcje. U mnie CALLBACK jest konwencją <code class="cpp">__stdcall

.

0

Myśle, że jednak źle podchodzisz do tego problemu. Konwencja MA znaczenie i MUSI być zadeklarowany wskaźnik do PRAWIDŁOWEGO typu. Inaczej możesz mieć bardzo dziwne błędy.

Jeżeli masz tak zadeklarowane wszystkie wskaźniki do funkcji to masz poważny błąd. Nie możesz tak podchodzić, że release (prawie) działa, a debug nie. Nie działa i tyle, znaczy JEST błąd. Na 99% jest to spowodowane brakiem CALLBACK w typedef, i nie koniecznie w tym jednym miejscu!
Już samo to, że użyłeś bezpośredniego rzutowania, na np. fmenuproc, oznacza, że coś jest nie za bardzo tak jak powinno być, w tym przypadku brak CALLBACK. Pozbyłeś sie informacji o błędzie po prostu wstawiając rzutowanie.

I powtórzę, musisz to poprawić we WSZYSTKICH deklaracjach tych swoich callbacków, bo to ma znaczenie.

0
marcinEc napisał(a)

Już samo to, że użyłeś bezpośredniego rzutowania, na np. fmenuproc, oznacza, że coś jest nie za bardzo tak jak powinno być, w tym przypadku brak CALLBACK. Pozbyłeś sie informacji o błędzie po prostu wstawiając rzutowanie.

Zaraz, moment. Czy chodzi o to:

menuProc = (fmenuProc)addr;

Jesli chodzi o typedef to mam wszedzie poprawione i wstawione CALLBACK, czyli tak to u mnie wyglada:

typedef UINT (CALLBACK *fMenuProc)(UINT status);

Czyli w jednym dll, gdzie funkcja jest zdefiniowana i posiada CALLBACK i w drugim gdzie jest typedef takze jest z CALLBACK'iem
Mimo tego wywolanie funkcji np. tak: menuProc(1) wyrzuca blad niezgodnosci konwencji.

0

Rozumiem, że wyrzucany błąd to run-time error.
To jest tylko SKUTEK złego wywołania funkcji, nawet niekoniecznie menuProc(), stos jest tak schrzaniony, że już run-time stwierdza błąd.
Czy nie jest tak, że "dzięki" bezpośredniemu rzutowaniu "pozbyłeś się" jeszcze innych błędów? No bo jeżeli ten program jest tak zamotany (po co te callbacki? czemu takie wywoływanie funkcji przez kilka poziomów?) to pewnie gdzieś się jeszcze walnąłeś, a na odległość stwierdzenie takich błędów jest praktycznie niemożliwe.. Mimo wszystko - powodzenia.

0

Po dlugim i zmudnym przegladnieciu kodu zadzialala aplikacja.
Dziekuje za uwagi, ktore podsunely mi pare ciekawych rozwiazan.

Jedno rzecz to faktycznie, jedna funkcja byla zle zdeklarowana i przez to sypaly sie wszystkie, ktore mialy CALLBACK.
Chociaz nawet po rozpracowaniu tego okazalo sie ze wszystko jest ok, a mimo to nadal sie program sypal - tym razem wine ponosila funkcja odpalajaca nowy watek, ktory wywolywal funkcje z CALLBACK. Takze ten sam Runtime error wywalalo i zglaszalo bledy w funkcjach z CALLBACK'iem. Troche dziwne, bo w calkiem innym miejscu pokazywalo blad niz byl w rzeczywistosci.
Pomoglo przestawienie mutexu w odpowiednie miejsce :)

marcinEc napisał(a)

(po co te callbacki? czemu takie wywoływanie funkcji przez kilka poziomów?)

Wiem, ze zakrecone, ale musi byc zgodnosc ze starym api.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.