COM(Component Object Model)是Windows里常用的组件对象模型,在Windows上是可以上升到操作系统级别,甚至到网络分布式级别的面象对象技术,也就是按照微软定义的标准实现的COM组件,不仅可以在本地Windows操作系统上让其它程序调用,而且可以供网络内的其它系统调用(DCOM)。
OLE是对象连接/嵌入技术,它其实就是COM,只是在COM的标准上主要增加了自动化功能,OLE大家最熟悉的应用就是在窗口程序上嵌入IE内核(WebBrowser类),然后程序通过WebBrowser提供的接口与里面的网页交互,然后就有了我们的遨游,360浏览器等等。而且OLE是脚本语言最重要的精神支持者之一
另外要指出的是COM是一种对象建模模型,任何人都可以使用这种模型为自己的项目建模,像腾讯的QQ软件,整个窗口上就嵌了一个OLE对象,但他们却又没有完全按照标准去写的,你无法拿到这个对象的包含的其它对象、属性和方法,这也是肯定的,要随便让人拿到那QQ软件就随便被其它程序控制了。
本文是以讲Windows上C语言访问COM为主的文章,并不打算讨论COM的实现细节,如果要了解COM的技术细节,可以参考微软的MSDN。文章后面附有我以前用汇编写的COM调用函数,我现在基本上不写汇编了,发上来是希望它能对汇编的学习人员提供帮助。
本文链接:
http://www.hoverlees.com/blog/?p=746
COM整个模型实现是比较复杂的,但最后我们拿到的对象结构却是很简单的,它的结构如下图:
上图可以看出来,之所以我们经常把对象指针称为ppv,因为它是指向一个指针,这个指针指向对象的函数表vtable。函数表的顺序及每个函数的定义是根据不同的接口有不同的定义的,但在Windows的COM上面,所有接口都是实现IUnknown接口的,所以前三个函数肯定是IUnknown定义的方式,即QueryInterface,AddRef,Release.AddRef和Release是提供系统真正释放对象的信息,觉得我没有必要多说,QueryInterface就是查询且取得这个组件的其它接口。
每一个对象包含自己的私有变量,它在指向vtable变量接下来的连续空间里,如果是你自己实现的当然好找到变量位置,如果是别人实现的嘛,只有靠兴趣去研究了。我觉得唯一需要强调的是C语言调用vtable里的函数的时候,不要忘了第一个参数,这个参数即是对象自身的引用,它是在MSDN中的函数声明里没有公开写出来的,但实际却存在的实体。
所以,例如访问COM的Release的时候,就要按照如下方式:
typedef ULONG (__stdcall *_Release)(LPVOID _this); //定义Release函数指针,第一个参数为对象本身 //其它实现... ... //pObject通过CoCreateInstance取得 _Release* vtable=*(void**)pObject; //取得函数表指针,我们假设这个函数表全部都是Release函数的指针,当然实际上不是。 vtable[2](pObject); //调用vtable函数表第三个函数,其实就是Release.本例即实现了pObject->Release();
接下来是自动化IDispatch接口,IDispatch接口也是标准的COM实现,只是,标准的IDispatch不需要它的调用者知道vtable的排列次序。就是你的vtable不管怎么排(当然IDispatch自己的函数QueryInterface,AddRef,Release,GetTypeInfoCount,GetTypeInfo,GetIDsOfNames,Invoke 这七个函数是标准排的,其它的函数都可以在vtable里随便排,就算开发人员改了后面vtable的结构,也同样可以使用IDispatch的Invoke函数正确地调用到。IDispatch对于对象的变量也是这样的“自动化”操作,只要你知道函数的形式和变量名,你就可以正确调用。
IDispatch提供的四个函数大致吹一下:
- GetTypeInfoCount 取得对象的信息数量
- GetTypeInfo 取得对象的信息,这些信息包括接口信息,接口的函数信息,接口的变量信息等。有了这两个函数,就可以了解这个对象的所有数据类型了。用过javascript的都知道javascript有一个for..in结构可以取得一个对象的所有变量和函数,在windows上,最终就是这两个函数提供的数据支持。当然你自己写的COM这两个函数可以不提供任何信息,以达到保护的目的。
- GetIDsOfNames 通过变量/函数名称取得这个变量/函数的ID。
- Invoke 包括读/写变量,调用函数,是通过上面得到的ID直接调用的。
我写的这个函数库,主要以调用IDispatch接口为主,当然也可以调用COM。代码可以很方便使用,如果不满足读者的需求,可以参考我的代码写成自己需要的。函数库的文件定义如下:
/** * @name C语言 COM 辅助函数 * @author Hoverlees me[at]hoverlees.com http://www.hoverlees.com * @comment 自动化COM辅助函数,简化IDispatch组件的创建,属性访问及函数调用。 * * 因为简化了调用,所以大多数函数只简单返回成功(数据)或者失败,没有复杂错误判断, * 如果读者需要进行错误判断可以修改代码后使用. * 原文地址:http://www.hoverlees.com/blog/?p=746 **/ #include <objbase.h> //定义IDispatch函数指针,库里和程序可能会用到。 typedef HRESULT (__stdcall *_QueryInterface) (LPVOID _this,REFIID iid,void ** ppvObject); typedef ULONG (__stdcall *_AddRef)(LPVOID _this); typedef ULONG (__stdcall *_Release)(LPVOID _this); typedef HRESULT (__stdcall *_GetTypeInfoCount)(LPVOID _this,unsigned int FAR* pctinfo); typedef HRESULT (__stdcall *_GetTypeInfo)(LPVOID _this,unsigned int iTInfo,LCID lcid,ITypeInfo FAR* FAR* ppTInfo); typedef HRESULT (__stdcall *_GetIDsOfNames)(LPVOID _this,REFIID riid,OLECHAR FAR* FAR* rgszNames,unsigned int cNames,LCID lcid,DISPID FAR* rgDispId); typedef HRESULT (__stdcall *_Invoke)(LPVOID _this,DISPID dispIdMember,REFIID riid,LCID lcid,WORD wFlags,DISPPARAMS FAR* pDispParams,VARIANT FAR* pVarResult,EXCEPINFO FAR* pExcepInfo,unsigned int FAR* puArgErr); //下面两个函数其实就是帮助用户调用CoInitialize,CoUninitialize而已。 void CComInit(); void CComUnInit(); //定义GUID,IID常量,为简化用户转换GUID和IID省点事儿 #define CCOM_CLSID_TYPE_REFIID 0 //参数是指向CLSID结构的指针 #define CCOM_CLSID_TYPE_STRINGID 1 //参数是指向"{XXXXXXXXX-XXXXXXX-XXXXX-XXXXX}"这样的CLSID字符串 #define CCOM_CLSID_TYPE_WSTRINGID 2 //参数是指向L"{XXXXXXXXX-XXXXXXX-XXXXX-XXXXX}"这样的CLSID宽字符串 #define CCOM_CLSID_TYPE_PROGID 4 //参数是指向如"Word.Application"这样的progid字符串 #define CCOM_CLSID_TYPE_WPROGID 8 //参数是指向如L"Word.Application"这样的progid宽字符串 #define CCOM_IID_TYPE_REFIID 0 //IID参数,跟上同 #define CCOM_IID_TYPE_STRINGID 16 #define CCOM_IID_TYPE_WSTRINGID 32 #define CCOM_IID_TYPE_PROGID 64 #define CCOM_IID_TYPE_WPROGID 128 //创建对象函数,函数返回成功创建的对象,或NULL //clsid和iid可以是指向CLSID结构的指针,也可以是CLSID字符串,也可以是progid. //dwClsContext指定上下文,可以参考MSDN上CoCreateInstance函数声明. //idType表示clsid和iid的type,用|连接 //调用示例: //pObject=CComCreateInstance(L"Word.Application",&IID_IDispatch,4,NULL,CCOM_CLSID_TYPE_WPROGID | CCOM_IID_TYPE_REFIID); //因为clsid使用的宽字符proid,iid用的CLSID指针,所以idType是 CCOM_CLSID_TYPE_WPROGID | CCOM_IID_TYPE_REFIID LPVOID CComCreateInstance(void* clsid,void* iid,DWORD dwClsContext,LPUNKNOWN pUnknown,DWORD idType); //查询接口函数,pObject是上函数返回的结果。iid和idType参考上函数。 LPVOID CComQueryInterface(LPVOID pObject,void* iid,DWORD idType); //通过函数/变量名取得对应的ID。 DISPID CComGetDispIDByName(LPVOID pObject,OLECHAR* name); //通过变量ID取得变量的值。 int CComGetPropertyByID(LPVOID pObject,DISPID pid,VARIANT* ret); //通过变量名取得变量值,其实就是先调用CComGetDispIDByName取得ID,再调用上面的函数。下同。 int CComGetPropertyByName(LPVOID pObject,OLECHAR* name,VARIANT* ret); //通过ID设置变量 int CComSetPropertyByID(LPVOID pObject,DISPID pid,VARIANT* value); //通过变量名设置变量 int CComSetPropertyByName(LPVOID pObject,OLECHAR* name,VARIANT* value); //通过函数ID调用函数,args是参数数组,注意参数是从后向前设置 int CComInvokeMethodByID(LPVOID pObject,DISPID pid,int numArgs,VARIANT args[],VARIANT* ret); //通过函数名调用函数。 int CComInvokeMethodByName(LPVOID pObject,OLECHAR* function,int numArgs,VARIANT args[],VARIANT* ret);
最后附带一个调用该函数库的例子,例子是调用Word服务将Word转换成纯文本文件。
/** * @name C语言 COM 辅助函数示例 打开word文档并转换成txt文件。前提是已经安装word。 * @author Hoverlees me[at]hoverlees.com http://www.hoverlees.com **/ #include <windows.h> #include "ccom.h" void main(int argc,char* argv[]){ LPVOID inst; LPVOID documents=NULL; LPVOID document=NULL; VARIANT var; VARIANT params[10]; int result; //初始化COM CComInit(); //取得Word.Application对象 inst=CComCreateInstance(L"Word.Application",(void*)&IID_IDispatch,4,NULL,CCOM_CLSID_TYPE_WPROGID | CCOM_IID_TYPE_REFIID); //显示word窗口 var.vt=VT_BOOL; var.boolVal=1; result=CComSetPropertyByName(inst,L"Visible",&var); //通过Application->Documents 取得Documents对象 result=CComGetPropertyByName(inst,L"Documents",&var); documents=var.pdispVal; //如果要循环转换文件,这里开始循环。 //调用Documents->Open 返回Document. params[0].vt=VT_BSTR; params[0].bstrVal=SysAllocString(L"C:/a.doc"); CComInvokeMethodByName(documents,L"Open",1,params,&var); SysFreeString(params[0].bstrVal); document=var.pdispVal; //调用Document->SaveAs(filename,format); //注意参数是两个参数,应该从后向前设置参数,所以第一个param是wdFileFormat,第二个参数是文件名 params[0].vt=VT_I4; params[0].lVal=2; //wdFormatText params[1].vt=VT_BSTR; params[1].bstrVal=SysAllocString(L"C:/bb.txt"); //C:/bb.txt CComInvokeMethodByName(document,L"SaveAs",2,params,&var); SysFreeString(params[1].bstrVal); //Document->Close关闭打开的文档 CComInvokeMethodByName(document,L"Close",0,NULL,&var); //如果要循环转换文件,这里结束循环。 //Application->Quit. CComInvokeMethodByName(inst,L"Quit",0,NULL,&var); CComUnInit(); }
源代码下载:点击这里
你查一下这两个符号是在哪个头文件里定义的, 经引入对应的头文件
你好!我是c语言的初学者,根据你的源码我用tcc编译出现如下错误,该怎么解决啊?
D:\我的配置\桌面\ccom>tcc word_test.c ccom.c -lOle32 -lOleAut32
tcc: error: undefined symbol ‘IID_IDispatch’
tcc: error: undefined symbol ‘GUID_NULL’
谢谢了!
这个您得调试一下,这样才能看出是卡在哪儿了啊
您好!我用C语言调用了第三方COM控件(第三方COM控件是个独立窗口,采用OpenGL编写,主程序会不断传递数据给第三方COM控件,第三方COM控件会动态刷新显示),运行一段时间后,第三方控件就像死机了一样(不再动态刷新),主程序没有死,在主程序里面把第三方控件关闭后再重新调出来,第三方控件又可以正常运行;而在VB里面采用容器的方式调用第三方COM控件,就不会出现运行时间长而死机的现象。找了很久都没有找到这个原因,希望您帮我分析一下问题出现在哪里,谢谢了!!!
谢谢了!按照您的提示,成功调用了!
BSTR不是这样用的吧,可以看我的例子。
对象获取成功,是对象放置路径和注册路径不一致才造成获取不到。现在有个新问题:对象有个函数SetFilePath(CString path);我用
var1.vt= BSTR;
var1.bstrVal=(BSTR)(“c:\\csCom”);//这是对象的路径
result=CComInvokeMethodByName(inst,L”SetFilePath”,1,&var1,NULL);总会提示打不开路径中的文件,测试表明路径设置没有成功。是参数设置有问题吗?
COM有很多的对象啊,只是自动化对象一般都会实现IDispatch接口。 你想创建的对象不能初始化有很多的原因,说不定还有可能是那个控件不开放调用呢。
您好!已经初始化COM了,COM除了Dispatch对象外还有一些什么对象?我对COM不了解
1.可能你没有初始化COM.
2.这个对象可能不是Dispatch对象。
您好!我使用您写的函数调用别人编写的COM实现(PVCMLathe.Document),inst=CComCreateInstance(L”PVCMLathe.Document”,(void*)&IID_IDispatch,4,NULL,CCOM_CLSID_TYPE_WPROGID | CCOM_IID_TYPE_REFIID);返回值inst总是为,不知道问题出现在哪里?
你好,在这篇文章右边的”推荐文章”里.
http://www.hoverlees.com/blog/?p=725
请问一下
那个汇编写的调用函数在哪里呀…
thanks for share!