NPAPI插件开发详细记录:插件开发入门
为什么这还是入门呢?因为前面的我们什么都没学会,只是学会了建立一个dll项目,并生成了一个Firefox可以识别dll,在测试页面中不会弹出需要下载插件的错误。而任何功能都没有实现。所以这里才是真正的入门。
本文随着作者本人的学习进度逐渐深入,难免有不周全之处,请看到本文的朋友帮忙指出有误的地方。
插件窗口在哪里
入门篇暂时只讲述一个问题,根据我的理解,plugin创建时,浏览器会创建一个子窗口,作为plugin的窗口(对于有窗口的plugin)。但是一直不知道这个子窗口怎么获取。仔细看上面的例子才发现,plugin类实现的不光是Plugin.cpp中的那几个函数,最重要的一个函数实现在Plugin.h中:NPBool init(NPWindow* pNPWindow) { m_bInitialized = TRUE; return TRUE;}
这个函数没有实现什么功能,但是注意这个函数的参数NPWindow*。啊哈!这个不就是我苦苦寻求的plugin窗口么?!!!
为了确认这一点,我就在这里下一个断点来看看,我觉得这个函数放在头文件中简直就是个错误,于是我将头文件中实现的三个函数都放到了.cpp文件中去实现了。
修改之后的头文件为:
- #pragma once
- #include "pluginbase.h"
- class Plugin :
- public nsPluginInstanceBase
- {
- private:
- NPP m_pNPInstance;
- NPBool m_bInitialized;
- public:
- Plugin(NPP pNPInstance);
- ~Plugin();
- NPBool init(NPWindow* pNPWindow);
- void shut();
- NPBool isInitialized();
- };
移动到Plugin.cpp文件中实现的三个函数为:
- NPBool Plugin::init(NPWindow* pNPWindow)
- {
- m_bInitialized = TRUE;
- return TRUE;
- }
- void Plugin::shut()
- {
- m_bInitialized = FALSE;
- }
- NPBool Plugin::isInitialized()
- {
- return m_bInitialized;
- }
这样再次生成这个插件,下面以此为例展示一下如何对插件进行调试。
插件调试
首先在程序中下好断点:
然后打开网页,接着附加到进程:
注意plugin-container可能不止一个,选择的时候要根据其路径看准用来调试的那个。
附加到进程之后,在浏览器中刷新含有要调试的插件的页面。程序就在下断点的地方断下了:
可以看到pNPWindow传递过来的参数。这就是调试插件的具体操作了。
使用窗口句柄
从上面的调试过程可以发现,pNPWindow的window成员应该就是插件窗口的句柄了,根据相关资料进一步得知,对于windowed的plugin确实是窗口的句柄,而对于winless的plugin,window成员是一个HDC。因此,如果是winless的plugin,可以直接将pNPWindow->window强制转换为HDC然后在窗口中进行绘制、输出内容。
插件分为winless和windowed的,其主要区别是winless的插件窗口不是真正意义上的窗口只是网页的一部分,而windowed的插件窗口是浏览器的一个子窗口。由于winless的插件没有窗口所以其事件处理有一个专门的函数NPP_HandleEvent来对窗口的事件进行处理。对于winless的插件,事件响应函数就需要继承nsPluginInstanceBase类并实现函数HandleEvent;而对于windowed 的插件,插件窗口完全由插件控制,因此需要根据这个窗口句柄自行在插件中实现各种功能,包括事件消息响应等等。下面分别探讨在winless的插件和windowed的插件中如何使用窗口句柄。
winless插件
要创建winless的插件需要在新实例创建的时候指定为winless的,否则插件会默认创建为windowed的。修改NS_NewPluginInstance函数(该函数是我们自己实现的,在Plugin.cpp中,尽量不要修改np_entry.cpp、npn_gate.cpp、npp_gate.cpp这几个文件更不用说npapi.h了)。
- nsPluginInstanceBase * NS_NewPluginInstance(nsPluginCreateData * aCreateDataStruct)
- {
- if(!aCreateDataStruct)
- return NULL;
- Plugin * plugin = new Plugin(aCreateDataStruct->instance);
- BOOL bWindowed = FALSE;
- NPN_SetValue(aCreateDataStruct->instance, NPPVpluginWindowBool, (void *)bWindowed);//winless插件需要在这里指出,默认为windowed
- return plugin;
- }
用NPN_SetValue指定我们要创建的插件是winless的。上面的写法是中规中矩的写法,如果图省事可以写成NPN_SetValue(aCreateDataStruct->instance, NPPVpluginWindowBool, NULL);
本例参考sdk中sample目录下的winless例子来写的,有必要的话你可以参考那个例子。下面要实现一个功能:将插件窗口的背景设置为蓝色,另外按下鼠标左键会发出声音,鼠标移动时在插件窗口画线。
首先,为plugin类添加一个私有成员mWindow保存窗口指针,覆写基类的SetWindow函数,
- NPError Plugin::SetWindow(NPWindow* Window)
- {
- mWindow=Window;
- return NPERR_NO_ERROR;
- }
其他功能都要在事件处理函数中实现,因此基类的虚函数HandleEvent需要加以实现。
- uint16_t Plugin::HandleEvent(void* aEvent)
- {
- NPEvent *event = (NPEvent *)aEvent;
- switch(event->event)
- {
- case WM_PAINT:
- {
- if(!mWindow)
- break;
- // get the dirty rectangle to update or repaint the whole window
- RECT * drc = (RECT *)event->lParam;
- if(drc)
- FillRect((HDC)event->wParam, drc, (HBRUSH)(COLOR_WINDOW));
- else {
- RECT rc;
- rc.bottom = mWindow->y + mWindow->height;
- rc.left = mWindow->x;
- rc.right = mWindow->x + mWindow->width;
- rc.top = mWindow->y;
- FillRect((HDC)event->wParam, &rc, (HBRUSH)(COLOR_WINDOW));
- }
- break;
- }
下一个功能:按下鼠标左键时发声:
- case WM_LBUTTONDOWN:
- Beep(1000,200);
- break;
最后实现画线的功能,这也是真正展示处理窗口的window成员的地方:
首先添加私有成员变量: int m_oldX,m_OldY,m_newX,m_newY;
在左键按下的事件中记录一个坐标,将左键按下的代码修改为:
- case WM_LBUTTONDOWN:
- Beep(1000,200);
- m_oldX=LOWORD(event->lParam);
- m_OldY=HIWORD(event->lParam);
- break;
接着实现鼠标移动的事件响应代码:
- case WM_MOUSEMOVE:
- {
- m_newX=LOWORD(event->lParam);
- m_newY=HIWORD(event->lParam);
- HDC hDC = (HDC)mWindow->window;
- MoveToEx(hDC,m_oldX,m_OldY,NULL);
- LineTo(hDC,m_newX,m_newY);
- m_oldX=m_newX;m_OldY=m_newY;
- break;
- }
这里直接将mWindow->window强制转换为HDC,是因为对于winless的插件window成员在windows平台下就是该插件的HDC。注意由于这里有变量hDC的定义,因此这个case的代码必须用大括号括起来,否则会报错。下面是事件处理函数最后一点代码:
- default:
- return 0;
- }
- return 1;
- }
实现完毕之后,生成项目,然后用前面的测试页面对这个新的dll进行测试。如下图:
上面实现的有一个问题,就是只有窗口重绘的时候才会显示出画的线,但我想已经说明了window成员在winless类型的插件中的使用,因此就不深入研究了,如果谁看到这个例子能够给出好的改进意见,我将不胜感激。
可能有的朋友一步步跟下来之后发现还是有问题,下面贴出前面这个项目的plugin.h文件及plugin.cpp文件的完整内容
- //Plugin.h文件
- #pragma once
- #include "pluginbase.h"
- class Plugin :
- public nsPluginInstanceBase
- {
- private:
- NPP m_pNPInstance;
- NPBool m_bInitialized;
- NPWindow* mWindow;
- int m_oldX,m_OldY,m_newX,m_newY;
- bool bMdown;
- public:
- Plugin(NPP pNPInstance);
- ~Plugin();
- NPBool init(NPWindow* pNPWindow);
- void shut();
- NPBool isInitialized();
- NPError SetWindow(NPWindow* Window);
- uint16_t HandleEvent(void* event);//windowed 插件窗口自己接收消息,不需handleevent。
- };
- //Plugin.cpp文件
- #include "Plugin.h"
- ////// functions /////////
- NPError NS_PluginInitialize()
- {
- return NPERR_NO_ERROR;
- }
- void NS_PluginShutdown()
- {
- }
- nsPluginInstanceBase * NS_NewPluginInstance(nsPluginCreateData * aCreateDataStruct)
- {
- if(!aCreateDataStruct)
- return NULL;
- Plugin * plugin = new Plugin(aCreateDataStruct->instance);
- BOOL bWindowed = FALSE;
- NPN_SetValue(aCreateDataStruct->instance, NPPVpluginWindowBool, (void *)bWindowed);//winless插件需要在这里指出,默认为windowed
- return plugin;
- }
- void NS_DestroyPluginInstance(nsPluginInstanceBase * aPlugin)
- {
- if(aPlugin)
- delete (Plugin *)aPlugin;
- }
- ////// Plugin /////////
- Plugin::Plugin(NPP pNPInstance):nsPluginInstanceBase(),
- m_pNPInstance(pNPInstance),
- m_bInitialized(FALSE)
- {
- bMdown=false;
- }
- NPBool Plugin::init(NPWindow* pNPWindow)
- {
- m_bInitialized = TRUE;
- return TRUE;
- }
- void Plugin::shut()
- {
- m_bInitialized = FALSE;
- }
- NPBool Plugin::isInitialized()
- {
- return m_bInitialized;
- }
- Plugin::~Plugin(void)
- {
- }
- NPError Plugin::SetWindow(NPWindow* Window)
- {
- mWindow=Window;
- return NPERR_NO_ERROR;
- }
- uint16_t Plugin::HandleEvent(void* aEvent)
- {
- NPEvent *event = (NPEvent *)aEvent;
- switch(event->event)
- {
- case WM_PAINT:
- {
- if(!mWindow)
- break;
- // get the dirty rectangle to update or repaint the whole window
- RECT * drc = (RECT *)event->lParam;
- if(drc)
- FillRect((HDC)event->wParam, drc, (HBRUSH)(COLOR_WINDOW));
- else {
- RECT rc;
- rc.bottom = mWindow->y + mWindow->height;
- rc.left = mWindow->x;
- rc.right = mWindow->x + mWindow->width;
- rc.top = mWindow->y;
- FillRect((HDC)event->wParam, &rc, (HBRUSH)(COLOR_WINDOW));
- }
- break;
- }
- case WM_LBUTTONDOWN:
- Beep(1000,200);
- m_oldX=LOWORD(event->lParam);
- m_OldY=HIWORD(event->lParam);
- bMdown=true;
- break;
- case WM_MOUSEMOVE:
- {
- m_newX=LOWORD(event->lParam);
- m_newY=HIWORD(event->lParam);
- if (bMdown)
- {
- HDC hDC = (HDC)mWindow->window;
- MoveToEx(hDC,m_oldX,m_OldY,NULL);
- LineTo(hDC,m_newX,m_newY);
- }
- NPN_ForceRedraw(m_pNPInstance);
- m_oldX=m_newX;m_OldY=m_newY;
- break;
- }
- case WM_LBUTTONUP:
- bMdown=false;NPN_ForceRedraw(m_pNPInstance);break;
- default:
- return 0;
- }
- //
- return 1;
- }
windowed插件
创建windowed的插件,可以这样:
- BOOL bWindowed = TRUE;
- NPN_SetValue(aCreateDataStruct->instance, NPPVpluginWindowBool, (void *)bWindowed);//winless插件需要在这里指出,默认为windowed
也可以什么都不写,因为默认就是创建windowed的插件。这个例子实现两个功能:设置背景并在创建窗口中输出一段文字。
首先,为Plugin类添加一个私有成员:HWND m_hWnd;
要对窗口进行处理需要使用到窗口子类化的函数,要包含头文件#include <windowsx.h>(Windows.h在其他头文件中已经包含了,这里可以不需要再写)
在plugin.cpp中声明一个回调函数和一个静态变量:
- static LRESULT CALLBACK PluginWinProc(HWND, UINT, WPARAM, LPARAM);
- static WNDPROC lpOldProc = NULL;
在类的构造函数中将m_hWnd初始化为NULL
修改Plugin::init为:
- NPBool Plugin::init(NPWindow* pNPWindow)
- {
- mWindow=pNPWindow;
- m_hWnd =(HWND)pNPWindow->window;
- if (m_hWnd==NULL) return FALSE;
- // 对窗口进行子类化,这样就可以对消息进行处理并在窗口中进行绘制
- lpOldProc = SubclassWindow(m_hWnd, (WNDPROC)PluginWinProc);
- // 将窗口与 Plugin 对象相关联,这样就可以在窗口处理中访问Plugin 对象
- SetWindowLongPtr(m_hWnd, GWLP_USERDATA, (LONG_PTR)this);
- m_bInitialized = TRUE;
- return TRUE;
- }
对于windowed的插件pNPWindow->window存储就是插件窗口的句柄,上面将其转换为HWND,并将窗口进行子类化,关键代码的作用请参看注释。将Plugin::shut修改为:
- void Plugin::shut()
- {
- // 反子类化subclass it back
- SubclassWindow(m_hWnd, lpOldProc);
- m_hWnd = NULL;
- m_bInitialized = FALSE;
- }
到这一步已经完成了对窗口句柄的获取与释放,最后还需要的就是消息处理函数了,如下:
- static LRESULT CALLBACK PluginWinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
- {
- switch (msg) {
- case WM_PAINT:
- {
- // draw a frame and display some string
- PAINTSTRUCT ps;
- HDC hdc = BeginPaint(hWnd, &ps);
- RECT rc;
- GetClientRect(hWnd, &rc);
- FillRect( hdc, &rc, (HBRUSH) (COLOR_WINDOW));
- FrameRect(hdc, &rc, GetStockBrush(BLACK_BRUSH));
- Plugin * p = (Plugin*) GetWindowLongPtr(hWnd, GWLP_USERDATA);
- if(p) {
- char *s = "Hello , MY FIRST PLUGIN!";//p->GetGuiText();
- DrawText(hdc, s, strlen(s), &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
- }
- EndPaint(hWnd, &ps);
- }
- break;
- default:
- break;
- }
- return DefWindowProc(hWnd, msg, wParam, lParam);
- }
这里只是对WM_PAINT消息进行了处理,其他消息用DefWindowProc进行默认的处理。在WM_PAINT的消息处理代码中我们为窗口设置了背景,并输出一段文字"Hello , MY FIRST PLUGIN!",没错,这就是我们的插件的hello world!!
效果如下图:
最后这个项目的plugin.h文件和plugin.cpp文件完整内容
- //Plugin.h
- #pragma once
- #include "pluginbase.h"
- class Plugin :
- public nsPluginInstanceBase
- {
- private:
- NPP m_pNPInstance;
- NPBool m_bInitialized;
- NPWindow* mWindow;
- HWND m_hWnd;
- public:
- Plugin(NPP pNPInstance);
- ~Plugin();
- NPBool init(NPWindow* pNPWindow);// { m_bInitialized = TRUE; return TRUE;}
- void shut();// { m_bInitialized = FALSE; }
- NPBool isInitialized();// { return m_bInitialized; }
- };
- //Plugin.cpp
- #include "Plugin.h"
- #include <windowsx.h>
- ////// functions /////////
- NPError NS_PluginInitialize()
- {
- return NPERR_NO_ERROR;
- }
- void NS_PluginShutdown()
- {
- }
- nsPluginInstanceBase * NS_NewPluginInstance(nsPluginCreateData * aCreateDataStruct)
- {
- if(!aCreateDataStruct)
- return NULL;
- Plugin * plugin = new Plugin(aCreateDataStruct->instance);
- return plugin;
- }
- void NS_DestroyPluginInstance(nsPluginInstanceBase * aPlugin)
- {
- if(aPlugin)
- delete (Plugin *)aPlugin;
- }
- ////// Plugin /////////
- Plugin::Plugin(NPP pNPInstance):nsPluginInstanceBase(),
- m_pNPInstance(pNPInstance),
- m_bInitialized(FALSE)
- {
- m_hWnd = NULL;
- }
- Plugin::~Plugin(void)
- {
- }
- static LRESULT CALLBACK PluginWinProc(HWND, UINT, WPARAM, LPARAM);
- static WNDPROC lpOldProc = NULL;
- NPBool Plugin::init(NPWindow* pNPWindow)
- {
- mWindow=pNPWindow;
- m_hWnd =(HWND)pNPWindow->window;
- if (m_hWnd==NULL) return FALSE;
- // 对窗口进行子类化,这样就可以对消息进行处理并在窗口中进行绘制
- lpOldProc = SubclassWindow(m_hWnd, (WNDPROC)PluginWinProc);
- // 将窗口与 Plugin 对象相关联,这样就可以在窗口处理中访问Plugin 对象
- SetWindowLongPtr(m_hWnd, GWLP_USERDATA, (LONG_PTR)this);
- m_bInitialized = TRUE;
- return TRUE;
- }
- void Plugin::shut()
- {
- // 将窗口子类化回去subclass it back
- SubclassWindow(m_hWnd, lpOldProc);
- m_hWnd = NULL;
- m_bInitialized = FALSE;
- }
- NPBool Plugin::isInitialized()
- {
- return m_bInitialized;
- }
- static LRESULT CALLBACK PluginWinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
- {
- switch (msg) {
- case WM_PAINT:
- {
- // draw a frame and display some string
- PAINTSTRUCT ps;
- HDC hdc = BeginPaint(hWnd, &ps);
- RECT rc;
- GetClientRect(hWnd, &rc);
- FillRect( hdc, &rc, (HBRUSH) (COLOR_WINDOW));
- FrameRect(hdc, &rc, GetStockBrush(BLACK_BRUSH));
- Plugin * p = (Plugin*) GetWindowLongPtr(hWnd, GWLP_USERDATA);
- if(p) {
- char *s = "Hello , MY FIRST PLUGIN!";//p->GetGuiText();
- DrawText(hdc, s, strlen(s), &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
- }
- EndPaint(hWnd, &ps);
- }
- break;
- default:
- break;
- }
- return DefWindowProc(hWnd, msg, wParam, lParam);
- }