COM组件设计与应用之四---简单调用COM组件

C语言实现COM组件 简单使用纯C语言实现COM组件,帮助理解COM实现机制 立即下载

组件的启动和释放

  在第三回中,大家用“小本本”记录了一个原则:COM 组件是运行在分布式环境中的 。于是,如何启动组件立刻就遇到了严重的问题,大家看这段代码:

      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 从方便性和效率出发,确实是这么设计的。
  

 

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语言的函数声明中,尤其当参数为指针的时候,你是看不出它传递方向的。比如:
void fun(char * p1, int * p2); 请问,p1、p2 哪个是入参?哪个是出参?甚或都是入参或都是出参?由于牵扯到内存分配和释放等问题,COM 需要明确标注参数方向。以后我们写程序,就类似下面的样子: 

      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 吧?)

         ::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();

 

 

示例二、如何使用“浏览文件夹”选择对话窗。

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 图象。

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();

}

 

示例四、在桌面建立快捷方式
    在阅读代码之前,先看一下关于“快捷方式”组件的结构示意图。

 
图二、快捷方式组件的接口结构示意图

  从结构图中可以看出,“快捷方式”组件(CLSID_ShellLink),有3个(其实不止)接口,每个接口完成一组相关功能的函数。IShellLink 接口(IID_IShellLink)提供快捷方式的参数读写功能(见图三),IPersistFile 接口(IID_IPersistFile)提供快捷方式持续性文件的读写功能。对象的持续性(注5),是一个非常常用,并且功能强大的接口家族。但今天,我们只要了解其中两函数,就可以了:IPersistFile::Save()和IPersistFile:Load()。(注6)

 
图三、快捷方式中的各种属性

#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(_T("\\") != *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 GetDestTop()
{
 TCHAR path[255];
 SHGetSpecialFolderPath(NULL, path, CSIDL_DESKTOPDIRECTORY, FALSE);
 MessageBox(NULL, path, L"提示", MB_OK);
}

void OnXXX()

{

         CreateShortcut(

                 _T("c:\\winnt\\notepad.exe"),  // 记事本程序。注意,你的系统是否也是这个目录?

                 _T("c:\\Documents and Settings\\Administrator\\桌面\\我的记事本.lnk")

         );

         // 桌面上建立快捷方式(lnk)文件的全路径名。注意,你的系统是否也是这个目录?

void GetDestTop()

{

TCHAR path[255];

SHGetSpecialFolderPath(NULL, path, CSIDL_DESKTOPDIRECTORY, FALSE);

MessageBox(NULL, path, L"提示", MB_OK);

}

七、小结

  本回介绍的内容比较实用。大家不要只抄袭代码,而一定要理解它。结合 MSDN 的说明去思索代码、理解其含义。好了,想方设法把代码忘掉!三天后(如过你还没有忘记,那就再过三天),你在不参考示例代码,但可以随便翻阅 MSDN 的情况下,自己能独立地再次完成这四个例程,那么恭喜你,你已经入门了:0) 从下回开始,我们要用 ATL 做 COM 的开发工作啦,您老人家准备好了吗?


作业,留作业啦......
  1、你已经学会如何建立快捷方式了,那么你知道怎么读取它的属性吗?(如果写不出这个程序,那么你就不用继续学习了。因为......动点脑筋呀!我还没有见过象你这么笨的学生呢!)
  2、示例程序三中使用了 IPicture 接口显示一个 JPG 图象。那么你现在去完成一个功能,把 JPG 文件转换为 BMP 文件。

注1:智能指针的概念和用法,后续介绍。
注2:IDL 文件,下回就要介绍啦。
注3:东北话,想干什么都可以,反正我不管啦。
注4:聚合,也许在第30回中介绍吧:-)
注5:持续性,IPersistXXXXXX是一个非常强大的接口家族,后续介绍。
注6:想知道 IShellLink、IPersistFile接口的所有函数吗?别愣着,快去看MSDN呀

 

c++如何采用com组件的方法直接调用动态库 com组件方法调用dll 立即下载

相关推荐

教程:使用树莓派连接Pixhawk飞控

教程:使用树莓派连接Pixhawk飞控树莓派可以与Pixhawk飞控相连,读取飞控中的状态信息,同时对飞控发送指令。树莓派作为一个更高性能的计算平台,可以运行图像识别、机器学习、实时路径规划等程序,为开源无人机带来了无限可能。本文将介绍如何在树莓派上使用APSync。不需要了解Linux,即可将树莓派连接到Pixhawk飞控。使用的器材为: Pixhawk飞控 树莓派3B(Raspberry Pi

Roger今天好好搬砖了吗 5万+

COM组件和调用

COM组件和调用1、引言2、实验环境3、COM组件介绍4、实验过程4.1 利用C#创建一个COM组件4.1.1 项目创建4.1.2 配置项目属性功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图...

babyx_back的博客 6958

OpenV2X 车路协同开源平台 Columbia 版本正式发布!

OpenV2X车路协同开源平台Columbia 版本正式发布!

weixin_65571684的博客 1198

com 组件调用不起来_iOS组件化实践之路

前言iOS开发的组件化方案的文章介绍已经很多了,但是很少有能介绍如何在项目工程中进行实施的,本文则是作者在实际项目中实施组件化方案后总结的一些经验。本文不会讨论太多理论上的知识,主要集中在实施方面。1 组件化实施工具实施业务组件化是将每一个业务模块单独封装成pods,然后在主工程中通过CocoaPods以组件的方式将所有模块集成进来。组件化的实施需要依赖Git和CocoaPods进行,所以在开始之...

weixin_39611666的博客 226

pgsql行列转换

‌是PostgreSQL中的一个函数,用于将一个字符串根据正则表达式进行分割,并将结果返回为一个表格,每个分割后的部分作为一行‌‌。string_agg() 函数是 PostgreSQL 中的一个聚合函数,用于将一个列中的值连接成一个字符串。

chimchim66的博客 3501

DC竞赛 国能日新功率预测题 rank21 解决方案(适合新手)

DataCastle国能日新光伏功率预测 数据异常处 本题在测试集中提供了时间、辐照度、风速、风向、温度、压强、湿度、实发辐照度8个特征,以及标签**实际功率**。 而在训练集中只有前七个特征,无实发辐照度。 策略 在观察了数据之后,我们确定实发辐照度是一个强力特征,于是我们采取了stacking的策略: 1. 第一次训练以实发辐照度为标签,预测 测试集的实发辐照度 2....

yyhhlancelot的博客 7294

多线程调用com组件的体会 转

<br />调用任何COM组件之前,你必须首先初始化COM套件环境,即调用CoInitialize或CoInitializeEx。COM套件环境在线程的生存周期内有效,线程退出前需要调用CoUninitialize释放COM套件。(文/庄园)<br /><br />所谓COM套件,实际上是微软为了方便大家理解而起的一个名字,不过个人认为改名词很难理解。COM套件只指COM组件运行时的环境,其中包括 COM组件的数据、变量、线程调度方式。<br /><br />COM套件分为两种模式,单线程套件(STA)和多

feihuadao的专栏 2610

matlab 点云学习目录【2026最新版】

matlab 点云学习目录。博客长期更新,本文最近更新时间为:2026年6月15日。

点云侠的博客 3万+

有效的使用和设计COM智能指针——条款2:引用计数的是与非

条款2:引用计数的是与非 更多条款请前往原文出处:http://blog.youkuaiyun.com/liuchang5 使用COM做开发的程序员往往会被接口引用计数所带来的问题搞得头破血流。引用计数这个老大难问题存在的原因也相当简单:在COM开发中,客户仅仅知道组件的接口。当使用

liuchang5的专栏 1261

使用PostgreSQL构建高可用数据库集群

在现代企业级应用中,数据库的高可用性已成为业务连续性的核心要求。PostgreSQL作为一款功能强大的开源关系型数据库,其高可用架构的设计直接影响着系统的稳定性和可靠性。

九天的专栏 1257

YOLOv11模型改进:利用注意力机制实现小目标检测精度跃升

如果你在实操中遇到精度提升不明显或速度下降过多的问题,不妨尝试调整注意力模块的类型(如替换为CA)或插入层级,或针对性优化小目标的数据集增强策略。记住,小目标检测的核心是“特征强化与空间定位”,只要结合业务场景灵活调整注意力模块的组合,你也能让YOLOv11在小目标识别任务中脱颖而出。接下来,我们从模块选型到代码落地,一步步带你打造“小目标检测专精版”YOLOv11,真正帮你解决工业场景中(如安防、质检)的小目标识别痛点。在城市安防场景中,改进后的YOLOv11对远处行人、车辆(小目标)的识别准确率提升。

m0_71589036的博客 454

CComPtr用法

CComPtr用法 COM接口指针很危险,因为使用过程中需要每一个使用者都要严格并且正确的AddRef和Release,一旦出现问题,就会造成对象不能被正常释放,或者对象被重复删除,造成程序崩溃。所以使用COM接口,必须小心翼翼才行。 但是,即使所有的代码中,都正确...

aimang9690的博客 588

【Bug已解决】Failed to fetch version ECONNREFUSED 解决方案

方案适用场景推荐指数难度方案一:NODE_EXTRA_CA_CERTS代理/企业网关证书⭐⭐⭐⭐⭐低方案二:浏览器导出证书不知证书在哪⭐⭐⭐⭐中方案三:使用系统证书macOS 用户⭐⭐⭐⭐中方案四:关闭代理直连快速验证⭐⭐⭐低方案五:更新 Node.js证书列表过时⭐⭐⭐低方案六:同步系统时间时间偏差导致⭐⭐⭐低根本原因。

博客 9973

COM组件设计与应用(四)简单调用组件

一、前言  同志们、朋友们、各位领导,大家好。    VCKBASE 不得了,     网友众多文章好。  

星 语 轩 1308

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

【转载】COM组件设计与应用(四)——简单调用组件

Version:1.0 StartHTML:0000000105 EndHTML:0000226456 StartFragment:0000000127 EndFragment:0000226438 一、前言在 VCKBASE 的顶力支持下,在各位网友回帖的鼓励下,我才能顺利完成系列论文的前三回。书到本回,我们终于开始写代码啦。写点啥那?恩,有了!咱们先从如何调用现成的简单的组件开始吧...

weixin_30695195的博客 117

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

COM 组件设计与应用(四)——简单调用组件

本文摘自:http://www.vckbase.net/index.php/wv/1211 一、前言      同志们、朋友们、各位领导,大家好。     VCKBASE 不得了,     网友众多文章好。     组件设计怎么学?     知识库里闷头找!       摘自---杨老师打油集录

IT民工 4096

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

COM组件设计与应用(四)——简单调用组件

目录(?)[-] 一、前言二、组件的启动和释放三、内存分配和释放四、参数传递方向五、示例程序六、小结 本文摘自:http://www.vckbase.net/index.php/wv/1211 一、前言      同志们、朋友们、各位领导,大家好。       VCKBASE 不得了,     网友众多文章好。     组件设计怎

jiangqin115的专栏 1995

COM组件设计与应用(四) 简单调用组件

作者:杨老师  原文:http://www.vckbase.net/document/viewdoc/?id=1493 一、前言   同志们、朋友们、各位领导,大家好。     VCKBASE 不得了,     网友众多文章好。     组件设计怎么学?     知识库里闷头找!       摘自---杨老师打

c++自学 496
上一篇: 使用IShellLink 创建快捷方式
下一篇: 虚拟列表控件使用
htt9931
博客等级 码龄24年 43粉丝 14原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值