扩展“Chrome扩展”
虽然基于强大的Javascript和Chrome扩展接口 (Extension API)所实现的扩展简单且强大,但是,如果你已经有一些实现的很不错的本地程序(Native Program)要通过浏览器复用怎么办?或者,浏览器的API不满足需求,你需要使用某些更强大的本地接口(Native API)时呢?
你可以配合Chrome扩展,实现NPAPI插件,通过Javascript与本地的二进制程序交互。
其实Chrome本身已经集成了一些NPAPI插件,比如PDF viewer,Flash player。
先简单区分一下扩展(Extension)和插件(Plguin),它们的共同点是都用于扩充增强浏览器的功能,
扩展是基于 浏览器提供的接口 ,运行在浏览器的沙箱(Sandbox)中,不与本地代码交互,安全性可控,也意味着可扩展的功能有限。
插件是基于与浏览器交互的框架(chrome用的是Mozilla的NPAPI框架),实现方式为本地二进制代码,插件其实就是能通过框架与浏览器通信的 本地程序 。它可以不运行在沙箱中,所以权限很大,实现不好很容易造成浏览器的漏洞。
实现NPAPI插件
创建动态库(DLL)项目
创建空项目
本文通过VS2010创建,首选新建Win32|Win32 Project, 然后在创建向导中选择DLL, empty project。复制示例代码
将示例包(文章附件)以下文件复制到项目中:common/* include/* lib/* Plugin.cpp Plugin.h PluginObject.cpp PluginObject.h
配置项目(Project)属性
修改输出的动态库名
修改Configuration Properties | General | Target Name,注意Mozilla规范要求,插件 必须以“np”开头 。添加Include目录
在Configuration Properties | C/C++ | General | Additional Include Directories添加自定义目录include。添加第三方库
这里要和前端JS交互,所以添加C++的第三方JSON库,
在Configuration Properties | Linker | General | Additional Library Directories添加链接库目录lib。
然后在Configuration Properties | Linker | Input | Additional Dependencies里添加库文件lib_json.lib。添加宏定义(Preprocessor)
在Configuration Properties | C/C++ | Preprocessor Definitions添加宏 WIN32;_WINDOWS;XP_WIN32;XP_WIN;X86;。禁止预编译头(Precompiled header)
Configuration Properties | C/C++ | Precompiled Header为“Not Using Precompiled Headers”。添加.def文件
在Configuration Properties | Linker | Input | Module Definition File中添加NPHello.def。修改Runtime Library
这一步比较关键,在发布插件时去除debug符号,以“ /MT ”方式编译,不然在某些系统会遇到缺少运行时库的问题。
Configuration Properties | C/C++ | Code Generation | Runtime Library设置为/MT。
实现插件
新建.def文件
在项目中新建NPHello.def:1
2
3
4
5LIBRARY "nphello"
EXPORTS
NP_GetEntryPoints @1
NP_Initialize @2
NP_Shutdown @3修改mimetype
npp_gate.cpp:1
2
3
4char *NPP_GetMIMEDescription(void)
{
return "application/x-hello";
}NPHello.rc:
1
2
3
4
5
6
7
8
9
10
11BEGIN
VALUE "CompanyName", "XB"
VALUE "FileDescription", "XB Hello Module"
VALUE "FileVersion", "1, 0, 0, 1"
VALUE "InternalName", "nphello.dll"
VALUE "LegalCopyright", "Copyright (C) 2013"
VALUE "MIMEType", "application/x-hello"
VALUE "OriginalFilename", "nphello.dll"
VALUE "ProductName", "NPHello"
VALUE "ProductVersion", "1, 0, 0, 1"
END删除NPP_GetJavaClass
np_entry.cpp1
2
3
4NPNFuncs.getJavaEnv = NULL;
NPNFuncs.getJavaPeer = NULL;
...
pluginFuncs->javaClass = NULL;定义与Javascript交互的接口
PluginObject.cpp1
2
3
4// define function name called by JS
const char* kPluginFunction = "function";
// define property name set by JS
const char* kPluginCallback = "callback";接口1:用于JS查询Plugin对象是否有该方法:
1
2
3
4
5
6
7
8
9
10
11bool PluginObject::hasMethod(NPIdentifier methodName)
{
bool bRev = false;
NPUTF8 *pName = NPNFuncs.utf8fromidentifier(methodName);
if (strcmp(pName, kPluginFunction) ==0){
return true;
} else {
return false;
}
}
接口2:用于JS查询Plguin对象是否有该属性:
1
2
3
4
5
6
7
8
9
10
11
12
bool PluginObject::hasProperty(NPIdentifier propertyName)
{
bool bRev = false;
NPUTF8 *pName = NPNFuncs.utf8fromidentifier(propertyName);
if (pName!=NULL){
if (strcmp(pName, kPluginCallback) == 0){
return true;
}
}
return bRev;
}
Javascript调用Plugin的入口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27bool PluginObject::invoke(NPIdentifier methodName,
const NPVariant* args, uint32_t argCount, NPVariant* result) {
char* name = NPNFuncs.utf8fromidentifier(methodName);
bool ret_val = false;
std::string outString;
if (!name) {
return ret_val;
}
if (strcmp(name, kPluginFunction)==0){
ret_val = true;
hThread = CreateThread(NULL, 0, FunctionThread, this, 0, NULL);
outString = "Called plugin method from external.";
} else {
// Exception handling.
outString = "Called an invalid method.";
}
char* npOutString = (char *)NPNFuncs.memalloc(outString.length() + 1);
if (!npOutString)
return false;
strcpy_s(npOutString, outString.length()+1, outString.c_str());
STRINGZ_TO_NPVARIANT(npOutString, *result);
NPNFuncs.memfree(name);
return ret_val;
}在新的线程中执行插件的逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// create a threed
DWORD WINAPI FunctionThread(LPVOID lparam){
PluginObject* pluginObj = (PluginObject*)lparam;
if (pluginObj==NULL)
return false;
pluginObj->PluginFunction();
return true;
}
void PluginObject::PluginFunction(){
try{
Sleep(3000);
Json::Value root;
root["msg"] = "Plugin process complete with three minutes!";
CallJSFunction(root.toStyledString());
} catch(HRESULT hr){
_com_error error(hr);
std::wstring msg(error.ErrorMessage());
Json::Value root;
root["msg"] = utf8_encode(msg);
CallJSFunction(root.toStyledString());
}
}当插件执行完毕,回调Javascript的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24void PluginObject::CallJSFunction(std::string result){
int iRev = 0;
if (mJSFunction != NULL){
// 转换参数列表
NPVariant relements[1];
STRINGZ_TO_NPVARIANT(result.c_str(), relements[0]);
// 调用JS函数
NPVariant jsResult;
NPN_InvokeDefault(npp, mJSFunction, relements, 1, &jsResult);
if (NPVARIANT_IS_STRING(jsResult)){
NPString rString = NPVARIANT_TO_STRING(jsResult);
char revBuf[255] = {0};
memcpy(revBuf, rString.UTF8Characters, rString.UTF8Length);
}
// 释放从浏览器那获取的结果
NPN_ReleaseVariantValue(&jsResult);
}
return;
}
编译项目
以Release方式编译
实现一个简单的扩展
Manifest
注意plugins属性,如果插件只希望被本扩展调用,那将public设置成false。
1 | { |
popup.html
1 |
|
popup.js
先设置回调函数,再去调用插件的方法。
1 | function callback(result){ |
运行结果
扩展阅读:使用 VS 编译 NPAPI 插件