9

首先,COM 对我来说就像黑魔法。但是我需要在我正在处理的一个项目中使用 COM dll。

所以,我有一个我正在开发的 DLL,我需要一些在单独的 COM DLL 中可用的功能。当我使用 Depends.exe 查看 COM DLL 时,我看到了 DllGetClassObject() 和其他函数之类的方法,但没有一个我感兴趣的函数。

我可以访问 COM DLL(旧版)源代码,但它一团糟,我宁愿像一个不​​知道里面发生了什么的大黑盒子一样使用二进制形式的 COM DLL。

那么,如何使用 LoadLibrary 从我的代码中调用 COM DLL 函数?是否可以?如果,是的,你能给我一个如何做的例子吗?

我正在为这个项目使用 Visual Studio 6。

非常感谢!

4

6 回答 6

22

一般来说,您应该更喜欢CoCreateInstanceCoGetClassObject而不是DllGetClassObject直接访问。但是,如果您正在处理一个您不能或不想注册的 DLL,那么下面将描述(部分)这些函数在幕后所做的事情。


给定一个 CLSID,DllGetClassObject允许您获取类对象,您可以从中创建实例(IClassFactory如果我没记错的话,通过接口)。

步骤总结(自从我上次接触 COM 以来已经有一段时间了,所以请原谅任何明显的错误):

  1. Call DllGetClassObject(clsid, IID_IClassFactory, &cf)clsid你想要获取类对象的 CLSID在哪里cf,当然是类工厂。
  2. Call ,你想使用的接口的 IID 在cf->CreateInstance(0, iid, &obj)哪里,当然是对象。iidobj
  3. ???
  4. 利润!

CoCreateInstance执行第 1 步和第 2步。CoGetClassObject执行第 1 步。CoGetClassObject如果您需要创建同一个类的许多实例,则可以使用,这样就不需要每次都重复第 1 步。)

于 2010-02-02T20:30:31.317 回答
14

通常,您将使用CoCreateInstance()从 COM DLL 实例化对象。执行此操作时,无需像处理普通 DLL 那样首先加载 DLL 并获取 proc 地址。这是因为 Windows “知道” COM DLL 实现的类型、实现它们的 DLL 以及如何实例化它们。(当然假设 COM DLL 已注册,通常是这样)。

假设您有一个带有要使用的 IDog 接口的 COM DLL。在这种情况下,

狗.idl

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

coclass Dog
{
  [default] Interface IDog;
};

我的代码.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 库带有类型库,其中列出了库提供的接口和 CoClass。这个类型库在您的硬盘驱动器上以文件的形式出现——通常它直接嵌入到与库本身相同的 DLL 或 EXE 中。通过查看 Windows 注册表,Windows 知道在哪里可以找到类型库和 COM 库本身。注册表中的条目告诉 Windows DLL 在硬盘上的位置。

当您调用 时CoCreateInstance,Windows 会在 Windows 注册表中查找 clsid,找到相应的 DLL,加载它,并在 DLL 中执行实现 COM 对象的适当代码。

此信息如何进入 Windows 注册表?安装 COM DLL 时,它会被注册。这通常是通过运行regsvr32.exe来完成的,它反过来将您的 DLL 加载到内存中并调用一个名为DllRegisterServer. 该功能在您的 COM 服务器中实现,将必要的信息添加到注册表。如果您使用 ATL 或其他 COM 框架,这可能是在后台完成的,因此您不必直接与注册表交互。 DllRegisterServer只需要在安装时调用一次。

如果您尝试通过/进程调用CoCreateInstance尚未注册的 COM 对象,则会失败并显示以下错误:regsvr32DllRegisterServerCoCreateInstance

未注册课程

幸运的是,解决此问题的方法是简单地调用regsvr32您的 COM 服务器,然后再试一次。

于 2010-02-02T21:31:21.753 回答
7

您不直接将 LoadLibrary() 与 COM 库一起使用。CoCreateInstance() 将调用此函数(如果尚未调用此函数),然后将您在库中实现的类的实例新建到堆上,最后向您返回指向该对象的原始指针。当然,它可能会在此过程中失败,因此有一些机制可以让您检查状态,例如 HRESULT。

为了简单起见,您可以将 COM 库视为一个通用 DLL,其中包含 1)一些预定义的条目(主)函数,2)您必须调用一些预定义的函数,如 CoCreateInstance() 来输入它,并接受它就像那是因为它必须。

于 2010-02-03T02:45:02.673 回答
3

如果类型库嵌入在 DLL 中,您可以将其导入到您的项目中:

#import "whatever.dll"

这将自动生成包含在项目中的头文件,并允许您使用导出的对象。

于 2010-02-03T04:40:36.870 回答
2

下面是一段代码,展示了如何获取类工厂并使用它来创建 COM 对象。它使用一个结构来跟踪模块句柄和 DllGetClassObject 函数指针。在完成 COM 对象之前,您应该保持模块句柄。

要使用此功能,您需要分配 ComModuleInfo 结构的实例并将 szDLL 设置为 DLL 文件名或完整路径名。然后使用要从该 DLL 获取的 COM 对象的类 ID 和接口 ID 调用该函数。

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;
}
于 2010-02-02T21:21:06.927 回答
0

如果它是 COM DLL,您只需将其添加为项目的引用,然后您就可以调用 DLL 中的函数。

是的,您可以使用像 DLLGetClassObject 这样的低级 COM 函数,但您为什么要这样做呢?

于 2010-02-02T20:33:51.077 回答