HTML文件如下:
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script language="javascript">
function ShowMessageBox()
{
if (cpp_object != null)
cpp_object.ShowMessageBox("你好,我是Javascript,你是谁?");
}
function GetProcessID()
{
if (cpp_object != null)
{
var id = cpp_object.GetProcessID();
document.getElementById("process_info").innerText = "本进程ID为:" + id;
}
}
function SaveCppObject(obj)
{
cpp_object = obj;
}
var cpp_object;
</script>
</head>
<body>
<p id="process_info"></p>
<button type="button" onclick="ShowMessageBox()">MessageBox</button>
<button type="button" onclick="GetProcessID()">Process ID</button>
</body>
</html>
在界面类中添加两个成员函数:
DWORD CJsCallCppDlg::GetProcessID()
{
return GetCurrentProcessId();
}
void CJsCallCppDlg::ShowMessageBox(const wchar_t *msg)
{
MessageBox(msg, L"这是来自javascript的消息");
}
接来下,我要用HTML中的这两个按钮,分别调用这两个C++函数,其中一个是ShowMessageBox,让Javascript调用它并传递一个字符串给它,最终C++这边通过Windows API的MessageBox实现弹出一个消息框。另外一个是GetProcessID,Javascript调用它,最终C++这边通过Windows API的GetCurrentProcessId()获取本进程ID,并给Javascript返回这个ID值,然后显示到HTML中。
实现js和C++交互的具体函数:
//我自己给我的两个函数拟定的数字ID,这个ID可以取0-16384之间的任意数
enum
{
FUNCTION_ShowMessageBox = 1,
FUNCTION_GetProcessID = 2,
};
//不用实现,直接返回E_NOTIMPL
HRESULT STDMETHODCALLTYPE CJsCallCppDlg::GetTypeInfoCount(UINT *pctinfo)
{
return E_NOTIMPL;
}
//不用实现,直接返回E_NOTIMPL
HRESULT STDMETHODCALLTYPE CJsCallCppDlg::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
{
return E_NOTIMPL;
}
//JavaScript调用这个对象的方法时,会把方法名,放到rgszNames中,我们需要给这个方法名拟定一个唯一的数字ID,用rgDispId传回给它
//同理JavaScript存取这个对象的属性时,会把属性名放到rgszNames中,我们需要给这个属性名拟定一个唯一的数字ID,用rgDispId传回给它
//紧接着JavaScript会调用Invoke,并把这个ID作为参数传递进来
HRESULT STDMETHODCALLTYPE CJsCallCppDlg::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
{
//rgszNames是个字符串数组,cNames指明这个数组中有几个字符串,如果不是1个字符串,忽略它
if (cNames != 1)
return E_NOTIMPL;
//如果字符串是ShowMessageBox,说明JavaScript在调用我这个对象的ShowMessageBox方法,我就把我拟定的ID通过rgDispId告诉它
if (wcscmp(rgszNames[0], L"ShowMessageBox") == 0)
{
*rgDispId = FUNCTION_ShowMessageBox;
return S_OK;
}
//同理,如果字符串是GetProcessID,说明JavaScript在调用我这个对象的GetProcessID方法
else if (wcscmp(rgszNames[0], L"GetProcessID") == 0)
{
*rgDispId = FUNCTION_GetProcessID;
return S_OK;
}
else
return E_NOTIMPL;
}
//JavaScript通过GetIDsOfNames拿到我的对象的方法的ID后,会调用Invoke,dispIdMember就是刚才我告诉它的我自己拟定的ID
//wFlags指明JavaScript对我的对象干了什么事情!
//如果是DISPATCH_METHOD,说明JavaScript在调用这个对象的方法,比如cpp_object.ShowMessageBox();
//如果是DISPATCH_PROPERTYGET,说明JavaScript在获取这个对象的属性,比如var n = cpp_object.num;
//如果是DISPATCH_PROPERTYPUT,说明JavaScript在修改这个对象的属性,比如cpp_object.num = 10;
//如果是DISPATCH_PROPERTYPUTREF,说明JavaScript在通过引用修改这个对象,具体我也不懂
//示例代码并没有涉及到wFlags和对象属性的使用,需要的请自行研究,用法是一样的
//pDispParams就是JavaScript调用我的对象的方法时传递进来的参数,里面有一个数组保存着所有参数
//pDispParams->cArgs就是数组中有多少个参数
//pDispParams->rgvarg就是保存着参数的数组,请使用[]下标来访问,每个参数都是VARIANT类型,可以保存各种类型的值
//具体是什么类型用VARIANT::vt来判断,不多解释了,VARIANT这东西大家都懂
//pVarResult就是我们给JavaScript的返回值
//其它不用管
HRESULT STDMETHODCALLTYPE CJsCallCppDlg::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid,
WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
//通过ID我就知道JavaScript想调用哪个方法
if (dispIdMember == FUNCTION_ShowMessageBox)
{
//检查是否只有一个参数
if (pDispParams->cArgs != 1)
return E_NOTIMPL;
//检查这个参数是否是字符串类型
if (pDispParams->rgvarg[0].vt != VT_BSTR)
return E_NOTIMPL;
//放心调用
ShowMessageBox(pDispParams->rgvarg[0].bstrVal);
return S_OK;
}
else if (dispIdMember == FUNCTION_GetProcessID)
{
DWORD id = GetProcessID();
*pVarResult = CComVariant(id);
return S_OK;
}
else
return E_NOTIMPL;
}
//JavaScript拿到我们传递给它的指针后,由于它不清楚我们的对象是什么东西,会调用QueryInterface来询问我们“你是什么鬼东西?”
//它会通过riid来问我们是什么东西,只有它问到我们是不是IID_IDispatch或我们是不是IID_IUnknown时,我们才能肯定的回答它S_OK
//因为我们的对象继承于IDispatch,而IDispatch又继承于IUnknown,我们只实现了这两个接口,所以只能这样来回答它的询问
HRESULT STDMETHODCALLTYPE CJsCallCppDlg::QueryInterface(REFIID riid, void **ppvObject)
{
if (riid == IID_IDispatch || riid == IID_IUnknown)
{
//对的,我是一个IDispatch,把我自己(this)交给你
*ppvObject = static_cast<IDispatch*>(this);
return S_OK;
}
else
return E_NOINTERFACE;
}
//我们知道COM对象使用引用计数来管理对象生命周期,我们的CJsCallCppDlg对象的生命周期就是整个程序的生命周期
//我的这个对象不需要你JavaScript来管,我自己会管,所以我不用实现AddRef()和Release(),这里乱写一些。
//你要return 1;return 2;return 3;return 4;return 5;都可以
ULONG STDMETHODCALLTYPE CJsCallCppDlg::AddRef()
{
return 1;
}
//同上,不多说了
//题外话:当然如果你要new出一个c++对象来并扔给JavaScript来管,你就需要实现AddRef()和Release(),在引用计数归零时delete this;
ULONG STDMETHODCALLTYPE CJsCallCppDlg::Release()
{
return 1;
}
###注意我的HTML中的JavaScript代码中,我用一个var cpp_object;全局变量来保存C++对象,然后我还写了一个SaveCppObject()函数给C++调用,在WebBrowser加载完毕HTML文档后,需要先用C++调用JavaScript的这个SaveCppObject()函数,并把C++对象指针传递给JavaScript,这样JavaScript才能把它保存到var cpp_object;中,才能进行接下来的JavaScript调用C++。C++调用JavaScript的SaveCppObject()方法代码如下:
//调用JavaScript的SaveCppObject函数,把我自己(this)交给它,SaveCppObject会把我这个对象保存到全局变量var cpp_object;中
//以后JavaScript就可以通过cpp_object来调用我这个C++对象的方法了
void CJsCallCppDlg::OnBnClickedOk()
{
CComQIPtr<IHTMLDocument2> document = m_webbrowser.get_Document();
CComDispatchDriver script;
document->get_Script(&script);
CComVariant var(static_cast<IDispatch*>(this));
script.Invoke1(L"SaveCppObject", &var);
}
对了,还有一点,写好的HTML文件不仅可以直接和EXE放在一个目录下使用,也可以在VisualStudio中把HTML文件作为资源添加到项目中,这样最终写出来的程序只有一个EXE,HTML文件已经在EXE里面了,至于如何让WebBrowser加载这个HTML文件,可以在CxxDlg::OnInitDialog()中使用如下代码:
//加载资源文件中的HTML,IDR_HTML1就是HTML文件在资源文件中的ID
wchar_t self_path[MAX_PATH] = { 0 };
GetModuleFileName(NULL, self_path, MAX_PATH);
CString res_url;
res_url.Format(L"res://%s/%d", self_path, IDR_HTML1);
m_webbrowser.Navigate(res_url, NULL, NULL, NULL, NULL);
本文由 Ryan 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为:
2017/11/15 09:16