如何修改jpg文件缩略图的图标

如何修改jpg文件缩略图的图标,第1张

摘要:本文讲述了在Visual C++ 60下显示JPEG、GIF等格式标准的图像的一种实现起来比较简便的方法,对实现过程作有详细的说明。 关键字:图像、JPEG、GIF、Microsoft Visual C++ 60 一、 引言 JPEG图像压缩标准随然是一种有损图像压缩标准,但由于人眼视觉的不敏感,经压缩后的画质基本没有发生变化,很快便以较高的压缩率得到了广泛的认可。GIF格式虽然仅支持256色但它对于颜色较少的图像有着很高的压缩率,甚至超过JPEG标准,也得到了广泛的认同。但作为众多程序员的一个重要的开发工具--Microsoft Visual C++ 60的MFC库却仅对没有经过任何压缩的BMP位图文件有着良好的支持,可以读取、显示、存储甚至在内存中创建一块内存位图。由于BMP格式的图像没有经过任何的压缩,不论是作为程序的外部文件,还是作为程序的内部资源都要占据大量的空间,尤其是后者会大大增加可执行文件的长度。可以看出,如果能用经过压缩、具有较好的压缩率的JPEG或GIF格式的图像来取代BMP文件在VC中的应用,无疑还是很有吸引力的。 二、 设计思路 虽然有一些 *** 作、处理JPEG、GIF等其他格式图像的Active X控件,但总的来说使用起来并不太方便,笔者经过实验摸索,总结出了一种借助于COM接口的OLE方法来实现上述功能的一种简便方法,现介绍如下以飨广大读者: 下面我们要使用IPicture 的COM接口,有必要对该图像接口做些了解:该接口主要管理图像对象及其属性,图像对象为 位图、图标和图元等提供一种与语言无关的抽象。和标准的字体对象一样,系统也提供了对图像对象的标准实现。其主要的接口是IPicture和IPictureDisp,后者是由IDispatch接口派生以便通过自动化对图像的属性进行访问。图像对象也支持外部接口IPropertyNotifySink,以便用户能在图像属性发生改变时作出决定。图像对象也支持IPersistStream接口,所以它能从一个IStream接口的实例对象保存、装载自己,而IStream接口也支持对流对象的数据读写。 我们可以用函数OleLoadPicture从包含有图像数据的流中装载图像。该函数简化了基于流的图像对象的创建过程,可以创建一个新的图像对象并且用流中的内容对它进行初始化。其函数原型为: STDAPI OleLoadPicture( IStream pStream, //指向包含有图像数据的流的指针 LONG lSize, //从流中读取的字节数 BOOL fRunmode, //图像属性对应的初值 REFIID riid, //涉及到的接口标识,描述要返回的接口指针的类型 VOID ppvObj // 在rrid中用到的接口指针变量的地址) ; 三、 具体的实现 在显示图像之前,首先要获取到图像文件的存放路径,这里采用标准的文件打开对话框来选取图像文件,文件名存放在CString 型的变量m_sPath中: CFileDialog dlg(TRUE,"jpg","jpg", OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT, "JPEG文件(jpg)|jpg|GIF文件(gif)|gif||",NULL); if(dlgDoModal()==IDOK) { m_sPath=dlgGetPathName(); Invalidate(); } 为简单计,图形显示的代码直接在视类中的OnDraw中编写,首先打开文件并判断文件的可用性,并把文件内容放到流接口IStream的对象pStm中: IStream pStm; CFileStatus fstatus; CFile file; LONG cb; …… if (fileOpen(m_Path,CFile::modeRead)&&fileGetStatus(m_Path,fstatus)&& ((cb = fstatusm_size) != -1)) { HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, cb); LPVOID pvData

IE实例遍历实现

---- 首先我们来看系统是如何知道当前有多少个IE的实例在运行。

----

我们知道在Windows体系结构下,一个应用程序可以通过 *** 作系统的运行对象表来和这些应用的实例进行交互。但是IE当前的实现机制是不在运行对象表中进行注册,所以需要采用其他的方法。我们知道可以通过ShellWindows集合来代表属于shell的当前打开的窗口的集合,而IE就是属于shell的一个应用程序。

---- 下面我们描述一下用VC实现对当前

IE实例的进行遍历的方法。IShellWindows是关于系统shell的一个接口,我们可以定义一个如下的接口变量:

SHDocVw::IShellWindowsPtr m_spSHWinds;

然后创建变量的实例:

m_spSHWindsCreateInstance

(__uuidof(SHDocVw::ShellWindows));

通过IShellWindows接口的方法GetCount

可以得到当前实例的数目:

long

nCount = m_spSHWinds- >GetCount();

通过IShellWindows接口的方法Item

可以得到每一个实例对象

IDispatchPtr

spDisp;

_variant_t

va(i, VT_I4);

spDisp

= m_spSHWinds->Item(va);

然后我们可以判断实例对象是不是

属于IE浏览器对象,通过下面的语句实现:

SHDocVw::IWebBrowser2Ptr

spBrowser(spDisp);

assert(spBrowser

!= NULL)

----在得到了IE浏览器对象以后,我们可以调用IWebBrowser2Ptr接口的方法来得到当前的文档对象的指针:

MSHTML::IHTMLDocument2Ptr spDoc(spBrowser->GetDocument());

----

然后我们就可以通过这个接口对这个文档对象进行 *** 作,比如通过Gettitle得到文档的标题。

----

我们在浏览网络的时候,一般总会同时开很多IE的实例,如果这些页面都是很好的话,我们可能想保存在硬盘上,这样,我们需要对每一个实例进行保存,而如果我们采用上面的原理,我们可以得到每一个IE的实例及其网页对象的接口,这样就可以通过一个简单的程序来批量的保存当前的所有打开的网页。采用上面介绍的方法实现了对当前IE实例的遍历,但是我们希望得到每一个IE实例所产生的事件,这就需要通过DLL的机制来实现。

---- 3.和IE相绑定的DLL的实现

----

我们介绍一下如何建立和IE进行绑定的DLL的实现的过程。为了和IE的运行实例进行绑定,我们需要建立一个能够和每一个IE实例进行绑定的DLL。IE的启动过程是这样的,当每一个IE的实例启动的时候,它都会在注册表中去寻找这个的一个CLSID,具体的注册表的键位置为:

HKEY_LOCALL_MACHINE\SOFTWARE\Microsoft\Windows

\CurrentVersion\Explorer\Browser Helper Objects

----

当在这个键位置下存在CLSIDs的时候,IE会通过使用CoCreateInstance()方法来创建列在该键位置下的每一个对象的实例。注意对象的CLSIDs必须用子键而非名字值的形式表现,比如{DD41D66E-CE4F-11D2-8DA9-00A0249EABF4}

就是一个有效的子键。我们使用DLL的形式而非EXE的形式的原因是因为DLL和IE实例运行在同一个进程空间里面。每一个这种形式的DLL必须实现接口IObjectWithSite,其中方法SetSite必须被实现。通过这个方法,我们自己的DLL就可以得到一个指向IE

COM对象的IUnknown的指针,实际上通过这个指针我们就可以通过COM对象中的方法QueryInterface来遍历所有可以得到的接口,这是COM的基本的机制。当然我们需要的只是IWebBrowser2这个接口。

----

实际上我们建立的是一个COM对象,DLL只不过是COM对象的一种表现形式。我们建立的COM对象需要建立和实现的方法有:

----1.

IOleObjectWithSite接口的方法SetSite必须实现。实际上IE实例通过这个方法向我们的COM对象传递一个接口的指针。假设我们有一个接口指针的变量,不妨设为:

----CComQIPtr< IWebBrowser2, &IID_IWebBrowser2 >

m_myWebBrowser2;

----

我们就可以在方法SetSite中把这个传进来的接口指针赋给m_myWebBrowser2。

2. 在我们得到了指向IE

COM对象的接口后,我们需要把自己的DLL和IE实例所发生的事件相关连,为了实现这个目的,需要介绍两个接口:

----(1)

IConnectionPointContainer。这里使用这个接口的目的是用来根据它得到的IID来建立和DLL的一个特定的连接。比如我们可以进行如下的定义:

CComQIPtr< IConnectionPointContainer,

&IID_IConnectionPointContainer

>

spCPContainer(m_myWebBrowser2);

----然后,我们需要把所有IE中发生的事件和我们的DLL进行通讯,可以使用

IConnectPoint。

----(2)

IConnectPoint。通过这个接口,客户可以对连接的对象开始或者是终止一个advisory循环。IConnectPoint有两个主要的方法,一个为Advice,另一个为Unadvise。对于我们的应用来说,Advise是用来在每一个IE发生的事件和DLL之间建立一个通道。而Unadvise就是用来终止以前用Advise建立的通知关系。比如我们可以定义IConnectPoint接口如下:

CComPtr< IConnectionPoint > spConnectionPoint;

----

然后,我们要使所有在IE实例中发生的事件和我们的DLL相关,可以使用如下的方法:

hr = spCPContainer->FindConnectionPoint(

DIID_DWebBrowserEvents2, &spConnectionPoint);

----然后我们通过IConnectPoint接口的方法Advice使每当IE有一个新的事件发生的时候,都能够让我们的DLL知道。可以用如下的语句实现:

hr = spConnectionPoint- >Advise(

(IDispatch)this, &m_dwIDCode);

----在把IE实例中的事件和我们的DLL之间建立联系以后,我们可以通过IDispatch接口的Invoke()方法来处理所有的IE的事件。

----3.

IDispatch接口的Invoke()方法。IDispatch是从IUnknown中继承的一个接口的类型,通过COM接口提供的任何服务都可以通过IDispatch接口来实现。IDispatch::Invoke的工作方式同vtbl幕后的工作方式是类似的,Invoke将实现一组按索引来访问的函数,我们可以对Invoke方法进行动态的定制以提供不同的服务。Invoke方法的表示如下:

STDMETHOD(Invoke)(DISPID dispidMember,REFIID

riid, LCID lcid, WORD wFlags,

DISPPARAMS pdispparams, VARIANT pvarResult,

EXCEPINFO pexcepinfo, UINT puArgErr);

----其中,DISPID是一个长整数,它标识的是一个函数。对于IDispatch的某一个特定的实现,DISPID都是唯一的。IDispatch的每一个实现都有其自己的IID,这里dispidMemeber实际上是可以认为是和IE实例所发生的每一个事件相关的方法,比如:DISPID_BEFORENAVIGATE2,DISPID_NAVIGATECOMPLETE2等等。这个方法中另外一个比较重要的参数是DISPPARAMS,它的结构如下:

typedef struct tagDISPPARAMS

{

VARIANTARG

rgvarg;

//VARIANTARG是同VARAIANT相同的,可以在

//OAIDLIDL中找到。所以实际上rgvarg是一个参数数

//组

DISPID rgdispidNameArgs; //命名参数的DISPID

unsigned

int

cArgs; //表示数组中元素的个数

unsigned

int

CnameArgs; //命名元素的个数

}DISPPARAMS

----要注意的是每一个参数的类型都是VARIANTARG,所以在IE和我们DLL之间可以传递的参数类型的数目是有限的。只有那些能够被放到VARIANTARG结构中的类型才可以通过调度接口进行传递。比如对于事件DISPID_NAVIGATECOMPLETE2来说:第一个参数表示IE在访问的URL的值,类型是VT_BYREF|VT_VARIANT。注意DISPID_NAVIGATECOMPLETE2等DISPID已经在VC中被定义,我们可以直接进行使用。如上说述,我们在方法Invoke中可以得到所有IE实例所发生的事件,我们可以把这些数据放到文件中进行事后的分析,也可以放到一个列表框中实时的显示。

---- 4.微软的HTML文档对象模型和应用分析

----

下面我们来看如何得到网页文档的接口:网页文档的接口为IHTMLDocument2,可以通过调用IE

COM对象的get_Document方法来得到网页的接口。使用如下的语句:

hr = m_spWebBrowser2- >get_Document(&spDisp);

CComQIPtr< IHTMLDocument2,

&IID_IHTMLDocument2 > spHTML;

spHTML = spDisp;

----

这样我们就得到了网页对象的接口,然后我们就可以对网页进行分析,比如通过IHTMLDocument2提供的方法get_URL我们可以得到和该网页相关的URL的地址值,通过get_forms方法可以该网页中所有的Form对象的集合。实际上W3C组织已经制定了一个DOM(Document

Objec

Model)标准,当然这个标准不仅仅是针对HTML,同时还是针对XML制定的。W3C组织只是定义了网页对象的接口,不同的公司可以采用不同的语言和方法进行具体的实现。按照W3C组织定义的网页对象被认为是动态的,即用户可以动态的对网页对象里面所包含的每一个对象进行 *** 作。这里的对象可以是指一个输入框,也可以是图象和声音等对象。同时按照W3C的正式文档的说明,网页对象是可以动态增加和删除的。事实上,很少有厂商实现了DOM定义的所有功能。微软对网页对象的定义也基本上是按照这个标准实现的。但是当前的接口还不支持动态的增加和删除元素,但是可以对网页中的基本元素进行属性的修改。比如IHTMLElementCollection表示网页中一些基本的元素的集合,IHTMLElement表示网页中的一个基本的元素。而象IHTMLOptionElement接口就表示一个特定的元素Option。基本的元素都有setAttribute和geAttribute方法来动态的设置和得到元素的名称和值。

----

较为常见的一个应用是我们能够分析网页中是否有需要填写的Forms,如果这个网址的Forms以前已经填写过而且数据我们已经保存下来的话,我们就可以把数据自动放到和该URL下的Forms的相关的位置中去。另外,我们可以总结网页上需要填写的Form的数据项,先对这些数据项进行赋值,以后碰到有相同的数据项的时候就自动把我们赋值的内容填写进去。实际上Form是对象,Form中包含的元素,比如INPUT,OPTION,SELECT等类型的输入元素都是对象。

----

另外一个可以想到的应用是自动对网页中的文本进行翻译,因为我们可以修改网页中任何对象的属性,所以我们可以把里面不属于本国语言的部分自动翻译成本国语言,当然真正的实现还要靠自然语言理解方面技术的突破,但是IE浏览器的接口和对象的形式使我们能够灵活的控制整个IE,无论是从事件对象还是到网页对象。

在COM标准中,一个组件程序也被称为一个模块,它可以是一个动态连接库(DLL), 被称为进程内组件(in-of-process component)也可以是一个可执行程序(EXE),被称为进程外组件(out-of-process component)

COM对象是建立在二进制可执行代码级的基础上,而C++等语言中的对象是建立在源代码级基础上的,因此COM对象是语言无关的。这一特性使用不同编程语言开发的组件对象进行交互成为可能。

在Microsoft Windows系统平台上,COM技术被应用于系统的各个层次,从底层的COM对象管理到上层的应用程序交互都用到了COM标准。

概述

COM既提出了组件之间进行交互的规范,也提供了实现交互的环境, 因为组件对象之间交互的规范不依赖于任何特定的语言,所以COM也可以是不同语言协作开发的一种标准。

OLE技术以COM规范为基础,OLE充分发挥了COM标准的优势,使Windows *** 作系统上的应用程序具有极强的可交互性。如果没有OLE的支持,Windows *** 作系统则会逊色很多。

但是,COM规范并不局限于OLE技术,实际上,OLE技术只是COM的一个应用而已,这几年,OLE技术在进行网络互连是显示出了很大的局限性,而COM则表现出了极强的适应能力。

COM标准包括规范和实现两大部分,规范部分定义了组件和组件之间通信的机制,这些规范不依赖于任何特定的语言和 *** 作系统,只要按照该规范,任何语言都可以使用; COM标准的实现部分是COM库,COM库为COM规范的具体实现提供了一些核心服务。

COM是面对对象的软件模型,因而对象是他的基本要素之一。类似于C++中对象的概念,对象是某个类(class)的一个实例;而类则是一组相关的数据和功能组和在一起的一个定义。使用对象的应用(或另一个对象)成为客户,有时也成为对象的用户。

接口是一组逻辑上相关的函数集合,其函数也被称为接口成员函数。对象通过接口成员函数为客户提供各种形式的服务。

在COM模型中,对象本身对于客户来说是不可见的,客户请求服务时,只能通过接口进行。每一个接口都由一个128位的全局唯一标识符(GUID,Globally Unique Identifier)来标识。客户通过GUID获得接口的指针,在通过接口指针,客户就可以调用其相应的成员函数。

一般来说,接口是不变的,只要客户期望的接口在组建对象中还存在,它就可以继续使用该接口所提供的服务。对象可以支持多个接口,因此对组件对象的升级可通过增加接口的办法实现,这样得到的新接口可以不影响老接口的使用。

客户如何来标识COM对象呢?与接口类似,每个对象也用一个128位GUID来标识,称为CLSID(class identifier,类标识符或类ID),用CLSID标识对象可以保证(概率意义上)在全球范围内的唯一性。

只要系统中含有这类COM对象的信息,并包括COM对象所在的模块文件(DLL或EXE文件)以及COM对象在代码中的入口点,客户程序就可以由此CLSID来创建COM对象。

那么客户怎么使用COM对象所提供的服务呢?客户获得的又是什么呢?

实际上,客户成功创建对象后,它得到的是一个指向对象某个接口的指针,因为COM对象至少实现一个接口,所以客户就可以调用该接口提供的所有服务。

但是COM对象可以有自己的状态,正是这种状态才使客户感觉到COM对象的存在。如果客户同时拥有两个相同CLSID的对象,则两个对象可以有不同的状态,客户完全不必关心COM对象是怎么实现的,以及两个对象的状态数据之间有什么关系(数组或者链表)。当然,COM对象也可以是无状态的,这种COM对象以提供功能服务为主,可以用来代替传统的API函数接口,使得应用程序编程接口更为有序,组织层次性更强。

COM本身除了规范之外,也有实现的部分,其中包括一些核心的系统级代码,也正是这部分核心代码,才使得对象和客户之间可通过接口在二进制代码级进行交互 。

在Microsoft Windows *** 作系统环境下,这些库以dll文件的形势存在,其中包括以下内容:

(1) 提供了少量的API函数实现客户和服务端COM应用的创建过程。在客户端,主要是一些创建函数;而在服务器端,提供一些对象的访问支持。

(2) COM通过注册表查找本地服务器即EXE程序,以及程序名与CLSID的转换等。

(3) 提供了一些标准的内存控制方法,使应用控制进程中内存的分配。

COM库一般不在应用程序层实现,而在 *** 作系统层次上实现,因此一个 *** 作系统只有一个COM库实现。而且COM库的实现必须依赖于具体的系统平台,尤其是系统底层的一些标准。

COM库可以保证所有的组件按照统一的方式进行交互 *** 作,而且它使我们在编写COM应用时,可以不用编写为进行COM通信而必需的大量基础代码,而是直接利用COM库提供的API进行编程,从而大大加快了开发的速度。例如,现在COM库的版本都支持远程组件即分布式COM,我们不用编写任何网络或者RPC(remote procedure call)的代码,就可以实现在网络上进行程序之间的通信。

如果我们用面向对象语言来实现COM对象,则很自然可以用类类定义对象。在C语言中,对象的概念可能变成一个逻辑概念,如果两个对象同时存在,则在接口实现中必须明确知道所进行的 *** 作是针对哪个对象的,这个过程可由COM接口的定义保证。

COM规范使用GUID来标识COM对象的思想源于OSF(Open Software Foundation)采用的UUID(Universallz Unique Identifier), UUID被定义为DCE(Distributed Computing Environment)的一部分,主要用于表识RPC通信的双方。

除了封装性和重用性,C++对象还有一个重要特性是多态性。正是C++对象的多态性,才体现了C++语言用类描述事物的高度抽象的特征;COM对象也 具有多态性,但这种多态性需要通过COM对象所具有的接口才能体现出来,就像C++对象的多态性需要通过其(virtual)函数才能体现一样。

从API到COM接口

假如我们要实现一个字处理应用系统,它需要一个查字典的功能,按照组件化程序设计的方法,自然应该把查字典的功能放到一个组件(dll)程序中实现。如果以后字典程序的查找算法或者字典库改变了,只要应用程序和组件之间的接口不变,则新的组件程序仍然可以被应用系统使用。这就是采用组件程序带来的灵活性。

为了把应用系统和组件程序连接起来,又能使它们协同工作,最简单的做法就是先定义一组查字典的函数,而且这组函数尽可能一般化,不要加入特定的与字典库相关的知识。

函数

功能说明

Initialize

初始化

LoadLibrary

装入字典库

InsertWord

插入一个单词

DeleteWord

删除一个单词

LookupWord

查找单词

RestoreLibrary

把内存中的字典库存入指定的文件中

FreeLibrary

释放字典库

平面型的API接口层可以很好地把两个程序连接起来,但存在以下一些问题:

(1) 当API函数非常多时,使用会非常不方便,需要对函数进行组织。

(2) API函数需要标准化,按照统一的调用方式进行处理,以适应不同的语言编程实现。参数的传递顺序,参数类型,寒暑返回处理都需要标准化。

COM定义了一套完整的接口规范,不仅可以弥补以上API作为组件借口的不足,还充分发挥了组件对象的优势,并实现了组件对象的多态性。

接口定义和标识

从技术上讲,接口是包含了一组函数的数据结构,通过这组数据结构,客户代码可以调用组件对象的功能。接口定义了一组成员函数,这组成员函数是组件对象暴露出来的所有信息,客户程序利用这些函数或的组件对象的服务。

客户程序用一个指向接口数据机构的指针来调用接口成员函数。接口指针实际上又指向另一个指针,这第二个指针指向一组函数,称为接口函数表(虚函数表),接口函数表中每一项为4个字节长的函数指针,每个函数指针与对象的具体实现连接起来。通过这种方式,客户只要获得了接口指针,就可以调用到对象的实际功能。

对于一个接口来说,他的虚函数表vtable是确定的,因此接口的成员函数个数是不变的,而且成员函数的先后顺序也是不变的;对于每个成员函数来说,其参数和返回值也是确定的。

在一个接口的定义中,所有这些信息都必须在二进制一级确定,不管什么语言,只要能支持这样的内存结构描述,也就是能够支持“structure“或“record“类型,并且这种类型能够包含双重的指向函数指针表的成员,则它就可以支持接口的描述,从而可以用于编写COM组件或者使用COM组件。

接口描述语言IDL

COM规范在采用OSF的DCE规范描述远程调用接口IDL的基础上,进行扩展形成了COM接口的描述语言。

COM规范使用的IDL接口描述语言不仅可用于定义COM接口,同时还定义了一些常用的数据类型,也可以描述自定义的数据结构,对于接口成员函数,我们可以指定每个参数的类型,输入输出特性,甚至支持可变长度的数组的描述。IDL支持指针类型,与C/C++很类似。

Microsoft Visual C++提供了MIDL工具,可以把IDL接口描述文件编译成C/C++兼容的接口描述头文件(h)。

IUnknown的定义(IDL):

interface IUnknown

{

HRESULT QueryInterface([in] REFIID iid, [out] void ppv);

ULONG AddRef(void);

ULONG Release(void);

}

IUnknown的定义(C++):

class IUnknown

{

Public:

virtual HRESULT _stdcall QueryInterface([in] REFIID iid, [out] void ppv)=0;

virtual ULONG _stdcall AddRef(void)=0;

virtual ULONG _stdcall Release(void)=0;

}

进程内组件

因为进程内组件和客户程序运行在同一个进程地址空间中,所以一旦客户程序与组件程序建立起通信关系之后,客户程序得到的接口指针指向组件程序中接口的vtable,这个vtable包含了所有成员函数地址,客户代码可以直接调用这些成员函数,所以其效率非常高。

因为DLL程序是在运行时刻被客户装入到内存中的,所以DLL模块本身也是独立的,它并不依赖于客户程序。

在C++语言中,为了使编制的DLL程序更为通用,一般指定DLL的引出函数使用_stdcall调用习惯,如果使用了_cdecl调用习惯,则有些编程语言环境就不能使用这些DLL程序。C++编译器为DLL程序的每个引出函数生成了一个修饰名,这些修饰名对于不同的编译器并不兼容,因此,从通用性角度出发,我们在每个函数定义前加上extern C“ 说明符。在Visual C++ 开发环境中,下面的说明语句可以很好的说明一个引出函数:

extern C“ int _stdcall MyFunction(int n);

为了编制DLL程序,我们可以按照这样的步骤:

(1) 创建一个DLL工程

(2) 对每个引出函数,使用extern C“说明符,以及_stdcall修饰符,如上面对MyFunction函数的说明。

(3) 按照传统的编程方法,我们还应该编写一个DEF文件,用来描述DLL程序的模块信息。在Win32平台上,我们可以不使用DEF文件,而是直接在函数说明时使用_declspec(dllexport)说明符,例如:

extern C“_declspec(dllexport) int _stdcall MyFunction(int n);

按照这样的方法建立起来的DLL模块可以被其他程序调用,因为C++连接器会把所有引出函数的信息连接到最终的目标代码中。

从客户程序一方来看,有三个系统函数可用于 *** 作DLL程序,LoadLibrary, GetProcAddress, 和FreeLibrary。

一般地,对于DLL程序的使用过程按照这样的步骤进行:

首先,客户程序使用LoadLibrary函数装入DLL,该函数返回模块的实例句柄,供以后 *** 作该模块使用。

然后,客户程序可以调用GetProcAddress函数获得DLL中引出的函数的地址,我们既可以按函数的序号(在DEF文件中指定)也可以按函数的名字来获取引出函数的地址,因为客户程序和DLL程序在相同的内存地址空间中,所以客户程序可以直接调用这些引出函数。

最后FreeLibrary,把DLL程序卸出内存,以便释放资源。

说明:

(1) DLL程序不仅可以引出函数,也可以引出全局变量,因为客户程序和DLL程序在同一个地址空间,所以,把DLL中的全局变量引出到客户程序中是有意义的。引用的方法并不复杂,或者把变量名放到DEF文件的EXPORTS部分,并加上DATA选项; 或者在变量说明前面加上_declspec(dllexport)说明符。

(2) DumpBin 通过/Exports选项可以列出DLL程序中的所有被引出的信息。

(3) 客户程序本身也可以是一个DLL程序,但它一定先被装入到进程空间中,以便可以调用系统函数 *** 作作为服务程序的DLL模块。

进程外组件

因为进程外组件程序和客户程序位于不同的进程空间之中,他们使用不同的地址空间,所以组件和客户之间的通信必须跨越进程边界,这就涉及到以下一些问题:

(1) 一个进程如何调用另外一个进程中的函数

(2) 参数如何从一个进程被传递到另外一个进程中

Windows平台上,在不同进程之间进行通信的办法很多,包括DDE, named pipe,或者共享内存等等,COM采用了LPC(Local Procedure Call)和RPC(Remote Procedure Call)

RegEdit可检查CLSID子键下的COM对象(63页)

Microsoft Visual C++提供OleViewexe,可列出当前机器上的所有类别信息,以及每一种类别下的组件对象列表。

RegSvr32 D:\DicComp\DictCompdll

RegSvr32 /u D:\DicComp\DictCompdll

DLL组件必须有DllRegisterServer和DllUnregisterServer两个用于注册的入口函数,才能用RegSvr32注册。

COM规定,支持自注册的进程外组件必须支持两个命令行参数/RegServer和/UnregServer,以便完成注册或注销 *** 作。

Class Factory

实际上,客户程序并不直接调用组件程序的引出函数,它调用COM库的函数进行组件对象的创建工作,COM库的创建函数根据注册表的信息调用组件程序的入口函数来创建组件对象。组件程序需要提供一个标准的入口函数DLLGetObjectClass,用于提供本组程序的组件信息。

Class Factory和DLLGetObjectClass函数

类厂是COM对象的生产基地,COM库通过类厂创建COM对象; 对应每一个COM类,有一个类厂专门用于该COM类的对象创 *** 作。类厂本身也是一个COM对象,它支持一个特殊的接口:IClassFactory,其定义如下:

Class IClassFactory : public IUnknown

{

virtual HRESULT _stdcall CreateInstance(IUnknown pUnknownOuter, const IID& iid, void ppv) = 0;

virtual HRESULT _stdcall LockServer(BOOL bLock) = 0;

};

接口IClassFactory有一个重要的成员函数CreateInstance,用于创建对应的COM对象。因为每个类厂之针对特定的COM类对象,所以CreateInstance成员函数知道该创建什么样的COM对象。在CreateInstance成员函数的参数中,第一个参数pUnknownOuter用于对象被聚合的情形,没有聚合设成NULL。IClassFactory的另一个成员函数LockServer用于控制组建的生存周期。

因为类厂本身也是个COM对象,它被用于其它COM对象的创建过程,那么类厂对象又由谁来创建呢?答案是DLLGetClassObject引出函数。DLLGetClassObject函数并不是COM库的函数,而是由组件程序实现的引出函数,我们先看一下DLLGetClassObject函数的原型:

HRESULT DLLGetClassObject(const CLSID& clsid,

Const IID& iid,

(void ) ppv

);

COM库在接到对象创建的指令后,它要调进程内组件的DLLGetClassObject函数,由该函数创类厂对象,并返回类厂对象的接口指针,COM库或者客户一旦有了类厂的接口指针,它们就可以通过类厂接口IClassFactory的成员函数CreateInstance创建相应的COM对象。

COM库与类厂的交互(67页)

在COM库中,有三个API函数可用于对象的创建,它们分别是CoGetClassObject, CoCreateInstance和CoCreateInstanceEx。通常情况下,客户程序调用其中之一完成对象的创建,并返回对象的初始接口指针。COM库与类厂也通过这三个函数进行交互。

将COM组件看成是DLL这种想法是十分肤浅的!

DLL是COM组件的一种发布形式,是一个组件服务器。

组件应看成是DLL中所实现的接口集!

DLL是一种形式,COM组件才是实质。

按照前文所说的,你不用去释放COM对象,需要做的仅仅是告诉它们你使用完了。每个COM对象实现的IUnknown接口都有一个Release()方法。你应该调用这个方法通知COM对象你不再需要它了。一旦调用了Release(),COM对象就从内存中消失,因此也就不能再使用接口指针了。如果你的应用程序使用许多不同的COM对象,那么当你使用完接口之后调用Release()就显得极为重要。如果你不释放(release)接口,COM对象(还有包括代码的那些DLLs)将被保存在内存中,并且毫无必要的加入到你的应用程序中。如果应用程序要运行很长一段时间,在程序闲时,你应该调用CoFreeUnusedLibraries()函数。这个函数将卸载没有显著作用的COM服务器,这也能减少应用程序的内存使用量。继续上面的示例,下面展示应该如何使用Release():// Create COM object as above Then if ( SUCCEEDED ( hr ) )

{

// Call methods using pISL here // Tell the COM object that we're done with it

pISL->Release();

}IUnknown接口将在下一部分详细说明。基本接口 - IUnknown每个COM接口都是从IUnknown继承而来。这个名字有点容易让人误解,因为实际它并不是一个未知(unknown)接口。这个名字意味着即使你有了一个指向COM对象的IUnknown指针,你也不会知道它下面的对象是什么,因为每个COM对象都实现了IUnknown。IUnknown 有三个方法:1 AddRef() - 告知COM对象增加它的引用计数。如果你拷贝了一个接口指针,你就需要使用这个方法,无论原始指针还是拷贝的副本都需要使用。在本文中,我们不必使用AddRef()方法。2 Release() - 告知COM对象减少它的引用计数。你可以从前面的代码片段中找到关于Release()的说明。3 QueryInterface() - 从COM对象中获取一个接口指针。当COClass实现二个或二个以上接口的时候,需要使用这个方法。我们已经了解Release()是怎样运作的,那么QueryInterface()又是怎样的呢?当你用CoCreateInstance()创建一个COM对象的时候,你将得到一个接口指针。如果COM对象实现了二个或二个以上的接口(不包括IUnknown),你可以使用QueryInterface()来获取任意你想要的额外指针。QueryInterface()的原型如下:HRESULT IUnknown::QueryInterface (

REFIID iid,

void ppv );

参数如下:iid 所请求接口的IIDppv 接口指针的地址。如果调用成功,QueryInterface()则通过这个参数返回接口。让我们继续那个快捷方式的的示例。生成快捷方式的COClass实现了IShellLink和IPersistFile接口。如果你已经有个一个IShellLink指针pISL,那面你可以像下面一样来从COM对象中获取IPersistFIle接口:HRESULT hr;

IPersistFile pIPF; hr = pISL->QueryInterface ( IID_IPersistFile, (void) &pIPF );

之后你可以用SUCCEEDED宏测试hr,然后证明QueryInterface()是否成功运行了。如果成功,你就可以像使用其它接口一样使用新的接口指针pIPF了。当然,在你使用完毕后,一定要调用pIPF->Release()。在讨论COM以前,我们得认识到一个事实,编写软件实际上是一个非常耗费时间和金钱的活动,所以人们不断寻找方法以减少这些花费,一个很重要的就是“软件重用”。在一个理想的环境下,我们应该能够编写一次代码,在任何地方都可以运行,即使这个环境编写者都没有想到过。当一个程序员修改了自己发布给别人使用的函数功能后,使用者应该不需要改变或者重新编译程序就可以使用这个功能。

早期的努力是使用类库,这个工作在C++中比较常见,但是这种做法是有很大缺陷的,要共享C++的二进制代码是非常困难的。为了解决这个问题,程序员们试图建立一种标准去达到软件在二进制级别上的共用。

Components Object Model (COM) 是软件组件互相通讯的一种方式。它是一种二进制和网络标准,允许任意两个组件互相通讯,而不管它们是在什么计算机上运行(只要计算机是相连的),不管各计算机运行的是什么 *** 作系统(只要该系统支持 COM),也不管该组件是用什么语言编写的。COM 还提供了位置透明性:当您编写组件时,其他组件是进程内 DLL、本地 EXE 还是位于其他计算机上的组件,对您而言都无所谓。COM 是基于对象的——但是这种对象概念与您熟悉的 C++ 或 Visual Basic 中的对象不太一样。

概括地说,COM具有如下一些优越性:

编程技术难度和工作量降低,开发周期变短,开发成本降低。一般编程人员只须根据应用功能要求选用合适的组件,而不必事无巨细都自己动手去完成。组件模块将编程的技术难度和工作量在人员个体和时间上进行了分摊。我们使用ESRI的COM组件编写程序就属于这一级别。

实现分层次的编程,从而促进了软件的专业化生产。专业人员可以开发出具有很强专业性的软件组件,这样既保证了普通的编程应用人员能够完成所需要的应用开发,又不至于降低使用的性能。应用人员不便实现的组件模块可以让专业人员定做。ESRI的程序员们使用C语言为我们辨析了一个个COM组件给我们使用。

软件的复用率提高,使软件的使用效率得到提高并延长了使用寿命。组件编程体系使大量的编程问题局部化了,使软件的更新和维护变得快速和容易,软件的成本大大降低。新的函数功能如果在接口没有改变的情况下很容易使用。

1 COM不是接口,也不是对象,它是一种标准。

2 符合COM标准的对象就是我们要谈论的重点——COM对象。其实COM对象也无非是实现了很多接口的对象而已。

3 COM对象必须实现Iunknown接口,这个接口是管理COM对象生命周期的,当COM对象不使用的时候,是这个接口定义的方法负责释放内存。一个COM对象可以没有任何别的接口,但是这个必须要,它是默认实现的接口。

4 QI,即所谓查询接口。由于COM对象有很多个接口,不同的接口管理着COM的不同类型的方法,因此从一个接口可以使用的方法转到另一个接口可以使用的方法的过程称为QI,这个过程是由Idispatch接口管理的。

5 GUIDs 每个组件都有一个独一无二的标识,这就是所谓的广泛唯一标识符。这个标识符就是COM组件的身份,它是一个128bits的数字,由系统自由分配,不要担心这个标识会有重复的一天。如果我们每秒产生1000万个UID,那么到5770年才可能遇到重复。别告诉我那个时候我们还使用WINDOWS的玩意。

6 一个COM对象可以有多个接口,一个接口也完全可以被多个COM对象实现。

7 接口分为两种,内置接口和外置接口。前一种定义的是COM对象的方法和属性,用implements实现,COM对象必须实现所有的接口内容;后一种定义的是COM对象的事件,用withEvents实现,这种接口在实现的时候不必实现所有的内容。

8 COM组件必须被注册后才能使用,它得到注册表那里去登记“户口”。

COM组件很不错,可是它也有致命的缺陷,这个缺陷就来自它本身。我们知道,COM是可以被重用的,COM对象的实现过程也可以被修改升级(定义是不能修改的哦),如果两个程序都使用一个COM对象,而这个COM组件升级了的话,很可能就出现某个程序无法使用新组件的情况,这就被称为“DLL HELL(DLL灾难)”,我们有时候安装了新软件后很多别的软件都无法使用,很多原因就是因为这个DLL HELL。

在COM标准中,一个组件程序也被称为一个模块,它可以是一个动态连接库(DLL), 被称为进程内组件(in-of-process component)也可以是一个可执行程序(EXE),被称为进程外组件(out-of-process component)

COM对象是建立在二进制可执行代码级的基础上,而C++等语言中的对象是建立在源代码级基础上的,因此COM对象是语言无关的。这一特性使用不同编程语言开发的组件对象进行交互成为可能。

在Microsoft Windows系统平台上,COM技术被应用于系统的各个层次,从底层的COM对象管理到上层的应用程序交互都用到了COM标准。

概述

COM既提出了组件之间进行交互的规范,也提供了实现交互的环境, 因为组件对象之间交互的规范不依赖于任何特定的语言,所以COM也可以是不同语言协作开发的一种标准。

OLE技术以COM规范为基础,OLE充分发挥了COM标准的优势,使Windows *** 作系统上的应用程序具有极强的可交互性。如果没有OLE的支持,Windows *** 作系统则会逊色很多。

但是,COM规范并不局限于OLE技术,实际上,OLE技术只是COM的一个应用而已,这几年,OLE技术在进行网络互连是显示出了很大的局限性,而COM则表现出了极强的适应能力。

COM标准包括规范和实现两大部分,规范部分定义了组件和组件之间通信的机制,这些规范不依赖于任何特定的语言和 *** 作系统,只要按照该规范,任何语言都可以使用; COM标准的实现部分是COM库,COM库为COM规范的具体实现提供了一些核心服务。

COM是面对对象的软件模型,因而对象是他的基本要素之一。类似于C++中对象的概念,对象是某个类(class)的一个实例;而类则是一组相关的数据和功能组和在一起的一个定义。使用对象的应用(或另一个对象)成为客户,有时也成为对象的用户。

接口是一组逻辑上相关的函数集合,其函数也被称为接口成员函数。对象通过接口成员函数为客户提供各种形式的服务。

在COM模型中,对象本身对于客户来说是不可见的,客户请求服务时,只能通过接口进行。每一个接口都由一个128位的全局唯一标识符(GUID,Globally Unique Identifier)来标识。客户通过GUID获得接口的指针,在通过接口指针,客户就可以调用其相应的成员函数。

一般来说,接口是不变的,只要客户期望的接口在组建对象中还存在,它就可以继续使用该接口所提供的服务。对象可以支持多个接口,因此对组件对象的升级可通过增加接口的办法实现,这样得到的新接口可以不影响老接口的使用。

客户如何来标识COM对象呢?与接口类似,每个对象也用一个128位GUID来标识,称为CLSID(class identifier,类标识符或类ID),用CLSID标识对象可以保证(概率意义上)在全球范围内的唯一性。

只要系统中含有这类COM对象的信息,并包括COM对象所在的模块文件(DLL或EXE文件)以及COM对象在代码中的入口点,客户程序就可以由此CLSID来创建COM对象。

那么客户怎么使用COM对象所提供的服务呢?客户获得的又是什么呢?

实际上,客户成功创建对象后,它得到的是一个指向对象某个接口的指针,因为COM对象至少实现一个接口,所以客户就可以调用该接口提供的所有服务。

但是COM对象可以有自己的状态,正是这种状态才使客户感觉到COM对象的存在。如果客户同时拥有两个相同CLSID的对象,则两个对象可以有不同的状态,客户完全不必关心COM对象是怎么实现的,以及两个对象的状态数据之间有什么关系(数组或者链表)。当然,COM对象也可以是无状态的,这种COM对象以提供功能服务为主,可以用来代替传统的API函数接口,使得应用程序编程接口更为有序,组织层次性更强。

COM本身除了规范之外,也有实现的部分,其中包括一些核心的系统级代码,也正是这部分核心代码,才使得对象和客户之间可通过接口在二进制代码级进行交互 。

在Microsoft Windows *** 作系统环境下,这些库以dll文件的形势存在,其中包括以下内容:

(1) 提供了少量的API函数实现客户和服务端COM应用的创建过程。在客户端,主要是一些创建函数;而在服务器端,提供一些对象的访问支持。

(2) COM通过注册表查找本地服务器即EXE程序,以及程序名与CLSID的转换等。

(3) 提供了一些标准的内存控制方法,使应用控制进程中内存的分配。

COM库一般不在应用程序层实现,而在 *** 作系统层次上实现,因此一个 *** 作系统只有一个COM库实现。而且COM库的实现必须依赖于具体的系统平台,尤其是系统底层的一些标准。

COM库可以保证所有的组件按照统一的方式进行交互 *** 作,而且它使我们在编写COM应用时,可以不用编写为进行COM通信而必需的大量基础代码,而是直接利用COM库提供的API进行编程,从而大大加快了开发的速度。例如,现在COM库的版本都支持远程组件即分布式COM,我们不用编写任何网络或者RPC(remote procedure call)的代码,就可以实现在网络上进行程序之间的通信。

如果我们用面向对象语言来实现COM对象,则很自然可以用类类定义对象。在C语言中,对象的概念可能变成一个逻辑概念,如果两个对象同时存在,则在接口实现中必须明确知道所进行的 *** 作是针对哪个对象的,这个过程可由COM接口的定义保证。

COM规范使用GUID来标识COM对象的思想源于OSF(Open Software Foundation)采用的UUID(Universallz Unique Identifier), UUID被定义为DCE(Distributed Computing Environment)的一部分,主要用于表识RPC通信的双方。

除了封装性和重用性,C++对象还有一个重要特性是多态性。正是C++对象的多态性,才体现了C++语言用类描述事物的高度抽象的特征;COM对象也 具有多态性,但这种多态性需要通过COM对象所具有的接口才能体现出来,就像C++对象的多态性需要通过其(virtual)函数才能体现一样。

从API到COM接口

假如我们要实现一个字处理应用系统,它需要一个查字典的功能,按照组件化程序设计的方法,自然应该把查字典的功能放到一个组件(dll)程序中实现。如果以后字典程序的查找算法或者字典库改变了,只要应用程序和组件之间的接口不变,则新的组件程序仍然可以被应用系统使用。这就是采用组件程序带来的灵活性。

为了把应用系统和组件程序连接起来,又能使它们协同工作,最简单的做法就是先定义一组查字典的函数,而且这组函数尽可能一般化,不要加入特定的与字典库相关的知识。

函数

功能说明

Initialize

初始化

LoadLibrary

装入字典库

InsertWord

插入一个单词

DeleteWord

删除一个单词

LookupWord

查找单词

RestoreLibrary

把内存中的字典库存入指定的文件中

FreeLibrary

释放字典库

平面型的API接口层可以很好地把两个程序连接起来,但存在以下一些问题:

(1) 当API函数非常多时,使用会非常不方便,需要对函数进行组织。

(2) API函数需要标准化,按照统一的调用方式进行处理,以适应不同的语言编程实现。参数的传递顺序,参数类型,寒暑返回处理都需要标准化。

COM定义了一套完整的接口规范,不仅可以弥补以上API作为组件借口的不足,还充分发挥了组件对象的优势,并实现了组件对象的多态性。

接口定义和标识

从技术上讲,接口是包含了一组函数的数据结构,通过这组数据结构,客户代码可以调用组件对象的功能。接口定义了一组成员函数,这组成员函数是组件对象暴露出来的所有信息,客户程序利用这些函数或的组件对象的服务。

客户程序用一个指向接口数据机构的指针来调用接口成员函数。接口指针实际上又指向另一个指针,这第二个指针指向一组函数,称为接口函数表(虚函数表),接口函数表中每一项为4个字节长的函数指针,每个函数指针与对象的具体实现连接起来。通过这种方式,客户只要获得了接口指针,就可以调用到对象的实际功能。

对于一个接口来说,他的虚函数表vtable是确定的,因此接口的成员函数个数是不变的,而且成员函数的先后顺序也是不变的;对于每个成员函数来说,其参数和返回值也是确定的。

在一个接口的定义中,所有这些信息都必须在二进制一级确定,不管什么语言,只要能支持这样的内存结构描述,也就是能够支持“structure“或“record“类型,并且这种类型能够包含双重的指向函数指针表的成员,则它就可以支持接口的描述,从而可以用于编写COM组件或者使用COM组件。

接口描述语言IDL

COM规范在采用OSF的DCE规范描述远程调用接口IDL的基础上,进行扩展形成了COM接口的描述语言。

COM规范使用的IDL接口描述语言不仅可用于定义COM接口,同时还定义了一些常用的数据类型,也可以描述自定义的数据结构,对于接口成员函数,我们可以指定每个参数的类型,输入输出特性,甚至支持可变长度的数组的描述。IDL支持指针类型,与C/C++很类似。

Microsoft Visual C++提供了MIDL工具,可以把IDL接口描述文件编译成C/C++兼容的接口描述头文件(h)。

IUnknown的定义(IDL):

interface IUnknown

{

HRESULT QueryInterface([in] REFIID iid, [out] void ppv);

ULONG AddRef(void);

ULONG Release(void);

}

IUnknown的定义(C++):

class IUnknown

{

Public:

virtual HRESULT _stdcall QueryInterface([in] REFIID iid, [out] void ppv)=0;

virtual ULONG _stdcall AddRef(void)=0;

virtual ULONG _stdcall Release(void)=0;

}

进程内组件

因为进程内组件和客户程序运行在同一个进程地址空间中,所以一旦客户程序与组件程序建立起通信关系之后,客户程序得到的接口指针指向组件程序中接口的vtable,这个vtable包含了所有成员函数地址,客户代码可以直接调用这些成员函数,所以其效率非常高。

因为DLL程序是在运行时刻被客户装入到内存中的,所以DLL模块本身也是独立的,它并不依赖于客户程序。

在C++语言中,为了使编制的DLL程序更为通用,一般指定DLL的引出函数使用_stdcall调用习惯,如果使用了_cdecl调用习惯,则有些编程语言环境就不能使用这些DLL程序。C++编译器为DLL程序的每个引出函数生成了一个修饰名,这些修饰名对于不同的编译器并不兼容,因此,从通用性角度出发,我们在每个函数定义前加上extern C“ 说明符。在Visual C++ 开发环境中,下面的说明语句可以很好的说明一个引出函数:

extern C“ int _stdcall MyFunction(int n);

为了编制DLL程序,我们可以按照这样的步骤:

(1) 创建一个DLL工程

(2) 对每个引出函数,使用extern C“说明符,以及_stdcall修饰符,如上面对MyFunction函数的说明。

(3) 按照传统的编程方法,我们还应该编写一个DEF文件,用来描述DLL程序的模块信息。在Win32平台上,我们可以不使用DEF文件,而是直接在函数说明时使用_declspec(dllexport)说明符,例如:

extern C“_declspec(dllexport) int _stdcall MyFunction(int n);

按照这样的方法建立起来的DLL模块可以被其他程序调用,因为C++连接器会把所有引出函数的信息连接到最终的目标代码中。

从客户程序一方来看,有三个系统函数可用于 *** 作DLL程序,LoadLibrary, GetProcAddress, 和FreeLibrary。

一般地,对于DLL程序的使用过程按照这样的步骤进行:

首先,客户程序使用LoadLibrary函数装入DLL,该函数返回模块的实例句柄,供以后 *** 作该模块使用。

然后,客户程序可以调用GetProcAddress函数获得DLL中引出的函数的地址,我们既可以按函数的序号(在DEF文件中指定)也可以按函数的名字来获取引出函数的地址,因为客户程序和DLL程序在相同的内存地址空间中,所以客户程序可以直接调用这些引出函数。

最后FreeLibrary,把DLL程序卸出内存,以便释放资源。

说明:

(1) DLL程序不仅可以引出函数,也可以引出全局变量,因为客户程序和DLL程序在同一个地址空间,所以,把DLL中的全局变量引出到客户程序中是有意义的。引用的方法并不复杂,或者把变量名放到DEF文件的EXPORTS部分,并加上DATA选项; 或者在变量说明前面加上_declspec(dllexport)说明符。

(2) DumpBin 通过/Exports选项可以列出DLL程序中的所有被引出的信息。

(3) 客户程序本身也可以是一个DLL程序,但它一定先被装入到进程空间中,以便可以调用系统函数 *** 作作为服务程序的DLL模块。

进程外组件

因为进程外组件程序和客户程序位于不同的进程空间之中,他们使用不同的地址空间,所以组件和客户之间的通信必须跨越进程边界,这就涉及到以下一些问题:

(1) 一个进程如何调用另外一个进程中的函数

(2) 参数如何从一个进程被传递到另外一个进程中

Windows平台上,在不同进程之间进行通信的办法很多,包括DDE, named pipe,或者共享内存等等,COM采用了LPC(Local Procedure Call)和RPC(Remote Procedure Call)

RegEdit可检查CLSID子键下的COM对象(63页)

Microsoft Visual C++提供OleViewexe,可列出当前机器上的所有类别信息,以及每一种类别下的组件对象列表。

RegSvr32 D:\DicComp\DictCompdll

RegSvr32 /u D:\DicComp\DictCompdll

DLL组件必须有DllRegisterServer和DllUnregisterServer两个用于注册的入口函数,才能用RegSvr32注册。

COM规定,支持自注册的进程外组件必须支持两个命令行参数/RegServer和/UnregServer,以便完成注册或注销 *** 作。

Class Factory

实际上,客户程序并不直接调用组件程序的引出函数,它调用COM库的函数进行组件对象的创建工作,COM库的创建函数根据注册表的信息调用组件程序的入口函数来创建组件对象。组件程序需要提供一个标准的入口函数DLLGetObjectClass,用于提供本组程序的组件信息。

Class Factory和DLLGetObjectClass函数

类厂是COM对象的生产基地,COM库通过类厂创建COM对象; 对应每一个COM类,有一个类厂专门用于该COM类的对象创 *** 作。类厂本身也是一个COM对象,它支持一个特殊的接口:IClassFactory,其定义如下:

Class IClassFactory : public IUnknown

{

virtual HRESULT _stdcall CreateInstance(IUnknown pUnknownOuter, const IID& iid, void ppv) = 0;

virtual HRESULT _stdcall LockServer(BOOL bLock) = 0;

};

接口IClassFactory有一个重要的成员函数CreateInstance,用于创建对应的COM对象。因为每个类厂之针对特定的COM类对象,所以CreateInstance成员函数知道该创建什么样的COM对象。在CreateInstance成员函数的参数中,第一个参数pUnknownOuter用于对象被聚合的情形,没有聚合设成NULL。IClassFactory的另一个成员函数LockServer用于控制组建的生存周期。

因为类厂本身也是个COM对象,它被用于其它COM对象的创建过程,那么类厂对象又由谁来创建呢?答案是DLLGetClassObject引出函数。DLLGetClassObject函数并不是COM库的函数,而是由组件程序实现的引出函数,我们先看一下DLLGetClassObject函数的原型:

HRESULT DLLGetClassObject(const CLSID& clsid,

Const IID& iid,

(void ) ppv

);

COM库在接到对象创建的指令后,它要调进程内组件的DLLGetClassObject函数,由该函数创类厂对象,并返回类厂对象的接口指针,COM库或者客户一旦有了类厂的接口指针,它们就可以通过类厂接口IClassFactory的成员函数CreateInstance创建相应的COM对象。

COM库与类厂的交互(67页)

在COM库中,有三个API函数可用于对象的创建,它们分别是CoGetClassObject, CoCreateInstance和CoCreateInstanceEx。通常情况下,客户程序调用其中之一完成对象的创建,并返回对象的初始接口指针。COM库与类厂也通过这三个函数进行交互。

将COM组件看成是DLL这种想法是十分肤浅的!

DLL是COM组件的一种发布形式,是一个组件服务器。

组件应看成是DLL中所实现的接口集!

DLL是一种形式,COM组件才是实质。

只要将VB窗口内的需要部分保存成文件就可以了

要用到API函数的:

Private Type POINTAPI

x As Long

y As Long

End Type

Private Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long

Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long

Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long

Private Declare Function DrawIcon Lib "user32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, ByVal hIcon As Long) As Long

Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hdc As Long) As Long

Private Sub Command1_Click()

Dim hdc As Long

Dim sw As Integer

Dim sh As Integer

Dim CurPos As POINTAPI

Dim Cur As Long

MeHide

DoEvents

Picture1AutoRedraw = True

hdc = GetDC(0)

GetCursorPos CurPos

Cur = GetCursor

Picture1Width = ScreenWidth

Picture1Height = ScreenHeight

sw = ScreenWidth / ScreenTwipsPerPixelX

sh = ScreenHeight / ScreenTwipsPerPixelY

BitBlt Picture1hdc, 0, 0, sw, sh, hdc, 0, 0, vbSrcCopy

MeShow

DrawIcon Picture1hdc, CurPosx - 10, CurPosy - 10, Cur

ReleaseDC 0, hdc

Picture1AutoRedraw = False

以下放到模块里:

Option Explicit

Type RECT_Type

left As Long

top As Long

right As Long

bottom As Long

End Type

'The following declare statements are case sensitive

Declare Function GetActiveWindow Lib "User32" () As Long

Declare Function GetDesktopWindow Lib "User32" () As Long

Declare Sub GetWindowRect Lib "User32" (ByVal Hwnd As Long, _

lpRect As RECT_Type)

Declare Function GetDC Lib "User32" (ByVal Hwnd As Long) As Long

Declare Function CreateCompatibleDC Lib "Gdi32" (ByVal hdc As Long) _

As Long

Declare Function CreateCompatibleBitmap Lib "Gdi32" (ByVal hdc _

As Long, ByVal nWidth As Long, _

ByVal nHeight As Long) As Long

Declare Function SelectObject Lib "Gdi32" (ByVal hdc As Long, _

ByVal hObject As Long) As Long

Declare Function BitBlt Lib "Gdi32" (ByVal hDestDC As Long, _

ByVal X As Long, ByVal Y _

As Long, ByVal nWidth As Long, _

ByVal nHeight As Long, _

ByVal hSrcDC As Long, _

ByVal XSrc As Long, _

ByVal YSrc As Long, _

ByVal dwRop As Long) As Long

Declare Function OpenClipboard Lib "User32" (ByVal Hwnd As Long) As Long

Declare Function EmptyClipboard Lib "User32" () As Long

Declare Function SetClipboardData Lib "User32" (ByVal wFormat As Long, _

ByVal hMem As Long) As Long

Declare Function CloseClipboard Lib "User32" () As Long

Declare Function ReleaseDC Lib "User32" (ByVal Hwnd As Long, _

ByVal hdc As Long) As Long

Declare Function DeleteDC Lib "Gdi32" (ByVal hdc As Long) As Long

Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

Global Const SRCCOPY = &HCC0020

Global Const CF_BITMAP = 2

Function ScreenDump()

Dim AccessHwnd As Long, DeskHwnd As Long

Dim hdc As Long

Dim hdcMem As Long

Dim rect As RECT_Type

Dim junk As Long

Dim fwidth As Long, fheight As Long

Dim hBitmap As Long

' DoCmdHourglass True

'---------------------------------------------------

' Get window handle to Windows and Microsoft Access

'---------------------------------------------------

DoEvents

DeskHwnd = GetDesktopWindow()

AccessHwnd = GetActiveWindow()

'---------------------------------------------------

' Get screen coordinates of Microsoft Access

'---------------------------------------------------

Call GetWindowRect(AccessHwnd, rect)

fwidth = rectright - rectleft

fheight = rectbottom - recttop

'---------------------------------------------------

' Get the device context of Desktop and allocate memory

'---------------------------------------------------

hdc = GetDC(DeskHwnd)

hdcMem = CreateCompatibleDC(hdc)

hBitmap = CreateCompatibleBitmap(hdc, fwidth, fheight)

If hBitmap <> 0 Then

junk = SelectObject(hdcMem, hBitmap)

'---------------------------------------------

' Copy the Desktop bitmap to memory location

' based on Microsoft Access coordinates

'---------------------------------------------

junk = BitBlt(hdcMem, 0, 0, fwidth, fheight, hdc, rectleft, _

recttop, SRCCOPY)

'---------------------------------------------

' Set up the Clipboard and copy bitmap

'---------------------------------------------

junk = OpenClipboard(DeskHwnd)

junk = EmptyClipboard()

junk = SetClipboardData(CF_BITMAP, hBitmap)

junk = CloseClipboard()

End If

'---------------------------------------------

' Clean up handles

'---------------------------------------------

junk = DeleteDC(hdcMem)

junk = ReleaseDC(DeskHwnd, hdc)

' DoCmdHourglass False

End Function

这里是拖动鼠标的框里内容截取,你也可以改成固定范围的截取。

以上就是关于如何修改jpg文件缩略图的图标全部的内容,包括:如何修改jpg文件缩略图的图标、VC下使用COM实现和IE浏览器交互的几种方法、C++中怎么建立一个标准的exe文件等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9318992.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-27
下一篇 2023-04-27

发表评论

登录后才能评论

评论列表(0条)

保存