PPAPI中使用OpenGL ES绘图

最后更新于:2022-04-01 16:03:14

在[**PPAPI中使用Chromium的3D图形接口**](http://blog.csdn.net/foruok/article/details/50563676)一文中我们介绍了怎么使用PPB_Graphics3D接口,提供了一个简单示例,单机鼠标可以变换插件颜色。 > foruok原创,如需转载请关注foruok的微信订阅号“程序视界”联系foruok。 PPB_Graphics3D是Chromium暴露给PPAPI的3D图形接口,类似衔接Open GL和本地窗口系统的EGL。我们使用PPB_Graphics3D的Create方法来创建context,然后使用PPB_Instance的BindGraphics将得到的OpenGL Context和插件实例绑定,再接下来,就可以使用OpenGL来绘图了。在这个过程中,PPB_Graphics3D替代了EGL。 Chromium使用的应该是WebGL,也就是OpenGL ES 2.0 for the Web,它暴露给PPAPI的gl接口是C结构体加函数指针的形式,有PPB_OpenGLES2、PPB_OpenGLES2FramebufferBlit、PPB_OpenGLES2ChromiumEnableFeature等,搜索ppb_opengles2*.h即可查看对应的接口定义。 关于OpenGL ES,可以看这里:[https://www.khronos.org/opengles/](https://www.khronos.org/opengles/)。 # PPAPI中使用OpenGL ES2 在[**PPAPI中使用Chromium的3D图形接口**](http://blog.csdn.net/foruok/article/details/50563676)一文中我们提到了glInitializePPAPI和glSetCurrentContextPPAPI两个方法,解释一下。 - **glInitializePPAPI** glInitializePPAPI做的事情就是把浏览器暴露给PPAPI的各种gl相关的接口都拿到(通过PPB_GetInterface),保存在全局变量中(参看gl2ext_ppapi.c),后续我们使用glXXX(参看gles2.c)函数时,实际上是通过保存下来的接口(结构体+函数指针),调用函数指针来实现的。我们用到的glXXX是宏定义,看起来和OpenGL的API一致,用起来方便。 我在ppapi_hello_gles.c的PPP_InitializeModule方法中添加了下面的代码: ~~~ if (GL_TRUE != glInitializePPAPI(get_browser_interface)) return -1; ~~~ 上面的代码初始化PPAPI相关的gl接口。 - **glSetCurrentContextPPAPI** PPB_Graphics3D的Create方法创建一个图形上下文,OpenGL就用它绘图。glSetCurrentContextPPAPI方法(参看gl2ext_ppapi.c)需要的参数就是PPB_Graphics3D接口Create出来的那个context。 ppapi_hello_gles.c中的MakeAndBindGraphics3D函数创建了Graphics3D context,并调用glSetCurrentContextPPAPI来传递给封装gl C接口的模块: ~~~ PP_Resource MakeAndBindGraphics3D(PP_Instance instance, const struct PP_Size* size) { PP_Resource graphics; int32_t attribs[] = { PP_GRAPHICS3DATTRIB_WIDTH, 800, PP_GRAPHICS3DATTRIB_HEIGHT, 800, PP_GRAPHICS3DATTRIB_NONE }; graphics = g_graphics_3d_interface->Create(instance, 0, attribs); if (!graphics) return 0; if (!g_instance_interface->BindGraphics(instance, graphics)) { g_core_interface->ReleaseResource(graphics); return 0; } glSetCurrentContextPPAPI(graphics); return graphics; } ~~~ 好啦,我们对[**PPAPI中使用Chromium的3D图形接口**](http://blog.csdn.net/foruok/article/details/50563676)一文做了一些补充说明,了解了在PPAPI中可以使用OpenGL ES 2.0的接口来进行3D绘图,这种方式比基于软件和共享内存的Graphics 2D接口效率要高,它利用了GPU加速。接下来看看怎么使用gl接口来绘制一个简单的三角形。 # 绘制三角形 修改了ppapi_hello_gles实例,添加了glTriangle.h和glTriangle.c两个文件,它们实现了三角形绘制。大部分代码是我从《[**OpenGL ES 2.0 Programming Guide**](http://opengles-book.com/)》一书中摘出来的,针对PPAPI做了修改。 glTriangle.h如下: ~~~ #ifndef GLTRIANGLE_DRAW_H #define GLTRIANGLE_DRAW_H #include "ppapi/lib/gl/include/GLES2/gl2.h" GLboolean InitTriangle(void **userData); void DrawTriangle(void *userData); #endif ~~~ 声明了两个函数,ppapi_hello_gles.c中会用到。 glTriangle.c如下: ~~~ /* Copyright (c) 2016 foruok. All rights reserved. * 欢迎关注我的微信订阅号程序视界 * see: 《OpenGL ES 2.0 Programming Guide》 */ #include "glTriangle.h" #include <Windows.h> #include "ppapi/lib/gl/include/GLES2/gl2.h" #include "ppapi/lib/gl/gles2/gl2ext_ppapi.h" #include "ppapi/lib/gl/include/GLES2/gl2ext.h" #include <tchar.h> typedef struct { GLuint programObject; } UserData; /* * create a shader object, load the shader source, and compile the shader */ GLuint LoadShader(GLenum type, const char *shaderSource) { GLuint shader; GLint compiled; // create the shader object shader = glCreateShader(type); if (shader == 0) return 0; // load the shader source glShaderSource(shader, 1, &shaderSource, NULL); // compile the shader glCompileShader(shader); // check the compile status glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); if (!compiled) { GLint infoLen = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); if (infoLen > 1) { char *infoLog = malloc(infoLen+1); glGetShaderInfoLog(shader, infoLen, NULL, infoLog); infoLog[infoLen] = 0; OutputDebugStringA(infoLog); free(infoLog); } glDeleteShader(shader); return 0; } return shader; } /* * Initialize the shader and program object */ GLboolean InitTriangle(void **userData) { GLbyte vShaderStr[] = "attribute vec4 vPosition; \n" "void main() \n" "{ \n" " gl_Position = vPosition; \n" "} \n"; GLbyte fShaderStr[] = "precision mediump float; \n" "void main() \n" "{ \n" " gl_FragColor = vec4(1.0, 0.5, 0.0, 1.0); \n" "} \n"; // load shaders GLuint vertexShader = LoadShader(GL_VERTEX_SHADER, vShaderStr); GLuint fragmentShader = LoadShader(GL_FRAGMENT_SHADER, fShaderStr); // create the program object GLuint programObject = glCreateProgram(); if (programObject == 0) return GL_FALSE; glAttachShader(programObject, vertexShader); glAttachShader(programObject, fragmentShader); // blind vPosition to attribute 0 glBindAttribLocation(programObject, 0, "vPosition"); // link the program glLinkProgram(programObject); glDeleteShader(vertexShader); glDeleteShader(fragmentShader); // check link status GLint linked; glGetProgramiv(programObject, GL_LINK_STATUS, &linked); if (!linked) { GLint infoLen = 0; glGetProgramiv(programObject, GL_INFO_LOG_LENGTH, &infoLen); if (infoLen > 1) { char *infoLog = malloc(infoLen + 1); glGetProgramInfoLog(programObject, infoLen, NULL, infoLog); infoLog[infoLen] = 0; OutputDebugStringA(infoLog); free(infoLog); } glDeleteProgram(programObject); return GL_FALSE; } UserData *data = *userData; if (data == 0) { data = (UserData*)malloc(sizeof(UserData)); *userData = data; } data->programObject = programObject; return GL_TRUE; } void DrawTriangle(void *userData) { OutputDebugString(_T("DrawTriagle\r\n")); UserData *data = userData; GLfloat vVertices[] = { 0.0f, 0.5f, 0.0f, -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f }; // use the program object glUseProgram(data->programObject); // load the vertex data glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vVertices); glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLES, 0, 3); } ~~~ glTriangle.c的三个函数都来自《[**OpenGL ES 2.0 Programming Guide**](http://opengles-book.com/)》一书,现在最新的书应该是《OpenGL ES 3.0 Programming Guide》。详细的代码解说请参考该书,非常详尽、有条理,超级棒! 最后,我修改了ppapi_hello_gles.c,以便使用glTriangle的接口。新的ppapi_hello_gles.c内容如下: ~~~ /* Copyright (c) 2016 foruok. All rights reserved. * 欢迎关注我的微信订阅号程序视界 */ #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_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" #include "ppapi/c/ppb_graphics_3d.h" #include "ppapi/c/ppb_opengles2.h" #include "ppapi/lib/gl/include/GLES2/gl2.h" #include "ppapi/lib/gl/gles2/gl2ext_ppapi.h" // foruok[1] #include "glTriangle.h" PPB_GetInterface g_get_browser_interface = NULL; const PPB_Core* g_core_interface; const PPB_Graphics3D* g_graphics_3d_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; /* PPP_Instance implementation -----------------------------------------------*/ typedef struct InstanceInfo { PP_Instance pp_instance; struct PP_Size last_size; PP_Resource graphics; // foruok[2] void *user_data; struct InstanceInfo* next; } InstanceInfo; /** Linked list of all live instances. */ struct InstanceInfo* all_instances = NULL; /** Returns a refed resource corresponding to the created graphics 3d. */ PP_Resource MakeAndBindGraphics3D(PP_Instance instance, const struct PP_Size* size) { PP_Resource graphics; int32_t attribs[] = { PP_GRAPHICS3DATTRIB_WIDTH, 800, PP_GRAPHICS3DATTRIB_HEIGHT, 800, PP_GRAPHICS3DATTRIB_NONE }; graphics = g_graphics_3d_interface->Create(instance, 0, attribs); if (!graphics) return 0; if (!g_instance_interface->BindGraphics(instance, graphics)) { g_core_interface->ReleaseResource(graphics); return 0; } glSetCurrentContextPPAPI(graphics); return graphics; } void ReinitializeGraphics3D(void *user_data, int32_t result) { InstanceInfo *inst = (InstanceInfo*)user_data; inst->graphics = MakeAndBindGraphics3D(inst->pp_instance, &inst->last_size); if (inst->graphics != 0) { // foruok[5] if(inst->user_data)InitTriangle(inst->user_data); OutputDebugString(_T("reinitialize graphics 3d context sucess\r\n")); } } void FlushCompletionCallback(void* user_data, int32_t result) { /* Don't need to do anything here. */ if (result == PP_ERROR_CONTEXT_LOST) { OutputDebugString(_T("PP_ERROR_CONTEXT_LOST")); //reinitialize context g_core_interface->CallOnMainThread(0, PP_MakeCompletionCallback(ReinitializeGraphics3D, user_data), 0); } } unsigned int g_colors[4] = { 0xFF0000FF, 0xFFFF00FF, 0xFF00FFFF, 0xFF2AFE00 }; unsigned int g_color_index = 0; #define GETA(clr) ((clr >> 24) & 0xFF) #define GETR(clr) ((clr >> 16) & 0xFF) #define GETG(clr) ((clr >> 8) & 0xFF) #define GETB(clr) (clr & 0xFF) void Repaint(struct InstanceInfo* instance, const struct PP_Size* size) { /* Ensure the graphics 3d is ready. */ if (!instance->graphics) { instance->graphics = MakeAndBindGraphics3D(instance->pp_instance, size); if (!instance->graphics) return; // foruok[3] if (GL_TRUE == InitTriangle(&instance->user_data)) OutputDebugString(_T("InitTriangle OK\r\n")); } g_color_index++; if (g_color_index >= sizeof(g_colors) / sizeof(g_colors[0])) g_color_index = 0; struct PP_CompletionCallback callback = { FlushCompletionCallback, instance, PP_COMPLETIONCALLBACK_FLAG_NONE, }; glViewport(0, 0, instance->last_size.width, instance->last_size.height); glClearColor(GETR(g_colors[g_color_index]), GETG(g_colors[g_color_index]), GETB(g_colors[g_color_index]), GETA(g_colors[g_color_index])); glClear(GL_COLOR_BUFFER_BIT); // foruok[4] if(instance->user_data)DrawTriangle(instance->user_data); g_graphics_3d_interface->SwapBuffers(instance->graphics, callback); } /** 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*)calloc(1, sizeof(struct InstanceInfo)); info->pp_instance = instance; /* 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; } } 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) { info->last_size.width = position.size.width; info->last_size.height = position.size.height; /* Got a resize, repaint the plugin. */ Repaint(info, &position.size); } 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; 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) { 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_graphics_3d_interface = (const PPB_Graphics3D*) get_browser_interface(PPB_GRAPHICS_3D_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_graphics_3d_interface || !g_view_interface || !g_input_interface || !g_mouse_interface) return -1; if (GL_TRUE != glInitializePPAPI(get_browser_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; } ~~~ 改动部分我用foruok[1]、foruok[2]之类的注释标注出来了,共有5处。可以搜索查看。 InitTriangle方法在调用MakeAndBindGraphics3D之后调用,创建了shader、program object。DrawTriangle方法是在Repaint中被调用的,它内部通过glDrawArrays来绘制三角形。 好啦,这个改造过的示例依然很简单,不过可以演示如何在PPAPI中使用OpenGL ES。 其他参考文章: - [**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) - [**在PPAPI插件中创建本地窗口**](http://blog.csdn.net/foruok/article/details/50513228) - [**PPAPI插件与浏览器的通信**](http://blog.csdn.net/foruok/article/details/50513315) - [**Windows下从源码编译Skia**](http://blog.csdn.net/foruok/article/details/50524726) - [**在PPAPI插件中使用Skia绘图**](http://blog.csdn.net/foruok/article/details/50526110) - [**加载DLL中的图片资源生成Skia中的SkBitmap对象**](http://blog.csdn.net/foruok/article/details/50543762) - [**PPAPI+Skia实现的涂鸦板**](http://blog.csdn.net/foruok/article/details/50547737) - [**PPAPI中使用Chromium的3D图形接口**](http://blog.csdn.net/foruok/article/details/50563676)
';

PPAPI中使用Chromium的3D图形接口

最后更新于:2022-04-01 16:03:12

使用PPAPI的Graphics 3D接口做了一个小示例,鼠标点击插件区域,绘制颜色,效果与ppapi_simple类似。 > foruok原创,如需转载请关注foruok的微信订阅号“程序视界”联系foruok。 # 项目 项目与[**VS2013编译最简单的PPAPI插件**](http://blog.csdn.net/foruok/article/details/50485461)这篇文章里说的ppapi_simple类似。 附加包含路径有些不同,如下: - E:\sources\CEF\2526\chromium\src\ - E:\sources\CEF\2526\chromium\src\ppapi\lib\gl\include 附加库路径如下: - E:\sources\CEF\2526\chromium\src\cef\binary_distrib\cef_binary_3.2526.1364.gf6bf57b_windows32\Release - E:\sources\CEF\2526\chromium\src\out\Release\obj\ppapi 链接ppapi_gles2.lib,在E:\sources\CEF\2526\chromium\src\out\Release\obj\ppapi目录下。 需要新建一个c文件:ppapi_hello_gles.c。 代码生成里链接的运行库选择MD。 # 源码 ppapi_hello_gles.c内容如下: ~~~ /* Copyright (c) 2016 foruok. All rights reserved. * 欢迎关注我的微信订阅号“程序视界” */ #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_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" #include "ppapi/c/ppb_graphics_3d.h" #include "ppapi/c/ppb_opengles2.h" #include "ppapi/lib/gl/include/GLES2/gl2.h" #include "ppapi/lib/gl/gles2/gl2ext_ppapi.h" PPB_GetInterface g_get_browser_interface = NULL; const PPB_Core* g_core_interface; const PPB_Graphics3D* g_graphics_3d_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; /* PPP_Instance implementation -----------------------------------------------*/ typedef struct InstanceInfo { PP_Instance pp_instance; struct PP_Size last_size; PP_Resource graphics; struct InstanceInfo* next; } InstanceInfo; /** Linked list of all live instances. */ struct InstanceInfo* all_instances = NULL; /** Returns a refed resource corresponding to the created graphics 3d. */ PP_Resource MakeAndBindGraphics3D(PP_Instance instance, const struct PP_Size* size) { PP_Resource graphics; int32_t attribs[] = { PP_GRAPHICS3DATTRIB_WIDTH, 800, PP_GRAPHICS3DATTRIB_HEIGHT, 800, PP_GRAPHICS3DATTRIB_NONE }; graphics = g_graphics_3d_interface->Create(instance, 0, attribs); if (!graphics) return 0; if (!g_instance_interface->BindGraphics(instance, graphics)) { g_core_interface->ReleaseResource(graphics); return 0; } glSetCurrentContextPPAPI(graphics); return graphics; } void ReinitializeGraphics3D(void *user_data, int32_t result) { InstanceInfo *inst = (InstanceInfo*)user_data; inst->graphics = MakeAndBindGraphics3D(inst->pp_instance, &inst->last_size); if (inst->graphics != 0) { glSetCurrentContextPPAPI(inst->graphics); OutputDebugString(_T("reinitialize graphics 3d context sucess\r\n")); } } void FlushCompletionCallback(void* user_data, int32_t result) { /* Don't need to do anything here. */ if (result == PP_ERROR_CONTEXT_LOST) { OutputDebugString(_T("PP_ERROR_CONTEXT_LOST")); //reinitialize context g_core_interface->CallOnMainThread(0, PP_MakeCompletionCallback(ReinitializeGraphics3D, user_data), 0); } } unsigned int g_colors[4] = { 0xFF0000FF, 0xFFFF00FF, 0xFF00FFFF, 0xFF2AFE00 }; unsigned int g_color_index = 0; #define GETA(clr) ((clr >> 24) & 0xFF) #define GETR(clr) ((clr >> 16) & 0xFF) #define GETG(clr) ((clr >> 8) & 0xFF) #define GETB(clr) (clr & 0xFF) void Repaint(struct InstanceInfo* instance, const struct PP_Size* size) { /* Ensure the graphics 3d is ready. */ if (!instance->graphics) { instance->graphics = MakeAndBindGraphics3D(instance->pp_instance, size); if (!instance->graphics) return; } g_color_index++; if (g_color_index >= sizeof(g_colors) / sizeof(g_colors[0])) g_color_index = 0; struct PP_CompletionCallback callback = { FlushCompletionCallback, instance, PP_COMPLETIONCALLBACK_FLAG_NONE, }; glViewport(0, 0, instance->last_size.width, instance->last_size.height); glClearColor(GETR(g_colors[g_color_index]), GETG(g_colors[g_color_index]), GETB(g_colors[g_color_index]), GETA(g_colors[g_color_index])); glClear(GL_COLOR_BUFFER_BIT); g_graphics_3d_interface->SwapBuffers(instance->graphics, callback); } /** 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*)calloc(1, sizeof(struct InstanceInfo)); info->pp_instance = instance; /* 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; } } 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) { info->last_size.width = position.size.width; info->last_size.height = position.size.height; /* Got a resize, repaint the plugin. */ Repaint(info, &position.size); } 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; 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) { 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_graphics_3d_interface = (const PPB_Graphics3D*) get_browser_interface(PPB_GRAPHICS_3D_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_graphics_3d_interface || !g_view_interface || !g_input_interface || !g_mouse_interface) return -1; if (GL_TRUE != glInitializePPAPI(get_browser_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_simple类似。不同的是使用了Graphics 3D接口。下面是代码中使用Graphics 3D接口的步骤: - 在PPP_InitializeModule中获取PPB_GRAPHICS_3D_INTERFACE接口 - 在PPP_InitializeModule中调用glInitializePPAPI()来初始化PPAPI相关的gl环境 - MakeAndBindGraphics3D函数创建Graphics 3D context并将其与插件实例对象绑定 - 调用glSetCurrentContextPPAPI给ppapi的gl接口设置上下文 - Repaint里面使用gles的接口绘图 这个示例仅仅通过glClear来清除颜色缓冲区来达到变换颜色的效果。后面会进一步使用gles的API绘制点儿东西。 其他参考文章: - [**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) - [**在PPAPI插件中创建本地窗口**](http://blog.csdn.net/foruok/article/details/50513228) - [**PPAPI插件与浏览器的通信**](http://blog.csdn.net/foruok/article/details/50513315) - [**Windows下从源码编译Skia**](http://blog.csdn.net/foruok/article/details/50524726) - [**在PPAPI插件中使用Skia绘图**](http://blog.csdn.net/foruok/article/details/50526110) - [**加载DLL中的图片资源生成Skia中的SkBitmap对象**](http://blog.csdn.net/foruok/article/details/50543762) - [**PPAPI+Skia实现的涂鸦板**](http://blog.csdn.net/foruok/article/details/50547737)
';

PPAPI+Skia实现的涂鸦板

最后更新于:2022-04-01 16:03:10

[**在PPAPI插件中使用Skia绘图**](http://blog.csdn.net/foruok/article/details/50526110)介绍了如何在PPAPI中使用Skia,文末说回头要提供一个简单的涂鸦板插件,这次我来兑现承诺了。 > foruok原创,关注微信订阅号“程序视界”可联系foruok。 示例很简单,先看看效果: ![doodle](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56caac34811fe.jpg "") # 涂鸦插件功能说明 功能列表: - 使用鼠标左键绘制线条 - 撤销、清除功能 - 支持CTRL+Z组合键撤销,支持ESC清除 # 项目说明 项目与[**在PPAPI插件中使用Skia绘图**](http://blog.csdn.net/foruok/article/details/50526110)这个文章里的差不多,只不过多了几个文件。VS2013中的项目视图如下: ![vcproj](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56caac349a4c8.jpg "") 做一点点说明吧。 ### ppapi_doodle.cpp 这个文件实现了PPAPI插件的入口,它获取浏览器侧接口并保存在一个类型为GlobalPPBInterface(PPBInterface.h)的全局变量中,它完成与浏览器的交互,并且将事件派发到某个实例。 与之前相比,更干净了,我把其它功能都移走了。 ### PluginInstance.h(.cpp) 这是我抽象出来的代表插件实例的类。 PluginInstance这个类主要做了下面几件事: - 定义了与PPAPI交互的接口 - 抽象了鼠标和键盘事件,定义了可供派生类重写的接口 - 融合了Skia,简单分离了绘图操作,提供给派生类自我绘制的接口 PluginInstance可以实例化,但就是画个白色背景,其它什么事儿也不干。所以,我另外实现了DoodleInstance类来实现涂鸦功能。 ### DoodleInstance.h(.cpp) DoodleInstance继承了PluginInstance,重写了下列方法: - void paint(const PP_Rect *rect); - PP_Bool mouseEvent(const MouseEvent &evMouse); - PP_Bool keyboardEvent(const KeyboardEvent &evKeyboard); 如果要实现其它的插件,重写上面几个函数也是必须的。 另外我还利用Skia里的SkPath来保存“从鼠标左键按下到释放”这“一笔”画出的所有东西,配套一个SkPaint,可以定制线条颜色、粗细、线型(没实现哈哈),这两者被我放在了一个ElementGroup类里,DoodleInstance的m_paths是一个集合,其中的每个元素都代表了一个“一笔画”,paint函数里会把这些“一笔画”给绘制出来。 ### Button.h(.cpp) 观看文前那张Gif动画,里面有两个按钮,它们对应的实现就在这两个文件里了,类名是ImageButton。具体看代码了,比较直接。 ### 从资源文件里加载图片 我给ppapi_doodle项目添加了两个png格式的图片,给前面提到的按钮用。 DLL中图片资源如何转化为SkBitmap,在utils.cpp中实现,[**加载DLL中的图片资源生成Skia中的SkBitmap对象**](http://blog.csdn.net/foruok/article/details/50543762)这篇文章里说过了。 # 项目源码 源码还不太完善,比如有些资源没释放,边界没考虑……没时间细整了……到这里下载吧:[**ppapi_doodle源码**](http://download.csdn.net/detail/foruok/9411313)。 其他参考文章: - [**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) - [**在PPAPI插件中创建本地窗口**](http://blog.csdn.net/foruok/article/details/50513228) - [**PPAPI插件与浏览器的通信**](http://blog.csdn.net/foruok/article/details/50513315) - [**Windows下从源码编译Skia**](http://blog.csdn.net/foruok/article/details/50524726) - [**在PPAPI插件中使用Skia绘图**](http://blog.csdn.net/foruok/article/details/50526110) - [**加载DLL中的图片资源生成Skia中的SkBitmap对象**](http://blog.csdn.net/foruok/article/details/50543762)
';

加载DLL中的图片资源生成Skia中的SkBitmap对象

最后更新于:2022-04-01 16:03:07

PPAPI Plugin在Windows下是DLL,可以嵌入图片文件,使用Skia绘图时需要根据DLL里的图片文件生成SkBitmap对象。下面是代码: ~~~ #include "utils.h" #include "SkStream.h" #include "SkImageDecoder.h" #include <tchar.h> SkBitmap* loadImageFromResource(UINT resId, LPCTSTR lpType) { TCHAR szLog[512] = { 0 }; HMODULE hModule = NULL; if (FALSE == GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS , (LPCTSTR)loadImageFromResource, &hModule)) { _stprintf_s(szLog, 512, _T("GetModuleHandleEx failed, error - %d\r\n"), GetLastError()); OutputDebugString(szLog); return NULL; } HRSRC hRsrc = FindResource(hModule, MAKEINTRESOURCE(resId), lpType); if (hRsrc == NULL) { _stprintf_s(szLog, 512, _T("FindResource failed, error - %d\r\n"), GetLastError()); OutputDebugString(szLog); return NULL; } HGLOBAL hImgData = LoadResource(hModule, hRsrc); if (hImgData == NULL) { _stprintf_s(szLog, 512, _T("LoadResource failed, error - %d\r\n"), GetLastError()); OutputDebugString(szLog); return NULL; } LPVOID lpData = LockResource(hImgData); if (lpData == NULL) { _stprintf_s(szLog, 512, _T("LockResource failed, error - %d\r\n"), GetLastError()); OutputDebugString(szLog); return NULL; } DWORD dwSize = SizeofResource(hModule, hRsrc); SkMemoryStream memoryStream(lpData, dwSize); SkImageDecoder *decoder = SkImageDecoder::Factory(&memoryStream); if (!decoder) { OutputDebugString(_T("SkImageDecoder::Factory failed\r\n")); FreeResource(hImgData); return NULL; } SkBitmap *bitmap = new SkBitmap(); if (SkImageDecoder::kSuccess == decoder->decode(&memoryStream, bitmap, kBGRA_8888_SkColorType, SkImageDecoder::kDecodePixels_Mode)) { FreeResource(hImgData); return bitmap; } //error OutputDebugString(_T("SkImageDecoder::decode failed\r\n")); FreeResource(hImgData); delete bitmap; return NULL; } ~~~ 使用非常简单: ~~~ SkBitmap *bitmap = loadImageFromResource(IDB_CLEAR, _T("PNG")); ~~~ 注意我添加的是png文件,rc文件内有类似这样的语句: ~~~ IDB_CLEAR PNG "images\\icon-clear.png" ~~~ 其中第二列是资源类型,调用loadImageFromResource时指定的类型与这里一致即可。 其他参考文章: - [**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) - [**在PPAPI插件中创建本地窗口**](http://blog.csdn.net/foruok/article/details/50513228) - [**PPAPI插件与浏览器的通信**](http://blog.csdn.net/foruok/article/details/50513315) - [**Windows下从源码编译Skia**](http://blog.csdn.net/foruok/article/details/50524726) - [**在PPAPI插件中使用Skia绘图**](http://blog.csdn.net/foruok/article/details/50526110)
';

在PPAPI插件中使用Skia绘图

最后更新于:2022-04-01 16:03:05

[**Windows下从源码编译Skia**](http://blog.csdn.net/foruok/article/details/50524726)一文介绍了Skia的编译,现在我们可以尝试在PPAPI插件中来使用Skia了。 > foruok原创,如需转载请关注foruok的微信订阅号“程序视界”联系foruok。 # Skia的关键类库 官网[https://skia.org/](https://skia.org/)上有文档,可以看。然后下载的源码,可以使用SourceInsight之类的工具来查看。具体不再细说,我这里只提用到的三个关键类: - [SkPaint](https://skia.org/user/api/skpaint) - [SkCanvas](https://skia.org/user/api/skcanvas) - SkBitmap SkCanvas是画布,你可以在它上面画任意的图元,比如矩形、圆型、文字、弧…… SkPaint则是工具箱,聚合了画笔、颜色等各种绘画时要用的配置选项。 SkBitmap代表了一块图像数据。 你调用SkCanvas的方法绘制图元,这些图元对应的数据一定要有个地方保存或显示,用于保存或显示图像数据的那个角色,被称为backends。SkCanvas的backends,有好几种,我们这个简单的示例ppapi_skia只用到了SkBitmap这种后端。 2D绘图类库的基本设计都差不多,我的《[**Qt on Android核心编程**](http://item.jd.com/11571639.html)》一书里对Qt绘图有介绍,两厢比对,就会发现设计得差不多。不过有一点不同,QPainter会保存你设置的绘图选项(颜色、画笔等),而SkCanvas则不会,需要自己设计数据结构和逻辑来保存。 # ppapi_skia项目 ppapi_skia项目基于ppapi_simple而来,请先参考[**PPAPI插件的绘图与输入事件处理**](http://blog.csdn.net/foruok/article/details/50499813)这篇文章。 不过为了方便调用Skia(C++类库),我这次用了cpp源文件,ppapi_skia.cpp。(吐槽下,VS2013对于C文件的即时提示太弱……) 我还改了ppapi_simple,使用了FlushCompletionCallback来优化绘图流程。 Skia默认编译出来的是静态库,对于一个简单的PPAPI+Skia插件,我不知道需要链接哪些lib,写好了代码,编译,根据undefined reference错误一通狂找,发现要链接下列lib: ~~~ skia_core.lib skia_ports.lib skia_utils.lib skia_effects.lib skia_skgpu.lib skia_opts.lib skia_opts_ssse3.lib skia_opts_sse41.lib skia_opts_avx.lib skia_images.lib skia_sfnt.lib libetc1.lib libSkKTX.lib opengl32.lib ~~~ [**Windows下从源码编译Skia**](http://blog.csdn.net/foruok/article/details/50524726)一文提到编译后的lib文件路径是:E:\sources\skia\out\Release。设置到项目的附加库目录列表中。skia还依赖其他一些库文件,在E:\sources\skia\out\Release\obj\gyp目录下,把这个目录添加到附加库目录列表里。 Skia需要OpenGL,我们得链接Opengl32.lib,Windows SDK里有。 skia编译时加了/MD参数,CEF编译时用的MT,如果你用PPAPI的C++接口,就会冲突了。要么改skia,要么改CEF。skia库小,改它吧。我还没找到怎么在生成构建文件时设置,最简单直接的,就是到E:\sources\skia\out\Release\obj\gyp这里,把*.ninja文件里的/MD都修改为/MT。然后重新编译。 如过用C接口,没关系,把PPAPI的代码生成选项里的运行库修改为MD即可。我偷懒,就用C接口,把ppapi_skia项目的运行库选项设置为了MD。 好,关于工程基本就这样了。 # 源码 分C++源码和HTML源码。 ### C++代码 So,源码来了,基于C代码改过来,有点乱,不过可以说明用法。 ~~~ /* * Copyright (c) 2016 foruok@程序视界. All rights reserved. * 2016-1-16, 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" #include "SkPaint.h" #include "SkBitmap.h" #include "SkCanvas.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; /* PPP_Instance implementation -----------------------------------------------*/ struct InstanceInfo { PP_Instance pp_instance; struct PP_Size last_size; PP_Resource graphics; PP_Resource image; SkBitmap *bitmap; SkCanvas *canvas; int inFlush; int requestPaint; unsigned int colorIndex; 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; } unsigned int g_colors[4] = { 0xFF0000FF, 0xFFFF00FF, 0xFF00FFFF, 0xFFEA00FF }; void DoPaint(struct InstanceInfo * instance); void FlushCompletionCallback(void* user_data, int32_t result) { struct InstanceInfo *inst = (struct InstanceInfo*)user_data; inst->inFlush = 0; if (inst->requestPaint) { inst->requestPaint = 0; DoPaint(inst); } } const char szHelloSkia[] = "Hello Skia in PPAPI"; void DoPaint(struct InstanceInfo * instance) { instance->inFlush = 1; instance->colorIndex++; if (instance->colorIndex >= sizeof(g_colors) / sizeof(g_colors[0])) instance->colorIndex = 0; /* [2] Skia Paint * foruok */ instance->canvas->drawColor(g_colors[instance->colorIndex]); SkPaint paint; paint.setColor(SK_ColorWHITE); paint.setAntiAlias(true); paint.setLCDRenderText(true); paint.setStrokeWidth(4); instance->canvas->drawText(szHelloSkia, ARRAYSIZE(szHelloSkia) - 1, 20.0f, 20.0f, paint); paint.setColor(0x80CCCCCC); instance->canvas->drawCircle(150, 130, 100, paint); /* Paint image to graphics 2d. */ g_graphics_2d_interface->ReplaceContents(instance->graphics, instance->image); g_graphics_2d_interface->Flush(instance->graphics, PP_MakeCompletionCallback(&FlushCompletionCallback, instance)); } void Repaint(struct InstanceInfo* instance, const struct PP_Size* size) { /* 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. */ if (!instance->image) { instance->image = g_image_data_interface->Create( instance->pp_instance, PP_IMAGEDATAFORMAT_BGRA_PREMUL, size, PP_TRUE); if (!instance->image) return; } /* [1] setup SkCanvas's backends * foruok */ if (!instance->bitmap) { /* foruok * get image data from PPB interfaces */ struct PP_ImageDataDesc image_desc; uint32_t* image_data; g_image_data_interface->Describe(instance->image, &image_desc); image_data = (uint32_t*)g_image_data_interface->Map(instance->image); if (!image_data) { g_core_interface->ReleaseResource(instance->image); return; } /* foruok * bind PPAPI image data to SkBitmap */ SkImageInfo ii = SkImageInfo::Make(size->width, size->height, kBGRA_8888_SkColorType, kPremul_SkAlphaType, kLinear_SkColorProfileType); instance->bitmap = new SkBitmap(); instance->bitmap->installPixels(ii, image_data, image_desc.stride); if (!instance->canvas) { /* foruok * construct SkCanvas with SkBitmap backend */ instance->canvas = new SkCanvas(*instance->bitmap); } } if (!instance->inFlush) { DoPaint(instance); } else { instance->requestPaint = 1; } } /** 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*)calloc(1, sizeof(struct InstanceInfo)); info->pp_instance = instance; /* 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; } } 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; } 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) { 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; } ~~~ 上面代码里我标注了两处与Skia有关的改动,[1]和[2],也加了注释。另外就是使用FlushCompletionCallback来处理绘图和刷新,把ppapi_simple中的一些全局变量修改为与插件实例对象绑定的变量,这样可以支持一个网页里嵌入多个plugin了。 ### HTML代码 ppapi_skia.html代码如下: ~~~ <!DOCTYPE html> <html> <!-- Copyright (c) 2016 foruok. All rights reserved. 如需转载,请关注微信订阅号“程序视界”,回复foruok获取其联系方式 --> <head> <title>PPAPI-SKIA</title> </head> <body> <embed id="plugin" type="application/x-ppapi-skia" width="400px" height="300px"> </body> </html> ~~~ ### 运行效果 使用下面命令来运行: ~~~ cefsimple.exe --ppapi-out-of-process --register-pepper-plugins="E:\sources\CEF\2526\chromium\src\cef\binary_distrib\cef_binary_3.2526.1364.gf6bf57b_windows32\Release\ppapi_skia.dll;application/x-ppapi-skia" --url=file:///E:/sources/CEF/2526/chromium/src/cef/binary_distrib/cef_binary_3.2526.1364.gf6bf57b_windows32/Release/ppapi_skia.html ~~~ 效果如下: ![PPAPI-Skia](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56caac346903b.jpg "") Ok,这个示例就这么着了。有时间我会用Skia+PPAPI的方式制作一个简单的涂鸦板插件。 其他参考文章: - [**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) - [**在PPAPI插件中创建本地窗口**](http://blog.csdn.net/foruok/article/details/50513228) - [**PPAPI插件与浏览器的通信**](http://blog.csdn.net/foruok/article/details/50513315) - [**Windows下从源码编译Skia**](http://blog.csdn.net/foruok/article/details/50524726)
';

Windows下从源码编译Skia

最后更新于:2022-04-01 16:03:03

在PPAPI里面绘图,可以结合第三方的图形库,比如Cairo、Skia。Google Chrome、Chromium和Android都使用Skia作为绘图引擎,我也来试试Skia,先过编译关。 > foruok原创,如需转载请关注foruok的微信订阅号“程序视界”联系foruok。 # CEF编译出的Skia不可单独用 Chromium使用Skia,[**Windows下从源码编译CEF**](http://blog.csdn.net/foruok/article/details/50498740)说明了怎么编译CEF,它会捎带着编译Chromium,里面有Skia,但它编译出来的skia_library.lib无法单独使用,加到VS工程里,报N多链接错误。 只好自己编译了。 # 单独编译Skia Windows下编译指南:[https://skia.org/user/quick/windows](https://skia.org/user/quick/windows)。首先你系统得有Python,我们编译过CEF,有了。 下载skia到这里:[https://skia.org/user/download](https://skia.org/user/download)。需要使用git,depot_tools很全,什么都有。 我们之前编译CEF,已自动下载depot_tools,在E:\sources\CEF\2526\depot_tools,设置到path中: ~~~ set path=E:\sources\CEF\2526\depot_tools;%path% ~~~ 然后使用git下载Skia: ~~~ git clone https://skia.googlesource.com/skia.git ~~~ 然后执行下列命令: ~~~ cd %SKIA_CHECKOUT_DIR% SET "GYP_GENERATORS=ninja,msvs" //告诉GYP,产生ninja构建文件和msvs构建文件 python bin/sync-and-gyp SET "GYP_GENERATORS=" ~~~ 上面命令完成后,基于ninja的构建文件在这里:E:\sources\skia\out\Release(Debug)\build.ninja。基于VS2013的解决方案在这里:E:\sources\skia\out\skia.sln。 我选择编译Release版本。默认编译“most”目标,most.ninja在这里:E:\sources\skia\out\Release\obj\gyp,它指定编译skia_lib和dm、SampleApp、HelloWorld等模块。 编译命令如下: ~~~ cd out\Release ninja ~~~ 编译到effects模块,出错了,说: > ninja: build stopped: subcommand failed. 加个参数,再来: ~~~ ninja -v > skia_build.log ~~~ 研究skia_build.log,发现错误信息如下: > [6/1807] ninja -t msvc -e environment.x86 – “E:\software\VS2013_64_update4\VC\bin\amd64_x86\cl.exe” /nologo /showIncludes /FC @obj\tools\imgslice.imgslice.obj.rsp /c ….\tools\imgslice.cpp /Foobj\tools\imgslice.imgslice.obj /Fdimgslice.pdb FAILED: ninja -t msvc -e environment.x86 – “E:\software\VS2013_64_update4\VC\bin\amd64_x86\cl.exe” /nologo /showIncludes /FC @obj\src\effects\effects.SkDisplacementMapEffect.obj.rsp /c ….\src\effects\SkDisplacementMapEffect.cpp /Foobj\src\effects\effects.SkDisplacementMapEffect.obj /Fdeffects.pdb e:\sources\skia\src\effects\skdisplacementmapeffect.cpp : error C2220: 警告被视为错误 - 没有生成“object”文件 e:\sources\skia\src\effects\skdisplacementmapeffect.cpp : warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失 错误C2220是因为把所有警告当做错误了,编译时加了/WX标记。而警告C4819,MSDN说,“在具有不能表示文件中所有字符的代码页的系统上编译ANSI 源文件时,出现C4819”。记得之前编译CEF(参考[**Windows下从源码编译CEF**](http://blog.csdn.net/foruok/article/details/50498740))时说要设置系统locale为英文,再试试吧。 (⊙o⊙)…,真过了。 编译完成后,lib文件和exe文件都在E:\sources\skia\out\Release目录下。有这么些skia库,见下图: ![skialibs](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56caac343f2e6.jpg "") 试着运行一下HelloWorld.exe(在E:\sources\skia\out\Release目录下),效果如下图: ![helloworld](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56caac3452645.jpg "") 嗯,不错,可以继续前进了。下次我会把PPAPI和Skia结合起来试试。 其他参考文章: - [**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) - [**在PPAPI插件中创建本地窗口**](http://blog.csdn.net/foruok/article/details/50513228) - [**PPAPI插件与浏览器的通信**](http://blog.csdn.net/foruok/article/details/50513315)
';

PPAPI插件与浏览器的通信

最后更新于:2022-04-01 16:03:00

PPAPI的插件,原本是可以使用JS与浏览器交互的,[https://code.google.com/p/ppapi/wiki/InterfacingWithJavaScript](https://code.google.com/p/ppapi/wiki/InterfacingWithJavaScript),这里还提供了一个JS与plugin交互的文档,但现在说不支持了,现在应该通过PPB_Messaging接口来完成Plugin和浏览器的交互,具体参考[https://src.chromium.org/viewvc/chrome/trunk/src/ppapi/c/ppb_messaging.h?revision=92312&view=markup](https://src.chromium.org/viewvc/chrome/trunk/src/ppapi/c/ppb_messaging.h?revision=92312&view=markup)这里。我实验了一下,通了。 Messaging接口很好,传递的消息可以自已定义,类型也无限制,非常方便。 > foruok原创,如需转载请关注foruok的微信订阅号“程序视界”联系foruok。 # 使用postMessage通信 Messaging接口与其它大多数接口一样,分PPB和PPP两侧。分开来说明一下要做的事情。 ### 插件侧 要做这么些事情: 1. 实现PPP_Messaging接口,关键是void (*HandleMessage)(PP_Instance instance, struct PP_Var message)方法 1. 在Get_Interface中返回名字是PPP_MESSAGING_INTERFACE的接口 1. 在PPP_InitializeModule中获取 PPB_Messaging、PPB_Var、PPB_VarArray、PPB_VarDictionary等接口。 PPB_Messaging的PostMessage用于向浏览器发送消息,发送过去的消息,JS代码可以接收到。 PPB_Var可以用来构造String类型的Var,可以操作Var的引用计数 PPB_VarArray是数组接口,可以创建、访问、设置数组 PPB_VarDictionary是字典(map)接口,可以创建字典Var,可以存取key-value对。 1. PPP_Messaging的HandleMessage处理浏览器的消息,如果需要,调用PPB_Messaging的PostMessage发送消息. 注意,插件侧调用PPB_Var接口的VarFromUtf8时,传入的len不包括’\0’在内。PPAPI的ppb_var.h的注释里的示例代码片段有误,调用时传递的长度是sizeof(hello_world),应该减去一。 还有一点,插件和浏览器交互的数据,都是数据的拷贝哦,调用接口会发生复制行为。 ### 浏览器侧 浏览器侧可以使用JavaScript来监听插件发送的message事件,也可以使用插件元素的postMessage发送消息给插件。 基本上做下面几件事即可: 1. 实现处理消息的JS函数,其参数是MessageEvent,data成员为插件发过来的信息,可以当做JS对象来访问。 1. 监听插件的message事件 1. 在合适的时候调用插件的postMessage(object)方法发送消息给插件 # 代码 分插件代码和HTML代码。 ### 插件代码 代码是在[**在PPAPI插件中创建本地窗口**](http://blog.csdn.net/foruok/article/details/50513228)一文示例代码的基础上改的,添加了消息处理的部分。只贴相关的部分了。 **获取消息相关接口的代码** 在PPP_InitializeModule中添加了获取PPB_Messaging接口以及其它可能用到的PP_Var类型的接口,有Array和Dictionary。 ~~~ g_var_interface = (const PPB_Var*)get_browser_interface(PPB_VAR_INTERFACE); g_dictionary_interface = (const PPB_VarDictionary*)get_browser_interface(PPB_VAR_DICTIONARY_INTERFACE); g_array_interface = (const PPB_VarArray*)get_browser_interface(PPB_VAR_ARRAY_INTERFACE); g_message_interface = (const PPB_Messaging*)get_browser_interface(PPB_MESSAGING_INTERFACE); ~~~ **PPP_Messaging接口的实现** PPP_Messaging接口的实现代码如下: ~~~ void Plugin_HandleMessage(PP_Instance instance, struct PP_Var message) { char szLog[256] = { 0 }; sprintf_s(szLog, 256, "Plugin_HandleMessage, type = %d\r\n", message.type); OutputDebugStringA(szLog); if (message.type == PP_VARTYPE_DICTIONARY) { char command[] = "command"; struct PP_Var commandKey = g_var_interface->VarFromUtf8(command, sizeof(command) - 1); struct PP_Var commandVar = g_dictionary_interface->Get(message, commandKey); int len = 0; const char *strCommand = g_var_interface->VarToUtf8(commandVar, &len); g_var_interface->Release(commandKey); sprintf_s(szLog, 256, "Plugin_HandleMessage, dict, command = %s, len = %d\r\n", strCommand, len); OutputDebugStringA(szLog); if (len == 0) { OutputDebugString(_T("Tang_plugin, recv invalid command\r\n")); g_var_interface->Release(commandVar); return; } if (strncmp(strCommand, "joinConf", len) == 0) { char confIdKey[] = "confId"; char userNameKey[] = "userName"; char *szConfId = 0; char*szUserName = 0; struct PP_Var idKey = g_var_interface->VarFromUtf8(confIdKey, sizeof(confIdKey) - 1); struct PP_Var userKey = g_var_interface->VarFromUtf8(userNameKey, sizeof(userNameKey) - 1); struct PP_Var var = g_dictionary_interface->Get(message, idKey); const char *value = g_var_interface->VarToUtf8(var, &len); if (len > 0) { szConfId = malloc(len + 1); strncpy_s(szConfId, len+1, value, len); szConfId[len] = 0; } g_var_interface->Release(var); var = g_dictionary_interface->Get(message, userKey); value = g_var_interface->VarToUtf8(var, &len); if (len > 0) { szUserName = malloc(len + 1); strncpy_s(szUserName, len+1, value, len); szUserName[len] = 0; } g_var_interface->Release(var); sprintf_s(szLog, 256, "Plugin_HandleMessage, dict, command = joinConf, user = %s, confId = %s\r\n", szUserName, szConfId); OutputDebugStringA(szLog); if (szConfId && szUserName) { sprintf_s(szLog, 256, "plugin got confId - %s, user - %s\r\n", szConfId, szUserName); OutputDebugStringA(szLog); joinConf(szConfId, szUserName); } else { OutputDebugString(_T("Invalid conference id or userName\r\n")); } if (szConfId) free(szConfId); if (szUserName) free(szUserName); g_var_interface->Release(idKey); g_var_interface->Release(userKey); /* fake attendees*/ char szMsgTypeValue[] = "userlist"; char szTypeKey[] = "type"; struct PP_Var typeKey = g_var_interface->VarFromUtf8(szTypeKey, sizeof(szTypeKey) - 1); struct PP_Var typeValue = g_var_interface->VarFromUtf8(szMsgTypeValue, sizeof(szMsgTypeValue) - 1); struct PP_Var attendee = g_dictionary_interface->Create(); g_dictionary_interface->Set(attendee, typeKey, typeValue); struct PP_Var userArray = g_array_interface->Create(); char szUser1[] = "ZhangSan"; char szUser2[] = "LiSi"; struct PP_Var user1 = g_var_interface->VarFromUtf8(szUser1, sizeof(szUser1) - 1); struct PP_Var user2 = g_var_interface->VarFromUtf8(szUser2, sizeof(szUser2) - 1); g_array_interface->Set(userArray, 0, user1); g_array_interface->Set(userArray, 1, user2); char szValueKey[] = "value"; struct PP_Var valueKey = g_var_interface->VarFromUtf8(szValueKey, sizeof(szValueKey) - 1); g_dictionary_interface->Set(attendee, valueKey, userArray); g_message_interface->PostMessage(instance, attendee); OutputDebugString(_T("Post attendee to browser")); g_var_interface->Release(typeKey); g_var_interface->Release(typeValue); g_var_interface->Release(user1); g_var_interface->Release(user2); g_var_interface->Release(valueKey); g_var_interface->Release(userArray); g_var_interface->Release(attendee); } else if (strncmp(strCommand, "viewVideo", len) == 0) { char userIdKey[] = "userId"; char *szUserId = 0; struct PP_Var idKey = g_var_interface->VarFromUtf8(userIdKey, sizeof(userIdKey) - 1); struct PP_Var var = g_dictionary_interface->Get(message, idKey); const char *value = g_var_interface->VarToUtf8(var, &len); if (len > 0) { szUserId = malloc(len + 1); strncpy_s(szUserId, len + 1, value, len); szUserId[len] = 0; } if (szUserId) { sprintf_s(szLog, 256, "plugin got userId - %s\r\n", szUserId); OutputDebugStringA(szLog); viewVideo(szUserId, g_child_window); } else { OutputDebugString(_T("Invalid viewVideo command without userId\r\n")); } if (szUserId) free(szUserId); g_var_interface->Release(var); g_var_interface->Release(idKey); } g_var_interface->Release(commandVar); } else if (message.type == PP_VARTYPE_STRING) { char hello_world[] = "Hello world!"; struct PP_Var var = g_var_interface->VarFromUtf8(hello_world, sizeof(hello_world) - 1); g_message_interface->PostMessage(instance, var); // var will be copyed g_var_interface->Release(var); } } static PPP_Messaging message_interface = { &Plugin_HandleMessage }; ~~~ 有点长,比较潦草。 **返回PPP_Messaging的代码** 完整的PPP_GetInterface函数如下: ~~~ 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; } else if (strcmp(interface_name, PPP_MESSAGING_INTERFACE) == 0) { OutputDebugString(_T("PPP_GetInterface, message_interface\r\n")); return &message_interface; } return NULL; } ~~~ ### 网页代码 网页代码如下: ~~~ <!DOCTYPE html> <html> <!-- Copyright (c) 2016 foruok@微信订阅号“程序视界”(programmer_sight). All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> <head> <style type="text/css"> #contacts { margin: 10px; width: 300px; height: 200px; background-color: gray; } </style> <script type="text/javascript"> function handleMessage(message) { alert(message.data.type); if(message.data.type.localeCompare("userlist") == 0){ var i = 0; ul = document.getElementById("attendee"); for(; i < message.data.value.length; i++){ var li = document.createElement("li"); li.appendChild(document.createTextNode(message.data.value[i])); ul.appendChild(li); } } } function joinConference(){ plugin = document.getElementById('tangplugin'); plugin.postMessage({ command:"joinConf", confId: document.getElementById("confId").value, userName: document.getElementById("userName").value }); } function viewSharedVideo(){ plugin = document.getElementById('tangplugin'); plugin.postMessage({ command:"viewVideo", userId: document.getElementById("userId").value }); } function initialize() { plugin = document.getElementById('tangplugin'); plugin.addEventListener('message', handleMessage, false); } document.addEventListener('DOMContentLoaded', initialize, false); </script> <title>Tang in Plugin</title> </head> <body> <form> ConferenceID: <input type="text" id="confId" />&nbsp;&nbsp;User: <input type="text" id="userName" />&nbsp;&nbsp;<input type="button" value="Join" onclick="joinConference()"/> </form> <hr> <div id="contacts"> <p>contacts:</p> <ul id="attendee"> <!-- here will show attendee list, added by JS callback --> </ul> UserId:<input type="text" id="userId" />&nbsp;&nbsp;<button type="button" onclick="viewSharedVideo()">View Video</button> </div> <p>share video:</p> <embed id="tangplugin" type="application/x-ppapi-tang-video" width="300px" height="200px" top="40px" left="20px"> </body> </html> ~~~ Join按钮会获取它前面两个文本框的内容,发送给插件,插件返回一个用户列表,网页解析(HandleMessage方法)出来,动态修改用户列表。 # 运行效果 贴两幅图,点击Join按钮之前是酱紫的: ![before](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56caac341008d.jpg "") 点击Join按钮后是酱紫的: ![after](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56caac34279dc.jpg "") 其他参考文章: - [**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) - [**在PPAPI插件中创建本地窗口**](http://blog.csdn.net/foruok/article/details/50513228)
';

在PPAPI插件中创建本地窗口

最后更新于:2022-04-01 16:02:58

实验了一个比较奇特的东西,在PPAPI插件里创建一个本地窗口,叠加在插件在网页的位置上。 CEF3默认是多进程架构,PPAPI插件在一个单独进程里跑,这个进程没启动Windows的消息循环,所以,要创建插件的话,得自己搞一个消息循环。另外浏览器窗口属于别的进程,怎么把创建出来的窗口成为浏览器窗口的子窗口,也是个问题。这个第一个要解决的问题。 CEF3还支持单进程运行,Browser、Render、Plugin合一,此时创建本地窗口又和多进程不同。这是第二个问题。 第三个问题是,如何把窗口定位到网页的插件区域。 琢磨了下,都解决了。 > foruok原创,如需转载请关注foruok的微信订阅号“程序视界”联系foruok。 # 效果 先看看效果图。先是跨进程的: ![multi-process](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56caac33c54f5.jpg "") 灰色区域那里是我创建的本地窗口,上面有一行字。 再看同一进程的: ![in-process](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56caac33dbea6.jpg "") 注意文字的变化。 # 代码 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)
';

PPAPI插件的绘图与输入事件处理

最后更新于:2022-04-01 16:02:56

在[PPAPI插件与浏览器的交互过程](http://blog.csdn.net/foruok/article/details/50494061)一文中学习了PPAPI插件与浏览器的交互流程、渲染逻辑、输入事件的处理逻辑,这次我们改造一下graphics_2d_example示例,加入处理鼠标事件的逻辑,演示一下PPAPI插件想要处理输入事件时的代码流程。 > foruok原创,如需转载请关注foruok的微信订阅号“程序视界”联系foruok。 # 工程 新建一个Win32项目,类型选DLL,去掉预编译头文件stdafx.h和stdafx.cpp,并且在项目属性–>配置属性–>C/C++–>预编译头,把预编译头选项的值设置为不使用预编译头。 使用UNICODE字符集,运行库选择MT。 项目名就叫ppapi_simple吧,把ppapi的上一级目录添加到附加包含目录里。 把graphics_2d_example.c拷贝过来,改名为ppapi_simple.c。内容修改成下面的样子: ~~~ /* 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-10, 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; // [1] const PPB_InputEvent *g_input_interface; const PPB_MouseInputEvent *g_mouse_interface; /* 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] = { 0xFF0000FF, 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; // [6] 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; // [5] 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; } } 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; } 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 }; // [3] 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; } // [3] static PPP_InputEvent input_interface = { &InputEvent_HandleInputEvent }; /* Global entrypoints --------------------------------------------------------*/ PP_EXPORT int32_t PPP_InitializeModule(PP_Module module, PPB_GetInterface get_browser_interface) { 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); // [2] 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; } // [4] else if (strcmp(interface_name, PPP_INPUT_EVENT_INTERFACE) == 0) { OutputDebugString(_T("PPP_GetInterface, input_interface\r\n")); return &input_interface; } return NULL; } ~~~ # 代码改动说明 ppapi_simple.c相对graphics_2d_example.c来讲,主要有6处改动,已在前面的源代码中使用[1]、[2]之类的符号标注出来了。大部分改动是为处理鼠标事件加入的代码,有两处是绘图相关的改动。 先说要处理鼠标事件需要添加的代码及流程。 [1] 这里声明了两个全局变量,g_input_interface和g_mouse_interface,它们将会在PPP_InitializeModule方法中被初始化(标号为[2]处的改动)。g_input_interface是浏览器暴露出来的输入事件接口,插件要请求处理某类事件就通过它。g_mouse_interface是鼠标事件,可以根据事件资源ID获取对应的详细信息,如位置、按下的鼠标按键等。 [3] 这处改动声明了插件测的输入处理接口。一个结构体,一个函数。 [4] 这里会被浏览器调用到,我们新添加的PPP_InputEvent就是在这里返回给浏览器。浏览器拿到PPP_InputEvent后就可以把事件传递给这个接口的HandleInputEvent方法,也就是我们实现的InputEvent_HandleInputEvent方法。在InputEvent_HandleInputEvent方法里,鼠标左键按下时我们调用Repaint()来重绘。 [5] 当插件对象实例创建时,我们向浏览器的输入事件接口请求处理鼠标事件。浏览器收到这个请求,才会派发事件给我们的插件实例对象。 [6] 渲染(绘图)函数。绘图过程,在[**PPAPI插件与浏览器的交互过程**](http://blog.csdn.net/foruok/article/details/50494061)中已介绍过了,不多说了。这里添加的代码,主要是为了让示例随着鼠标点击有变化。通过一个颜色数组和索引,变换插件实例对象对应视图区域的颜色。 我还在关键处插入了一些日志代码,可以通过DbgView来查看。 下面是运行效果图: ![ppapi_simple](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56caac33ae1df.jpg "") 通过CEF运行时使用的命令参数如下: –ppapi-out-of-process –register-pepper-plugins=”D:\projects\cef_binary_3.2357.1271.g8e0674e_windows32\Release\stub.dll;application/x-ppapi-simple” –url=file:///d:/projects/cef_binary_3.2357.1271.g8e0674e_windows32/Release/simple.html simple.html和stub.html类似,改了下title和embed标签的type。 好啦,新的示例主要就这些内容了,处理输入事件的流程应该也清楚了。 相关文章参考: - [**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的media_stream_video示例

最后更新于:2022-04-01 16:02:54

[**Windows下从源码编译CEF**](http://blog.csdn.net/foruok/article/details/50498740)一文编译了CEF和PPAPI的C++接口对应的静态库,现在来编译media_stream_video示例了。 使用VS 2013来编译。 > foruok原创,如需转载请关注foruok的微信订阅号“程序视界”联系foruok。 # VS项目的创建 新建一个名为media_stream_video的Win32项目,类型选DLL,去掉预编译头文件stdafx.h和stdafx.cpp,并且在项目属性–>配置属性–>C/C++–>预编译头,把预编译头选项的值设置为不使用预编译头。 删除自动生成的media_stream_video.cpp。 在“配置属性–>C/C++–>代码生成–>运行库”中设置为MT。 字符集使用 Unicode 字符集。 添加下列包含目录: ~~~ E:\sources\CEF\2526\chromium\src\cef\binary_distrib\cef_binary_3.2526.1364.gf6bf57b_windows32 //为了CEF E:\sources\CEF\2526\chromium\src\third_party\khronos //为了GLES E:\sources\CEF\2526\chromium\src //为了ppapi E:\sources\CEF\2526\chromium\src\gpu //这个是为了gl2chromium.h ~~~ 拷贝chromium\src\ppapi\examples\media_stream_video\media_stream_video.cc到项目目录下,并添加到工程。 添加下列附加库路径: ~~~ E:\sources\CEF\2526\chromium\src\cef\binary_distrib\cef_binary_3.2526.1364.gf6bf57b_windows32\Release //所有的lib都拷贝到这里吧,省事儿。 ~~~ 添加ppapi_cpp.lib、ppapi_cpp_objects.lib、libGLESv2.dll.lib、ppapi_gles2.lib等依赖库。 基本就这样了。 # 编译与运行 如果你编译工程,会遇到类似下面的链接错误: > media_stream_video.obj : error LNK2001: 无法解析的外部符号 _GLES2EnableVertexAttribArray@4 media_stream_video.obj : error LNK2001: 无法解析的外部符号 _GLES2UseProgram@4 media_stream_video.obj : error LNK2001: 无法解析的外部符号 _GLES2VertexAttribPointer@24 media_stream_video.obj : error LNK2001: 无法解析的外部符号 _GLES2ClearColor@16 media_stream_video.obj : error LNK2001: 无法解析的外部符号 _GLES2GetAttribLocation@8 media_stream_video.obj : error LNK2001: 无法解析的外部符号 _GLES2ActiveTexture@4 这是因为media_stream_video需要链接ppapi_gles2.lib,而编译CEF时没有生成这个库。我们得手动编译下。 ### 编译ppapi_gles2 默认编译CEF时,并没生成ppapi_gles2.lib这个文件。不过幸运的是,chromium\src\out\Release\obj\ppapi目录下生成了ppapi_gles2.ninja文件,它是ppapi_gles2.lib的构建文件。同时Release版本的构建文件chromium\src\out\Release\build.ninja内起始也添加了ppapi_gles2的build指令和subninja指令,只是build all目标中没有把ppapi_gles2添加进去。所以,其实一切都为我们准备好了,在chromium\src\out\Release目录下执行“ninja ppapi_gles2”命令就可以把ppapi_gles2.lib编译出来。 这也是手动编译部分模块的方法。 ### 运行 用下面的命令可以运行: ~~~ cefsimple.exe --ppapi-out-of-process --register-pepper-plugins="E:\sources\CEF\2526\chromium\src\cef\binary_distrib\cef_binary_3.2526.1364.gf6bf57b_windows32\Release\media_stream_video.dll;application/x-ppapi-example-media-stream-video" --url=file:///E:/sources/CEF/2526/chromium/src/cef/binary_distrib/cef_binary_3.2526.1364.gf6bf57b_windows32/Release/media_stream_video.html ~~~ 效果如下: ![video](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56caac3397699.jpg "") 仅止于此? 继续研究中…… 相关文章参考: - [**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)
';

Windows下从源码编译CEF

最后更新于:2022-04-01 16:02:51

PPAPI提供了C和C++两种插件开发接口。要使用C++版本的接口,需要ppapi_cpp.lib和ppapi_cpp_objects.lib这两个静态库,然而CEF的SDK里没有这两个库,只能编译CEF的源码来得到。所以,我就编译了CEF的源码。 花了几天时间,反复编译几次,过程比较艰辛,记录一下。如果你参考我的过程编译,请看完这篇文章再动手。 > foruok原创,如需转载请关注foruok的微信订阅号“程序视界”联系foruok。 [https://bitbucket.org/chromiumembedded/cef/wiki/BranchesAndBuilding](https://bitbucket.org/chromiumembedded/cef/wiki/BranchesAndBuilding)这里描述了怎么从源码编译CEF,这是主要的参考资料。 CEF支持自动、手动两种编译方法,我们使用基于git的自动构建流程。 注意,必须在无墙状态下来编译。 # 编译步骤 Windows系统必须是Windows 7 x64 或更高版本,x86的不支持哦。 保证已经安装了python,并设置了环境变量。 e:/sources/CEF是我的编译目录,以后都以此为例来说明。 安装过程参考了[http://www.chromium.org/developers/how-tos/build-instructions-windows](http://www.chromium.org/developers/how-tos/build-instructions-windows)和[https://bitbucket.org/chromiumembedded/cef/wiki/BranchesAndBuilding](https://bitbucket.org/chromiumembedded/cef/wiki/BranchesAndBuilding),我试验了几次,出了一些错。现在,按我下面的步骤来即可: 1. 设置系统locale为English,否则可能遇到类似下图的错误: ![error_unicode](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56caac333fcfd.jpg "") 1. 安装VS 2013 Update 4,其他版本的VS都歇菜。安装过程中确保选择MFC组件(Microsoft Foundation Classes for C++) 1. Windows 10 SDK,安装到默认路径下。 1. 配置环境变量,使用Ninja编译、使用VS 2013调试,按下面的命令设置环境变量(在cmd.exe内): set DEPOT_TOOLS_WIN_TOOLCHAIN=0 set GYP_GENERATORS=ninja,msvs-ninja set GYP_MSVS_VERSION=2013 1. 下载[automate-git.py](https://bitbucket.org/chromiumembedded/cef/raw/master/tools/automate/automate-git.py)脚本文件。 1. 自动构建版本为2526的分支,用下面的命令: python e:/sources/CEF/automate-git.py –download-dir=e:/sources/CEF/2526 –branch=2526 提一下,automate-git.py会自动下载depot_tools、Chromium和CEF源码、构建CEF的调试和发布版本、创建SDK包。SDK包在e:/sources/CEF/2526/chromium/src/cef/binary_distrib目录下。 **特别说明**: - 如果你因为系统locale出错,再次执行automate-git.py时,可能会遇见下面的错误(红线所示): ![error_out_exsit](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56caac3350b7f.jpg "") 此时删除e:/sources/CEF/2526/chromium/src/out目录,再来一遍即可。 - VS 2013如果没有安装到默认目录,也会出错(我就是酱紫的): ![error_vcvars](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56caac3360e3b.jpg "") 我编译了一天多,出了上面的错误。 沿着上图出错信息回溯,发现已经执行到automate-git.py的975行,这是最后一步了,所有中间编译已完成,就剩打包生成发行包了。 错误原因如图上标注的,找不到vcvars32.bat。跟到msvs_env.bat文件里去看,发现其中通过硬编码的VS路径来引用vcvars32.bat,对于VS2013,它用”%PROGRAMFILES(X86)%\Microsoft Visual Studio 12.0\VC\bin\vcvars32.bat”,我安装到别的地方去了……还好,有一个**CEF_VCVARS**变量,设置一下就好了。使用下列命令: ~~~ set CEF_VCVARS=E:\software\VS2013_64_update4\VC\bin\vcvars32.bat ~~~ 然后我需要研究如何只生成SDK,执行“python e:/sources/CEF/automate-git.py –help”,查看帮助,发现有一个–no-build选项还有一个–force-distrib选项,还有很多,最后用下面的命令生成了SDK发行包: ~~~ python e:/sources/CEF/automate-git.py --download-dir=e:/sources/CEF/2526 --branch=2526 --no-build --no-update --force-distrib ~~~ 编译成功后,生成的CEF SDK发行包在chromium\src\cef\binary_distrib目录下,我这里是cef_binary_3.2526.1364.gf6bf57b_windows32.zip。 注意,这里面没有PPAPI C++接口对应的静态库……但它们确实生成了,ppapi_cpp.lib和ppapi_cpp_objects.lib,在chromium\src\out\Release\obj\ppapi目录下,拷贝到你的SDK里就可以用了。 opengl库libGLESv2.dll和libEGL.dll也在编译过程生成了,DLL和对应的lib文件都在chromium\src\out\Release下,也可以拷贝到SDK目录下,PPAPI的有些示例会用到。 chromium\src\out\Release目录下还有cefclient.exe,就是CEF SDK里那个示例,可以运行。运行后效果如下图: ![cefclient](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56caac3380a68.jpg "") # 一点总结 编译是个漫长的过程,可能遇到各种错误,基本找不到中文资料,Google猛搜吧…… 闲言碎语: 1. automate-git.py默认会构建debug和release两个版本,很耗时,得十几个小时。其实可以执行python e:/sources/CEF/automate-git.py –help查看帮助,–no-debug-build选项可以不编译Debug版本,–no-release-build可以不编译Release版本,只编译一个版本,会快很多。 1. 编译过程中出错,再次编译时,可以指定 –no-update选项,跳过源码更新 1. CEF编译时链接的运行库为MT(多线程)。如果你要编译CEF或PPAPI示例,也要在“配置属性–>C/C++–>代码生成–>运行库”中设置为MT。 1. CEF编译时定义了UNICODE和_UNICODE宏。编译示例时最好也使用 Unicode 字符集。 相关文章参考: - [**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)
';

PPAPI插件与浏览器的交互过程

最后更新于:2022-04-01 16:02:49

[上一篇](http://blog.csdn.net/foruok/article/details/50486788)理解了一下PPAPI的设计,并从代码角度理解了一下相关主题,这篇文章关注下面几点: - 插件实例对象的创建与使用流程 - 实例大小的确认 - 渲染(绘图) - 处理输入事件 > foruok原创,如需转载请关注foruok的微信订阅号“程序视界”联系foruok。 # 插件实例对象的创建与使用流程 画了一个简单的调用流程图: ![pppapi_call_flow](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56caac332a630.jpg "") 结合stub.c或graphics_2d_example.c的代码,应该比较容易理解。 前两步我们在上一篇文章“[**理解PPAPI的设计**]()”里已经介绍了。接着往下说。 当PPP_GetInterface()被调用后,浏览器拿到了PPP_Instance接口,会调用PPP_Instance::DidCreate()来创建插件实例对象。一般我们在这个函数里会分配一些数据结构来保存新创建的实例。graphics_2d_example.c是这么实现DidCreate函数的: ~~~ 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; return PP_TRUE; } ~~~ 上面的代码,仅仅是malloc了struct InstanceInfo,做了初始化,保存插件实例对象的句柄,将新实例加入一个全局的实例对象链表。 一般的插件实例对象会对应网页所在视图(view)上的一个区域,这个区域会有坐标、大小,插件绘制的内容就显示在这个区域内,用户也通过这个区域和插件交互(比如鼠标、按键、触摸等)。这个区域的大小,是在插件实例对象与view关联起来时确定的。 当插件实例对象与view关联时,PPP_Instance::DidChangeView方法会被调用。下面是graphics_2d_example中的实现: ~~~ 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; } } ~~~ 上面的代码,先根据插件实例对象的句柄从全局链表找到插件实例对象,然后通过之前在PPP_InitializeModule()方法中获取到的PPP_View接口(g_view_interface)来获取由view所代表的视图(区域)的rect,再接着重绘,保存大小; 再接下来,就进入到了交互和渲染循环,直到Web页面被关闭时,PPP_Instance::DidDestroy被调用,再后来,PPP_ShutdownModule被调用。 # 渲染(绘图) 这里只说明PPAPI插件这一侧的渲染逻辑。实际上插件是在一块代表图像的内存上渲染,渲染完毕后通知浏览器,浏览器在合适的时候更新。 前面我们展示了graphics_2d_example中的DidChangeView函数实现。它在view尺寸变化时会调用Repaint方法来更新自己。Repaint方法里包含了基本的绘图逻辑,我们来分析一下。代码如下: ~~~ 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; for (i = 0; i < num_words; i++) image_data[i] = 0xFF0000FF; /* 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); } ~~~ 首先它会确保当前插件实例对象绑定了2D图形上下文。这是在MakeAndBindGraphics2D()函数内完成,代码如下: ~~~ 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; } ~~~ 可以看到它使用了g_graphics_2d_interface接口指针(类型是PPP_Graphics2D),这个接口在PPP_InitializeModule()中使用get_browser_interface获取,名字是PPP_GRAPHICS_2D_INTERFACE。 PPP_Graphics2D操作图形上下文(2D Graphics Context)的接口,它的Create方法可以创建一个2D Graphics Context。2D图形上下文与插件实例对象绑定后,插件实例对象就可以操作图形上下文,进行渲染。 图形上下文与插件实例对象的绑定是通过PPP_Instance接口的BindGraphics方法完成的(参见ppb_instance.h)。PPP_Instance接口在PPP_InitializeModule()中使用get_browser_interface获取,名字是PPP_INSTANCE_INTERFACE。 PPP_Graphics2D能操作2D图形上下文,通知浏览器更新。但是更新的图像数据,是放在别处的(一个由句柄标识的内存区域),不在PPP_Graphics2D内。保存在内存中的图形数据,可以通过PPP_ImageData接口来操作。这个接口,同样也是在PPP_InitializeModule()中使用get_browser_interface获取,名字是PPP_IMAGEDATA_INTERFACE。接口的具体定义在ppb_image_data.h文件内。 PPP_ImageData的Create方法可以根据指定格式和大小分配一块代表图形数据的内存资源,Map方法可以把图形数据资源映射到插件模块所在的进程内,返回内存起始地址,我们可以操作内存进行绘图操作。前面的Repaint方法就是这么干的。 Repaint方法绘图完成后,调用PPP_Graphics2D的ReplaceContents方法来更新上下文,调用Flush来刷新(不调用不生效哦)。 刷新是异步的,你可以指定一个回调接口(PP_CompletionCallback,见pp_completion_callback.h),当刷新完成后,PP_CompletionCallback的func成员会被调用。有一个方便的内联函数PP_MakeCompletionCallback可以帮你创建一个PP_CompletionCallback实例,它接受一个回调函数和一个void*作为user_data。 graphics_2d_example中的回调函数FlushCompletionCallback嘛事儿没干。我们在实际开发时,可以在回调完成后继续复用我们的分配的图形资源,这样可以提高效率。 好啦,这就是绘图的基本流程了。 # 处理输入事件 输入事件在ppb_input_event.h中定义。看文件名开头,就明白输入事件接口是由浏览器这端提供的。 输入事件的处理也分浏览器和插件两侧。 浏览器侧实现了PPP_InputEvent接口,定义接口名字的宏是PPP_INPUT_EVENT_INTERFACE。插件可以在PPP_InitializeModule函数中通过get_browser_interface函数指针按名字获取到。 PPP_InputEvent是入口,通过这个接口,插件实例对象可以通过RequestInputEvents和RequestFilteringInputEvents两个函数来声明自己感兴趣的输入事件(鼠标、键盘、触摸、滚轮、输入法等,见ppb_input_event.h)。 浏览器发现有插件实例对象关心某一类输入事件,就会根据这个插件模块导出的PPP_GetInterface函数来获取名为PPP_INPUT_EVENT_INTERFACE的接口。PPP_GetInterface应该返回PPP_InputEvent接口。 插件侧负责实现PPP_InputEvent接口(见ppp_input_event.h),这个接口(结构体)定义了一个HandleInputEvent方法。浏览器通过调用HandleInputEvent方法把事件传递给插件实例对象。 PPP_InputEvent的HandleInputEvent函数原型如下: ~~~ PP_Bool (*HandleInputEvent)(PP_Instance instance, PP_Resource input_event); ~~~ 第一个参数是插件实例对象句柄,第二个参数是输入事件句柄(32位整数)。使用PPP_InputEvent接口和输入事件句柄,可以判断事件类型、获取事件的其它信息。 但是某一类别的事件,有时需要专门的接口来获取特定的信息,比如鼠标事件对应的浏览器侧接口是PPP_MouseInputEvent,可以以PPP_MOUSE_INPUT_EVENT_INTERFACE为参数调用get_browser_interface函数指针获取。 PPP_MouseInputEvent定义了一些方法,比如GetButton可以获取是左键还是右键点击,GetPosition可以获取鼠标点击的坐标,这些方法允许我们获取完整的事件信息,根据事件信息做逻辑处理。 好啦,处理输入事件的流程就这样子了。 这次到这里,下篇我们结合绘图和交互,来完成一个简单的示例。 相关文章参考: - [**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的设计

最后更新于:2022-04-01 16:02:47

要理解PPAPI插件的设计,先仔细阅读下面这些文章: - [**Chromium的Process Models**](http://www.chromium.org/developers/design-documents/process-models) - [**Chromium的Multi-process Architecture**](http://www.chromium.org/developers/design-documents/multi-process-architecture) - [**Chromium的Plugin Architecture**](http://www.chromium.org/developers/design-documents/plugin-architecture) - [**Pepper plugin implementation**](http://www.chromium.org/developers/design-documents/pepper-plugin-implementation) 理解了架构设计,再看代码层面的文档: - [**Important concepts for working with PPAPI**](https://code.google.com/p/ppapi/wiki/Concepts) - [**GettingStarted, Writing a simple plugin**](https://code.google.com/p/ppapi/wiki/GettingStarted) 有的链接需要翻墙,天朝的局域网,我爱死你了。 好啦,现在对PPAPI应该有基本的理解了。接下来我从代码角度来理解一下。 # Module、Instance、Interface HTML页面可以通过embed标签来嵌入一个插件,HTML页面被加载时,解析到embed标签,就会根据type属性定位我们注册的PPAPI插件,加载对应的插件库(DLL)。 当PPAPI的库文件(DLL)被加载到浏览器进程中时,一个Module就产生了。在代码中,通过PP_Module(定义在pp_module.h中)来表示,用于标识一个Module的PP_Module类型实际上是一个int32。Module的标识符通过PPP_InitializeModule函数传入。PPP_InitializeModule函数的原型如下: ~~~ int32_t PPP_InitializeModule(PP_Module module, PPB_GetInterface get_browser_interface) ; ~~~ 第一个参数就是浏览器分配给你的插件库文件的标识符(handle),一般你需要保存它,后续的有些API会用到。 所以,一个Module,仅仅标识了library。要想在浏览器上显示点什么,还需要从这个Module里创建一个实例,这个实例代表了我们可以看见并与之交互的网页对象。一个插件实例对象又有两个层面的属性,一个就是标示符,通过PP_Instance(32位整型)来表示;另外一个是用来操作实例对象的接口,用PPP_Instance表示(聚合了各种函数指针的结构体)。 PPAPI的plugin会导出PPP_GetInterface函数,其原型如下: ~~~ const void* PPP_GetInterface(const char* interface_name); ~~~ 当浏览器要为HTML页面创建插件实例时,会先调用PPP_GetInterface函数获取一个实例模板指针(可以理解为PPP_Instance的实例,类似一个类,实际上是一个定义了函数指针成员的结构体)。 PPP_GetInterface函数接受一个字符串名字,返回void*,浏览器拿到万能的void*后会根据名字转换为具体的PPP_instance接口,在后续使用中就通过PPP_instance接口来与插件实例交互(比如具体的创建、销毁等动作)。 简单的理解,就是PPP_GetInterface会返回能创建Instance的接口PPP_Instance,浏览器调用PPP_Instance来创建实例并与实例交互。 PPP_instance接口声明如下: ~~~ struct PPP_Instance_1_1 { PP_Bool (*DidCreate)(PP_Instance instance, uint32_t argc, const char* argn[], const char* argv[]); void (*DidDestroy)(PP_Instance instance); void (*DidChangeView)(PP_Instance instance, PP_Resource view); void (*DidChangeFocus)(PP_Instance instance, PP_Bool has_focus); PP_Bool (*HandleDocumentLoad)(PP_Instance instance, PP_Resource url_loader); }; ~~~ 如你所见,它就像一个类,DidCreate是构造函数指针,DidDestroy是析构函数指针。创建插件实例对象时,DidCreate会被调用,其第一个参数instance,就是浏览器分配给这个插件实例对象的句柄(32位整数),通常我们可以保存起来供后续的调用使用。具体的说明,可以看ppp_instance.h。 之前在[**VS2013编译最简单的PPAPI插件**](http://blog.csdn.net/foruok/article/details/50485461)中我们编译了stub插件,它的PPP_GetInterface函数返回NULL,所以,其实浏览器可以加载stub库文件,生成Module,但无法创建Instance。 要想实作一个有用的PPAPI plugin,必须在PPP_GetInterface中返回真实的PPP_instance接口。下面是graphics_2d_example.c里的PPP_GetInterface函数实现: ~~~ PP_EXPORT const void* PPP_GetInterface(const char* interface_name) { if (strcmp(interface_name, PPP_INSTANCE_INTERFACE) == 0) return &instance_interface; return NULL; } ~~~ 它返回的instance_interface,是这么定义的: ~~~ static PPP_Instance instance_interface = { &Instance_DidCreate, &Instance_DidDestroy, &Instance_DidChangeView, &Instance_DidChangeFocus, &Instance_HandleDocumentLoad }; ~~~ 如你所见,它是一个PPP_Instance,在定义时进行了初始化,把文件内实现的几个函数,赋值给了结构体的5个函数指针。 # 浏览器接口PPB_GetInterface PPAPI插件要与浏览器交互,也得先有渠道来获取浏览器的功能接口。浏览器提供了很多功能接口,比如PPB_INSTANCE_INTERFACE,PPB_IMAGEDATA_INTERFACE,PPB_GRAPHICS_2D_INTERFACE等。 这些宏都是字符串,浏览器提供的接口名字宏,以PPB_开头,插件提供的,以PPP_开头。 插件被加载时,模块初始化函数PPP_InitializeModule会被调用,其原型如下: ~~~ int32_t PPP_InitializeModule(PP_Module module_id, PPB_GetInterface get_browser_interface); ~~~ 注意第二个参数,get_browser_interface,它的类型是PPB_GetInterface,是一个函数指针,定义如下: ~~~ typedef const void* (*PPB_GetInterface)(const char* interface_name); ~~~ 如你所见,这是一个接受一个字符串参数返回void*的函数指针。插件可以在PPP_InitializeModule被调用时保存第一个参数,用它来获取浏览器提供的各种接口。根据接口名字,把返回的void*强制转换为对应的接口来使用。参看graphics_2d_example.c里的实现: ~~~ PP_EXPORT int32_t PPP_InitializeModule(PP_Module module, PPB_GetInterface get_browser_interface) { 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); if (!g_core_interface || !g_instance_interface || g_image_data_interface || !g_graphics_2d_interface || !g_view_interface) return -1; return PP_OK; } ~~~ 当我们拿到了浏览器暴露的各种接口,就可以做想干的事情了。 对于PPAPI插件的设计,先理解到这里,下次我们看插件的加载与使用流程、如何绘图、如何处理交互。 相关文章参考: - [**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)
';

VS2013编译最简单的PPAPI插件

最后更新于:2022-04-01 16:02:44

我想在CEF里使用PPAPI,CEF使用VS 2013 Update 4编译。因此我尝试了使用VS 2013来编译PPAPI插件。 PPAPI的代码在这里:[https://chromium.googlesource.com/chromium/src/ppapi/](https://chromium.googlesource.com/chromium/src/ppapi/),可以用下列命令check出来: ~~~ git clone https://chromium.googlesource.com/chromium/src/ppapi ~~~ 也可以下载master分支的tgz包。 # VS工程 新建一个Win32项目,类型选DLL,去掉预编译头文件stdafx.h和stdafx.cpp,并且在项目属性–>配置属性–>C/C++–>预编译头,把预编译头选项的值设置为不使用预编译头。 复制ppapi/examples/stub/stub.c文件到项目文件夹下,并添加到项目里。做简单修改,打印点儿调试信息。stub.c内容如下: ~~~ // Copyright (c) 2010 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. // This is the simplest possible C Pepper plugin that does nothing. If you're // using C++, you will want to look at stub.cc which uses the more convenient // C++ wrappers. #include <stddef.h> #include <stdint.h> #include <Windows.h> #include <tchar.h> #include "ppapi/c/pp_errors.h" #include "ppapi/c/pp_module.h" #include "ppapi/c/ppb.h" #include "ppapi/c/ppp.h" #include "ppapi/c/pp_instance.h" #include "ppapi/c/ppp_instance.h" PP_Module g_module_id; PPB_GetInterface g_get_browser_interface = NULL; PP_EXPORT int32_t PPP_InitializeModule(PP_Module module_id, PPB_GetInterface get_browser_interface) { // Save the global module information for later. g_module_id = module_id; g_get_browser_interface = get_browser_interface; OutputDebugString(_T("PPP_InitializeModule was called\r\n")); return PP_OK; } PP_EXPORT void PPP_ShutdownModule() { OutputDebugString(_T("PPP_ShutdownModule was called\r\n")); } PP_EXPORT const void* PPP_GetInterface(const char* interface_name) { // You will normally implement a getter for at least PPP_INSTANCE_INTERFACE // here. OutputDebugString(_T("PPP_GetInterface was called\r\n")); return NULL; } ~~~ # PPAPI plugin 参考[https://code.google.com/p/ppapi/wiki/GettingStarted](https://code.google.com/p/ppapi/wiki/GettingStarted),C语言版的PPAPI plugin,必须实现下列函数: - PPP_InitializeModule,插件加载时会被调用,返回0表示成功 - PPP_ShutdownModule,插件卸载时会被调用 - PPP_GetInterface,浏览器创建插件实例时会被调用 这些函数在ppp.h中定义。实现这些函数时,使用PP_EXPORT宏修饰一下即可。 一个DLL,实现了上述三个函数,就可以做为PPAPI插件来用了,不过只是样子货,只能看到被加载、创建,干不了什么实际的事儿,是个PPAPI 版本的Hello World。 后面我们会改造stub,显示点东西出来。 相关文章参考: - [**CEF Windows开发环境搭建**](http://blog.csdn.net/foruok/article/details/50468642) - [**CEF加载PPAPI插件**](http://blog.csdn.net/foruok/article/details/50485448)
';

CEF加载PPAPI插件

最后更新于:2022-04-01 16:02:42

CEF基于Chromium和Webkit而来,支持PPAPI和NaCI。 CEF3的binary包默认已经支持PPAPI(参考[http://magpcss.org/ceforum/viewtopic.php?f=10&t=10509](http://magpcss.org/ceforum/viewtopic.php?f=10&t=10509)),以cefsimple为例(参考[**CEF Windows开发环境搭建**](http://blog.csdn.net/foruok/article/details/50468642)),可以通过命令行参数来注册PPAPI plugin,通过–url参数传递一个加载对应plugin的html页面。 下面是我测试可用的一个命令行参数 ~~~ --ppapi-out-of-process --register-pepper-plugins="D:\projects\cef_binary_3.2357.1271.g8e0674e_windows32\Release\stub.dll;application/x-ppapi-stub" --url=file:///d:/projects/cef_binary_3.2357.1271.g8e0674e_windows32/Release/stub.html ~~~ stub.html非常简单,代码如下: ~~~ <!DOCTYPE html> <html> <head> <title>stub</title> </head> <body> <embed id="plugin" type="application/x-ppapi-stub"> </body> </html> ~~~ 其中stub.dll是我编译的PPAPI SDK里的示例,做了些许改动。stub.c代码如下: ~~~ // Copyright (c) 2010 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. // This is the simplest possible C Pepper plugin that does nothing. If you're // using C++, you will want to look at stub.cc which uses the more convenient // C++ wrappers. #include <stddef.h> #include <stdint.h> #include <Windows.h> #include <tchar.h> #include "ppapi/c/pp_errors.h" #include "ppapi/c/pp_module.h" #include "ppapi/c/ppb.h" #include "ppapi/c/ppp.h" #include "ppapi/c/pp_instance.h" #include "ppapi/c/ppp_instance.h" PP_Module g_module_id; PPB_GetInterface g_get_browser_interface = NULL; PP_EXPORT int32_t PPP_InitializeModule(PP_Module module_id, PPB_GetInterface get_browser_interface) { // Save the global module information for later. g_module_id = module_id; g_get_browser_interface = get_browser_interface; OutputDebugString(_T("PPP_InitializeModule was called\r\n")); return PP_OK; } PP_EXPORT void PPP_ShutdownModule() { OutputDebugString(_T("PPP_ShutdownModule was called\r\n")); } PP_EXPORT const void* PPP_GetInterface(const char* interface_name) { // You will normally implement a getter for at least PPP_INSTANCE_INTERFACE // here. return NULL; } ~~~ 如你所见,我只是使用OutputDebugString函数输出了调试信息。运行cefsimple,使用DbgView工具可以看到我们输出的信息。 关于PPAPI插件的细节,后面会有一些文章来讲。 相关文章参考: - [**CEF Windows开发环境搭建**](http://blog.csdn.net/foruok/article/details/50468642)
';

CEF Windows开发环境搭建

最后更新于:2022-04-01 16:02:40

CEF,Chromium Embedded Framework的缩写。CEF 的官网介绍的很简洁:A simple framework for embedding chromium browser windows in other applications。具体地说就是一个可以将浏览器功能(页面渲染,JS 执行)嵌入到其他应用程序的框架。感兴趣的看这里:[https://bitbucket.org/chromiumembedded/cef](https://bitbucket.org/chromiumembedded/cef)。 用CEF的好处是开发Web和Native混合的应用非常方便,比调用Windows的ie组件要好得多,最起码可以随应用携带一个稳定版本的Chrominum,而ie版本太多,你的应用很依赖用户的操作系统,各种兼容性的坑…… 花了大半天时间,终于跑通了cefsimple这个小示例。记录下过程。 # 工具 我的搭配: - VS 2013 - [CMake 3.4.1](https://cmake.org/download/) - CEF 3.2357.1271([CSDN下载频道的binary包](http://download.csdn.net/detail/zhang116868/8750429)) 我把CEF Binary for windows解压到了这个目录:D:\projects\cef_binary_3.2357.1271.g8e0674e_windows32,下文我们用CEFDIR来代替它。 现在的CEF Binary里没有VS项目和解决方案了,得自己生成。 # 环境配置与编译过程 cefsimple示例依赖libcef_dll_wrapper,需要先编译libcef_dll这个项目。 ### 编译libcef_dll 1. 到[https://cmake.org/download/](https://cmake.org/download/)下载CMake 3.4.1(cmake-3.4.1-win32-x86.zip) 1. 解压到某个目录,我这里是E:\software\cmake-3.4.1-win32-x86,后面有用 your-cmake-dir指代。打开命令行(cmd.exe),执行“set path=your-cmake-dir/bin;%path%”命令。 1. 切换到CEFDIR/libcef_dll目录,执行“CMake .”命令,生成VS 2013的项目文件和solution文件 1. 使用VS 2013打开CEFDIR/libcef_dll目录下的Project.sln,选中libcef_dll_wrapper这个项目,打开项目属性,设置C++附加包含目录,把CEFDIR目录添进去 1. 选中libcef_dll_wrapper,鼠标右键,属性–>配置属性–>C/C++–>预处理器定义,编辑,新增一个NOMINMAX宏。这是为了解决std::min/std::max和VC全局定义的min/max宏冲突。libcef_dll里有些cc文件内用了std::min/max模板函数,不添加NOMINMAX,可能出现C2589和C2059错误 1. Debug版本,运行库选择“多线程调试(MTd)”;Release版本选择MT。 1. 编译,生成libcef_dll_wrapper.lib文件 1. 把libcef_dll_wrapper.lib放到CEFDIR/Release(或Debug)目录下 我编译中遇到了一些错误,找到了解决办法,已经包含在上面的步骤里了。 ### 编译cefsimple示例 有了libcef_dll_wrapper,就可以编译cefsimple了。 我本来想用CMake生成,结果折腾半天,CMake执行过了,却没生成对应的cefsimple.vcxproj……只好手动创建了一个VS项目。 过程如下: 1. 自己创建一个cefsimple项目,Win32类型,空项目 1. 将cefsimple_win.cc,simple_app.cc,simple_handler.cc,simple_handler_win.cc作为源文件添加到项目里 1. 将resource.h,simple_app.h,simple_handler.h作为头文件添加到项目里 1. 设置附加包含目录,把CEFDIR目录加进去 1. 设置附加库目录,CEFDIR/Release(或Debug) 1. 添加附加依赖项,把libcef_dll_wrapper.lib、libcef.lib、DbgHelp.lib加进去 1. simple_app.cc中的www.google.com替换为www.baidu.com(Google打不开啊……) 1. Debug版本,运行库选择“多线程调试(MTd)”;Release版本选择MT。注意这里的选择,和libcef_dll要一致,不然会出链接错误 1. 把工作目录设置为CEFDIR/Release(或Debug) 1. 编译运行 运行时可能会Crash,把CEFDIR/Resources目录下的内容拷贝到CEF/Release(或Debug)下即可。CEF需要里面的icudtl.dat、语言文件等。 运行效果如下图: ![cefsimple效果图](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56caac2b80dff.jpg "") CEFDIR目录下有个README.txt文件,一定要看,我编译通过后运行总crash,在VS一点一点Debug、看程序输出,花了老长时间才发现时Resources的问题! Ok,环境就这样好了,接下来可以研究CEF的SDK了。
';

前言

最后更新于:2022-04-01 16:02:37

> 原文出处:[CEF与PPAPI开发](http://blog.csdn.net/column/details/cef-ppapi.html) 作者:[安晓辉](http://blog.csdn.net/foruok) **本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!** # CEF与PPAPI开发 > 从零开始,记录CEF(The Chromium Embedded Framework )和PPAPI插件的学习与开发过程,为使用CEF和PPAPI的同仁们提供参考。
';