http://geeklu.com/2010/10/getting-started-with-npapi-plugin/
最近需要写一个Mac平台上的简单的跨浏览器的插件,需要在js中调用本地方法,而npapi满足此要求。 NPAPI就是Netscape Plugin Application Programming Interface的缩写了,虽然Netscape已经去了,但是这个却被沿用下来,在各大浏览器中都得以实现。还是纪念下曾经的浏览器的鼻祖啊。
在网络上搜索了很长时间,一直没有找到合适的满足自己需求的代码例子。且这方面的文档也少的可怜。 还是先提一下,有两个系列的文章还是不错,虽然或许可能也不完全正确,但是帮助理解npapi的编程模型是非常有帮助的:
http://colonelpanic.net/category/plugindev/npapi-plugindev/ http://rintarou.dyndns.org/category/browser/plugin
下面是我寻找解决方法的过程 一开始找到mozilla的一篇官方文档:Writing a plugin for Mac OS X,这里面提到了了官方的代码库中有demo,于是就跑到公司checkout下mozilla的分支。并看了下里面的一个mac的例子. 不过这个例子没有使用npruntime的东西,加上一开始的时候对npapi不熟悉,就继续搜索. 在google, stackoverflow,github, google codes 中不停的搜索,结果甚少,最后找到一个npruntime的例子,Call Native API from Google Chrome Extension on Mac OS X只可惜编译后只能在mac的chrome下运行。 后来又找到了Firebreath这个跨平台的浏览器NPAPI插件开发框架,可以通过python和cmake的配合,生成适合不同操作系统的浏览器插件的工程,于是就测试了,果真生成xcode项目后,编译,然后测试了下js调用插件的方法,还真能在各个浏览器(测试了三个比较常用的safari,chrome,firefox)下运行。但是无奈由于包含一些高级的程序代码其最小编译后的大小都2m+对于一个功能单一的插件来讲,无疑是让人无法接受的,所以继续找demo. 后来找到了npsimple-win32 这个windows下的vs的项目,我将其移植到mac上,编译运行后,唯独在safari下无法运行,悲剧的一塌糊涂,对于一个npapi不同的浏览器在实现的具体细节上竟然有细微的差别,真是太变态鸟。 最后无奈之下,将Weikit开源项目中的例子弄出来,然后和npruntime的相关代码进行整合,发现在safari,chrome可以运行在firefox中没法运行,好吧,至少现在两个例子的并集是完整的,我就将两个代码进行查看,最终找出了问题的所在,在safari下需要启动CoreGraphics,而在firefox下scriptable的NPObject的hasProperty和getProperty必须设置,可能在firefox下调用某函数,先去scriptable NPObject中找有没有这个名字的属性,然后在找方法吧。 最后终于写出了一个简单的例子
#import <WebKit/npapi.h>
#import <WebKit/npfunctions.h>
#import <WebKit/npruntime.h>
// Browser function table,可以通过它来得到浏览器提供的功能
static NPNetscapeFuncs* browser;
static const char *plugin_method_name_open = "open";
/*******各种接口的声明*********/
//在NPAPI编程的接口中你会发现有NP_打头的,有NPP_打头的,有NPN_打头的
//NP是npapi的插件库提供给浏览器的最上层的接口
//NPP即NP Plugin是插件本身提供给浏览器调用的接口,主要被用来填充NPPluginFuncs的结构体
//NPN即NP Netscape ,是浏览器提供给插件使用的接口,这些接口一般都在NPNetscapeFuncs结构体中
//Mach-o entry points,浏览器和创建交流的最上层的接口
NPError NP_Initialize(NPNetscapeFuncs *browserFuncs);
NPError NP_GetEntryPoints(NPPluginFuncs *pluginFuncs);
void NP_Shutdown(void);
//NPP Functions
NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData* saved);
NPError NPP_Destroy(NPP instance, NPSavedData** save);
NPError NPP_SetWindow(NPP instance, NPWindow* window);
NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16_t* stype);
NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason);
int32 NPP_WriteReady(NPP instance, NPStream* stream);
int32 NPP_Write(NPP instance, NPStream* stream, int32 offset, int32 len, void* buffer);
void NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname);
void NPP_Print(NPP instance, NPPrint* platformPrint);
int16_t NPP_HandleEvent(NPP instance, void* event);
void NPP_URLNotify(NPP instance, const char* URL, NPReason reason, void* notifyData);
NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value);
NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value);
//Functions for scriptablePluginClass
bool plugin_has_method(NPObject *obj, NPIdentifier methodName);
bool plugin_invoke(NPObject *obj, NPIdentifier methodName, const NPVariant *args, uint32_t argCount, NPVariant *result);
bool hasProperty(NPObject *obj, NPIdentifier propertyName);
bool getProperty(NPObject *obj, NPIdentifier propertyName, NPVariant *result);
static struct NPClass scriptablePluginClass = {
NP_CLASS_STRUCT_VERSION,
NULL,
NULL,
NULL,
plugin_has_method,
plugin_invoke,
NULL,
hasProperty,
getProperty,
NULL,
NULL,
};
//接口的实现
NPError NP_Initialize(NPNetscapeFuncs* browserFuncs)
{
browser = browserFuncs;
return NPERR_NO_ERROR;
}
NPError NP_GetEntryPoints(NPPluginFuncs* pluginFuncs)
{
pluginFuncs->version = 11;
pluginFuncs->size = sizeof(pluginFuncs);
pluginFuncs->newp = NPP_New;
pluginFuncs->destroy = NPP_Destroy;
pluginFuncs->setwindow = NPP_SetWindow;
pluginFuncs->newstream = NPP_NewStream;
pluginFuncs->destroystream = NPP_DestroyStream;
pluginFuncs->asfile = NPP_StreamAsFile;
pluginFuncs->writeready = NPP_WriteReady;
pluginFuncs->write = (NPP_WriteProcPtr)NPP_Write;
pluginFuncs->print = NPP_Print;
pluginFuncs->event = NPP_HandleEvent;
pluginFuncs->urlnotify = NPP_URLNotify;
pluginFuncs->getvalue = NPP_GetValue;
pluginFuncs->setvalue = NPP_SetValue;
return NPERR_NO_ERROR;
}
void NP_Shutdown(void)
{
}
bool plugin_has_method(NPObject *obj, NPIdentifier methodName) {
// This function will be called when we invoke method on this plugin elements.
NPUTF8 *name = browser->utf8fromidentifier(methodName);
bool result = strcmp(name, plugin_method_name_open) == 0;
browser->memfree(name);
return result;
}
bool plugin_invoke(NPObject *obj, NPIdentifier methodName, const NPVariant *args, uint32_t argCount, NPVariant *result) {
// Make sure the method called is "open".
NPUTF8 *name = browser->utf8fromidentifier(methodName);
if(strcmp(name, plugin_method_name_open) == 0) {
browser->memfree(name);
BOOLEAN_TO_NPVARIANT(false, *result);
// Meke sure the arugment has at least one String parameter.
if(argCount > 0 && NPVARIANT_IS_STRING(args[0])) {
// Build CFURL object from the arugment.
NPString str = NPVARIANT_TO_STRING(args[0]);
CFURLRef url = CFURLCreateWithBytes(NULL, (const UInt8 *)str.UTF8Characters, str.UTF8Length, kCFStringEncodingUTF8, NULL);
if(url) {
// Open URL with the default application by Launch Service.
OSStatus res = LSOpenCFURLRef(url, NULL);
CFRelease(url);
BOOLEAN_TO_NPVARIANT(res == noErr, *result);
}
}
return true;
}
browser->memfree(name);
return false;
}
bool hasProperty(NPObject *obj, NPIdentifier propertyName) {
return false;
}
bool getProperty(NPObject *obj, NPIdentifier propertyName, NPVariant *result) {
return false;
}
//NPP Functions Implements
NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData* saved)
{
// Create per-instance storage
//obj = (PluginObject *)malloc(sizeof(PluginObject));
//bzero(obj, sizeof(PluginObject));
//obj->npp = instance;
//instance->pdata = obj;
if(!instance->pdata) {
instance->pdata = browser->createobject(instance, &scriptablePluginClass);
}
// Ask the browser if it supports the CoreGraphics drawing model
NPBool supportsCoreGraphics;
if (browser->getvalue(instance, NPNVsupportsCoreGraphicsBool, &supportsCoreGraphics) != NPERR_NO_ERROR)
supportsCoreGraphics = FALSE;
if (!supportsCoreGraphics)
return NPERR_INCOMPATIBLE_VERSION_ERROR;
// If the browser supports the CoreGraphics drawing model, enable it.
browser->setvalue(instance, NPPVpluginDrawingModel, (void *)NPDrawingModelCoreGraphics);
return NPERR_NO_ERROR;
}
NPError NPP_Destroy(NPP instance, NPSavedData** save)
{
// If we created a plugin instance, we'll destroy and clean it up.
NPObject *pluginInstance=instance->pdata;
if(!pluginInstance) {
browser->releaseobject(pluginInstance);
pluginInstance = NULL;
}
return NPERR_NO_ERROR;
}
NPError NPP_SetWindow(NPP instance, NPWindow* window)
{
return NPERR_NO_ERROR;
}
NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16_t* stype)
{
*stype = NP_ASFILEONLY;
return NPERR_NO_ERROR;
}
NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason)
{
return NPERR_NO_ERROR;
}
int32 NPP_WriteReady(NPP instance, NPStream* stream)
{
return 0;
}
int32 NPP_Write(NPP instance, NPStream* stream, int32 offset, int32 len, void* buffer)
{
return 0;
}
void NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname)
{
}
void NPP_Print(NPP instance, NPPrint* platformPrint)
{
}
int16_t NPP_HandleEvent(NPP instance, void* event)
{
return 0;
}
void NPP_URLNotify(NPP instance, const char* url, NPReason reason, void* notifyData)
{
}
NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value)
{
NPObject *pluginInstance=NULL;
switch(variable) {
case NPPVpluginScriptableNPObject:
// If we didn't create any plugin instance, we create it.
pluginInstance=instance->pdata;
if (pluginInstance) {
browser->retainobject(pluginInstance);
}
*(NPObject **)value = pluginInstance;
break;
default:
return NPERR_GENERIC_ERROR;
}
return NPERR_NO_ERROR;
}
NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value)
{
return NPERR_GENERIC_ERROR;
}
测试代码
<html>
<head>
<script>
function run() {
var plugin = document.getElementById("pluginId");
plugin.open("http://www.geeklu.com");
}
</script>
</head>
<body >
<embed width="0" height="0" type="test/x-open-with-default-plugin" id="pluginId">
<button οnclick="run()">run</button>
</body>
</html>