⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 com(2).txt

📁 关于ie控制的编程各种方面的内容和源代码
💻 TXT
📖 第 1 页 / 共 2 页
字号:

构造函数、析构函数和IUnknown方法都和前面例子中的一样,不同的只有IClassFactory的方法,LockServer(),看起来相当更简单: 

HRESULT CSimpleMsgBoxClassFactory::LockServer ( BOOL fLock ) 
{ 
    fLock ? g_uDllLockCount++ : g_uDllLockCount--; 
    return S_OK; 
} 

CreateInstance()是重点。我们说过这个方法负责创建新的CSimpleMsgBoxImpl对象。让我们进一步探讨一下它的原型和参数: 
HRESULT CSimpleMsgBoxClassFactory::CreateInstance ( IUnknown* pUnkOuter, 
                                                    REFIID    riid, 
                                                    void**    ppv ); 

    第一个参数pUnkOuter只用于聚合的新对象,指向“外部的”COM对象,也就是说,这个“外部”对象将包含此新对象。对象的聚合超出了本文的讨论范围,本文的例子对象也不支持聚合。 
riid 和ppv 与在QueryInterface()中的用法一样——它们是客户端所请求的接口IID和存储接口指针的指针缓冲。 
下面是CreateInstance()的实现。它从参数的有效性检查和参数的初始化开始。 
HRESULT CSimpleMsgBoxClassFactory::CreateInstance ( IUnknown* pUnkOuter, 
                                                    REFIID    riid, 
                                                    void**    ppv ) 
{ 
    // 因为不支持聚合,所以这个参数pUnkOuter必须为NULL. 
    if ( NULL != pUnkOuter ) 
        return CLASS_E_NOAGGREGATION; 

    //检查指针ppv是不是void*类型 
    if ( IsBadWritePtr ( ppv, sizeof(void*) )) 
        return E_POINTER; 

    *ppv = NULL; 

检查完参数的有效性后,就可以创建一个新的对象了。 
CSimpleMsgBoxImpl* pMsgbox; 

    // 创建一个新的COM对象 
    pMsgbox = new CSimpleMsgBoxImpl; 

    if ( NULL == pMsgbox ) 
        return E_OUTOFMEMORY; 

最后,用QI()来查询客户端所请求的新对象的接口。如果QI()失败,则这个对象不可用,必须删除它。 

HRESULT hrRet; 

    // 用QI查询客户端所请求的对象接口 
    hrRet = pMsgbox->QueryInterface ( riid, ppv ); 

    // 如果QI失败,则删除这个COM对象,因为客户端不能使用它(客户端没有 
    //这个对象的任何接口) 
    if ( FAILED(hrRet) ) 
        delete pMsgbox; 

    return hrRet; 
} 

深入DllGetClassObject() 
   现在让我们深入DllGetClassObject()内部。它的原型是: 
HRESULT DllGetClassObject ( REFCLSID rclsid, REFIID riid, void** ppv ); 
rclsid是客户端所请求的组件对象类的CLSID。这个函数必须返回指定组件对象类的类工厂。 
这里的两个参数: riid 和 ppv类似QI()的参数。不过在这个函数中,riid指的是COM库所请求的类工厂接口的IID。通常就是IID_IClassFactory。 
    因为DllGetClassObject()也创建一个新的COM对象(类工厂),所以代码与IClassFactory::CreateInstance()十分相似。开始也是进行一些有效性检查以及初始化。 
HRESULT DllGetClassObject ( REFCLSID rclsid, REFIID riid, void** ppv ) 
{ 
    // 检查客户端所要的CSimpleMsgBoxImpl类工厂 
    if ( !InlineIsEqualGUID ( rclsid, __uuidof(CSimpleMsgBoxImpl) )) 
        return CLASS_E_CLASSNOTAVAILABLE; 

    //检查指针ppv是不是void*类型 
    if ( IsBadWritePtr ( ppv, sizeof(void*) )) 
        return E_POINTER; 

    *ppv = NULL; 

第一个if语句检查rclsid参数。我们的服务器只有一个组件对象类,所以rclsid必须是CSimpleMsgBoxImpl类的CLSID。__uuidof操作符获取先前在__declspec(uuid())声明中指定的CsimpleMsgBoxImpl类的GUID。 
下一步是创建一个类工厂对象。 
CSimpleMsgBoxClassFactory* pFactory; 

    // 构造一个新的类工厂对象 
    pFactory = new CSimpleMsgBoxClassFactory; 

    if ( NULL == pFactory ) 
        return E_OUTOFMEMORY; 

    这里的处理与CreateInstance()中所做的有所不同。在CreateInstance()中是调用了QI(),并且如果调用失败,则删除COM对象。 
我们可以把自己假设成一个所创建的COM对象的客户端,调用AddRef()进行一次引用计数(COUNT = 1)。然后调用QI()。如果QI()调用成功,它将再一次用AddRef()进行引用计数(COUNT = 2)。如果QI()调用失败。引用计数将保持为原来的值(COUNT = 1)。 
在QI()调用之后,类工厂对象就使用完了,因此要调用Release()来释放它。如果QI()调用失败,这个对象将自我删除(因为引用计数将为零),所以最终结果是一样的。 
// 调用AddRef()增加一个类工厂引用计数,因为我们正在使用它 
pFactory->AddRef(); 

HRESULT hrRet; 

    // 调用QI()查询客户端所要的类工厂接口 
    hrRet = pFactory->QueryInterface ( riid, ppv ); 
     
    // 使用完类工厂后调用Release()释放它 
    pFactory->Release(); 

    return hrRet; 
} 

再谈QueryInterface() 

    前面讨论过QI()的实现,但还是有必要再看一看类工厂的QI(),因为它是一个很现实的例子,其中COM对象实现的不光是IUnknown。首先进行的是对ppv缓冲的有效性检查以及初始化。 
HRESULT CSimpleMsgBoxClassFactory::QueryInterface( REFIID riid, void** ppv ) 
{ 
HRESULT hrRet = S_OK; 

    //检查指针ppv是不是void*类型 
    if ( IsBadWritePtr ( ppv, sizeof(void*) )) 
        return E_POINTER; 

    //标准的QI初始化,将赋值为NULL. 
    *ppv = NULL; 

接下来检查riid,看看它是不是类工厂实现的接口之一:IUnknown 或 IclassFactory。 
    // 如果客户端请求一个有效接口,则扶植给 *ppv. 
    if ( InlineIsEqualGUID ( riid, IID_IUnknown )) 
        { 
        *ppv = (IUnknown*) this; 
        } 
    else if ( InlineIsEqualGUID ( riid, IID_IClassFactory )) 
        { 
        *ppv = (IClassFactory*) this; 
        } 
    else 
        { 
        hrRet = E_NOINTERFACE; 
        } 

最后,如果riid是有效接口,则调用接口的AddRef(),然后返回。 
    //如果返回有效接口指针,则调用AddRef() 
    if ( S_OK == hrRet ) 
        { 
        ((IUnknown*) *ppv)->AddRef(); 
        } 

    return hrRet; 
} 

ISimpleMsgBox实现 

    最后的也是必不可少的一关是ISimpleMsgBox实现,我们的代码只实现ISimpleMsgBox的方法DoSimpleMsgBox()。首先用微软的扩展类_bstr_t将bsMessageText转换成TCHAR串。 
HRESULT CSimpleMsgBoxImpl::DoSimpleMsgBox ( HWND hwndParent, BSTR bsMessageText ) 
{ 
_bstr_t bsMsg = bsMessageText; 
LPCTSTR szMsg = (TCHAR*) bsMsg;   // 如果需要的话,用_bstr_t将串转换为ANSI 
做完转换的工作后,显示信息框,然后返回。 
    MessageBox ( hwndParent, szMsg, _T("Simple Message Box"), MB_OK ); 
    return S_OK; 
} 

使用服务器的客户端 

我们已经完成了一个超级棒的COM服务器,如何使用它呢? 我们的接口一个定制接口,也就是说它只能被C或C++客户端使用。(如果在组件对象类中同时实现IDispatch接口,那我们几乎就可以在任何客户端环境中——Visual Basic,Windows Scripting Host,Web页面,PerlScript等使用COM对象。有关这方面的内容我们留待另外的文章讨论)。本文提供了一个使用ISimpleMsgBox的例子程序。这个程序基于用Win32应用程序向导建立的Hello World例子。文件菜单包含两个测试服务器的命令: 
如图所示: 
 
Test MsgBox COM Server菜单命令创建CSimpleMsgBoxImpl对象并调用DoSimpleMsgBox()。因为这 
是个简单的方法,要写的代码不长。 

我们先用CoCreateInstance()创建一个COM对象。 

void DoMsgBoxTest(HWND hMainWnd) 
{ 
ISimpleMsgBox* pIMsgBox; 
HRESULT hr; 

hr = CoCreateInstance ( __uuidof(CSimpleMsgBoxImpl),  // 组件对象类的CLSID 
                            NULL,                // 非聚合 
                            CLSCTX_INPROC_SERVER,  // 只使用进程内服务器 
                            __uuidof(ISimpleMsgBox), // 所请求接口的IID 
                           (void**) &pIMsgBox );   // 容纳接口指针的缓冲 

    if ( FAILED(hr) ) 
        return; 

然后调用DoSimpleMsgBox()方法并释放接口。 
    pIMsgBox->DoSimpleMsgBox ( hMainWnd, _bstr_t("Hello COM!") ); 
    pIMsgBox->Release(); 
} 

就这么简单。代码中从头到尾都有TRACE语句,这样在调试器中运行测试程序就可以看到服务器的每一个方法 
是如何被调用的。 
另外一个菜单命令是调用CoFreeUnusedLibraries()函数,从中你能看到服务器DllCanUnloadNow()函数的运行。 
其它细节-COM宏 
    COM代码中有些宏隐藏了实现细节,并允许在C和C++客户端使用相同的声明。本文中没有使用宏,但在例子代 
码中用到了这些宏,所以必须掌握它们的用法。下面是ISimpleMsgBox的声明 
struct ISimpleMsgBox : public IUnknown 
{ 
    // IUnknown 方法 
    STDMETHOD_(ULONG, AddRef)() PURE; 
    STDMETHOD_(ULONG, Release)() PURE; 
    STDMETHOD(QueryInterface)(REFIID riid, void** ppv) PURE; 

    // ISimpleMsgBox 方法 
    STDMETHOD(DoSimpleMsgBox)(HWND hwndParent, BSTR bsMessageText) PURE; 
}; 
STDMETHOD()包含virtual关键字,返回类型和调用规范。STDMETHOD_()也一样,除非你指定不 
同的返回类型。PURE扩展了C++的“=0”,使此函数成为一个纯虚拟函数。 
STDMETHOD()和STDMETHOD_()有对应的宏用于方法实现——STDMETHODIMP和STDMETHODIMP_()。 
例如DoSimpleMsgBox()的实现: 
STDMETHODIMP CSimpleMsgBoxImpl::DoSimpleMsgBox ( HWND hwndParent, BSTR bsMessageText ) 
{ 
  ... 
} 
最后,标准的输出函数用STDAPI宏声明,如: 
STDAPI DllRegisterServer() 
STDAPI包括返回类型和调用规范。要注意STDAPI不能和__declspec(dllexport)一起使用, 
因为STDAPI的扩展。输出必须使用.DEF文件。 

服务器注册以及反注册 
     前面讲过服务器实现了DllRegisterServer()和DllUnregisterServer()两个函数。它们的工作是创建和 
删除关于COM服务器的注册表入口。其代码都是对注册表的处理,所以在此不必赘言,只是列出DllRegisterServer()创建的注册表入口: 

 键名 键值 
 
HKEY_CLASSES_ROOT 
 
  CLSID 
 
    {7D51904E-1645-4a8c-BDE0-0F4A44FC38C4} Default="SimpleMsgBox class" 
 
      InProcServer32 Default=[path to DLL]; ThreadingModel="Apartment" 
 

关于例子代码的注释 
    本文的例子代码在一个WORKSPACE(工作间)文件中(SimpleComSvr.dsw)同时包含了服务器的源代码和测试服 
务器所用的客户端源代码。在VC的IDE环境中可以同时加载它们进行处理。在工作间的同级层次有两个工程都要 
用到的头文件,但每个工程都有自己的子目录。 
同级的公共头文件是: 
ISimpleMsgBox.h——定义ISimpleMsgBox的头文件。 
SimpleMsgBoxComDef.h——包含__declspec(uuid())的声明。这些声明都在单独的文件中,因为客户 
端需要CSimpleMsgBoxImpl的GUID,不是它的定义。将GUID移到单独的文件中,使客户端在存取GUID时不依赖 
CSimpleMsgBoxImpl的内部结构。它是接口,ISimpleMsgBox,对客户端很重要。 
正如前面所说的,必须用.DEF文件来从服务器输出四个标准的输出函数。下面是例子工程的.DEF文件: 
EXPORTS 
    DllRegisterServer   PRIVATE 
    DllUnregisterServer PRIVATE 
    DllGetClassObject   PRIVATE 
    DllCanUnloadNow     PRIVATE 

    每一行都包含函数名和PRIVATE关键字。这个关键字的意思是:此函数是输出函数,但不包含在输入库(import lib)中。也就是说客户端不能直接从代码中调用这个函数,即使是链接了输入库也不行。这个关键字时必须要用的,否则链接器会出错。 

在服务器中设置断点链 
    如果你想在服务器代码中设置断点,有两种方法:第一种是将服务器工程(MsgBoxSvr)设置为活动工程,然后开始调试。MSVC将问你调试会话要运行的可执行程序。输入客户端测试程序的全路径,你必须事先建立好。第二种方法是将客户端工程(TestClient)设置为活动工程,配置工程的从属(dependencies)属性,以便服务器工程从属于客户端工程。这样如果你改变了服务器的代码,那么在编译客户端工程时会自动重新编译服务器工程代码。最后还要做的是当你开始调试客户端时必须告诉MSVC加载服务器符号(symbols)。 
下面是设置工程属性的对话框:Project->Dependencies菜单 
 
为了加载服务器符号,打开TestClient的工程设置(Project->Settings菜单),选择Debug标签,并在Category组合框中选择Additional DLLs。在列表框中单击New一个入口,然后输入服务器DLL的全路径名。如下图所示: 
 
这样设置以后,根据实际源代码的所在位置,DLL的路径将会做自动调整。  
  
资料来源:赵湘宁 
  
碧波山庄-编程世界    
2001年3月12日收录     

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -