[MEGOLDVA] Adott .def + MinGW + C++ -> dll

 ( Kuvik | 2009. július 12., vasárnap - 18:12 )

Hasonló problémába ütköztem mint ez:

kiszedtem a linket, mert úgy veszem észre, hogy mindenkit átirányít, vissza meg már senki sem jön :(

de nálam pont fordított a helyzet: én készítem a dll-t és nincs befolyásom arra a környezetre, ahol majd meghívódik.

Szóval. Egy dll-t szeretnék készíteni, amiből valószínűleg az MSVC forma szerint lehet hívni a függvényeket, legalábbis, amit eddig összeszedtem, az erre utal. A környezetre, ahol fel kell használni, semmilyen ráhatásom nincs (megelőzendő az "írjál új függvényhívást, wrappert, operációs rendszert, stb." válaszokat).
A fordításhoz a MinGW gcc-t használnám, mint kézenfekvő cross-compilert, de ha van jobb ötlet jöhet az is.

A dll elkészítéséhez kaptam egy .def fájlt, ami kb. így néz ki:

LIBRARY probadll
EXPORTS probafv

tehát nincsenek benne _, @1, stb. sallangok.

A problémám az, hogy a MinGW-s gcc (ezzel szeretném fordítani) valószínűleg mégsem eszerint a forma szerint dolgozik, mert a függvényhívás nem működik. Itt találtam ötleteket:

http://www.geocities.com/yongweiwu/stdcall.htm

amik közül a dllwrap tűnt használhatónak de arra meg ezt dobja:

probadll.exp:fake:(.edata+0x38): undefined reference to `probafv'

Azt szeretném valahogy megoldani, hogy a dll a megadott .def-et figyelembe véve forduljon.

amúgy a kód releváns része:

#define EXTFUNC __declspec(dllexport)

EXTFUNC void __stdcall probafv(int a, int b, double &c){
	...
}

próbáltam extern "C"-vel is, úgy sem ment.

Mi lehet a probléma?

___________________________________________________

MEGOLDÁS:

csak röviden, mert elfáradtam:

függvények fejléce:
extern "C" __declspec(dllexport) típus __stdcall függvénynév(...)

fordítás (értelemszerűen itt a MinGW-s gcc-ről van szó):
gcc -shared -o fájlnév.dll fájlnév.cpp fájlnév.def

Az extern "C" volt a megoldás kulcsa, a korábbi fordítási kísérleteknél is erre vezethető vissza az, hogy az exportált függvények "nem látszottak" kifelé és ez már valószínűleg a linkelésnél is problémát okozott. Ezen kívül úgy néz ki, hogy a gcc-nek NEM kell a -Wl kapcsoló.

Ismert jelenségek:

-az exportálás valamiért duplán történik ilyenkor (nyilván a .def fájl miatt), a függvények kétféle formában is megvannak:

EXPORTS
függvénynév
függvénynév@n

de ez nem zavar, az a lényeg, hogy @n nélkül megvan. Valószínűleg fordítási beállításokkal lehetne finomítani ( --kill-at?)

-referencia szerinti paraméterátadás ebben a felállásban okozhat problémákat, de pointerekkel ezt meg lehet kerülni.

Hozzászólás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

fel

Ha te úgy szeretnéd terjeszteni a dll-edet, hogy .lib és .h, akkor bizony
minden fordítóhoz kell készítened egyet-egyet, ha csak a dll-t akkor mivel annak
az ABI-ja egyértelműen definiált windowsban azért mindegy mivel fordítod, a kérdés
az, hogy aki felhasználni akarja a dll-t az név szerint akarja betölteni a függvényt
vagy sorszám szerint ? Bármelyiket is választja tudnia kell ezeket az infokat, ergo
jó doksit kell írni hozzá. Gondolom.

--
"Megtanultam a zenét, de nem csináltam, s azóta tudással, de irigység nélkül hallgatom.
Megtanultam egy sereg tudományt, mesterséget és művészetet, értek hozzájuk, de nem csinálom, s így érdektelenül tudom azokat élvezni. "
Hamvas Béla

csak egy dll kell, és név szerint hívódnak a függvények. De valahogy mégiscsak függhet a fordítótól, vagy legalábbis a fordítástól, hogy hogyan exportálódnak a függvények a dll-ből, mert van egy adott .def fájl, aminek meg kellene felelni.
Egyébként a fordítónkénti eltérésekről ír az a leírás is, amit belinkeltem: "The decorations could change when they appear in DLLs or when they are produced by different compilers."

Ha a dll C-s, akkor no problem, mert win-en az ABI "szabványos".

Nem kell se .def, se semmi, csak a fv-nevek elé __declspec(dllexport), __declspec(dllimport)-ból a megfelelő. Pl így:

#ifdef MYLIB_EXPORTS
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif

extern "C"
{
MYLIB_API void fv1();
MYLIB_API void fv2(void* data);
...
}

Amikor fordítod legyen definiálva a MYLIB_EXPORTS, amikor használják akkor meg ne.

Én így msvc alatt fordított lib-et probléma nélkül használtam mingw alól.

Ha C++ a dll, akkor felejtsd el, nem lehet megoldani. (C-s wrappert persze írhatsz, majd fogadó oldalon egy C++-osat. De nem feltétlenül érdemes ennyit szívni vele...)

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Ezt nem egészen értem. C++-al miért nem lehet megoldani? Az lenne egyébként... Amúgy valahol olvastam, hogy hasonló problémát már több helyen megoldottak.
Egyébként nagyjából így próbálom én is, ahogy itt leírtad. Azt mondjuk nem igazán értem, hogy a fordítás és a használat miért különül el, nálam fixen van egy #define az exportra (ezt egy másik leírás szerint csináltam így).

Egyébként én pont fordítva, MinGW-vel akarok fordítani, és MSVC(?)-vel (vagy hasonló exportformátummal) használni.

Nekem egyébként az a gyanús, amit a linkelt leírásban olvastam,

( http://www.geocities.com/yongweiwu/stdcall.htm )

hogy az egyes fordítók más-más formára alakítják a függvényhívásokat, ha pl. __stdcall-ról van szó (ld. táblázat)
Emiatt úgy gondolom, hogy a MinGW ezt nem olyanra fordítja, mint ahogy a hívóoldalon hívni szeretnék, gondolom ezért is adták meg a .def-et, hogy rögzítsék, hogy milyen formában kell lennie a függvények exportjának, vagy ilyesmi. A linkelt leírásban elvileg vannak is varázslások a .def-ekkel, de nekem valahogy nem működnek :(

A C++ nem jol tamogatja a komponensalapu programozast.

A tobbfele win-es fordito pl. az alabbiakban masmilyen megoldast hasznal:
- memoriakezeles
- kivetelek
- object layout

Azert jo a pure c, mert ott problemat csak a name mangling es a struktura mezo igazitas okoz, amit forditasi opciobol konnyen beallithatsz.

Szerintem is felejtos a C++ ilyen celra, plane, ha keves iranyitasod van az interfesz es a usecase-ek felett. Erre talalta ki a Microsoft a COM-ot, ami lehetove teszi, hogy dll-ekbol (aka. binaris komponensekbol) nyelvfuggetlen modon objektumokkal dolgozz. Ez azonban megszoritasokat is jelent. Pl. GUID-dal regisztralnod kell az interfeszedet, nem hasznalhatsz tobbszoros oroklodest es speci memoriakezelest kell hasznalnod, tovabba a mezotipusaidra is lesznek kotottsegek. Igy lehet pl. a DirectX-et elerni VisualBasic-bol es C++-bol is.

A probléma az, hogy C++-ban nincs szabványos ABI. Ez egyrészt jó, mert lehetőséget ad a fejlődésre (pl új kivételkezelés, stb.), másrészt rossz, mert nincs (bináris) átjárás a fordítók között.

Ez alól a kivétel a win, mert ott a COM miatt egy minimális egyezés van. Ezzel lehet trükközni:
http://aegisknight.org/cppinterface.html
A gond vele az, hogy annyira macerás, hogy nem érdemes. Egyszerűbb 2-3 dll-t fordítani...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Igen, a .def file azért kell amiért írtad.

De ha megnézed a táblázatot, __cdecl-lel a dll-ekben a nevek megegyeznek, azaz nem kell .def fájlokkal szórakozni...

Azt ajánlom keress C-s egy projectet, amiben megoldották a kérdést.

dllexport/import:
http://msdn.microsoft.com/en-us/library/8fskxacy(VS.80).aspx
http://msdn.microsoft.com/en-us/library/a90k134d(VS.80).aspx
http://msdn.microsoft.com/en-us/library/3y1sfaz2.aspx

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Köszönöm a válaszokat, közeledek a megoldáshoz.

Most ott tartok, hogy szereztem egy dll-t forrással együtt, ami az adott környezetben működik. A lefordított változatot megnéztem pexports-al, és olyan formában adja vissza a függvényneveket, ahogy a megadott .def-ben is van.
Ezt a formát nekem is sikerült reprodukálnom, ha extern "C"-vel és __cdecl-el fordítok (__stdcall helyett), de az így lefordított dll az első függvényhíváskor dob egy exceptiont és hanyattvágja magát.

Nem tudom, hogy az én kódomban, a fordítóban, vagy esetleg ott van-e a hiba, hogy __cdecl-t használok __stdcall helyett.
Ezért szeretném újrafordítani a működő dll-t az én fordítómmal (mingw-gcc), de sehogyan sem sikerül olyan formában megoldani a függvények exportálását, ahogy a .def-ben van. Feltételezem, hogy lehetséges, ha már egyszer lefordították és működik.

Elsősorban a függvénynevek után rakott @-okkal van probléma, a többi sallang eltűnik, ha extern "C"-t használok.

A korábban linkelt leírás szerint főleg a dllwrap-al próbálkoztam ("GNU dllwrap could produce a DLL by a DEF file." - dllwrap --def DEF_file -o DLL_file OBJ_files), de sehogy sem fut le, mindig ezt adja:
forrás_neve.exp:fake:(.edata+0x38): undefined reference to `függvénynév'

A leírásban van egy rész ami elvileg interface-libraryról szól, de azt írja, hogy a sallangokat el lehetne tüntetni, a mingw-s gcc --kill-at kapcsolójával, de ennek a kapcsolókank semmi hatását nem tapasztalom, akárhogyan is fordítok, a pexports mindig ugyanazt adja vissza.
Ebben az esetben elsősorban így próbáltam fordítani:

gcc -shared -o DLL_file OBJ_files -Wl,--kill-at

Úgy gondolom, hogy csak az az egy lépés hiányzik hogy a dllwrapot működésre bírjam, vagy alternatívaként a gcc valahogy fordításkor vágja le a @-okat, de nem jövök rá egyikre sem, hogy hol lehet a hiba, elsősorban ezekkel kapcsolatban kérnék segítséget.

ne fektess bele túl nagy energiát, nem valószínű, hogy működni fog.
más gondok is vannak:
- memória allokáló, felszabadító függvények. Ha ilyen lefut a VC DLL-ben, akkor véged
- ütközések vc stdlib és mingw stdlib között
- statikus változók inicializációja
stb

Szóval az ügy a reménytelenhez közelít.
Keress hasonló projektet, és abból kiindulva írd újra a kódot, nincs más megoldás

Azért kösz... :)

"memória allokáló, felszabadító függvények. Ha ilyen lefut a VC DLL-ben, akkor véged"

Ezzel semmi gond nincs, amíg a dll-ben lefoglalt memóriát a dll szabadítja fel, az alkalmazás által lefoglaltat pedig az alkalmazás. Ha keveredik, akkor persze elszáll.

"ütközések vc stdlib és mingw stdlib között"
Megint csak azt tudom mondani, hogy ha nem keveredik a kettő, semmi gond nincs.
Persze char*-nál meg saját struct-nál bonyolultabb dolgokat ne küldözgess...

"statikus változók inicializációja"
Singleton pattern...
dll-nek lehet init függvénye, amit az OS lefuttat betöltéskor...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

"Ezzel semmi gond nincs, amíg a dll-ben lefoglalt memóriát a dll szabadítja fel, az alkalmazás által lefoglaltat pedig az alkalmazás. Ha keveredik, akkor persze elszáll."

?? úgy érted, ha a vc dll használ new-t, akkor nekem nem szabad?
ezt én nem érzem kielégítő megoldásnak

"Persze char*-nál meg saját struct-nál bonyolultabb dolgokat ne küldözgess..."
vagyis STL használat kizárva?

szerintem ennek a kínlódásnak értelme közelít a nullához.

"szerintem ennek a kínlódásnak értelme közelít a nullához."

Ez lehet, de ettől még köszöni, egész jól működik :)

"Ismert jelenségek:

-az exportálás valamiért duplán történik ilyenkor (nyilván a .def fájl miatt), a függvények kétféle formában is megvannak:"

Mint már írtam, a .def felesleges szerintem.

"-referencia szerinti paraméterátadás ebben a felállásban okozhat problémákat, de pointerekkel ezt meg lehet kerülni."

Mivel a felület C-s, ezért konkrétan nincs referencia szerinti paraméterátadás.
Ha a fordító lefordítja, az bug.

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

- def nélkül nem működik

- gcc lefordítja... :)

Ahogy már írtam:

#ifdef MYLIB_EXPORTS
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif

extern "C"
{
MYLIB_API void fv1();
MYLIB_API void fv2(void* data);
...
}

Ezután

g++ -shared test.cpp -o test.dll

és máris van egy dll-ed.

Visual C++ alól amennyire tudom ezt nem tudod használni, mert kell neki egy .lib fájl: (A te megoldásod szerintem nem csinál .lib-et. Hogy használod a dll-t?)

g++ -shared -Wl,--out-implib,test.lib test.cpp -o test.dll

Ez létrehoz egy .lib-et is. Tadaaam...

Legalábbis elvileg:
VS 2005 alatt azt mondja hogy: "Warning LNK4078" és a dll-t használó progi elszáll.
MSDN szerint: "This warning can be caused by an import library [...] that was created by a previous version of LINK or LIB."

Hát persze, amennyiben a gnu link előző verziónak számít... :))

Na most kell a .def fájl: Fordítsunk az előző helyett így:

g++ -shared -Wl,--output-def,test.def test.cpp -o test.dll

Csináljunk .lib-et:

lib /DEF:test.def

Az új .lib tökéletesen működik...

Összefoglalva:
- dll-hez nem kell mágia
- VC-hez kell .lib, amit a gcc elvileg létre tud hozni
- gyakorlatban nekünk kell létrehozni, de a .def generálható

Extra kérdés:
Ha a MS szerint létre lehet hozni egy .lib-et csupán a dll birtokában, akkor vajon miért nem teszi ezt meg magától, azaz miért nem lehet .dll-t linkelni .lib (.a) nélkül mint gcc-vel???

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Nem én fogom felhasználni a dll-t. Arra csak kaptam egy specifikációt, hogy kifelé hogyan kell kinéznie.
Hogy a felhasználási oldalon milyen feketemágiát alkalmaznak, hogy kell-e ott lib, vagy sem, vagy mittomén, az az én szempontomból mindegy. Nekem egy adott pofájú dll-t kell szállítanom és kész.

.def nélkül pedig azért nem működik, mert alapesetben nem abban a formában exportálódnak a függvények, ahogy kérik és ahogy a .def-ben le van írva. Ezt meg tudom kerülni, ha __stdcall helyett __cdecl-t használok, de ilyenkor hanyattvágja magát egy bazi nagy exceptionnel, amikor a dll-ből megtörténne az első függvényhívás. Hogy ez bug-e vagy feature (számít-e, hogy nem __stdcall), azt nem tudom, futásidejű low-level debugoláshoz meg se időm se képességeim nincsenek.

De igazából mindegy is, a lényeg, hogy a mostani felállásban működik a dolog :)

Ha "kaptál" egy .def fájlt amit követned kell, akkor mond azt. :)

Amennyire tudom, ha van .def fájlod, akkor nem kell _declspec(dllimport/dllexport), mert mindkettő azt mondja meg a fordítónak, hogy mit kell kiexportálnia.
Ha mindkettő van, akkor a függvények 2x jelennek meg...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o