<html>
<head>
<SCRIPT language="JScript">
function MyFunc()
{
external.HelloWorld(); //HelloWorld是我们扩展的方法
}
</SCRIPT>
</head>
<body>
<input type="Button" value="Show hello world" onClick="MyFunc();" />
</body>
</html>
HelloWorld就是我们扩展的一个方法,当点击按钮的时候external对象会调用HelloWorld方法调用本地代码,对于external对象则会调用上面提到的GetExternal方法来查询是否提供了扩展,下面是如何实现GetExternal方法来实现扩展external对象,代码如下:
class MyDocHandler :public IDocHostUIHandler
{
long refcount;
public:
MyDocHandler() :refcount(1){ }
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID classid, void** intf) {
if (classid == IID_IUnknown)
{
*intf = (IUnknown*)this;
AddRef();
}
else if (classid == IID_IDocHostUIHandler)
{
*intf = (IDocHostUIHandler*)this;
AddRef();
}
else if (classid == IID_IDispatch)
{
*intf = (IDispatch*)this;
AddRef();
}
else
return E_NOINTERFACE;
return S_OK;
}
virtual ULONG STDMETHODCALLTYPE AddRef() {
InterlockedIncrement(&refcount);
return refcount;
}
virtual ULONG STDMETHODCALLTYPE Release() {
InterlockedDecrement(&refcount);
if (refcount == 0)
delete this;
return refcount;
}
//返回S_OK,屏蔽掉右键菜单
virtual HRESULT STDMETHODCALLTYPE ShowContextMenu(
/* [in] */ DWORD dwID,
/* [in] */ POINT __RPC_FAR *ppt,
/* [in] */ IUnknown __RPC_FAR *pcmdtReserved,
/* [in] */ IDispatch __RPC_FAR *pdispReserved) {
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE GetHostInfo(
/* [out][in] */ DOCHOSTUIINFO __RPC_FAR *pInfo) {
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE ShowUI(
/* [in] */ DWORD dwID,
/* [in] */ IOleInPlaceActiveObject __RPC_FAR *pActiveObject,
/* [in] */ IOleCommandTarget __RPC_FAR *pCommandTarget,
/* [in] */ IOleInPlaceFrame __RPC_FAR *pFrame,
/* [in] */ IOleInPlaceUIWindow __RPC_FAR *pDoc) {
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE HideUI( void) {
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE UpdateUI( void) {
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE EnableModeless(
/* [in] */ BOOL fEnable) {
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE OnDocWindowActivate(
/* [in] */ BOOL fActivate) {
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE OnFrameWindowActivate(
/* [in] */ BOOL fActivate) {
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE ResizeBorder(
/* [in] */ LPCRECT prcBorder,
/* [in] */ IOleInPlaceUIWindow __RPC_FAR *pUIWindow,
/* [in] */ BOOL fRameWindow) {
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE TranslateAccelerator(
/* [in] */ LPMSG lpMsg,
/* [in] */ const GUID __RPC_FAR *pguidCmdGroup,
/* [in] */ DWORD nCmdID) {
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE GetOptionKeyPath(
/* [out] */ LPOLESTR __RPC_FAR *pchKey,
/* [in] */ DWORD dw) {
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE GetDropTarget(
/* [in] */ IDropTarget __RPC_FAR *pDropTarget,
/* [out] */ IDropTarget __RPC_FAR *__RPC_FAR *ppDropTarget) {
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE GetExternal(
/* [out] */ IDispatch __RPC_FAR *__RPC_FAR *ppDispatch) {
*ppDispatch = new MyCommandHandler();
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE TranslateUrl(
/* [in] */ DWORD dwTranslate,
/* [in] */ OLECHAR __RPC_FAR *pchURLIn,
/* [out] */ OLECHAR __RPC_FAR *__RPC_FAR *ppchURLOut) {
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE FilterDataObject(
/* [in] */ IDataObject __RPC_FAR *pDO,
/* [out] */ IDataObject __RPC_FAR *__RPC_FAR *ppDORet) {
return E_NOTIMPL;
}
};
上面重载了ShowContextMenu方法屏蔽掉右键菜单,使用户不能得到网页代码,关于GetExternal是这样实现的:
virtual HRESULT STDMETHODCALLTYPE GetExternal(
/* [out] */ IDispatch __RPC_FAR *__RPC_FAR *ppDispatch) {
*ppDispatch = new MyCommandHandler();
return S_OK;
}
可以看到只是简单返回了MyCommandHandler对象,MyCommandHandler必须继承自IDispatch接口来实现支持自动化的调用方式,它是这样实现的:
class MyCommandHandler : public IDispatch
{
long refcount;
public:
MyCommandHandler() :refcount(1){ }
virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(
/* [out] */ UINT *pctinfo){
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(
/* [in] */ UINT iTInfo,
/* [in] */ LCID lcid,
/* [out] */ ITypeInfo **ppTInfo){
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(
/* [in] */ REFIID riid,
/* [size_is][in] */ LPOLESTR *rgszNames,
/* [in] */ UINT cNames,
/* [in] */ LCID lcid,
/* [size_is][out] */ DISPID *rgDispId){
*rgDispId=1;
return S_OK;
}
virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke(
/* [in] */ DISPID dispIdMember,
/* [in] */ REFIID riid,
/* [in] */ LCID lcid,
/* [in] */ WORD wFlags,
/* [out][in] */ DISPPARAMS *pDispParams,
/* [out] */ VARIANT *pVarResult,
/* [out] */ EXCEPINFO *pExcepInfo,
/* [out] */ UINT *puArgErr){
if(dispIdMember==1)
{
MessageBox(0,"Hello World","Hello",0); //place your code here
frmweb->Edit1->Text="Hello World(这也可以?)";
}
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID classid, void** intf) {
if (classid == IID_IDispatch)
{
*intf = (IDispatch*)this;
AddRef();
}
else
return E_NOINTERFACE;
return S_OK;
}
virtual ULONG STDMETHODCALLTYPE AddRef() {
InterlockedIncrement(&refcount);
return refcount;
}
virtual ULONG STDMETHODCALLTYPE Release() {
InterlockedDecrement(&refcount);
if (refcount == 0)
delete this;
return refcount;
}
};
如果大家了解一些COM知识,我们知道这里关键的是GetIDsOfNames和Invoke方法的实现,因为自动化对象只能通过这样的方式来调用,而不能使用函数指针直接调用虚拟方法,GetIDsOfNames查询指定的函数名的调用ID,就是说如果有一个方法是“HelloWorld”,那么它会先调用GetIDsOfNames方法来查询这个方法是否支持,如果支持则给出该方法的调用ID(通过修改rgDispId[out]参数),如果不支持则返回E_NOTIMPL,他的实现简单如下:
virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(
/* [in] */ REFIID riid,
/* [size_is][in] */ LPOLESTR *rgszNames,
/* [in] */ UINT cNames,
/* [in] */ LCID lcid,
/* [size_is][out] */ DISPID *rgDispId){
*rgDispId=1;
return S_OK;
}
这里我们假定了只有一个扩展函数HelloWorld,如果有多个,我们需要比较rgszNames参数的函数名返回不同的调用ID,有了调用ID,实现Invoke方法就很简单了:
virtual HRESULT STDMETHODCALLTYPE Invoke(
/* [in] */ DISPID dispIdMember,
/* [in] */ REFIID riid,
/* [in] */ LCID lcid,
/* [in] */ WORD wFlags,
/* [out][in] */ DISPPARAMS *pDispParams,
/* [out] */ VARIANT *pVarResult,
/* [out] */ EXCEPINFO *pExcepInfo,
/* [out] */ UINT *puArgErr){
if(dispIdMember==1)
{
MessageBox(0,"Hello World","Hello",0); //place your code here
frmweb->Edit1->Text="Hello World(这也可以?)";
}
return S_OK;
}
根据dispIdMember的不同实现不同的代码,如果方法是有参数的可以在pDispParams中取得,如果有什么不明白可以参考MSDN和一些COM的书籍,这里就不详细解释了。
最后我们要做的就是使我们的浏览器知道我们扩展了external,代码如下:
dochandler = new MyDocHandler;
webBrowser->Navigate(WideString(L"E:\\Projects\\extWeb\\ext.htm"));
while(webBrowser->Busy)
Application->ProcessMessages();
ICustomDoc *custdoc;
webBrowser->Document->QueryInterface(&custdoc); //取得IcustomDoc接口
if (custdoc)
{
custdoc->SetUIHandler(dochandler); //设置我们自己的界面处理器
custdoc->Release();
}
注意上面的粗体“我们的浏览器”,因为这样的WebBrowser控件,不影响IE本身的扩展,也就是说那个ext.htm文件只能在我们的程序中有效,就算其他用户得到了这段htm代码也不能正常运行的,如果你想测试,你得到的是:

说句实话设计这样界面还是有一定难度的,那么它在实际开发中到底有什么好处呢?我想至少有以下几点:
1. 界面设计和程序逻辑设计分离,美工可以和程序员一起工作,界面设计再也不是没有审美细胞程序员的问题;
2. 轻松实现Skin功能,界面的改变不需要重新编译代码,只需要换一个不同htm代码文件就可以;
3. 再也无法使用Spy工具获得窗体Handler做各种Hook,使你的程序运行的更安全;
4. 充分使用IE现有技术,搭建功能更强大的软件;
5. 使你的软件看起来更酷,更专业。
怎么样?心动了吗?赶快改善你的界面吧。如果你有更多想法,可以通过 siney@yeah.net 取得联系。