Használata com dll tudta nélkül az ügyfél
Egy nap, amikor írtam egy DLL, szembesültem az elemi, első pillantásra, a feladat: létrehozása és használata a COM objektumot. De kiderült, hogy ez nem olyan egyszerű. A fő probléma abban rejlik, hogy az ügyfelem DLL semmit sem tudott a COM, így nem fogja elindítani, de ez volt egy csomó szálat, amelyek mindegyike rendszeresen hívják DLL függvény. Úgy tűnik számomra, miután néhány gondolat kísérlet (és konzultációs fórum RSDN COM / DCOM / COM +) általános és meglehetősen elegáns megoldást találtunk, és ez ebben a cikkben ismertetett.
Meg kell írni egy DLL, nem ír elő semmilyen követelményeknek az ügyfél kapcsolódó COM. Az ilyen igény felmerül, hogy ha már van egy sor különböző ügyfelek a DLL hasonló felület, és ki kell cserélni azt. Vagy, ha írunk egy plug-in, hogy semmit.
Bármilyen okból, a végrehajtás „szeretném használni COM. Az én esetemben, az volt az oka a lehetőséget, hogy egyértelmű (DLL az én ügyfél) használja a szerver egy másik folyamat (na jó, tényleg nem akarja használni a hagyományos módszerek közötti kölcsönhatás folyamatokban. Kellett kitalálni). Az Ön esetében ez lehet például a jelenléte COM-összetevők esetén, amelyek a kívánt funkciót.
- Néhány ügyfél-flow nem tud semmit a COM. A DLL szeretnénk használni. És CoInitialize [Ex] hozzáférés előtt a DLL hívás nem hiszem.
- Bizonyos kliens flow tudni valamit COM. Mindannyian tartoznak a különböző lakások, de ennek ellenére, felváltva (vagy egyszerre) eléréséhez DLL.
finomítása a probléma
Megoldjuk egyszerűsített változata az én problémám. Ez egy egyszerű, de illusztratív eset. Remélem ez a probléma csökkenthető valami ilyesmi, vagy legalábbis ugyanazt a megközelítést.
Azt akarja, hogy hozzon létre egy DLL, az exportáló a következő funkciókat:
Ha a DLL-interfész nincs funkciója hasonló Init és karbantartása, akkor hívja őket burkoltan. Azaz, ha fuggveny felfedezi, hogy Init még nem hívott, hogy felhívja a saját. A razzia hívható DllMain. Ha a végrehajtás az alábbiakban ismertetett init Az init hívást DllMain patthelyzetet eredményezhet patakok.
Ezen kívül vannak olyan COM-komponens CoolServer, végrehajtja a felület ICoolServer, amely tartalmaz fuggveny. Ha a COM inicializált mindenütt, és nem lenne probléma a lakás, ez volt végrehajtásához szükséges DLL, mint ez:
Ez azt jelenti, meg kell, hogy csak egy interfészt használ mutatót kapott a könyvtár inicializálás, és megjelent a tisztítás során. Ha tud létrehozni egy objektumot fuggveny és van, hogy elpusztítsa azt, akkor az init és lezáró funkciót el lehet távolítani a lakásban problémákat is eltűnnek. Amennyiben egy példányát COM-objektumot kell tartani a hívások között, sokkal érdekesebb.
Tehát, meg kell:
Nem aktív COM fontos! Egy lehetséges forgatókönyv: az ügyfél egy művelet a DLL, inicializálja COM, nem tiszta maguk után, akkor a kliens próbál önálló COM inicializálása és letörik, mert azt akarja, hogy ez egy kicsit rossz (inkább az STA, nem az MTA, vagy fordítva, és ezek az ügyfelek kiszámíthatatlan.).
Teljesítette az első két követelmény, azt javasoljuk, hogy hozzon létre egy további áramot inicializált, és hozzon létre egy COM objektumot. Ezt követően a menet vár tisztítására, amelynek során felszabadítja a mutatót a felület COM deinitialized és teljes.
További áramlás biztosításához szükséges létezését létrehozott objektumok közé függvény hívások. Ezért, ha a hívások között DLL funkciók nem kell tárolni mutatókat felületek, nem kellene bütykölni a további áramlás, Init-én-én és karbantartása könnyebb létrehozni és törölni az említett funkciók használják őket.
A pszeudo-kód a következő:
Néhány pont, hogy kell figyelembe venni, hogy ez a munka:
Várva a befejezése inicializálás az Init funkciót, meg kell kezdeni a Messenger mintavételi ciklusban. Ellenkező esetben az ügyfél, aki a meggondolatlanság COM inicializálása, és adja meg az STA leteszi a hívást az Init InitCleanupThread amikor megpróbál létrehozni egy objektumot.
Amikor létrehoz egy objektumot InitCleanupThread nagyon kívánatos, hogy a menet modell az objektum hozhatnak létre ez nem a fő STA. Ellenkező esetben, ha a fő STA létezik, egy objektum jön létre, és ennek következtében a pusztítás a fő STA (hívás CoUninitialize megfelelő stream) tárgyat is el fog tűnni. Helyezzünk egy otmarshalennomu interfész mutatót az objektum visszatér RPC_E_SERVER_DIED_DNE.
Takarmány InitCleanupThread kell lépnie az MTA. Egyébként véletlenül az STA főnök, ott jön létre az ügyfél tárgyak, és amikor minden tárgy fog halni után az áramlás (és módszerek visszatér RPC_E_SERVER_DIED_DNE), az ügyfél lesz nagyon meglepett. Sőt, azt mondanám, elképedt. Ő, és nem tudja, hogy létezik a flow.
Ha valamilyen oknál fogva az áramlás InitCleanupThread mindig része a STA, a vár kezelésre, köteles az üzenet hurok mintát.
tudjuk folytatni, hogy végre a követelményeket a harmadik az alábbiak szerint:
1. Kísérlet COM inicializálása.
ha CoInitialize [Ex] olyan hibaüzenetet RPC_E_CHANGED_MODE, a COM már inicializált, csak egy kicsit rossz.
ha CoInitialize [Ex] visszatért S_OK, a COM nem sikerült inicializálni.
ha CoInitialize [Ex] visszatért S_FALSE, a COM már inicializált, és ugyanazokkal a paraméterekkel.
ha CoInitialize [Ex] vissza valami mást, valami nagyon nincs rendben ment.
2. Kap egy érvényes használható a lakás egy mutatót a megfelelő felület. Módszer előállítására index függhet az eredménye a hívás CoInitialize [Ex].
3. Hívja a fuggveny.
4. Ha CoInitialize [Ex] visszatért S_OK vagy S_FALSE, Leépítve COM.
1. Próbálja letölteni egy érvényes használható a lakások egy mutatót a kívánt felületet.
- ha a kapott eredmény a hiba CO_E_NOTINITIALIZED, ugorjon a 2. lépésre;
- Ha minden rendben a 4. lépésre;
- ha valami mást, valami nagyon nincs rendben ment.
2. COM inicializálása.
3. Get felhasználása megengedett a lakásban van egy mutató a megfelelő felület.
4. Hívja a fuggveny.
5. Ha végzett, 2. bekezdés, inicializálást COM.
Ha InitCleanupTheard áram belép az MTA, akkor függetlenül attól, hogy a COM indítjuk az aktuális téma, és CoUnmarshalInterface IGlobalInterfaceTable :: GetInterfaceFromGlobal adják vissza S_OK rám. Ebben az esetben a hívás fuggveny módszer működik. De garantálom, hogy ez működni fog mindig, én nem. Mert valami talán még kell inicializálni a COM?
Ha InitCleanupTheard áramlás belép az STA (mert persze nem tudja, de azt volt kíváncsi.), Majd IGlobalInterfaceTable :: GetInterfaceFromGlobal visszatér E_UNEXPECTED, amíg legalább egyszer nem hajtja végre a szekvencia CoInitialize [Ex] + IGlobalInterfaceTable :: GetInterfaceFromGlobal. Ezután ő, mint az várható volt, vissza fog térni CO_E_NOTINITIALIZED.
Gyakorlati alkalmazásuk nem ajánlott.
Kap az „érvényes használatra a lakásban van egy mutató a megfelelő felület” lehet háromféleképpen:
CoMarshalInterface jobb okot MSHLFLAGS_TABLESTRONG zászló. Mert MSHLFLAGS_TABLEWEAK néha nem működik. Őszintén szólva, amíg meg nem érti, hogy miért.
Mielőtt hívja a istream_FAR CoUnmarshalInterface kell visszatekerni az elejére. Ez olyasmi, mint ez:
pStream-> Lépés (li, STREAM_SEEK_SET, NULL);
Ellenkező esetben nem fog működni. És mellesleg, nem várható törekedjetek valaha is visszatér CO_E_NOTINITIALIZED, belül - a tisztán manuális műveletet. Bár a csekket, természetesen, nem ártalmas.
Az utóbbi módszer csak akkor alkalmazható, ha a szál, amely megteremtette a tárgy (InitCleanupThread), és az áramlás a élni kívánnak e lehetőség, tartozik a lakás, amely egyaránt tartalmazza az MTA; Az első két módszer alkalmazható minden esetben.
Példaként, írtam két szerver (egy - exe, egy másik - DLL), két DLL (per szerver egy) és a kliens egy sor vizsgálatok.
Mindkét szerver végrehajtja a következő képernyő:
SomeFunction egyszerűen visszaadja S_OK, WorkFunction megjeleníti a húr átadott (egy DLL szerver - keresztül printf, a szerver az exe - a MessageBox). Az első használják összehasonlítani a teljesítményét a különböző hívó módszerek, a második, hogy ellenőrizze azok működőképességének fogalom.
Mint általában, a szerverek regisztrálnia kell. Azonban mindketten megpróbálják regisztrálja típustárat. Mindkét szerver megköveteli, hogy a könyvtár az úgynevezett «iface.tlb» és ugyanabban a könyvtárban, mint a szerver végrehajtható.
Az ügyfél, attól függően, hogy az értéke a parancssor (egy szám 0-tól 6-nem érv egyenértékű „0”), egyikével elvégzett vizsgálatok. Test „0” működőképességének ellenőrzése a többit, hogy segítsen értékelni. Az eredményeket a „Statisztika” részben.
Ezek különböznek egymástól csak a nevét az exportált funkciók és s GUID létrehozott objektumokat. Elméleti konstrukciók a „Megoldás” szakaszban hajtják végre az alábbiak szerint:
- InitCleanupThread áram belép az MTA.
- Takarmány InitCleanupThread beszámol Init inicializálás befejeződött az esemény, ami történik, mint a paraméter.
- Razzia jelentések folyni InitCleanupThread kezdeni tisztítás segítségével a globális esemény.
- Razzia nem várja végzett takarítási. Lustaság.
- InitCleanupThread regisztrációhoz interfész GIT.
- InitCleanupThread termel rendezését interfész a globális istream_FAR. CoMarshalInterface megadta MSHLFLAGS_TABLESTRONG zászló.
- Amikor megpróbálja COM inicializálása, minden szál próbál meg belépni az MTA. Ezért, ha kapnak rá, elrendezésében van szükség.
- Dll kivitel a következő funkciókat ( «xx» - vagy «A», vagy «Ki»): xx_Init, xx_Cleanup, xx_One, xx_Two, xx_Three, xx_Four, xx_One_Work, xx_Two_Work, xx_Three_Work, xx_Four_Work. «Egy», «két», «Három», «Négy» - négy módszerek megszerzésének egy mutatót az objektum. Work-függvényhívás WorkFunction, hétköznapi funkciók - SomeFunction.
Az init paraméter értéke igaz, ha a CoInitializeEx-t hívták és sikeresen működött, és ezért a DLL-ről való visszatérés előtt szükség van a CoUninitialize hívására.
statisztika
Valószínűleg, miután elolvastad, hogy minden egyes hívásért a COM vagy ilyen jellegű szolgáltatás inicializálásával / deinicializálásával kell fizetned, a következő kérdést fontolgatta: mennyibe kerül? Érdeklődtem is, néhány kísérletet tettem.
Minden kísérletet az alábbiak szerint végeztük:
COM nem inicializálva
Nos, segítek egy kicsit :) A "COINIT_APARTMENTTHREADED" és a "COINIT_MULTITHREADED" oszlopokban szereplő adatok körülbelül így kaptak:
Ez azt jelenti, hogy a COM-t az xx_Init hívása előtt inicializálta. Ez megmagyarázza az első táblázat "COINIT_APARTMENTTHREADED" oszlopának különös eredményeit. Ebben az esetben a fő szál először inicializálta a COM-ot, ezért lépett be a fő STA-ba, és létrehozta a CoolServer objektumot. Ezért egyes hívások a SomeFunction-hoz közvetlenül érkeztek.
Ui Még a Microsoft is ezt teszi ...
Gondolt már arra, hogy hogyan hajtják végre a ShellExecute [Ex] funkciót? Egyrészt nem igényli a COM előinicializálását, másrészt nyilvánvalóan azt használja ... Nem gondoltam rá. De kiderült, hasonló megközelítést alkalmaz.
Az SHCoInitialize funkció így néz ki:
Ez azt jelenti, hogy ha nem sikerült inicializálni a COM-t a COINIT_APARTMENTTHREADED paraméterrel, megpróbálja inicializálni a COINIT_MULTITHREADED értékkel. Ha nem történik semmi váratlan, akkor a funkció végrehajtása után:
- győződjön meg róla, hogy a COM be van állítva
- bátran hívja CoUninitialize, mert az ügyfél nem fog fájni
Ez a ShellExecuteExW.
köszönöm
A kód megírásakor, amely a szöveg alapjául szolgál, és a példát, nagyon segítséget nyújtott a Vi2 (Victor Sharakhov). Nagyon köszönöm. Második köszönet Pavel Bludovnak, aki felhívta a figyelmet a ShellExecute [Ex] funkcióra.