(译)创建火狐浏览器(NPAPI)插件(二)

Building a firefox plugin – part two
作者:Richard

Recap 概述

Last time, I talked about the fundamentals of implementing a NPAPI plugin. Today, I’m going to go into a little more detail on how to use the strange callback architecture that firefox exposes.

上次, 我谈到了实现 NPAPI 插件的基础知识。 今天, 我将更详细地介绍如何使用 Firefox 公开的奇怪的回调体系结构。

The NPAPI uses a rather unique (in my experience) methodology for providing an interface between the plugin and browser, but it does have some rather significant advantages. Instead of providing binaries to be linked against, the browser simply expects you to implement the entry point NP_Initialize in your dll that the browser will call and tell you where the methods that you will be using are located. In order for the browser to interface with you, it calls NP_GetEntryPoints with a pointer to a structure for your plugin to fill out with function pointers that the browser will be calling on your plugin.

NPAPI 使用了一种相当独特的 (根据我的经验) 方法来提供插件和浏览器之间的接口, 但它确实有一些相当显著的优势。 浏览器无需提供要链接的二进制文件, 而只需插件 DLL 具有入口函数 NP_Initialize。浏览器会调用该入口函数, 并通过它向插件暴露可以供其使用的方法函数(浏览器的内部函数)的地址。 为了使浏览器与插件交互, 它还会调用 NP_GetEntryPoints, 其中包含一个指向插件结构体的指针, 插件通过将指向其内部对应函数的指针填充到该结构体中,回传给浏览器,以便浏览器能够调用插件的相应函数。

This removes the need for a large number of include and library files in your project. In fact, in practice I found that I only actually needed 12 include files from the Gecko SDK:

这样就无需在项目中包含大量的源文件和库文件。 事实上, 在实践中, 我发现我实际上只需要包含 Gecko SDK 中的 12 个文件:

  • jni.h
  • jni_md.h
  • jri.h
  • jritypes.h
  • jri_md.h
  • npapi.h
  • npruntime.h
  • nptypes.h
  • npupp.h
  • obsolete/protypes.h
  • prcpucfg.h
  • prtypes.h

Edit (Sep 16, 2010): Now over a year later there is a better way to do this. There is a Google Code project called npapi-headers that has just the header files you need — and they actually work on all browsers, all platforms (unlike the ones from the gecko SDK sometimes).

2010年9月16日 修订: 一年多之后,现在我们有更好的方法来做到这一点。 有一个名为 npapi-headers 的 Google Code 项目, 它只包含您所需的头文件, 它们实际上适用于所有浏览器、所有平台 (与来自 gecko SDK 的这个不同)。

Basic Lifecycle of a plugin: 插件的基本生命周期

Jean-Lou Dupont has an excelent visual diagram of this on his blog, which you can find here:

你可以在 Jean-Lou Dupont 的博客上找到一个很棒的图解:
http://jldupont.blogspot.com/2009/11/notes-on-npapi-based-plugins.html

The lifecycle of a plugin is actually pretty simple, once you figure it out. The initial entrypoints NP_Initialize and NP_GetEntryPoints could, according to the documentation, get called in any order; however, in practice, NP_GetEntryPoints seems to get called first on Windows. With that in mind, here is the order of calls for basic windowed plugin initialization on Windows:

一旦弄明白之后, 你其实会发现,插件的生命周期一点都不复杂。 根据文档, NP_Initialize 和 NP_GetEntryPoints 的调用顺序没有先后关系;然而, 在实践中,在 Windows 系统 上似乎总是 NP_GetEntryPoints 先被调用。因此,以下是 Windows 系统上,带有基本窗口的插件的初始化的顺序:

NP_GetEntryPoints – Plugin fills out a function table with the addresses of NPP_New, NPP_Destroy, NPP_SetWindow, etc

插件用 NPP_New, NPP_Destroy, NPP_SetWindow 等函数地址填充一个函数表

NP_Initialize – Plugin stores a copy of a function table with the addresses of NPN_CreateObject, NPN_MemAlloc, etc

插件存储一个包含 NPN_CreateObject, NPN_MemAlloc 等函数地址的函数表的副本

NPP_New – Plugin creates a new instance of itself and initializes it

插件创建一个新的实例, 并对其进行初始化

*NPP_SetWindow – This is called multiple times for each instance — each time the instance’s window is created, resized, or otherwise changed

每次创建、调整大小或以其他方式更改实例窗口时, 都会对每个实例进行多次调用

NPP_GetValue (Variable = NPPVpluginScriptableNPObject) – Plugin creates a scriptable NPObject and returns a pointer to it (after calling NPN_RetainObject on it)

插件创建一个可通过脚本操纵的 NPObject, 并返回指向它的指针 (在调用 NPN_RetainObject 之后)

Standard Plugin Activity 标准插件活动

NPP_Destroy – Plugin instance is destroyed

插件实例被销毁

NP_Shutdown – All remaining plugin resources are destroyed

所有剩余的插件资源都将被销毁

It is worth our time to investigate a few of these calls in more detail.

其中几个调用的细节值得我们花时间进一步探究。

NPError NPP_New(NPMIMEType pluginType, NPP npp, uint16 mode, int16 argc, char* argn[], char* argv[], NPSavedData* saved)

NPP_New is called once for each instance of the plugin. Note the difference in terminology: There is one plugin, and there are multiple instances of that plugin. NP_Initialize is only called once for the plugin — that is, the entire memory space.

对于插件的每个实例, 都会调用一次 NPP_New。 请注意术语的区别: 有一个插件, 并且该插件有多个实例。 NP_Initialize 只对插件调用一次, 即整个内存空间。

From the Gecko SDK source:

来自 Gecko SDK 源码:

/*
 *  NPP is a plug-in's opaque instance handle
 */
typedef struct _NPP
{
  void*pdata;      /* plug-in private data */
  void*ndata;      /* netscape private data */
} NPP_t;

typedef NPP_t*  NPP;

This structure is normally seen as NPP and serves as the handle of a plugin instance. As you can see, there is private data for the plugin and private data for the browser. When NPP_New is called, you should:

上述通常被命名为 NPP 的结构体,常作为插件实例的句柄。正如您所看到的, 该结构体包含指向插件私有数据和浏览器私有数据的指针。 调用 NPP_New 时, 您应该:

Create some sort of data structure to uniquely identify this instance of the plugin

创建某种数据结构来唯一地标识插件的当前实例

Assign a pointer to that data structure to npp->pdata

用指向该数据结构的指针给 npp->pdata 赋值

What I like to do is to create a PluginInstance class and use that as my “pdata”. Then my NPP_New function looks like this:

我喜欢做的是创建一个插件实例类, 并将其用作我的 “pdata”。 然后, 我的 NPP_New 函数如下所示:

// Called by the browser to create a new instance of the plugin
//由浏览器调用以创建插件的新实例
NPError NPP_New(NPMIMEType pluginType,
            NPP npp,
            uint16 mode,
            int16 argc,
            char* argn[],
            char* argv[],
            NPSavedData* saved)
{
    if (npp == NULL)
        return NPERR_INVALID_INSTANCE_ERROR;

    NPError rv = NPERR_NO_ERROR;
    PluginInstance* pPluginObj = new PluginInstance(npp);

    if (pPluginObj == NULL)
        return NPERR_OUT_OF_MEMORY_ERROR;

    npp->pdata = pPluginObj;
    return pPluginObj->NpapiNew(pluginType, mode, argc, argn, argv, saved);
}

In this way I can have a PluginInstance class that handles the entire lifecycle of a given instance of a plugin, and I simply create stub static functions to dereference the pdata pointer and call the correct function.

通过这种方式, 我可以有一个插件实例类, 用于处理插件特定实例的整个生命周期, 我只需创建 stub static 函数,即可解除对 pdata 指针的引用并调用正确的函数。

NPP_SetWindow (NPP npp, NPWindow* pNPWindow)

For many, this will be where the real fun starts — this function is called to tell the plugin which window they are in. From the Gecko SDK (npapi.h):

对许多人来说, 这才是真正有趣的地方 —— (浏览器)通过调用这个函数向插件传递插件所属窗口的信息。以下代码来自 Gecko SDK (npapi.h):

typedef struct _NPWindow
{
  void* window;  /* Platform specific window handle */
                 /* OS/2: x - Position of bottom left corner  */
                 /* OS/2: y - relative to visible netscape window */
  int32 x;       /* Position of top left corner relative */
  int32 y;       /* to a netscape page.					*/
  uint32 width;  /* Maximum window size */
  uint32 height;
  NPRect clipRect; /* Clipping rectangle in port coordinates */
                   /* Used by MAC only.			  */
  void * ws_info; /* Platform-dependent additonal data, linux specific */
  NPWindowType type; /* Is this a window or a drawable? */
} NPWindow;

A pointer to this structure is passed in with each call. On windows, the “void* window” will dereference to an HWND. On other platforms, it will likewise be dereferenced as an appropriate type.

每次调用 NPP_SetWindow 时都会传入指向 NPWindow 结构体的指针。 在 Windows 系统上, “void* window” 将解除对 HWND的引用。在其他平台上, 它也将作为适当的类型进行解除引用。

Notice that again NPP npp is the first parameter. This will be the case on all NPP functions except NPP_New, where the mimetype is also passed in. Since we created a PluginInstance object and assigned it to npp->pdata in NPP_New, we need to create a small stub function to forward our NPP_New to a method on that object, like so:

请注意, NPP npp 是第一个参数。除 NPP_New 函数还有一个 NPMIMEType 类型的参数(作为第一个参数)之外,所有其它 NPP 函数上均是如此。 由于我们在 NPP_New 中创建了一个插件的实例对象, 并将其赋值给 npp->pdata, 因此我们需要创建一个小存根函数, 以便对 NPP_New 创建实例对象的方法函数进行转发,如下所示:

// Called by browser whenever the window is changed, including to set up or destroy 
//在窗口更改时(含设置或销毁时)由浏览器调用
NPErrorNPP_SetWindow (NPP npp, NPWindow* pNPWindow)
{
    if (npp == NULL)
        return NPERR_INVALID_INSTANCE_ERROR;
    else if (npp->pdata == NULL)
        return NPERR_GENERIC_ERROR;

    PluginInstance *inst = (PluginInstance *)npp->pdata;
    return inst->NpapiSetWindow(pNPWindow);
}

On windows, when SetWindow is called we need to save the HWND and subclass the window so that we can get our own window event proc.

在Windows系统中调用 SetWindow 时, 我们需要保存窗口句柄( HWND )并对窗口进行子类化, 以便获得对应窗口事件(消息循环)。

NPError PluginInstance::NpapiSetWindow (NPWindow* pNPWindow)
{
    NPError rv = NPERR_NO_ERROR;

    if(pNPWindow == NULL)
        return NPERR_GENERIC_ERROR;

    // window just created; in initWindow, set initialized to true
    if(!this->initialized) {
        if(!this->initWindow(pNPWindow)) {
            return NPERR_MODULE_LOAD_FAILED_ERROR;
        }
    }

    // Window was already created; just pass on the updates
    this->updateWindow(pNPWindow);

    return rv;
}

With these functions, we get notified in one function when the window is first set, and another is called each time an update is made.

通过这些函数, 在首次设置窗口时,我们都会在一个函数中收到通知, 且窗口每次刷新都会调用另一个函数。

NPP_Destroy (NPP npp, NPSavedData** save)

When the Browser is ready to shut down a given plugin instance (the user leaves the page or the object tag is removed from the DOM), the browser calls NPP_Destroy. This call is responsible to free any memory being used by that plugin instance.

当浏览器准备关闭给定的插件实例 (用户离开页面或从 DOM 中删除插件对象标签) 时, 浏览器将调用 NPP_Destroy。 该函数负责释放该插件实例使用的内存。

// Called by browser to destroy an instance of the plugin 由浏览器调用以销毁插件的实例
NPError NPP_Destroy (NPP npp, NPSavedData** save)
{
    if (npp == NULL)
        return NPERR_INVALID_INSTANCE_ERROR;
    else if (npp->pdata == NULL)
        return NPERR_GENERIC_ERROR;

    PluginInstance *inst = (PluginInstance *)npp->pdata;
    NPError rv = inst->NpapiDestroy(save);

    delete getPluginObject(npp);
    npp->pdata = NULL;

    return rv;
}

Next Time 未完待续

That’s all the time I have today. Next time I will discuss NPObjects and how they are used to provide an interface by which Javascript can interact with your plugin.

以上就是今天的全部内容。 下次, 我将讨论 NPObjects, 及如何通过 NPObjects 提供一个插件与 Javascript 交互的接口。

Building a firefox plugin – part one
Building a firefox plugin – part two
Building a firefox plugin – part three
Building a firefox plugin – part four

猜你喜欢

转载自blog.csdn.net/tomggx/article/details/83964858