D语言中国主页  D语言编辑器SciTE4D   DWin库 D语言官方网站
D语言编译器1.x最新版 OpenSource   Tango   webnews  Wiki

查看完整版本: COM组件设计与应用4 简单调用组件

yidabu 2007-5-16 13:39

COM组件设计与应用4 简单调用组件

COM组件设计与应用4 简单调用组件知识若不分享 实在没有意义 http://www.d-programming-language-china.org 20070512点击下面网址查看原文:http://www.d-programming-language-china.org        by:        杨老师前言        同志们、朋友们、各位领导,大家好。        VCKBASE 不得了,        网友众多文章好。        组件设计怎么学?        知识库里闷头找!        摘自---杨老师打油集录        在 VCKBASE 的顶力支持下,在各位网友回帖的鼓励下,我才能顺利完成系列论文的前三回。书到本回,我们终于开始写代码啦。写点啥那?恩,有了!咱们先从如何调用现成的简单的组件开始吧,同时也顺便介绍一些相关的知识。组件的启动和释放        在第三回中,大家用“小本本”记录了一个原则:COM 组件是运行在分布式环境中的 。于是,如何启动组件立刻就遇到了严重的问题,大家看这段代码:        QUOTE:                                p = new 对象;                p->对象函数();                delete p;                        这样的代码再熟悉不过了,在本地进程中运行是不会有问题的。但是你想想,如果这个对象是在“地球另一边”的计算机上,结果会如何?嘿嘿,C++ 在设计 new 的时候,可没有考虑远程的实现呀(计算机语言当然不会,也没必要去设计)。因此启动组件、调用接口的功能,当然就由 COM 系统来实现了。        图一 组件调用机制                由上图可以看出,当调用组件的时候,其实是依靠代理(运行在本地)和存根(运行在远端)之间的通讯完成的。具体来说,当客户程序通过 CoCreateInstance() 函数启动组件,则代理接管该调用,它和存根通讯,存根则它所在的本地(相对于客户程序来说就是远程了)执行 new 操作加载对象。对于初学者,你可以不用理它,代理和存根对我们来说是透明的。只要大约知道是怎么一回事就一切OK了。                问题又来了,这个远程的对象什么时候消灭呢?在第二回介绍接口概念的时候,当时我们特意忽略了两个函数,就是IUnknown::AddRef()和IUnknown::Release(),从函数名就能猜到了,一个是对内部引用记数器(Ref)加1,一个是释放(减1),当记数器减为0的时候,就是释放的机会啦。看起来很复杂,没办法,因为这是在介绍原理。其实在我们写程序的时候到比较简单,请大家遵守几个原则:                1、启动组件得到一个接口指针(Interface)后,不要调用AddRef()。因为系统知道你得到了一个指针,所以它已经帮你调用了AddRef()函数;        2、通过QueryInterface()得到另一个接口指针后,不要调用AddRef()。因为......和上面的道理一样;        3、当你把接口指针赋值给(保存到)另一个变量中的时候,请调用AddRef();        4、当不需要再使用接口指针的时候,务必执行Release()释放;        5、当使用智能指针的时候,可以省略指针的维护工作;(注1)内存分配和释放                自从学习了C语言,老师就教导我们说:对于动态内存的申请和释放,一定要遵守“谁申请,谁释放”的原则。在此原则的指导下,不仅是我、不仅是你,就连特级大师都设计了这样怪怪的函数:                        函数        说明        评论                        GetWindowText(HWND,LPTSTR,int)        取得窗口标题。需要在参数中给出保存标题所使用的内存指针,和这块内存的尺寸。        晕!我又不知道窗口标题的长度,居然还要我提供尺寸?!没办法,只能估摸着给一个大一些的尺寸吧。                        sprintf(char *,const char *,...)        格式化一个字符串。这个函数不用给出缓冲区的长度啦。        恩,虽然不用给出长度了,但你敢给个小尺寸吗?哼!                        int CListBox::GetTextLen(int)CListBox::GetText(int,LPTSTR)        取得列表窗中子项目的标题。需要调用两个函数,先取得长度,然后分配内存,再实际取得标题内容。        真烦!                        说实在的,不但函数调用者感觉别扭,就连函数设计者心情也不会爽的,而这一切都是为了满足所谓“谁申请,谁释放”的原则。 解决这个问题最好的方式就是:函数内部根据实际需要动态申请内存,而调用者负责释放。这虽然违背了上述原则,但 COM 从方便性和效率出发,确实是这么设计的。( 本文出处: http://www.d-programming-language-china.org )                                 C语言        C++语言        Windows 平台        COM        IMalloc 接口        BSTR                        申请        malloc()        new        GlobalAlloc()        CoTaskMemAlloc()        Alloc()        SysAllocString()                        重新申请        realloc()                 GlobalReAlloc()        CoTaskRealloc()        Realloc()        SysReAllocString()                        释放        free()        delete        GlobalFree()        CoTaskMemFree()        Free()        SysFreeString()                        以上这些函数必须要按类型配合使用(比如:new 申请的内存,则必须用 delete 释放)。在 COM 内部,当然你可以随便使用任何类型的内存分配释放函数,但组件如果需要与客户进行内存的交互,则必须使用上表中的后三类函数族。        1、BSTR 内存在上回书中,已经有比较丰富的介绍了,不再重复;        2、CoTaskXXX()函数族,其本质上就是调用C语言的函数(malloc...);        3、IMalloc 接口又是对 CoTaskXXX() 函数族的一个包装。包装后,同时增强了一些功能,比如:IMalloc::GetSize()可以取得尺寸,使用 IMallocSpy 可以监视内存的使用;参数传递方向        在C语言的函数声明中,尤其当参数为指针的时候,你是看不出它传递方向的。比如:        QUOTE:                                void fun(char * p1, int * p2);                请问,p1、p2 哪个是入参?哪个是出参?甚或都是入参或都是出参?由于牵扯到内存分配和释放等问题,COM 需要明确标注参数方向。以后我们写程序,就类似下面的样子:        QUOTE:                                HRESULT Add([in] long n1, [in] long n2, [out] long *pnSum);    // IDL文件(注2)                STDMETHOD(Add)(/*[in]*/ long n1, /*[in]*/ long n2, /*[out]*/ long *pnSum);    // .h文件                如果参数是动态分配的内存指针,那么遵守如下的规定:                        方向        申请人        释放人        提示                        [in]        调用者        调用者        组件接收指针后,不能重新分配内存                        [out]        组件        调用者        组件返回指针后,调用者“爱咋咋地”(注3)                        [in,out]        调用者        调用者        组件可以重新分配内存                示例程序        示例一、由 CLSID 得到 ProgID。(程序以 word 为例子。如果运行不正确,嘿嘿,你没有安装 word 吧?)( 本文出处: http://www.d-programming-language-china.org )        QUOTE:                                ::CoInitialize( NULL );                                HRESULT hr;                // {000209FF-0000-0000-C000-000000000046} = word.application.9                CLSID clsid = {0x209ff,0,0,{0xc0,0,0,0,0,0,0,0x46}};                LPOLESTR lpwProgID = NULL;                                hr = ::ProgIDFromCLSID( clsid, &lpwProgID );                if ( SUCCEEDED(hr) )                {                    ::MessageBoxW( NULL, lpwProgID, L"ProgID", MB_OK );                                    IMalloc * pMalloc = NULL;                    hr = ::CoGetMalloc( 1, &pMalloc );    // 取得 IMalloc                    if ( SUCCEEDED(hr) )                    {                        pMalloc->Free( lpwProgID );    // 释放ProgID内存                        pMalloc->Release();    // 释放IMalloc                    }                }                                ::CoUninitialize();                D语言论坛 http://www.d-programming-language-china.org 按:        D语言的版本是这样写的:        [Copy to clipboard] [ - ]CODE:                                import std.c.windows.com;                import std.c.windows.windows;                import std.stdio;                            //for writefln                                pragma(lib,"ole32.lib");        //for ProgIDFromCLSID                                // http://bbs.d-programming-language-china.org  注:先声明函数原型                extern(Windows)                {                    //http://msdn2.microsoft.com/en-us/library/ms690567.aspx                    HRESULT ProgIDFromCLSID(ref CLSID clsid, LPOLESTR* lplpszProgID);                }                                extern(C) int wcslen(wchar*);        //得到宽字符的长度                                //ProgID=InternetExplorer.Application.1                static CLSID CLSID_IE1 =                    {0x0002DF01,0x0000,0x0000,[0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46]};                                void main()                {                    try                    {                        wchar* lpwProgID;                        HRESULT hr = ProgIDFromCLSID(CLSID_IE1,&lpwProgID);                        if (hr==S_OK)                        {                            writefln("lpwProgID=%s", lpwProgID[0..wcslen(lpwProgID)] );        //指针和数组相似,这是yidabu第一次用指针                        }                    }                    catch(Exception e)                    {                        printf("catch %.*s\n", e.msg);                    }                    finally                    {                                    }                }                结果打印出:        QUOTE:                                lpwProgID=InternetExplorer.Application.1                示例二、如何使用“浏览文件夹”选择对话窗。        QUOTE:                                CString BrowseFolder(HWND hWnd, LPCTSTR lpTitle)                {                // 调用 SHBrowseForFolder 取得目录(文件夹)名称                // 参数 hWnd: 父窗口句柄                // 参数 lpTitle: 窗口标题                                char szPath[MAX_PATH]={0};                BROWSEINFO m_bi;                                m_bi.ulFlags = BIF_RETURNONLYFSDIRS    | BIF_STATUSTEXT;                m_bi.hwndOwner = hWnd;                m_bi.pidlRoot = NULL;                m_bi.lpszTitle = lpTitle;                m_bi.lpfn = NULL;                m_bi.lParam = NULL;                m_bi.pszDisplayName = szPath;                                LPITEMIDLIST pidl = ::SHBrowseForFolder( &m_bi );                if ( pidl )                {                    if( !::SHGetPathFromIDList ( pidl, szPath ) )    szPath[0]=0;                                    IMalloc * pMalloc = NULL;                    if ( SUCCEEDED ( ::SHGetMalloc( &pMalloc ) ) )    // 取得IMalloc分配器接口                    {                        pMalloc->Free( pidl );    // 释放内存                        pMalloc->Release();    // 释放接口                    }                }                return szPath;                }                示例三、在窗口中显示一幅 JPG 图象。        QUOTE:                                void CxxxView::OnDraw(CDC* pDC)                {                ::CoInitialize(NULL);    // COM 初始化                HRESULT hr;                CFile file;                                file.Open( "c:\\aa.jpg", CFile::modeRead | CFile::shareDenyNone );    // 读入文件内容                DWORD dwSize = file.GetLength();                HGLOBAL hMem = ::GlobalAlloc( GMEM_MOVEABLE, dwSize );                LPVOID lpBuf = ::GlobalLock( hMem );                file.ReadHuge( lpBuf, dwSize );                file.Close();                ::GlobalUnlock( hMem );                                IStream * pStream = NULL;                IPicture * pPicture = NULL;                                // 由 HGLOBAL 得到 IStream,参数 TRUE 表示释放 IStream 的同时,释放内存                hr = ::CreateStreamOnHGlobal( hMem, TRUE, &pStream );                ASSERT ( SUCCEEDED(hr) );                                hr = ::OleLoadPicture( pStream, dwSize, TRUE, IID_IPicture, ( LPVOID * )&pPicture );                ASSERT(hr==S_OK);                                long nWidth,nHeight;    // 宽高,MM_HIMETRIC 模式,单位是0.01毫米                pPicture->get_Width( &nWidth );    // 宽                pPicture->get_Height( &nHeight );    // 高                                ////////原大显示//////                CSize sz( nWidth, nHeight );                pDC->HIMETRICtoDP( &sz );    // 转换 MM_HIMETRIC 模式单位为 MM_TEXT 像素单位                pPicture->Render(pDC->m_hDC,0,0,sz.cx,sz.cy,                    0,nHeight,nWidth,-nHeight,NULL);                                ////////按窗口尺寸显示////////                //    CRect rect;    GetClientRect(&rect);                //    pPicture->Render(pDC->m_hDC,0,0,rect.Width(),rect.Height(),                //        0,nHeight,nWidth,-nHeight,NULL);                                if ( pPicture ) pPicture->Release();// 释放 IPicture 指针                if ( pStream ) pStream->Release();    // 释放 IStream 指针,同时释放了 hMem                                ::CoUninitialize();                }                示例四、在桌面建立快捷方式        在阅读代码之前,先看一下关于“快捷方式”组件的结构示意图。( 本文出处: http://www.d-programming-language-china.org )        图二、快捷方式组件的接口结构示意图                从结构图中可以看出,“快捷方式”组件(CLSID_ShellLink),有3个(其实不止)接口,每个接口完成一组相关功能的函数。IShellLink 接口(IID_IShellLink)提供快捷方式的参数读写功能(见图三),IPersistFile 接口(IID_IPersistFile)提供快捷方式持续性文件的读写功能。对象的持续性(注5),是一个非常常用,并且功能强大的接口家族。但今天,我们只要了解其中两函数,就可以了:IPersistFile::Save()和IPersistFile:Load()。(注6)        图三、快捷方式中的各种属性        QUOTE:                                #include < atlconv.h >                void CreateShortcut(LPCTSTR lpszExe, LPCTSTR lpszLnk)                {                // 建立块捷方式                // 参数 lpszExe: EXE 文件全路径名                // 参数 lpszLnk: 快捷方式文件全路径名                                ::CoInitialize( NULL );                                IShellLink * psl = NULL;                IPersistFile * ppf = NULL;                                HRESULT hr = ::CoCreateInstance(    // 启动组件                    CLSID_ShellLink,    // 快捷方式 CLSID                    NULL,    // 聚合用(注4)                    CLSCTX_INPROC_SERVER, // 进程内(Shell32.dll)服务                    IID_IShellLink,    // IShellLink 的 IID                    (LPVOID *)&psl );    // 得到接口指针                                if ( SUCCEEDED(hr) )                {                    psl->SetPath( lpszExe );    // 全路径程序名                //        psl->SetArguments();    // 命令行参数                //        psl->SetDescription();    // 备注                //        psl->SetHotkey();    // 快捷键                //        psl->SetIconLocation();    // 图标                //        psl->SetShowCmd();    // 窗口尺寸                                    // 根据 EXE 的文件名,得到目录名                    TCHAR szWorkPath[ MAX_PATH ];                    ::lstrcpy( szWorkPath, lpszExe );                    LPTSTR lp = szWorkPath;                    while( *lp )    lp++;                    while( ''\\'' != *lp )    lp--;                    *lp=0;                                    // 设置 EXE 程序的默认工作目录                    psl->SetWorkingDirectory( szWorkPath );                                    hr = psl->QueryInterface(    // 查找持续性文件接口指针                        IID_IPersistFile,    // 持续性接口 IID                        (LPVOID *)&ppf );    // 得到接口指针                                    if ( SUCCEEDED(hr) )                    {                        USES_CONVERSION;    // 转换为 UNICODE 字符串                        ppf->Save( T2COLE( lpszLnk ), TRUE );    // 保存                    }                }                if ( ppf )    ppf->Release();                if ( psl )    psl->Release();                                ::CoUninitialize();                }                                void OnXXX()                {                CreateShortcut(                    _T("c:\\winnt\\notepad.exe"),    // 记事本程序。注意,你的系统是否也是这个目录?                    _T("c:\\Documents and Settings\\Administrator\\桌面\\我的记事本.lnk")                );                // 桌面上建立快捷方式(lnk)文件的全路径名。注意,你的系统是否也是这个目录?                // 如果用程序实现寻找桌面的路径,则可以查注册表                // HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders                }        小结        本回介绍的内容比较实用。大家不要只抄袭代码,而一定要理解它。结合 MSDN 的说明去思索代码、理解其含义。好了,想方设法把代码忘掉!三天后(如过你还没有忘记,那就再过三天),你在不参考示例代码,但可以随便翻阅 MSDN 的情况下,自己能独立地再次完成这四个例程,那么恭喜你,你已经入门了:0) 从下回开始,我们要用 ATL 做 COM 的开发工作啦,您老人家准备好了吗?        作业,留作业啦......          1、你已经学会如何建立快捷方式了,那么你知道怎么读取它的属性吗?(如果写不出这个程序,那么你就不用继续学习了。因为......动点脑筋呀!我还没有见过象你这么笨的学生呢!)        2、示例程序三中使用了 IPicture 接口显示一个 JPG 图象。那么你现在去完成一个功能,把 JPG 文件转换为 BMP 文件。        --------------------------------------------------------------------------------        注1:智能指针的概念和用法,后续介绍。        注2:IDL 文件,下回就要介绍啦。        注3:东北话,想干什么都可以,反正我不管啦。        注4:聚合,也许在第30回中介绍吧:-)        注5:持续性,IPersistXXXXXX是一个非常强大的接口家族,后续介绍。        注6:想知道 IShellLink、IPersistFile接口的所有函数吗?别愣着,快去看MSDN呀......( 本文出处: http://www.d-programming-language-china.org )( lastupdate:20070516 最新文章请访问http://www.d-programming-language-china.org )关于一大步成功社区:yidabu提倡在交流中学习,在分享中提高收集感兴趣的知识,写下心得,通过网络与别人一起分享理解一点就实践一步,收获什么就分享什么,成功就是这样一点点一步步累积起来的网络只是一个工具,只有自己身心提高才是实实在在的。d-programming-language-china.org为大家提供一个学习交流各种知识的平台
页: [1]
查看完整版本: COM组件设计与应用4 简单调用组件