李超

cc编程笔记本。

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

COM -- 跨平台跨语言的解决方案。

这篇笔记是我学习过程中为了加深理解而写的,难免有误,如果你想学习COM,仅供参考,原文地址我贴出来,我也只是将原文照抄并整理一下条理性而已。
http://www.vckbase.com/document/viewdoc/?id=212


1. COM是什么
COM是Component Object Model的缩写,直译为中文就是组件对象模型,这句话是废话。

COM的作用是: 提供一种独立于平台与编程语言之外的,共享二进制代码的方法。

对,COM的诞生是为了功能模块的重用,可能有些人问为什么不使用STL,而使用COM呢?STL是C++源码级重用,你可以实现用VB来调用STL吗?不行,可VB可以调用C++写的COM。 因为COM是共享的二进制代码,也就是说,COM是一种二进制代码结构规范,无论C,C++,VB,DELPHI等等编程语言及开发平台,通过编译器编译出来的COM组件的二进制代码都拥有相同的结构以及内存布局,能够被互相理解,互相调用。

2. COM中的基本元素(一些COM术语)
 (1)接口:一个名字以大写的I开头的抽象基类,包含一组虚方法,接口可以从其他接口继承,但不能继承于多个接口,只能从一个接口继承。
 (2)coclass(component object class,组件对象类,通常被称为COM类): COM类通常就是一个C++类,这个类继承自一个或者多个接口,并实现它们,COM对象在内存中的表现就是这个COM类的一个实例。
 (3)COM服务器: 包含了一个或者多个coclass的二进制DLLs或者EXE执行体。
 (4)注册(Registration): 创建注册表入口的一个过程,告诉Windows操作系统COM服务器放在什么位置。
 (5)取消注册(Unregistration):从注册表删除这些注册入口。
 (6)GUID(谐音fluid,意思是全球唯一标示符Globally unique identifier): 是一个128位的数字,其实它和COM无关,在其他地方我们也经常看到它,只不过COM中的接口和coclass都拥有一个GUID,因为是全球唯一的,所以避免了名称冲突。
 (7)UUID(universally unique identifier): 同GUID。
 (8)CLSID(类ID): 是coclass的GUID。
 (9)IID(接口ID): 是interface的GUID。
 (10)HRESULT: 是一个代表着成功或错误代码的整型或长整型的数字,对COM对象的调用经常会返回一个HRESULT,虽然用H开头,但并不是句柄的意思。
 (11)COM库:操作系统的一部分,调用COM组件时就是COM库在协助你完成调用。

3. 使用和处理COM对象
  对象是类在内存中的一个实例,类决定了对象在内存中的布局,这是一句废话。
  每一种编程语言都有自己创建与管理对象的方式,比如C++使用new来开辟一块以类为模板布局的内存空间,COM既然要实现跨语言,就必须由COM库来提供COM对象的管理方式。
  下面是COM对象管理和C++对象管理所做的一个比较:
  (1) 创建一个新对象:
  C++中使用new操作符,或者在栈中创建一个对象。
  COM中使用COM库的API创建一个对象 比如 CreateObject()
  (2) 删除一个对象
  C++中使用delete操作符,或者将栈对象踢出。
  COM中,所有的COM对象保持它们自己的引用计数,调用者必须通知对象什么时候用完这个对象。当引用计数为0时,COM对象自己从内存中释放。

   创建和销毁缺一不可,创建COM对象的时候要通知COM库使用哪个接口,如果对象创建成功,COM返回一个所请求接口的指针,然后用指针指向创建的对象,以后通过该指针调用创建的对象的方法,就像使用常规C++对象一个样子。
  下面是COM对象的创建和删除的方式。
  (1)COM对象的创建:COM对象的创建需要调用COM库的API函数CoCreateInstance():

HRESULT CoCreateInstance (
REFCLSID  rclsid, 
//coclass的CLSID,例如可以传递CLSID_ShellLink创建一个COM对象来建立快捷方式。
LPUNKNOWN pUnkOuter,//只用于COM对象的聚合,利用它向现有的coclass添加新方法,参数null表示不使用聚合
DWORD     dwClsContext,//表示使用COM服务器的种类,比如进程内DLL:CLSCTX_INPROC_SERVER
REFIID    riid,// 表示请求接口的IID,例如可以传递IID_IShellLink获得IShellLink接口指针。
LPVOID*   ppv );//接口指针的地址,COM库通过这个参数返回请求的接口

在调用CoCreateInstance()的时候,他负责在注册表查找COM服务器的位置,将服务器加载到内存,并且创建你请求的coclass的实例,然后是一个例子,创建一个CLSID_ShellLink对象的实例并请求指向这个对象IShellLink接口指针。
    HRESULT hr;
    IShellLink
* pISL;
    hr 
= CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void **)&pISL);

    
if (SUCCEEDED(hr))
    
{
        cout 
<< "succeeded!" << endl;
    }

    
else
    
{
        cout 
<< "failed" << endl;
    }


  (2)删除一个COM对象:我们不需要自己去手动释放一个COM对象,当使用完的时候我们只需要告诉他你已经用完了就可以了,IUnknown是一个接口,每个COM对象都必须实现这个接口,IUnknown有一个Release()方法,可以调用这个方法通知COM对象你已经不需要使用他了,一旦调用了这个方法,就不能再次使用这个指向对象的接口了,因为COM对象已经从内存消失了。

    ::CoInitialize(NULL);

    HRESULT hr;
    IShellLink
* pISL;
    hr 
= CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void **)&pISL);

    
if (SUCCEEDED(hr))
    
{
        cout 
<< "succeeded!" << endl;
        pISL
->Release();
    }

    
else
    
{
        cout 
<< "failed" << endl;
    }


    ::CoUninitialize();

  如果你的应用程序中使用了许多不同的COM对象,再不使用某个接口的时候调用Release()方法就很重要,可以及时释放内存,以免造成不必要的开销,如果程序长时间运行,就应该在空闲时间调用CoFreeUnusedLibraries()API。这个API将检查内存中没有任何引用的COM服务器,并卸载他们,降低了应用程序的开销。 

4. 基本接口 - IUnknown
 
每个COM对象都派生自IUnknown接口。Unknown的意思并不是说这个指针是一个未知的指针,而是说这个指针可以指向任何一个COM对象,你可以不知道他究竟指向的时什么COM对象。
  IUnknown有三个方法:
  (1) AddRef() -- 通知COM对象增加它的应用计数,如果进行了一次接口指针的拷贝,就必须调用一次这个方法,并且原始的值和拷贝的值都要用到。
  (2) Release() -- 通知COM对象减少他的引用计数。
  (3) QueryInterface() -- 从COM对象请求一个接口指针,当coclass实现一个以上的接口的时候,就要用到这个方法。

前面的例子中示例了Release()的使用方式,如果一个COM对象实现了一个以上的接口的时,我们想使用这个COM对象另外一个接口的方法,就要使用QueryInterface()方法来获取另外的指针了。QueryInterface()的原型声明:

HRESULT IUnknown::QueryInterface (
    REFIID iid, 
//所请求接口的IID
    void** ppv ); //接口指针的地址,QueryInterface()通过这个参数在成功时返回接口

继续看下如何使用QueryInterface()这个方法,就拿刚才的ShellLink的例子,ShellLink实现了IShellLink和IPersistFile接口,如果已经有一个IShellLink指针,就可以调用QueryInterface()方法请求IPersistFile接口。
下面是一个完整的示例

// learncom.cpp : 定义控制台应用程序的入口点。
//

#include 
"stdafx.h"

#include 
"iostream"

#include 
"icrsint.h"  
#include 
"shobjidl.h"
#include 
"shlguid.h"

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    ::CoInitialize(NULL);

    HRESULT hrCreateShellLink;
    IShellLink
* pISL;

    
//创建一个CLSID为CLSID_ShellLink的COM对象,并使用pISL接口引用该COM对象
    hrCreateShellLink = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void **)&pISL);

    
if (SUCCEEDED(hrCreateShellLink))
    
{
        cout 
<< "CoCreateInstance ShellLink Succeeded" << endl;

        IPersistFile
* pIPF;
        HRESULT hrQIPersistFile;

        
//获取ShellLink对象的IPersistFile接口
        hrQIPersistFile = pISL->QueryInterface(IID_IPersistFile, (void**)&pIPF);

        
if (SUCCEEDED(hrQIPersistFile))
        
{
            cout 
<< "Queryinterface IID_IPersistFile Succeeded" << endl;
        }

        
else
        
{
            cout 
<< "QUeryInterface IID_IPersistFile Failed" << endl;
        }


        
//通知ShellLink对象  以后不再使用他
        pISL->Release();
    }

    
else
    
{
        cout 
<< "CoCreateInstance ShellLink Failed" << endl;
    }


    cin.
get();

    ::CoUninitialize();
    
return 0;
}


5. 仔细做好串处理
  因为COM的DLLs和EXE要独立于语言和开发平台,而不同的开发语言与开发平台对字符串的定义是不同的,比如MFC的CString和stl库中的string,C中的以0为结尾标识的char*,VB的前导位数的String等等,所以COM要定义自己的串结构来实现跨语言跨开发平台。
  任何时候,COM方法返回一个串,都会是一个UNICODE串,可以很方便的把这个串转换成wchar类型串,如果想转换成char类型串,可以看这篇笔记。
http://www.cnblogs.com/coderlee/archive/2008/01/25/1053311.html

6. 用示例代码做总结
 
1) 使用单接口COM对象

// learncom.cpp : 定义控制台应用程序的入口点。
//

#include 
"stdafx.h"
#include 
"learncom.h"
#include 
<atlconv.h>                    // ATL string conversion macros

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// 唯一的应用程序对象

CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
    
int nRetCode = 0;

    
// 初始化 MFC 并在失败时显示错误
    if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
    
{
        
// TODO: 更改错误代码以符合您的需要
        _tprintf(_T("错误: MFC 初始化失败\n"));
        nRetCode 
= 1;
    }

    
else
    
{
        WCHAR wszWallpaper[MAX_PATH];
        
string strPath;
        HRESULT hr;
        IActiveDesktop
* pIAD;

        
//初始化COM库
        CoInitialize(NULL);
        
        
//创建一个COM对象,CLSID为CLSID_ActiveDesktop,接口为IID_IActiveDesktop,使用pIAD接收
        hr = CoCreateInstance(CLSID_ActiveDesktop,
            NULL,
            CLSCTX_INPROC_SERVER,
            IID_IActiveDesktop,
            (
void**)&pIAD
            );

        
if (SUCCEEDED(hr))
        
{
            hr 
= pIAD->GetWallpaper ( wszWallpaper, MAX_PATH, 0 );
            
if (SUCCEEDED(hr))
            
{
                
char cWallpaper[MAX_PATH];

                
//将Unicode串转换成ANSI类型串
                wcstombs(cWallpaper, wszWallpaper, MAX_PATH);

                cout 
<< cWallpaper << endl;
            }

            
else
            
{
                cout 
<< "GetWallpaper Fail!" << endl;
            }


            
//释放接口
            pIAD->Release();

        }

        
else
        
{
            cout 
<< "CreateCOMObject Fail!" << endl;
        }





        
//卸载COM库
        CoUninitialize();
        cin.
get();
    }


    
return nRetCode;
}

要注意必须引用wininet.h库  而且必须在stdafx.h中的include "afx.h"后面接着引用wininet.h 位置不能放错

 2) 使用多接口的COM对象(通过QueryInterface()查询其它接口)

简述代码流程:

1 CoInitialize(NULL) 初始化COM环境

2 使用CoCreateInstance创建COM对象ActiveDesktop(活动桌面对象),GUID为宏:CLSID_ActiveDesktop 返回给接口IActiveDesktop

3 调用IActiveDesktop->GetWallpaper()方法获取当前墙纸路径

4 创建标识为CLSID_ShellLink的COM对象(快捷方式对象) 返回给接口IShellLink

5 调用IShellLink->SetPath()设置快捷方式的路径

6 调用IShellLink->QueryInterface()获取IID_IPersistFile接口

7 调用IPersistFile->Save()方法 保存快捷方式

代码如下

// learncom.cpp : 定义控制台应用程序的入口点。
//

#include 
"stdafx.h"
#include 
"learncom.h"
#include 
<atlconv.h>                    // ATL string conversion macros

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// 唯一的应用程序对象

CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
    
int nRetCode = 0;

    
// 初始化 MFC 并在失败时显示错误
    if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
    
{
        
// TODO: 更改错误代码以符合您的需要
        _tprintf(_T("错误: MFC 初始化失败\n"));
        nRetCode 
= 1;
    }

    
else
    
{
        WCHAR wszWallpaper[MAX_PATH];
        
string strPath;
        HRESULT hr;
        IActiveDesktop
* pIAD;
        IShellLink
* pISL;
        IPersistFile
* pIPF;

        
//初始化COM库
        CoInitialize(NULL);
        
        
//创建一个COM对象,CLSID为CLSID_ActiveDesktop,接口为IID_IActiveDesktop,使用pIAD接收
        hr = CoCreateInstance(CLSID_ActiveDesktop,
            NULL,
            CLSCTX_INPROC_SERVER,
            IID_IActiveDesktop,
            (
void**)&pIAD
            );

        
if (SUCCEEDED(hr))
        
{
            hr 
= pIAD->GetWallpaper ( wszWallpaper, MAX_PATH, 0 );
            
if (SUCCEEDED(hr))
            
{
                
char cWallpaper[MAX_PATH];

                
//将Unicode串转换成ANSI类型串
                wcstombs(cWallpaper, wszWallpaper, MAX_PATH);

                cout 
<< cWallpaper << endl;

                
//创建Shell Link组件对象 并使用IShellLink类型的接口pISL指向新创建的对象
                hr = CoCreateInstance(CLSID_ShellLink,
                    NULL,
                    CLSCTX_INPROC_SERVER,
                    IID_IShellLink,
                    (
void **&pISL);

                
if (SUCCEEDED(hr))
                
{
                    hr 
= pISL->SetPath(wszWallpaper);
                    
if (SUCCEEDED(hr))
                    
{
                        
//获取IPersistFile接口
                        hr = pISL->QueryInterface(IID_IPersistFile, (void **)&pIPF);
                        
if (SUCCEEDED(hr))
                        
{
                            pIPF
->Save(TEXT("C:\\quick.lnk"), FALSE);
                            cout 
<< "save success!" << endl;

                            
//释放IPersistFile接口
                            pIPF->Release();
                        }

                        
else
                        
{
                            cout 
<< "persistfaile save fail!" << endl;
                        }

                    }

                    
else
                    
{
                        cout 
<< "shell link setpath fail!" << endl;
                    }


                    
//释放IShellLink接口
                    pISL->Release();
                }

                
else
                
{
                    cout 
<< "create shell link fail!" << endl;
                }

            }

            
else
            
{
                cout 
<< "GetWallpaper Fail!" << endl;
            }


            
//释放IActiveDesktop接口
            pIAD->Release();

        }

        
else
        
{
            cout 
<< "CreateCOMObject Fail!" << endl;
        }





        
//卸载COM库
        CoUninitialize();
        cin.
get();
    }


    
return nRetCode;
}

posted on 2008-01-25 10:27  coderlee  阅读(4027)  评论(0编辑  收藏  举报