Как да използвам COM DLL с LoadLibrary в C++

Първо, COM е като черна магия за мен. Но трябва да използвам COM dll в един проект, върху който работя.

И така, имам DLL, който разработвам, и имам нужда от някои функционалности, които са налични в отделен COM DLL. Когато гледам COM DLL с Depends.exe, виждам методи като DllGetClassObject() и други функции, но нито една от функциите, които ме интересуват.

Имам достъп до изходния код на COM DLL (наследен), но това е бъркотия и бих предпочел да използвам COM DLL в двоичен код като голяма черна кутия, без да знам какво се случва вътре.

И така, как мога да извикам COM DLL функциите от моя код с помощта на LoadLibrary? Възможно ли е? Ако да, можете ли да ми дадете пример как да го направя?

Използвам Visual Studio 6 за този проект.

Благодаря много!


person Etienne Savard    schedule 02.02.2010    source източник


Отговори (6)


Обикновено бихте използвали CoCreateInstance() за инстанциране на обект от COM DLL. Когато направите това, няма нужда първо да зареждате DLL и да получавате proc адреси, както бихте трябвало да направите с нормален DLL. Това е така, защото Windows "знае" за типовете, които COM DLL имплементира, в каква DLL са имплементирани и как да ги инстанцира. (Ако приемем, разбира се, че COM DLL е регистриран, което обикновено е).

Да предположим, че имате COM DLL с IDog интерфейса, който искате да използвате. В този случай,

dog.idl

interface IDog : IUnknown
{
  HRESULT Bark();
};

coclass Dog
{
  [default] Interface IDog;
};

myCode.cpp

IDog* piDog = 0;
CoCreateInstance(CLSID_DOG, 0,  CLSCTX_INPROC_SERVER, IID_IDOG,  &piDog); // windows will instantiate the IDog object and place the pointer to it in piDog
piDog->Bark();  // do stuff
piDog->Release();  // were done with it now
piDog = 0;  // no need to delete it -- COM objects generally delete themselves

Всички тези неща за управление на паметта обаче могат да станат доста груби и ATL предоставя интелигентни указатели, които правят задачата за инстанциране и управление на тези обекти малко по-лесна:

CComPtr<IDog> dog;
dog.CoCreateInstance(CLSID_DOG);
dog->Bark();

РЕДАКТИРАНЕ:

Когато казах по-горе това:

Windows „знае“ за типовете, които COM DLL имплементира [...и] в какви DLL са имплементирани

...наистина премълчах как точно Windows знае това. Не е магия, въпреки че в началото може да изглежда малко окултно.

COM библиотеките идват с библиотеки за типове, които изброяват интерфейсите и CoClasses, които библиотеката предоставя. Тази библиотека с типове е под формата на файл на вашия твърд диск -- много често тя е вградена директно в същата DLL или EXE като самата библиотека. Windows знае къде да намери Type Library и самата COM Library, като търси в системния регистър на Windows. Записите в регистъра казват на Windows къде на твърдия диск се намира DLL.

Когато извикате CoCreateInstance, Windows търси clsid в системния регистър на Windows, намира съответния DLL, зарежда го и изпълнява правилния код в DLL, който имплементира COM обекта.

Как тази информация влиза в системния регистър на Windows? Когато се инсталира COM DLL, той се регистрира. Това обикновено се прави чрез стартиране на regsvr32.exe, което от своя страна зарежда вашата DLL в паметта и извиква функция с име DllRegisterServer. Тази функция, внедрена във вашия COM сървър, добавя необходимата информация към регистъра. Ако използвате ATL или друга COM рамка, това вероятно се прави под капака, така че да не се налага да взаимодействате директно с регистъра. DllRegisterServer трябва да се извика само веднъж, по време на инсталиране.

Ако се опитате да извикате CoCreateInstance за COM обект, който все още не е регистриран чрез процеса regsvr32/DllRegisterServer, тогава CoCreateInstance ще се провали с грешка, която казва:

Класът не е регистриран

За щастие решението за това е просто да извикате regsvr32 на вашия COM сървър и след това да опитате отново.

person John Dibling    schedule 02.02.2010
comment
+1, това е правилният отговор. COM може също така да извърши допълнителна работа зад кулисите, за да гарантира, че моделите на нишки съвпадат: така приложение, написано да използва множество нишки, може безопасно да създаде компонент, който приема една нишка, и обратно. Има куп други неща, които COM може да направи, в зависимост от това как е регистриран компонентът, но това е може би най-важното, което трябва да знаете. Накратко, просто използвайте CoCreateInstance; ако се опитате да пуснете свой собствен и не сте експерт по COM, има голям шанс да попаднете някъде с фин бъг. - person BrendanMcK; 29.11.2011
comment
Регистрацията на COM DLL не е автоматична и ако това е домашна DLL, тази стъпка може да е пропусната. В този случай CoCreateInstance ще се провали. Трябва да го засегнаш някъде в отговора. - person Mark Ransom; 01.12.2011
comment
@Mark: Добро предложение, редактирано. Наистина си изровил старото тук. :) - person John Dibling; 01.12.2011
comment
Не го изрових, някаква дейност от невеж новак го върна в активния списък. Хубаво резюме за регистриране на DLL. - person Mark Ransom; 01.12.2011

По принцип трябва да предпочитате CoCreateInstance или CoGetClassObject вместо директен достъп до DllGetClassObject. Но ако имате работа с DLL, който не можете или не искате да регистрирате, тогава по-долу е описано (част от) какво правят тези функции зад кулисите.


При даден CLSID, DllGetClassObject ви позволява да получите обекта на класа, от който можете да създавате екземпляри (чрез интерфейса IClassFactory, ако си спомням правилно).

Обобщение на стъпките (измина известно време, откакто за последен път докоснах COM, така че извинете за очевидни грешки):

  1. Извикайте DllGetClassObject(clsid, IID_IClassFactory, &cf), където clsid е CLSID, за който искате да получите обекта на класа, а cf разбира се е фабриката за класове.
  2. Извикайте cf->CreateInstance(0, iid, &obj), където iid е IID на интерфейса, който искате да използвате, а obj разбира се е обектът.
  3. ???
  4. печалба!

(CoCreateInstance изпълнява стъпки 1 и 2. CoGetClassObject изпълнява стъпка 1. Бихте използвали CoGetClassObject, ако трябва да създадете много екземпляри от един и същи клас, така че стъпка 1 не трябва да се повтаря всеки път.)

person Chris Jester-Young    schedule 02.02.2010
comment
+1 за народния език на покер форумите, проникващ в други области на маниакалното изкуство. - person John Dibling; 03.02.2010
comment
Има ли причина да препоръчвате директно извикване на експортирания DllGetClassObject() вместо използване на COM функцията CoGetClassObject()? Струва ми се като... нестандартен подход. - person Phil Booth; 03.02.2010
comment
@Phil: Не, не го препоръчвам сам по себе си; Просто отговарях на въпроса на ОП. Но мисля, че има случайни приложения за това, например когато искате да тествате COM компонент за вътрешна разработка, който имате множество версии, който не искате да пререгистрирате през цялото време. - person Chris Jester-Young; 03.02.2010
comment
Това е опасно нещо да се направи с случаен код на трета страна: ако целевият COM обект използва различен модел на нишки от вашите собствени нишки, могат да се случат лоши неща (множество нишки изпълняват код, написан само за една). Ако имате късмет, ще се срине; ако нямате късмет, ще получите повредени данни. CoCreate върши допълнителна работа зад кулисите, за да съпостави моделите за нишки. Направете това само ако това е вашият код или сте абсолютно сигурни в модела на нишката. (COM също се грижи за разтоварването на библиотеката в подходящия момент; макар че поне в този случай можете просто никога да не разтоварвате, за да избегнете проблема там.) - person BrendanMcK; 29.11.2011
comment
@BrendanMcK: Наистина, аз съм 100% съгласен с вашите точки и като цяло не бих си помислил да създавам екземпляри на COM обекти чрез средства, различни от CoCreateInstance или други подобни. Публикацията ми беше написана, за да отговори директно на въпроса на OP, който е как да използвам средства от ниско ниво за достъп до COM DLL, като например, ако се занимавам с DLL, който не е регистриран (и не се саморегистрира). - person Chris Jester-Young; 29.11.2011
comment
@ChrisJester-Young; номерът с това qu е, че не е ясно дали OP всъщност иска да направи това - или просто е нов за COM, иска да използва COM-базирана DLL и споменава LoadLibrary само, защото това е всичко, което са запознати с използване на обикновени Win32 DLL преди. Добавянето на подходящ отказ от отговорност в горната част на отговора (Използвайте CoCreate, но ако не можете, напр. защото трябва да X, тогава...) често е най-безопасният подход за използване, особено ако отговорът е нещо, което бихте направили обикновено правете сами; в противен случай ОП или случаен читател, намерил това чрез търсене, може да го обърка с препоръчан подход. - person BrendanMcK; 30.11.2011
comment
@BrendanMcK: Добра точка. Добавих такъв отказ от отговорност, заедно с някои обяснения за това какво всъщност правят препоръчаните функции (минус нещата за проверка на модел на нишка). - person Chris Jester-Young; 01.12.2011
comment
Благодаря, това наистина е полезно, тъй като исках начин за отстраняване на грешки в разширение на обвивката (IThumbnailProvider), без да се налага да регистрирам отново dll след всяка промяна на компилирането. - person Andrew Wyatt; 26.07.2015

Не използвате директно LoadLibrary() с COM библиотека. CoCreateInstance() ще извика тази функция, ако все още не е, и след това ще създаде ново копие на класа, който сте внедрили в библиотеката, в купчината и накрая ще ви върне необработен указател към този обект. Разбира се, той може да се провали по време на процеса и следователно някакъв механизъм за проверка на състоянието като HRESULT.

За по-лесно използване можете да мислите за COM библиотека като за обикновен DLL с 1) някаква предварително дефинирана функция за запис (главна), 2) трябва да извикате някаква предварително дефинирана функция като CoCreateInstance(), за да я въведете, и да приемете, че е като че защото трябва.

person t.g.    schedule 03.02.2010
comment
Харесва ми обяснението ти за COM. - person Contango; 05.10.2010

Ако библиотеката с типове е вградена в DLL, можете да я импортирате във вашия проект:

#import "whatever.dll"

Това автоматично ще генерира заглавни файлове, които се включват във вашия проект и ви позволяват да използвате експортираните обекти.

person Luke    schedule 03.02.2010

Ето малко код, показващ как да получите фабриката за класове и да я използвате за създаване на COM обект. Той използва структура, за да следи манипулатора на модула и указателя на функцията DllGetClassObject. Трябва да държите манипулатора на модула, докато приключите с COM обекта.

За да използвате тази функция, трябва да разпределите екземпляр на структурата ComModuleInfo и да зададете szDLL на името на DLL файл или пълното име на пътя. След това извикайте функцията с идентификатора на класа и идентификатора на интерфейса на COM обекта, който искате да получите от този DLL.

typedef struct {
   TCHAR   szDLL[MAX_PATH];
   HMODULE hModule;
   HRESULT (WINAPI *pfnGetFactory)(REFCLSID, REFIID, void**);
   } ComModuleInfo;

HRESULT CreateCOMObject(
   ComModuleInfo & mod,  // [in,out] 
   REFCLSID iidClass,    // [in] CLSID of the COM object to create
   REFIID iidInterface,  // [in] GUID of the interface to get
   LPVOID FAR* ppIface)  // [in] on success, interface to the COM object is returned
{
    HRESULT hr = S_OK;

    *ppIface = NULL; // in case we fail, make sure we return a null interface.

    // init the ComModuleInfo if this is the first time we have seen it.
    //
    if ( ! mod.pfnGetFactory)
    {     
       if ( ! mod.hModule)
       {
          mod.hModule = LoadLibrary(mod.szDLL);
          if ( ! mod.hModule)
             return HRESULT_FROM_WIN32(GetLastError());
       }
       mod.pfnGetFactory = (HRESULT (WINAPI *)(REFCLSID, REFIID, void**))GetProcAddress(mod.hModule, "DllGetClassObject");
       if ( ! mod.pfnGetFactory)
          return HRESULT_FROM_WIN32(GetLastError());
    }

    IClassFactory* pFactory = NULL;
    hr = mod.pfnGetFactory(iidClass, IID_IClassFactory, (void**)&pFactory);
    if (SUCCEEDED(hr))
    {
       hr = pFactory->CreateInstance(NULL, iidInterface, (void**)ppIface);
       pFactory->Release();
    }

    return hr;
}
person John Knoeller    schedule 02.02.2010
comment
Може да искате да изясните кога всъщност бихте използвали това: изглежда, че използването на CoCreateInstance трябва да е обичайният начин и бихте използвали тази техника само в краен случай - ако класът не е регистриран, например - и вие сте сигурни, че обектът използва модел на нишки, който е съвместим с извикващия. Това е потенциално опасен отговор за начинаещ в COM да чете без контекст, тъй като може неправилно да предположи, че този подход обикновено е за предпочитане пред използването на CoCreateInstance(). - person BrendanMcK; 29.11.2011

Ако това е COM DLL, всичко, което трябва да направите, е да го добавите като препратка към вашия проект и след това можете да извикате функциите, които са в DLL.

Да, можете да използвате COM функциите на ниско ниво като DLLGetClassObject, но защо бихте го направили?

person Greg    schedule 02.02.2010
comment
Една от предимствата на ръчното зареждане е, че вашият DLL не трябва да бъде регистриран, за да може да се използва. - person Chris Jester-Young; 02.02.2010
comment
Защо ще имате COM DLL, който не е регистриран? Това изглежда МНОГО малко вероятно... - person Greg; 02.02.2010
comment
На XP и по-нови можете да използвате файлове с манифест за достъп до нерегистриран COM DLL - person Nemanja Trifunovic; 03.02.2010
comment
Регистрираните COM обекти могат да се използват от вашите конкуренти. Много компании смятат, че това е лошо нещо. - person John Knoeller; 03.02.2010