当我们实现的COM对象,或者ActiveX控件在浏览器中调用的时候,往往会出现警告框,提示不安全的控件正在运行。这是因为浏览器安全策略所限定的,浏览器认为只有“安全的对象”才能够被执行。
所谓安全的对象就是指那些不访问本地资源的对象,例如不会去读注册表,不会写文件等等。一个满足条件的对象通过支持IObjectSafety接口告诉浏览器,自己是合法的。
下面就简单的介绍一下怎么在C#中实现对于IObjectSafety接口的支持。
思路
C/C++d的程序可以直接在SDK中找到IObjectSafety的定义,所以需要支持的话非常容易。C#比较麻烦,因为我们没有办法获得IObjectSafety的定义,不过没有问题,我们可以按照IObjectSafety在SDK中的定义,在C#的工程中重新定义该接口。
如果大家了解COM机制一定会知道,所谓借口的定义之出现在类型库中,与实现无关。而判断一个接口唯一性就是定义接口时指定的UUID。此外自己重新定义时需要保证接口中没有函数的参数与返回值必须与原定义一致即可。
我们的做法就是,找到ObjSafe.idl,然后复制其中的UUID,利用这个UUID在C#中定一个interface IObjectSafety,并且申明其中的两个函数;定义完成之后,让需要检查安全接口的组件继承该接口,并在该组件内部实现IObjectSafety的两个函数,按照要求做适当的返回,那么用这个组件包装的COM对象在IE中调用就被认为是安全的了。
第一次尝试
按照上面的思路,我们开始进行尝试
idl中的接口定义
[
object,
uuid(CB5BDC81-93C1-11cf-8F20-00805F2CD064),
pointer_default(unique)
]
interface IObjectSafety : IUnknown
{
HRESULT GetInterfaceSafetyOptions(
[in] REFIID riid, // Interface that we want options for
[out] DWORD * pdwSupportedOptions, // Options meaningful on this interface
[out] DWORD * pdwEnabledOptions); // current option values on this interface
HRESULT SetInterfaceSafetyOptions(
[in] REFIID riid, // Interface to set options for
[in] DWORD dwOptionSetMask, // Options to change
[in] DWORD dwEnabledOptions); // New option values
}
IObjectSafety接口定义
因为接口中存在指针,所以直接采用Int32的整型形式,用到了unsafe code。
[Guid("CB5BDC81-93C1-11cf-8F20-00805F2CD064")]
public interface IObjectSafety
{
// methods
unsafe void GetInterfacceSafyOptions(
System.Int32 riid,
System.Int32* pdwSupportedOptions,
System.Int32* pdwEnabledOptions);
void SetInterfaceSafetyOptions(
System.Int32 riid,
System.Int32 dwOptionsSetMask,
System.Int32 dwEnabledOptions);
}
继承
public class MyControl : System.Windows.Forms.UserControl,IObjectSafety
实现
// implement functions of IObjectSafety
public unsafe void GetInterfacceSafyOptions(System.Int32 riid,System.Int32* pdwSupportedOptions,System.Int32* pdwEnabledOptions)
{
...
}
public void SetInterfaceSafetyOptions(System.Int32 riid,System.Int32 dwOptionsSetMask,System.Int32 dwEnabledOptions)
{
...
}
一切正常编译通过,但是通过IE调用测试页面,在装载页面的时候却产生了一个关闭应用程序系统异常,仔细察看内容Error Report是非法的内存地址访问。无语中...
第二次尝试
由于是非法的内存地址访问,很自然的联想到是接口定义的问题,因为存在unsafe code,查查文档发现根本无需使用unsafe code这么夸张,可以通过out这个参数修饰符解决。
修改定义和实现如下
IObjectSafety接口定义
[Guid("CB5BDC81-93C1-11cf-8F20-00805F2CD064")]
public interface IObjectSafety
{
// methods
void GetInterfacceSafyOptions(
System.Int32 riid,
out System.Int32 pdwSupportedOptions,
out System.Int32 pdwEnabledOptions);
void SetInterfaceSafetyOptions(
System.Int32 riid,
System.Int32 dwOptionsSetMask,
System.Int32 dwEnabledOptions);
}
实现
// implement functions of IObjectSafety
public unsafe void GetInterfacceSafyOptions(System.Int32 riid,out System.Int32 pdwSupportedOptions,out System.Int32 pdwEnabledOptions)
{
...
}
public void SetInterfaceSafetyOptions(System.Int32 riid,System.Int32 dwOptionsSetMask,System.Int32 dwEnabledOptions)
{
...
}
编译通过,不错;IE调用测试页面,同样的错误!郁闷,无斗志,回家。
第三次尝试
睡了一觉,饱餐战饭,继续思考。
自己比较了编译器生成的类型库,发现一些很奇怪的现象,在类型库中IObjectSafety居然被定义了两次interface IObjectSafety : IUnknown,以及dispinterface IObjectSafety : IDispatch。而偏偏MyControl是从dispinterface IObjectSafety上继承的。这就与正确的IObjectSafety的接口说明相违背,问题应该出在这里。
MSDN,查文档。System.Runtime.InteropService下有很多关于描述接口的属性,从中可以找到产生问题的原因。有一个属性InterfaceTypeAttribute,就是用来说明定义的接口是从IUnknown继承还是IDispatch继承,缺省情况下是Dual的,所以是两份。
再次定义如下:
[Guid("CB5BDC81-93C1-11CF-8F20-00805F2CD064"),InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IObjectSafety
{
// methods
void GetInterfacceSafyOptions(
System.Int32 riid,
out System.Int32 pdwSupportedOptions,
out System.Int32 pdwEnabledOptions);
void SetInterfaceSafetyOptions(
System.Int32 riid,
System.Int32 dwOptionsSetMask,
System.Int32 dwEnabledOptions);
}
其余代码不变,重新编译,通过;察看导出类型库,果然少了很多垃圾;调用测试页面,正确。激动中...
怎样让IE认为你的对象安全
实现了这个接口,剩下的事情就很简单了。前面提到过如果按照正规的途径你需要确保你的代码没有访问系统的本地自然,然后按照文档要求,当该对象被不同的接口调用查询的时候,做不同的反馈。具体实现可以在MSDN的Sample中找到。
当然我们可以写一个对象读写本地文件,但是支持IObjectSafety接口,并且始终声明自己是合法的,这样来欺骗浏览器,那么代码就很简单了,如下:
// implement functions of IObjectSafety
public void GetInterfacceSafyOptions(System.Int32 riid,out System.Int32 pdwSupportedOptions,out System.Int32 pdwEnabledOptions)
{
pdwSupportedOptions = CLsObjectSafety.INTERFACESAFE_FOR_UNTRUSTED_CALLER;
pdwEnabledOptions = CLsObjectSafety.INTERFACESAFE_FOR_UNTRUSTED_DATA;
}
public void SetInterfaceSafetyOptions(System.Int32 riid,System.Int32 dwOptionsSetMask,System.Int32 dwEnabledOptions)
{
}
只要这么些,就不会再有讨厌的对话框弹出了。
如果你的组件是在客户端安装,在浏览器中调用,那么所有的工作已经完成;所以是希望通过Codebase的方式下载发布,你还需要去搞一个数字签字,已经不是本文讨论的范围了,就到这里,结束了。
参考文档
1、HOWTO: Implement IObjectSafety in Visual Basic Controls(http://support.microsoft.com/default.aspx?scid=kb;EN-US;q182598)
2、Exposing .NET Framework Components to COM (http://msdn.microsoft.com/library/en-us/cpguide/html/cpconexposingnetframeworkcomponentstocom.asp)
3、System.Runtime.InteropServices(http://msdn.microsoft.com/library/en-us/cpref/html/frlrfSystemRuntimeInteropServices.asp)