异步可插协议允许开发者创建可插协议处理器,MIME过滤器,以及命名空间处理器工作在微软IE4.0浏览器以及更高版本或者URL moniker中。这篇文章涵盖了Urlmon.dll动态链接库所公开(输出)的可插协议诸多功能。关于Urlmon.dll针对其他APIs所公开(输出)的信息,可以查看URL Monikers和URL Security Zones文章。
(以下链接指向原文)
益处
应用程序可以使用可插协议处理器来处理自定义URL协议方案或者为指定的MIME类型过滤数据。
允许开发者有能力通过使用URL monikers为IE4.0以上版本和应用程序实现新的或者自定义的协议方案处理自定义URL协议方案的可插协议处理方案。(The ability to handle a custom URL protocol scheme using a pluggable protocol handler allows developers to implement new or custom protocol schemes for Internet Explorer 4.0 (and later) and for applications that use URL monikers.)Windows Internet Explorer已存在的协议如HTTP和FTP都包括在默认的协议处理器中。
可插协议过滤器可以被用来为指定的MIME类型过滤数据。不像标准的协议处理器和可插的命名空间处理器只提供数据,可插协议过滤器不仅提供数据,还要读取数据。可插MIME过滤器通过可插协议处理器通过实现IInternetProtocolSink接口读取下载的数据。在数据被处理完之后,MIME过滤器通过IInternetProtocol接口允许系统重新得到经过处理的数据。
概要
让我们说说已经介绍的新的协议方案和你的公司想要为IE4.0以上版本用户提供的支持。可插协议允许你在新的协议方案基础上,提供能够被任何请求调用的新的协议处理程序。
或者说,你的公司想要设计一个能够帮助他们的用户(一些家长)监管他们的孩子在互联网上阅读黄色的信息的产品。你可以设计一个可插的MIME过滤器,将它们注册为监管所有的text/*的MIME类型,并且将所有这些亵渎的东西替换成<BEEP>标签或者一些类似的东西。任何内容都有一个匹配的MIME类型,就像网页(MIME通常为text/html),将调用可插拔的MIME过滤器并将过滤后的数据传给用户。
先决依赖条件
这个文档假设你明白Microsoft Win32编程。并且你需要明白OLE和组件对象编程(COM)以及URL中的格式化和符号。更多信息可以参看RFC-1738 on Uniform Resource Locators (URL)。
使用Urlmon.dll提供的功能编译程序,确保在你的include目录下包含了Urlmon.h头文件并且Urlmon.lib类库文件在你用的的C/C++编辑器所使用的类库文件夹中。
关于URLs以及Namespaces
URL跟随的符号描述在RFC1738中,指定了协议方案跟随的指定方案的一部分(<scheme>:<scheme-specific portion>)。例如,在URL http://www.microsoft.com中,“http”是scheme,“//www.microsoft.com”是指定方案的一部分。
这个指定方案的一部分的起始节(section)包含服务器名。这部分URL通常被引用来作为URL的命名空间。
关于可插协议
IE使用两种注册新URL协议处理器的机制。第一种方式是注册URL协议并且将它和应用程序相关联并尝试让所有的这种URL都转向指定的URL协议处理器以起到用协议启动程序的作用。(例如,注册用于处理mailto:或者new:的URL协议的应用程序)。第二种方式是使用异步可插协议API,它允许你通过映射协议方案到一个类来定义新的协议。
关于如何为一个应用程序注册一个指定的URL协议的更多信息,请参看Registering an Application to a URL Protocol。(中译:[翻译]将应用程序注册为URL协议(Registering an Application to a URL Protocol))
异步可插协议提供三种将协议方案映射到类的方式:
注意:所有异步可插协议必须支持BINDF_NO_UI 和 BINDF_SILENTOPERATION 标记。
异步可插协议相关接口
异步可插协议相关函数
关于异步可插协议
异步可插协议处理器是一个用于处理任何注册为协议方案的调用的线程单元COM对象。当客户端程序作出请求,Urlmon在注册表中查看协议方案并创建一个已经为这个协议方案注册的协议处理程序的实例。如果协议注册方案被成功地映射到协议处理器的类标识(CLSID),将调用IClassFactory接口的CoCreateInstance方法。协议处理器通过IClassFactory::CreateInstance函数来获得实例。
注册自定义协议,在注册表中为协议方案添加一个key到HKEY_CLASSES_ROOT\PROTOCOLS\Handler\protocol_scheme下。在这个key名下,添加一个字符串为CLSID,必须设置这个协议处理器的CLSID。
一个注册自定义协议方案的例子,增加一个名为example的key到注册表HKEY_CLASSES_ROOT\PROTOCOLS\Handler下。在这个新key,HKEY_CLASSES_ROOT\PROTOCOLS\Handler\example,添加字符串,CLSID,必须为这个处理程序指派CLSID。任何URLs使用形如example:的协议方案将被这个CLSID标识关联的协议处理器所处理。
协议处理器不能使用任何微软Windows消息机制转回到线程中,协议处理器必须工作在非GUI线程中。
注意:所有的异步可插协议必须支持the BINDF_NO_UI 和BINDF_SILENTOPERATION标记。
关于异步命名空间处理器
临时可插命名空间处理器用来处理所有用(指定)协议方案的URLs,临时可插命名空间处理器被注册在一个特殊的进程中。你可以通过IInternetSession接口注册或者取消临时可插命名空间处理器。
关于可插MIME过滤器
可插MIME过滤器是一个接收流的异步可插协议,执行一些数据操作并且返回数据流。输出流可能不同于原始的流。
更多关于如何在IE4.0中定义MIME类型的信息,请参看MIME Type Detection in Internet Explorer。
注意:所有的异步可插MIME过滤器必须支持BINDF_NO_UI和BINDF_SILENTOPERATION标记。
另外,之可以通过指定于URL的特定资源通过IE来调用可插的MIME过滤器。MIME将不被其他通过指定URL关联的资源调用(就像HTML页里面的图像)。
永久的可插MIME过滤器
你必须使用key HKEY_CLASSES_ROOT\PROTOCOLS\Filter\<mime_filter>以及设置了CLSID的value,在注册表中注册一个永久的可插MIME过滤器。
临时可插MIME过滤器
你可以通过IInternetSession接口注册或者取消一个临时可插MIME过滤器。
创建一个异步可插协议处理器
按以下步骤来创建一个可插协议处理器:
在创建玩协议处理器并将其添加到注册表后,任何程序都可以通过使用由Urlmon.dll提供的功能使用协议处理器。下面的步骤提供当一个拥有你注册的处理器协议方案的URL被调用的时候,Urlmon.dll和你的协议处理器之间的调用的大致的步骤:
1.Urlmon.dll调用QueryInterface查看你的协议处理器是否实现了IInternetProtocolInfo接口。
2.如果IInternetProtocolInfo接口已经实现了,Urlmon.dll调用你的可插处理协议的IInternetProtocolInfo::ParseUrl 接口
当更新了订阅后,IE调用可插协议处理器的IInternetProtocolInfo::QueryInfo方法将QUERYOPTION 值设置为QUERY_USES_CACHE。IE订阅机制将仅支持采用IE缓存机制的可插协议并且让QUERY_USES_CACHE标志为TRUE。
3.Urlmon.dll调用QueryInterface查看你的可插协议处理器是否支持IInternetProtocol接口
4.Urlmon.dll通过URL调用你的可插协议处理器的IInternetProtocolRoot::Start 方法,并且传递Urlmon.dll的IInternetProtocolSink和IInternetBindInfo接口的地址。
5.你的可插协议处理器将需要接受请求数据。首先将从Internet请求数据。
6.当你的可插协议处理器开始下载数据之后,推荐处理器调用Urlmon.dll的IInternetProtocolSink::ReportData方法。
7.Urlmon.dll调用你的协议的IInternetProtocol::Read方法。
8.你的可插协议处理器可以调用Urlmon.dll的IInternetProtocolSink::ReportProgress方法。可插协议处理器必须支持MIME类型,通过使用BINDSTATUS_MIMETYPEAVAILABLE状态码,允许一个可插MIME过滤器被调用(如果有一个为这个MIME类型注册的MIME过滤器的话)。
9.步骤6到步骤8将一直重复到你的协议处理程序完成请求数据的下载任务之后。
10.你的可插协议必须调用Urlmon.dll的IInternetProtocolSink::ReportResult方法。
11.Urlmon.dll调用你的可插协议处理的IInternetProtocol::LockRequest方法。
12.Urlmon.dll调用你可插协议处理程序的IInternetProtocolRoot::Terminate方法。
13.Urlmon.dll调用你的可插协议的IInternetProtocol::Read方法知道所有的数据都被重新取回。
14.Urlmon.dll调用你的可插协议的IInternetProtocol::UnlockRequest方法。
注意:你的可插协议的IInternetProtocol::Read方法将在调用Urlmon.dll之后持续运行,甚至所有的数据已经读取完毕还将继续。所有的一部可插协议处理程序必须做好处理这些可能的准备。
创建一个可插的MIME过滤器
一个可插的MIME过滤器本质上只是一个实现了IInternetProtocolSink接口的异步可插处理器。Urlmon.dll使用实现了IInternetProtocolSink的可插的MIME过滤器来通知Urlmon.dll数据已经被过滤了。
另外,过滤器处理多种MIME类型必须为每一种MIME类型注册一个特殊的CLSID单独处理它们。
1.事务处理器调用可插协MIME过滤器的IInternetProtocolRoot::Start方法。
2.事务处理器调用可插协MIME过滤器的InternetProtocolSink::ReportProgress和IInternetProtocolSink::ReportData方法。
3.可插MIME过滤器调用事务处理器的IInternetProtocol::Read方法。
4.可插MIME过滤器调用事务处理器的IInternetProtocolSink::ReportData方法。
5.事务处理器调用可插协MIME过滤器的IInternetProtocol::Read方法。
相关主题
译注
1.异步可插协议处理器可以被指定为在所有进程内有效,而异步可插命名空间协议处理器仅被指定为在某个具体的进程内有效。
IE中有很多我们比较熟悉的协议,如http,https,mailto,ftp等。当然你也可以实现自己定义的协议,稍微谈一下这里所说的协议,从我的理解来说这里的协议只有当你的网页引用某个资源时才会调用,而不是随便在某个属性的值前面加上某个协议的名称就可以了。常见的协议调用如img的src属性中,很多元素style中的background-image属性中,还有a标签的href属性中。
言归正传,前面说到的实现自定义协议就用到了一种IE下异步可插入协议的技术。
从分类上来说,这种异步可插入协议的技术还分为两种:
更详细介绍异步可插入协议的资源有http://www.cppblog.com/bigsml/archive/2008/03/23/45145.html。
因为网上介绍永久的异步可插入协议的资源还很多,如codeproject上的:
http://www.cppblog.com/bigsml/archive/2008/03/23/45145.html
http://www.codeproject.com/KB/aspnet/AspxProtocol.aspx
这篇就主要谈谈如何实现临时的异步可插入协议的方法。
下面谈下具体的实现。
在本实现中主要用到了下面这几个接口:
IInternetProtocol接口
它有四个方法:
LockRequest |
Locks the requested resource so that the IInternetProtocolRoot::Terminate method can be called, and the remaining data can be read. |
Read |
Reads data that the pluggable protocol handler gets. |
Seek |
Moves the current seek offset. |
UnlockRequest |
Frees any resources associated with a lock. |
主要用于下载资源,将处理后的资源传递给IE进行显示。
IInternetProtocolRoot接口
Abort |
Cancels an operation that is in progress. |
Continue |
Enables the pluggable protocol handler to continue processing data on the apartment thread. |
Resume |
Not currently implemented. |
Start |
Starts the operation. |
Suspend |
Not implemented. |
Terminate |
Releases the resources used by the pluggable protocol handler. |
主要用于解析资源,准备待下载的资源。
IInternetSession接口
它包括9个方法,根据需要我们只用到了下面两个方法:
RegisterNameSpace |
Registers a temporary pluggable namespace handler on the current process. |
UnregisterNameSpace |
Unregisters a temporary pluggable namespace handler. |
实现临时可插入协议的注册和取消。
IInternetProtocolInfo接口
它包括4个方法。
CombineUrl |
Combines a base URL and relative URL into a full URL. |
CompareUrl |
Compares two URLs and determines if they are equal. |
ParseUrl |
Parses a URL. |
QueryInfo |
Gets information related to the specified URL. |
主要提供了对于Url的处理。
此外,在构造IInternetSession的时候还用到了一个外部方法:
[DllImport("urlmon.dll")]
private static extern void CoInternetGetSession(int sessionMode,
out IInternetSession session, int reserved);
预备的知识介绍完,下面就是具体实现了。
一般方法是在一个类中实现IInternetProtocol,IInternetProtocolRoot,IInternetProtocolInfo三个接口,然后通过IInternetSession接口的RegisterNameSpace方法来注册这个自定义协议,用完这后再调用UnregisterNameSpace方法来注销这个自定义协议。
关于IE和IInternetProtocol,IInternetProtocolRoot,IInternetProtocolInfo三个接口的调用流程可以参考msdn上的介绍,中文版的翻译可以参考:
http://www.cnblogs.com/volnet/archive/2008/03/28/About_Asynchronous_Pluggable_Protocols.html
首先通过CoInternetGetSession方法得到一个IInternetSession对象,然后注册自定义的协议:
IInternetSession session;
CoInternetGetSession(0, out session, 0);
Guid guid = new Guid("79EAC9E4-BAF9-11CE-8C82-00AA004BA90B");
session.RegisterNameSpace(new ClassFactory(), ref guid, ProcotolName, 0, null, 0);
在注册的时候要传入一个实现了IClassFactory接口的对象,下面是对次接口的实现:
// Interface IClassFactory is here to provide a C# definition of the
// COM IClassFactory interface.
[
ComImport, // This interface originated from COM.
ComVisible(true), // It is not hard to imagine that this interface must not be exposed to COM.
InterfaceType(ComInterfaceType.InterfaceIsIUnknown), // Indicate that this interface is not IDispatch-based.
Guid("00000001-0000-0000-C000-000000000046") // This GUID is the actual GUID of IClassFactory.
]
public interface IClassFactory
{
void CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject);
}
[ComVisible(true)]
public class ClassFactory : IClassFactory
{
#region IClassFactory Implementations
public void CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject)
{
ppvObject = Marshal.GetComInterfaceForObject(new MyImageProtocol(), typeof(IInternetProtocolInfo));
}
#endregion
}