實驗了一個比較奇特的東西,在PPAPI插件里創建一個本地窗口,疊加在插件在網頁的位置上。
CEF3默認是多進程架構,PPAPI插件在一個單獨進程里跑,這個進程沒啟動Windows的消息循環,所以,要創建插件的話,得自己搞一個消息循環。另外瀏覽器窗口屬于別的進程,怎么把創建出來的窗口成為瀏覽器窗口的子窗口,也是個問題。這個第一個要解決的問題。
CEF3還支持單進程運行,Browser、Render、Plugin合一,此時創建本地窗口又和多進程不同。這是第二個問題。
第三個問題是,如何把窗口定位到網頁的插件區域。
琢磨了下,都解決了。
> foruok原創,如需轉載請關注foruok的微信訂閱號“程序視界”聯系foruok。
# 效果
先看看效果圖。先是跨進程的:

灰色區域那里是我創建的本地窗口,上面有一行字。
再看同一進程的:

注意文字的變化。
# 代碼
Talk is cheap,show me the code:
~~~
/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
* 2016-1-13, edited by foruok.
* 如需轉載,請關注微信訂閱號“程序視界”,回復foruok獲取其聯系方式
*
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <Windows.h>
#include <tchar.h>
#include "ppapi/c/pp_completion_callback.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/c/pp_instance.h"
#include "ppapi/c/pp_module.h"
#include "ppapi/c/pp_rect.h"
#include "ppapi/c/pp_var.h"
#include "ppapi/c/ppb.h"
#include "ppapi/c/ppb_core.h"
#include "ppapi/c/ppb_graphics_2d.h"
#include "ppapi/c/ppb_image_data.h"
#include "ppapi/c/ppb_instance.h"
#include "ppapi/c/ppb_view.h"
#include "ppapi/c/ppp.h"
#include "ppapi/c/ppp_instance.h"
#include "ppapi/c/ppb_input_event.h"
#include "ppapi/c/ppp_input_event.h"
PPB_GetInterface g_get_browser_interface = NULL;
const PPB_Core* g_core_interface;
const PPB_Graphics2D* g_graphics_2d_interface;
const PPB_ImageData* g_image_data_interface;
const PPB_Instance* g_instance_interface;
const PPB_View* g_view_interface;
const PPB_InputEvent *g_input_interface;
const PPB_MouseInputEvent *g_mouse_interface;
/******foruok: create native window begin******/
static HWND g_child_window = NULL;
struct CreateChildWinParam {
struct PP_Rect r;
HWND hWndParent;
};
HANDLE g_hThread = NULL;
DWORD g_dwThreadId = 0;
BOOL g_bInProcess = 0;
static LRESULT CALLBACK VideoWindowProc(HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
PAINTSTRUCT ps;
HDC hdc;
RECT r;
TCHAR textIn[] = _T("Child Window(in-process)");
TCHAR text[] = _T("Child Window(out-process)");
switch (uMsg)
{
case WM_PAINT:
GetClientRect(hwnd, &r);
hdc = BeginPaint(hwnd, &ps);
SetTextColor(hdc, RGB(0, 200, 0));
FillRect(hdc, &r, GetStockObject(GRAY_BRUSH));
if (g_bInProcess)
{
TextOut(hdc, 10, 50, textIn, ARRAYSIZE(textIn) - 1);
}
else
{
TextOut(hdc, 10, 50, text, ARRAYSIZE(text) - 1);
}
EndPaint(hwnd, &ps);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
static void RegisterVideoWindowClass()
{
WNDCLASSEX wcex = {
/* cbSize = */ sizeof(WNDCLASSEX),
/* style = */ CS_HREDRAW | CS_VREDRAW,
/* lpfnWndProc = */ VideoWindowProc,
/* cbClsExtra = */ 0,
/* cbWndExtra = */ 0,
/* hInstance = */ GetModuleHandle(NULL),
/* hIcon = */ NULL,
/* hCursor = */ LoadCursor(NULL, IDC_ARROW),
/* hbrBackground = */ 0,
/* lpszMenuName = */ NULL,
/* lpszClassName = */ _T("_ChildWindowClass"),
/* hIconSm = */ NULL,
};
RegisterClassEx(&wcex);
}
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
MSG msg;
struct CreateChildWinParam *para = (struct CreateChildWinParam *)lpParam;
TCHAR szLog[256] = { 0 };
g_child_window = CreateWindowEx(0, _T("_ChildWindowClass"), _T("ChildWindow"),
para->hWndParent == NULL ? (WS_OVERLAPPEDWINDOW | WS_VISIBLE) : (WS_CHILD | WS_VISIBLE | WS_DISABLED),
para->r.point.x, para->r.point.y, para->r.size.width, para->r.size.height,
para->hWndParent, NULL, GetModuleHandle(NULL), NULL);
_stprintf_s(szLog, 256, _T("create child window(standalone msg loop) at (%d, %d) child = 0x%08x\r\n"),
para->r.point.x, para->r.point.y,
g_child_window);
OutputDebugString(szLog);
BOOL fGotMessage;
while ((fGotMessage = GetMessage(&msg, (HWND)NULL, 0, 0)) != 0 && fGotMessage != -1)
{
TranslateMessage(&msg);
if (msg.message == WM_USER && msg.hwnd == NULL)
{
OutputDebugString(_T("child window message loop quit\r\n"));
g_dwThreadId = 0;
g_hThread = NULL;
g_child_window = NULL;
return 0;
}
DispatchMessage(&msg);
}
return msg.wParam;
}
void CreateChildWindowOnMainThread(void *param, int32_t result)
{
struct CreateChildWinParam *p = (struct CreateChildWinParam *)param;
g_child_window = CreateWindowEx(0, _T("_ChildWindowClass"), _T("ChildWindow"),
WS_CHILD | WS_VISIBLE,
p->r.point.x, p->r.point.y, p->r.size.width, p->r.size.height,
p->hWndParent, NULL, GetModuleHandle(NULL), NULL);
TCHAR szLog[256] = { 0 };
_stprintf_s(szLog, 256, _T("create child window(in-process) at (%d, %d) child = 0x%08x\r\n"),
p->r.point.x, p->r.point.y,
g_child_window);
OutputDebugString(szLog);
ShowWindow(g_child_window, SW_SHOW);
UpdateWindow(g_child_window);
}
void CreateChildWindow(struct PP_Rect *r)
{
HWND hwnd = FindWindowEx(NULL, NULL, _T("CefBrowserWindow"), NULL);
HWND hwndWeb = FindWindowEx(hwnd, NULL, _T("Chrome_WidgetWin_0"), NULL);;
/*if (hwndWeb)
{
hwndWeb = FindWindowEx(hwndWeb, NULL, _T("Chrome_RenderWidgetHostHWND"), NULL); //web contents
}*/
if (hwndWeb != NULL)OutputDebugString(_T("Got Chrome_RenderWidgetHostHWND\r\n"));
DWORD pluginPid = GetCurrentProcessId();
DWORD browserPid = 0;
GetWindowThreadProcessId(hwnd, &browserPid);
TCHAR szLog[256] = { 0 };
_stprintf_s(szLog, 256, _T("Browser pid - %d, plugin pid - %d, brower hwnd - 0x%08x, webpage hwnd - 0x%08x\r\n"),
browserPid, pluginPid, hwnd, hwndWeb);
OutputDebugString(szLog);
struct CreateChildWinParam *para = (struct PP_Rect *)malloc(sizeof(struct CreateChildWinParam));
para->r = *r;
para->hWndParent = hwndWeb;
if (browserPid == pluginPid)
{
g_bInProcess = TRUE;
g_core_interface->CallOnMainThread(0, PP_MakeCompletionCallback(CreateChildWindowOnMainThread, para), 0);
}
else
{
g_bInProcess = FALSE;
g_hThread = CreateThread(NULL, 0, ThreadProc, para, 0, &g_dwThreadId);
if (g_hThread != NULL)
{
OutputDebugString(_T("Launch child window thread.\r\n"));
}
else
{
OutputDebugString(_T("Launch child window thread FAILED!\r\n"));
}
}
}
/* PPP_Instance implementation -----------------------------------------------*/
struct InstanceInfo {
PP_Instance pp_instance;
struct PP_Size last_size;
PP_Resource graphics;
struct InstanceInfo* next;
};
/** Linked list of all live instances. */
struct InstanceInfo* all_instances = NULL;
/** Returns a refed resource corresponding to the created graphics 2d. */
PP_Resource MakeAndBindGraphics2D(PP_Instance instance,
const struct PP_Size* size) {
PP_Resource graphics;
graphics = g_graphics_2d_interface->Create(instance, size, PP_FALSE);
if (!graphics)
return 0;
if (!g_instance_interface->BindGraphics(instance, graphics)) {
g_core_interface->ReleaseResource(graphics);
return 0;
}
return graphics;
}
void FlushCompletionCallback(void* user_data, int32_t result) {
/* Don't need to do anything here. */
}
unsigned int g_colors[4] = { 0xFF888888, 0xFFFF00FF, 0xFF00FFFF, 0xFFEA00FF };
unsigned int g_color_index = 0;
void Repaint(struct InstanceInfo* instance, const struct PP_Size* size) {
PP_Resource image;
struct PP_ImageDataDesc image_desc;
uint32_t* image_data;
int num_words, i;
/* Ensure the graphics 2d is ready. */
if (!instance->graphics) {
instance->graphics = MakeAndBindGraphics2D(instance->pp_instance, size);
if (!instance->graphics)
return;
}
/* Create image data to paint into. */
image = g_image_data_interface->Create(
instance->pp_instance, PP_IMAGEDATAFORMAT_BGRA_PREMUL, size, PP_TRUE);
if (!image)
return;
g_image_data_interface->Describe(image, &image_desc);
/* Fill the image with blue. */
image_data = (uint32_t*)g_image_data_interface->Map(image);
if (!image_data) {
g_core_interface->ReleaseResource(image);
return;
}
num_words = image_desc.stride * size->height / 4;
g_color_index++;
if (g_color_index >= sizeof(g_colors) / sizeof(g_colors[0])) g_color_index = 0;
for (i = 0; i < num_words; i++)
image_data[i] = g_colors[g_color_index];
/* Paint image to graphics 2d. */
g_graphics_2d_interface->ReplaceContents(instance->graphics, image);
g_graphics_2d_interface->Flush(instance->graphics,
PP_MakeCompletionCallback(&FlushCompletionCallback, NULL));
g_core_interface->ReleaseResource(image);
}
/** Returns the info for the given instance, or NULL if it's not found. */
struct InstanceInfo* FindInstance(PP_Instance instance) {
struct InstanceInfo* cur = all_instances;
while (cur) {
if (cur->pp_instance == instance)
return cur;
cur = cur->next;
}
return NULL;
}
PP_Bool Instance_DidCreate(PP_Instance instance,
uint32_t argc,
const char* argn[],
const char* argv[]) {
struct InstanceInfo* info =
(struct InstanceInfo*)malloc(sizeof(struct InstanceInfo));
info->pp_instance = instance;
info->last_size.width = 0;
info->last_size.height = 0;
info->graphics = 0;
/* Insert into linked list of live instances. */
info->next = all_instances;
all_instances = info;
g_input_interface->RequestInputEvents(instance, PP_INPUTEVENT_CLASS_MOUSE);
g_input_interface->RequestFilteringInputEvents(instance, PP_INPUTEVENT_CLASS_MOUSE);
OutputDebugString(_T("Instance_DidCreate\r\n"));
return PP_TRUE;
}
void Instance_DidDestroy(PP_Instance instance) {
/* Find the matching item in the linked list, delete it, and patch the
* links.
*/
struct InstanceInfo** prev_ptr = &all_instances;
struct InstanceInfo* cur = all_instances;
while (cur) {
if (instance == cur->pp_instance) {
*prev_ptr = cur->next;
g_core_interface->ReleaseResource(cur->graphics);
free(cur);
return;
}
prev_ptr = &cur->next;
cur = cur->next;
}
/**foruok: close native window**/
if(g_child_window != NULL)SendMessage(g_child_window, WM_CLOSE, 0, 0);
if (g_dwThreadId != 0)
{
OutputDebugString(_T("Plugin was destroyed, tell child window close and thread exit\r\n"));
PostThreadMessage(g_dwThreadId, WM_USER, 0, 0);
}
}
void Instance_DidChangeView(PP_Instance pp_instance,
PP_Resource view) {
struct PP_Rect position;
struct InstanceInfo* info = FindInstance(pp_instance);
if (!info)
return;
if (g_view_interface->GetRect(view, &position) == PP_FALSE)
return;
if (info->last_size.width != position.size.width ||
info->last_size.height != position.size.height) {
/* Got a resize, repaint the plugin. */
Repaint(info, &position.size);
info->last_size.width = position.size.width;
info->last_size.height = position.size.height;
/**foruok: call create window**/
if (g_child_window == NULL)
{
CreateChildWindow(&position);
}
}
OutputDebugString(_T("Instance_DidChangeView\r\n"));
}
void Instance_DidChangeFocus(PP_Instance pp_instance, PP_Bool has_focus) {
}
PP_Bool Instance_HandleDocumentLoad(PP_Instance pp_instance,
PP_Resource pp_url_loader) {
return PP_FALSE;
}
static PPP_Instance instance_interface = {
&Instance_DidCreate,
&Instance_DidDestroy,
&Instance_DidChangeView,
&Instance_DidChangeFocus,
&Instance_HandleDocumentLoad
};
PP_Bool InputEvent_HandleInputEvent(PP_Instance instance, PP_Resource input_event)
{
struct PP_Point pt;
TCHAR szLog[512] = { 0 };
switch (g_input_interface->GetType(input_event))
{
case PP_INPUTEVENT_TYPE_MOUSEDOWN:
pt = g_mouse_interface->GetPosition(input_event);
_stprintf_s(szLog, 512, _T("InputEvent_HandleInputEvent, mouse down at [%d, %d]\r\n"), pt.x, pt.y);
OutputDebugString(szLog);
break;
/*
case PP_INPUTEVENT_TYPE_MOUSEUP:
OutputDebugString(_T("InputEvent_HandleInputEvent, mouse up\r\n"));
break;
case PP_INPUTEVENT_TYPE_MOUSEMOVE:
OutputDebugString(_T("InputEvent_HandleInputEvent, mouse move\r\n"));
break;
case PP_INPUTEVENT_TYPE_MOUSEENTER:
OutputDebugString(_T("InputEvent_HandleInputEvent, mouse enter\r\n"));
break;
case PP_INPUTEVENT_TYPE_MOUSELEAVE:
OutputDebugString(_T("InputEvent_HandleInputEvent, mouse leave\r\n"));
break;
*/
default:
return PP_FALSE;
}
struct InstanceInfo* info = FindInstance(instance);
if (info && info->last_size.width > 0)
{
Repaint(info, &info->last_size);
}
return PP_TRUE;
}
static PPP_InputEvent input_interface = {
&InputEvent_HandleInputEvent
};
/* Global entrypoints --------------------------------------------------------*/
PP_EXPORT int32_t PPP_InitializeModule(PP_Module module,
PPB_GetInterface get_browser_interface) {
/**foruok: register window class**/
RegisterVideoWindowClass();
g_get_browser_interface = get_browser_interface;
g_core_interface = (const PPB_Core*)
get_browser_interface(PPB_CORE_INTERFACE);
g_instance_interface = (const PPB_Instance*)
get_browser_interface(PPB_INSTANCE_INTERFACE);
g_image_data_interface = (const PPB_ImageData*)
get_browser_interface(PPB_IMAGEDATA_INTERFACE);
g_graphics_2d_interface = (const PPB_Graphics2D*)
get_browser_interface(PPB_GRAPHICS_2D_INTERFACE);
g_view_interface = (const PPB_View*)
get_browser_interface(PPB_VIEW_INTERFACE);
g_input_interface = (const PPB_InputEvent*)get_browser_interface(PPB_INPUT_EVENT_INTERFACE);
g_mouse_interface = (const PPB_MouseInputEvent*)get_browser_interface(PPB_MOUSE_INPUT_EVENT_INTERFACE);
if (!g_core_interface || !g_instance_interface || !g_image_data_interface ||
!g_graphics_2d_interface || !g_view_interface ||
!g_input_interface || !g_mouse_interface)
return -1;
OutputDebugString(_T("PPP_InitializeModule\r\n"));
return PP_OK;
}
PP_EXPORT void PPP_ShutdownModule() {
}
PP_EXPORT const void* PPP_GetInterface(const char* interface_name) {
if (strcmp(interface_name, PPP_INSTANCE_INTERFACE) == 0)
{
OutputDebugString(_T("PPP_GetInterface, instance_interface\r\n"));
return &instance_interface;
}
else if (strcmp(interface_name, PPP_INPUT_EVENT_INTERFACE) == 0)
{
OutputDebugString(_T("PPP_GetInterface, input_interface\r\n"));
return &input_interface;
}
return NULL;
}
~~~
工程和原來差不多,不說了。
代碼基于[**PPAPI插件的繪圖與輸入事件處理**](http://blog.csdn.net/foruok/article/details/50499813)改造,添加的部分,在注釋處加入了foruok標簽,搜索即可查看。下面說明幾點。
### 本地窗口的創建
CreateChildWindow()函數是創建本地窗口的入口函數,它利用進程id,判斷了插件進程與瀏覽器進程是否同一進程。
如果是同一進程,通過調用PPB_Core的CallOnMainThread方法,在主線程執行CreateChildWindowOnMainThread來創建子窗口。
如果不是同一進程,就創建一個線程,在線程里創建窗口并啟動了消息循環。注意,Windows下窗口的父子關注,支持跨進程,所以調用CreateWindowEx時只要指定父窗口句柄即可。
### 窗口定位
DidChangeView方法之前在[**理解PPAPI的設計**](http://blog.csdn.net/foruok/article/details/50486788)里提到過,當插件和瀏覽器的視圖關聯起來或者視圖發生變化時,DidChangeView方法會被調用。這次我們就在這里創建窗口。窗口的位置,可以通過PPB_View接口的GetRect()方法獲取到。這個位置就是我們要創建的本地窗口的位置,傳遞給CreateWindowEx方法即可。
好啦,新的示例主要就這些內容了,處理輸入事件的流程應該也清楚了。
這次實驗的東西比較奇特,不走尋常路,僅僅是為了兼容老代碼……
相關文章參考:
- [**CEF Windows開發環境搭建**](http://blog.csdn.net/foruok/article/details/50468642)
- [**CEF加載PPAPI插件**](http://blog.csdn.net/foruok/article/details/50485448)
- [**VS2013編譯最簡單的PPAPI插件**](http://blog.csdn.net/foruok/article/details/50485461)
- [**理解PPAPI的設計**](http://blog.csdn.net/foruok/article/details/50486788)
- [**PPAPI插件與瀏覽器的交互過程**](http://blog.csdn.net/foruok/article/details/50494061)
- [**Windows下從源碼編譯CEF**](http://blog.csdn.net/foruok/article/details/50498740)
- [**編譯PPAPI的media_stream_video示例**](http://blog.csdn.net/foruok/article/details/50498873)
- [**PPAPI插件的繪圖與輸入事件處理**](http://blog.csdn.net/foruok/article/details/50499813)
- 前言
- CEF Windows開發環境搭建
- CEF加載PPAPI插件
- VS2013編譯最簡單的PPAPI插件
- 理解PPAPI的設計
- PPAPI插件與瀏覽器的交互過程
- Windows下從源碼編譯CEF
- 編譯PPAPI的media_stream_video示例
- PPAPI插件的繪圖與輸入事件處理
- 在PPAPI插件中創建本地窗口
- PPAPI插件與瀏覽器的通信
- Windows下從源碼編譯Skia
- 在PPAPI插件中使用Skia繪圖
- 加載DLL中的圖片資源生成Skia中的SkBitmap對象
- PPAPI+Skia實現的涂鴉板
- PPAPI中使用Chromium的3D圖形接口
- PPAPI中使用OpenGL ES繪圖