1 NPAPI插件
1.1 NPAPI簡介
NPAPI(Netscape Plugin Application Programming Interface,網景插件應用程序接口)是網景公司當年制定的開發基於網景瀏覽器,用於在瀏覽器中執行外部應用程序的通用接口。該接口基於插件機制,制定了一系列的標準和API,因此也有NPAPI插件一說。同期的微軟,也在IE中支持ActiveX爲瀏覽器插件,不得不承認微軟在這一點上,把瀏覽器和OS結合的更爲緊密,這也可能是當年微軟能夠擊潰網景的原因之一。
但網景的影響深遠,除了微軟特立獨行之外,其他瀏覽器開發廠商奇蹟般的都一致採用了NPAPI來對瀏覽器進行擴展(這包括後來從灰燼中重生的FireFox及新生的Chrome;當然,Chrome在不久前時間已經在嘗試拋棄NPAPI了)。因此,在目前來看NPAPI幾乎是IE之外的瀏覽器插件開發的統一標準。
1.2 準備工作
根據參考資料,從ftp://ftp.mozilla.org/pub/mozilla.org/firefox/releases/4.0.1/source/中下載了firefox早期的源代碼,並從中摳出了NPAPI相關的部分(恩,資料上說的不夠詳細)。
解壓源代碼,把modules\plugin\base\public和modules\plugin\sdk\samples\include兩個目錄中的文件複製出來放在一起(我創建了D:\npapi,把文件都放這了)。
另外,資料裏提到的三個文件在modules\plugin\sdk\samples\common下(記住位置,待會會用到)。
1.3 創建插件
順便提一下,本文以VS2003爲範例。
插件實現的功能:對瀏覽器(貼切點說是對javascript引擎)暴露對象Sample,而Sample又提供了一個sayHello的方法。這樣一來,我們可以在瀏覽器中,使用javascript通過Sample.sayHello();來調用插件所提供的功能。
1.3.1 創建Win32 DLL工程
1.3.2 引入NPAPI庫
在工程屬性中,添加“附加包含目錄”:D:\npapi(之前摳出來的部分)。
1.3.3 添加宏定義_X86_
1.3.4 添加模塊定義文件(.def文件)
創建sample.def文件,內容爲:
LIBRARY"sample"
EXPORTS
NP_GetEntryPoints @1
NP_Initialize @2
NP_Shutdown @3
1.3.5 編輯stdafx.h文件
增加tchar頭文件的引入:
#include<tchar.h>
增加NPAPI頭文件的引入:
//Mozilla-API
#include<npfunctions.h>
#include<npruntime.h>
#include"npruntime.h"
1.3.6 添加基礎框架文件
找到np_entry.cpp、npn_gate.cpp和npp_gate.cpp,複製到工程目錄下,並添加到工程(恩恩,位置在modules\plugin\sdk\samples\common)。
在編輯器裏分別打開着三個文件,並在文件頭部加入:
#include"stdafx.h"
1.3.7 編輯sample.cpp文件
將文件代碼修改爲:
#include"stdafx.h"
#include"sample.h"
NPErrorNS_PluginInitialize()
{
return NPERR_NO_ERROR;
}
voidNS_PluginShutdown()
{
}
nsPluginInstanceBase* NS_NewPluginInstance(nsPluginCreateData * aCreateDataStruct)
{
if(!aCreateDataStruct)
return NULL;
CPlugin* plugin = newCPlugin(aCreateDataStruct->instance);
return plugin;
}
voidNS_DestroyPluginInstance(nsPluginInstanceBase * aPlugin)
{
if(aPlugin)
delete (CPlugin *)aPlugin;
}
1.3.8 編輯sample.h文件
如果文件不存在,創建一個,並添加到工程。將文件內容修改爲:
#include"stdafx.h"
#include"npruntime.h"
#include"pluginbase.h"
boolIsStringNPIdentifier(NPIdentifier name)
{
return *(char**)name == (char*)name + 8;
}
char*CopyNPString(NPString str)
{
char* r= new char[str.UTF8Length + 1];
strncpy(r, str.UTF8Characters,str.UTF8Length);
r[str.UTF8Length] = 0;
return r;
}
classCSample : public NPObject
{
public:
CSample(NPP npp) : mNpp(npp) { }
~CSample() { }
static NPObject* _Creator(NPP npp,NPClass *aClass) { return new CSample(npp); }
static void _Deallocate(NPObject *npobj){ delete (CSample*)npobj; }
static void _Invalidate(NPObject *npobj){ ((CSample*)npobj)->Invalidate(); }
static bool _HasMethod(NPObject *npobj,NPIdentifier name) { return ((CSample*)npobj)->HasMethod(name); }
static bool _Invoke(NPObject *npobj,NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result){ return ((CSample*)npobj)->Invoke(name, args, argCount, result); }
static bool _InvokeDefault(NPObject*npobj, const NPVariant *args, uint32_t argCount, NPVariant *result) { return((CSample*)npobj)->InvokeDefault(args, argCount, result); }
static bool _HasProperty(NPObject *npobj, NPIdentifier name) { return ((CSample*)npobj)->HasProperty(name); }
static bool _GetProperty(NPObject *npobj,NPIdentifier name, NPVariant *result) { return((CSample*)npobj)->GetProperty(name, result); }
static bool _SetProperty(NPObject *npobj,NPIdentifier name, const NPVariant *value) { return((CSample*)npobj)->SetProperty(name, value); }
static bool _RemoveProperty(NPObject*npobj, NPIdentifier name) { return ((CSample*)npobj)->RemoveProperty(name);}
static bool _Enumerate(NPObject *npobj,NPIdentifier **identifier, uint32_t *count) { return((CSample*)npobj)->Enumerate(identifier, count); }
static bool _Construct(NPObject *npobj,const NPVariant *args, uint32_t argCount, NPVariant *result) { return((CSample*)npobj)->Construct(args, argCount, result); }
virtual void Invalidate() { }
virtual bool HasMethod(NPIdentifier name)
{
if(IsStringNPIdentifier(name))
{
char* methodName = *(char**)name;
if(_tcscmp(methodName,TEXT("sayHello")) == 0)
return true;
}
return false;
}
virtual bool Invoke(NPIdentifier name,const NPVariant *args, uint32_t argCount, NPVariant *result)
{
if(IsStringNPIdentifier(name))
{
char* methodName = *(char**)name;
if(_tcscmp(methodName,TEXT("sayHello")) == 0)
{
MessageBox(NULL,TEXT("hello, npapi."), TEXT("plugin-sample"), MB_OK |MB_ICONINFORMATION);
return true;
}
}
return false;
}
virtual bool InvokeDefault(constNPVariant *args, uint32_t argCount, NPVariant *result) { return true; }
virtual bool HasProperty(NPIdentifiername) { return false; }
virtual bool GetProperty(NPIdentifiername, NPVariant *result) { return false; }
virtual bool SetProperty(NPIdentifiername, const NPVariant *value) { return false; }
virtual bool RemoveProperty(NPIdentifiername) { return false; }
virtual bool Enumerate(NPIdentifier**identifier, uint32_t *count) { return false; }
virtual bool Construct(const NPVariant*args, uint32_t argCount, NPVariant *result) { return false; }
private:
NPP mNpp;
};
staticNPClass Sample = {
NP_CLASS_STRUCT_VERSION_CTOR,
CSample::_Creator,
CSample::_Deallocate,
CSample::_Invalidate,
CSample::_HasMethod,
CSample::_Invoke,
CSample::_InvokeDefault,
CSample::_HasProperty,
CSample::_GetProperty,
CSample::_SetProperty,
CSample::_RemoveProperty,
CSample::_Enumerate,
CSample::_Construct
};
classCPlugin : public nsPluginInstanceBase
{
public:
CPlugin(NPP pNPInstance) :nsPluginInstanceBase(), m_pNPInstance(pNPInstance), m_bInitialized(FALSE),m_sample(NULL) { }
~CPlugin() { }
NPBool init(NPWindow* pNPWindow)
{
m_bInitialized = TRUE;
return TRUE;
}
void shut()
{
if(m_sample)
{
// NPN_ReleaseObject(m_sample);
delete m_sample;
m_sample = NULL;
}
m_bInitialized = FALSE;
}
NPBool isInitialized()
{
return m_bInitialized;
}
NPError GetValue(NPPVariable variable,void *value)
{
switch(variable)
{
case NPPVpluginNameString:
*((char**)value) ="plugin-sample";
break;
case NPPVpluginDescriptionString:
*((char**)value) ="plugin-sample for Chrome";
break;
case NPPVpluginScriptableNPObject:
// if(m_sample == NULL)
// m_sample =(CSample*)NPN_CreateObject(m_pNPInstance, &Sample);
//
// if(m_sample != NULL)
// NPN_RetainObject(m_sample);
if(m_sample == NULL)
{
m_sample = newCSample(m_pNPInstance);
m_sample->_class= &Sample;
}
*((NPObject**)value) =m_sample;
break;
}
returnnsPluginInstanceBase::GetValue(variable, value);
}
private:
NPP m_pNPInstance;
NPBool m_bInitialized;
CSample* m_sample;
};
1.3.9 添加Version資源
以文本編輯器方式打開資源文件,在版本信息BLOCK中添加:
VALUE"MIMEType", "application/plugin-sample"
1.3.10 編譯輸出
自此,sample.dll已經躺在Debug目錄下了。
1.4 需要注意的問題
1.4.1 庫文件的捆綁
考慮到工程的獨立性,我們可以把庫文件與工程捆綁在一起,我的做法是在工程內創建一個inc目錄,把之前提到的D:\npapi下所有文件複製過來,並把“附加包含目錄”改爲:inc。
1.4.2 謹記MIMEType
一定要記得添加Version資源,並添加MIMEType項。
1.4.3 無效的NPN_CreateObject?
在後續的測試過程中,NPN_CreateObject總是無法有效的創建對象。因此,在sample.h中,我們採用了直接new CSample();的方式(具體原因有待研究)。
2 Chrome擴展
2.1 簡介
不愧是Google出品,Chrome從一推出就受到了業界大量的關注和用戶的青睞,幾年下來,市場份額一直在膨脹。其中原因不僅是小巧輕量和啓動快速,也有其快速支持最新Web標準等多方面的緣故。對於第三方開發商,Google也提供了Chrome擴展編程接口,用來提升瀏覽器本身的個性化定製。
Chrome擴展基於HTML5構建,面向javascript引擎暴露瀏覽器內部對象,使用javascript即可直接操作瀏覽器對象,從而實現功能擴展。當然,如果我們希望實現的功能超出了Chrome本身提供的內置對象所涵蓋的範圍,則需要之前提到的NPAPI插件的支持了(這就類似IE瀏覽器中通過new ActiveXObject創建COM對象來增強瀏覽器功能一樣)。
2.2 開始編寫
哦哦,提醒一下,下面提到的所有文件,務必放到同一個目錄中。
擴展實現的功能:在每個頁面(空白處)的右鍵菜單中,添加“sayHello”菜單項,用戶點擊這個菜單項時,擴展程序通過調用NPAPI插件的sayHello方法,實現彈出“hello,npapi.”對話框的功能。
2.2.1 準備一個圖標文件(.png)
去網上下載一個png文件吧,32x32、48x48、64x64、128x128等尺寸的都可以。總之,這是一件彰顯個性的事情。
2.2.2 準備NPAPI插件(.dll)
恩,之前編譯好,已經在Debug目錄躺的妥妥的sample.dll,把他複製過來吧。
2.2.3 編寫manifest.json
內容如下:
{
"manifest_version" : 2,
"minimum_chrome_version" :"6.0.0.0",
"name" : "我的擴展",
"description" : "我的擴展",
"version" : "1.0.0",
"permissions" : [
"contextMenus",
"tabs",
"http://*/*",
"https://*/*"
],
"icons" : {
"128" : "sayHello.png"
},
"background" : {
"page" :"background.html"
},
"plugins" : [
{ "path" :"sample.dll", "public" : true }
]
}
2.2.4 編寫background.html
內容如下:
<html>
<head></head>
<body>
<embedtype="application/plugin-sample"id="Sample"></embed>
<scripttype="text/javascript"src="background.js"></script>
</body>
</html>
2.2.5 編寫background.js
內容如下:
var bkgnd= chrome.extension.getBackgroundPage();
var sample= bkgnd.document.getElementById("Sample");
functiongetClickHandler(type) {
return function(info, tab) {
var url = info.pageUrl;
var title = tab.title;
if(type == "page") {
sample.sayHello();
}
}
}
chrome.contextMenus.create({"title" : "sayHello", "type" :"normal", "contexts" : [ "page" ],"onclick" : getClickHandler("page") });
2.3 安裝與測試
打開Chrome設置的“擴展程序”頁面,勾選“開發者模式”,點擊“加載正在開發的擴展程序”,在彈出的對話框中,選擇Chrome擴展所在的目錄,然後再確認“添加”即可。
2.4 發佈
同上,在開發者模式下,選擇“打包擴展程序”,在彈出的對話框中,選擇Chrome擴展所在的目錄,然後再次點擊“打包擴展程序”即可(第一次打包時,Chrome會自動生成一個密鑰文件;以後每次打包,都需要選擇這個密鑰文件)。
打包之後的Chrome擴展,是一個.crx的zip壓縮文件,可以直接拖拽到Chrome的擴展程序頁面,實現安裝。
3 參考資料
Mozilla官方文檔(英文):
https://developer.mozilla.org/en-US/docs/Plugins
https://developer.mozilla.org/en-US/docs/Gecko_Plugin_API_Reference/Plug-in_Basics
NPAPI開發詳解(中文):
http://mozilla.com.cn/post/21666/
http://wenku.baidu.com/view/c4b939f59e314332396893ce.html
Chrome擴展官方文檔(英文):
https://developer.chrome.com/extensions/index.html
Chrome擴展(中文):
http://open.chrome.360.cn/extension_dev/overview.html
4 本文代碼
http://download.csdn.net/detail/lonely001/6419841