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" /> User:
<input type="text" id="userName" /> <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" /> <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的同仁们提供参考。