您的位置:首页 > 编程语言 > C语言/C++

基于 wke 的浏览器:如何实现 js 和 c++ 的互相调用

2017-04-21 12:02 651 查看

一、引言

最近,老大给了我一个学习研讨任务,也就是如何让 js 和 C++ 进行互调使用。比如我可以在网页中,使用 js 代码调用 c++ 函数,也可以在 c++ 函数中调用 js 对于界面进行控制。

这是为后期的软件接入 Html5 做技术调研。

那么如何实现呢?

这里需要感谢 Redrain 的博客给Webkit内核的浏览器控件增加互交功能,通过参考他的博客,加之我自己的探索,一步一坑终于完成了这个 demo。

如果想要直接看大神的博客可以点击上面那个链接,如果你是个跟我一样在之间一无所知的小白,那就可以看看我作为小白的一步一步的探索吧,应该会比上面大神的博客详细一些。

二、基于 SOUI 的浏览器控件

我这个浏览器 demo 是基于 SOUI 界面库,而 Redrain 的 demo 是基于 duilib 的。其中 SOUI 界面库是我工作中接触到的第一个开源的界面库,功能非常强大,感兴趣的同学可以点击这里 UI神奇-SOUI

其实用的什么界面库在这里真的没有什么大的影响,因为都是同样封装了 wke 的浏览器内核来调用而已。那么 wke 是什么呢? wke 是 BlzFans (国内大神)对于 webkit 浏览器内核的一个小巧而又强大的封装库,可以实现浏览器内核的相关功能。

那么,怎么开始呢?我们就假设你已经有了一个可以访问网页的浏览器 demo 了,并且使用的是 wke 内核。接下来我们就一步一步的实现 js 和 c++ 之间的交互吧。

三、c++ 调用 js

首先,我们来实现稍微简单点的,c++ 调用 js。

如何实现呢?其实我们仔细翻看 wke.h 文件,就可以看到其实 blzFans 已经封装好了。我们打开 wke.h 文件,可以看到以下的两行代码:

virtual jsValue runJS(const utf8* script) = 0;
virtual jsValue runJS(const wchar_t* script) = 0;


通过函数名称,我们就可以推测这是调用 js 的方法,那么我们只需要在封装浏览器控件的地方,封装一个函数方法,调用
runJS()
方法即可。

这里我使用的是 SOUI 界面库,在 demo 里面已经封装好了一个基本成型的浏览器控件,这里直接在
SWkeWebkit
类中添加
RunJS()
方法即可:

SStringW SWkeWebkit::RunJS(SStringW strValue)
{
if (m_pWebView == NULL) return SStringW(L"wke 对象为空");
jsValue jsRet = m_pWebView->runJS(strValue);
return jsToStringW(m_pWebView->globalExec(), jsRet);
}


这里需要解释一下,
SStringW
是 SOUI 界面库封装的字符串类,我们将 wke 对象设置为
SWkeWebkit
类的私有变量进行存储,这里由
m_pWebView
就可以来调用
runJS()
方法调用 js 代码了。

值得一说的是最后的 return 句,这里是参考 Redrain 的代码写的,刚开始也不知道是什么含义,经过多次测试后发现,这一句可以用来返回可能需要返回的 js 代码结果,比如寻找一个元素后返回某些属性值什么的。

比如说,我在 demo 里面写了一个 index.html ,其中有这么一句代码:

<img id="img_track_event_id" name="img_track_event_name" class="" src="images/bd_logo.png" width="300" height="128" onmousedown="CallCPlusPlus()" />


这个 img 元素的 id 值为
img_track_event_id
,name 值为
img_track_event_name
,然后我们可以尝试用 c++ 调用 js 代码:

document.getElementById("img_track_event_id").name


来返回我们需要返回的 img 控件的 name 值。



这里可以看到,我在浏览器地址栏输入了 js 代码,并点击 run js 后,响应按钮信息,从地址栏获取到了 js 代码,运行后获取到了 js 返回的值,并以弹框形式显示出来。

这里我感觉我已经讲述的非常详细了,其中关于如何自己在本地建立一个静态的 html 文件进行测试还没有细说,下一节将会讲到,其他如果还有不懂的地方,可以参看我的 demo。

四、js 调用 c++

在之前,我们已经实现了 c++ 调用 js 的功能,是通过 wke 封装好的
runJS
方法实现的。那么 js 调用 c++ 应该如何实现呢?

甚至有些人会问, js 调用 c++ 应该如何测试呢?

1. 创建测试网页

其实在上一节,我漏了很重要的一点没有讲述,那就是如何建立本地的网页测试。为什么要建立呢?因为我们要让 js 与 c++ 交互,就必定需要一个本地的可以测试的界面。

这里我在软件运行路径下,建立了一个文件夹
Html
,在这个文件夹中建立了文件
index.html
,这个 html 文件里面主要就是有一个 img 元素,用来响应指定的 js 方法。

<img id="img_track_event_id" name="img_track_event_name" class="" src="images/bd_logo.png" width="300" height="128" onmousedown="CallCPlusPlus()" />


至于这个
index.html
文件如何书写,可以参看我的 demo 项目,这里就不再赘述了(其实作为一个小白,这一块我也是遇到坑了的,不过可以看代码解决的问题,就不在博客里说了,代码里一切都有)。

需要说一下的是,如何使用 wke 载入本地的 html 文件呢:

webView->loadFile(_T("Html/index.html"));
bool b = webView->isLoaded();


只需要让 wke 对象调用
loadFile
方法即可,注意这里的路径设置,需要将 Html 文件夹放到程序的当前运行目录下(或许有些人会问,怎么知道程序的当前运行目录呢?一般都是 sln 的同级目录,不过也可以使用
GetCurrentDirectory
函数来获取,我就是这么获取到的)。

到了现在,我们已经创建了测试的环境,并编写好了测试的网页元素,那么让我们开始吧!

2. 查看 wke 相关接口

首先,让我们看看 wke 提供的接口函数:

WKE_API void jsBindFunction(const char* name, jsNativeFunction fn, unsigned int argCount);
WKE_API void jsBindGetter(const char* name, jsNativeFunction fn); /*get property*/
WKE_API void jsBindSetter(const char* name, jsNativeFunction fn); /*set property*/


这里
jsBindFunction
就是用来绑定 c++ 函数的方法。那么回调函数应该怎么写呢?

#define JS_CALL __fastcall
typedef jsValue (JS_CALL *jsNativeFunction) (jsExecState es);


Got it!

我们现在有了绑定的函数,有了回调函数,只要琢磨清除用法就可以使用了!

那么该如何使用呢?

首先,在
index.html
中写好我们触发的 js 函数:

<script type="text/javascript">
function CallCPlusPlus() {
msgBox("点击图片由 js 调用 C++ 弹窗", "提示");
}
</script>


这里需要解释下的就是,
msgBox
我理解为就是一个标签,是一个什么标签呢?是用来在绑定的时候,让 wke 知道,哦,我在 js 中看到了
msgBox
我就把它与某个 c++ 函数绑定起来,然后
msgBox
函数里面自然可以传入参数。这样,通过这个标签,就完成了 js 和 c++ 对于同一个函数的认定响应。

然后,我们在 html 中编写能够触发我们这个 js 函数的元素:

<img id="img_track_event_id" name="img_track_event_name" class="" src="images/bd_logo.png" width="300" height="128" onmousedown="CallCPlusPlus()" />


注意这里选择了
onmousedown
也就是鼠标单击行为触发此 js 函数,至此,我们已经完成了界面上需要做的事情。

再然后,我们到调用处,编写我们需要绑定的全局的 c++ 函数:

// 全局的 js 调用 c++ 的函数
jsValue JS_CALL jsMsgBox(jsExecState es)
{
const wchar_t *text = jsToStringW(es, jsArg(es, 0));
const wchar_t *title = jsToStringW(es, jsArg(es, 1));

SOUI::SMessageBox(NULL, text, title, MB_OK);

return jsUndefined();
}


这里需要解释下,
jsMsgBox
并不是我们之前在 js 函数中写的那个标签
msgBox
。我们在 js 函数里的只是一个绑定时 c++ 和 js 双方都认定的一个标签而已,c++ 函数的名称是可以随便取的,但是函数返回值和参数必须与 wke 声明一致。

另外,传入的参数只有一个
es
,wke 通过
jsToStringW(es, jsArg(es, 0))
来解析出函数所需要的参数。这里我们解析出来了两个参数,并使用
SMessageBox
函数显示出来,这里的
SMessageBox
是 SOUI 封装的弹框类。

最后,我们将 js 中的标签
msgBox
与 全局函数
jsMsgBox
绑定起来即可:

// 绑定 js 函数,让 js 主动调用 C++ 函数
jsBindFunction("msgBox", jsMsgBox, 2);


这里
jsBindFunction
函数,第一个参数的含义就是 js 函数中使用到的标签,第二个参数是需要绑定的 c++ 函数名,第三个参数是参数个数。

至此,我们完成了 c++ 部分的编写。

现在,我们运行程序,点击
run c++
,进入测试界面,点击图片,触发 js 方法,调用 c++ 方法,弹框:



成功撒花!!!

五、总结

这些实现的方法都不是我做的。

SOUI 界面库 是 SOUI 作者启程写的;

wke 浏览器内核是 BlzFans 写的;

js 和 c++ 互调方法是 Redrain 写的;

这里我只是摸索着他们的后路,将自己同样实现后的经验和感想分享。

最后,附上我的 demo 代码的 github 地址,希望能对有需要的同学有点启发,JsCplusplusInteractons
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息