上一篇 CEF:C++ 调用 JavaScript 函数 Demo(VS2013)讲了外部 C++ 代码如何调用网页内 JavaScript 函数的问题,这一篇换个方向,说一说网页内 JavaScript 如何调用外部 C++ 函数。
创建 CEF 项目
参照 CEF:MFC 对话框 Demo(VS2013) 编写一个 MFC 对话框程序。注意:最后一步“添加网址输入框”不需要做。
添加 CefV8Handler 派生类
在“解决方案资源管理器”里,在项目名称“CefMfcDemo”上点击鼠标右键,然后在弹出菜单上选择“添加(D)->类(C)…”项,打开“添加类”窗口,在左侧选择“已安装->Visual C++ ->C++”,再选择中间的“C++ 类”,然后点击 [添加(A)] 按钮,进入“一般 C++ 类向导”窗口。在“类名(L)”里输入 CSimpleV8Handler,然后点击 [完成] 按钮,生成 CSimpleV8Handler 类。
添加 CefApp 派生类
在“解决方案资源管理器”里,在项目名称“CefMfcDemo”上点击鼠标右键,然后在弹出菜单上选择“添加(D)->类(C)…”项,打开“添加类”窗口,在左侧选择“已安装->Visual C++ ->C++”,再选择中间的“C++ 类”,然后点击 [添加(A)] 按钮,进入“一般 C++ 类向导”窗口。在“类名(L)”里输入 CSimpleApp,然后点击 [完成] 按钮,生成 CSimpleApp 类。
修改 SimpleApp.h 文件内容如下:
#pragma once
#include "include/cef_app.h"
class CSimpleApp :public CefApp, public CefRenderProcessHandler
{
public:
CSimpleApp();
~CSimpleApp();
virtual CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler()
OVERRIDE{ return this; }
virtual void OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context) OVERRIDE;
private:
// Include the default reference counting implementation.
IMPLEMENT_REFCOUNTING(CSimpleApp);
};
在 SimpleApp.cpp 文件里添加如下函数:
void CSimpleApp::OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context)
{
// The var type can accept all object or variable
CefRefPtr<CefV8Value> pV8 = context->GetGlobal();
// bind function
CefRefPtr<CSimpleV8Handler> pJsHandler(new CSimpleV8Handler());
CefRefPtr<CefV8Value> pFunc = CefV8Value::CreateFunction(CefString("ShowJsInfo"), pJsHandler);
pV8->SetValue(CefString("ShowJsInfo"), pFunc, V8_PROPERTY_ATTRIBUTE_NONE);
}
并包含如下文件:
#include "SimpleV8Handler.h"
这里向 CEF 注册了一个名为 ShowJsInfo
的函数,并同时指明会在 CSimpleV8Handler 里接收这个函数的调用请求。稍后会在 Web 页面里调用这个函数。
处理函数调用请求
修改 SimpleV8Handler.h 文件内容如下:
#pragma once
#include "include/cef_v8.h"
class CSimpleV8Handler : public CefV8Handler
{
public:
CSimpleV8Handler();
~CSimpleV8Handler();
public:
virtual bool Execute(const CefString& name, CefRefPtr<CefV8Value> object, const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval, CefString& exception) OVERRIDE;
private:
IMPLEMENT_REFCOUNTING(CSimpleV8Handler);
};
在 SimpleV8Handler.cpp 文件里添加如下函数:
bool CSimpleV8Handler::Execute(const CefString& name, CefRefPtr<CefV8Value> object, const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval, CefString& exception)
{
if (name == CefString("ShowJsInfo"))
{
if (arguments.size() == 1 && arguments[0]->IsString())
{
CefString arg = arguments[0]->GetStringValue();
std::wstring strInfo(L"这是 JavaScript 发来的数据:");
strInfo += arg.ToWString();
::MessageBox(nullptr, strInfo.c_str(), L"C++ 提示框", MB_OK);
return true;
}
}
return false;
}
添加测试网页
点击菜单 [文件(F)->新建(N)->文件(F)…],打开“新建文件”对话框,在左侧选择 [已安装->Web],然后在中间选中“HTML 页”:
点击对话框右下角的 [打开(O)] 按钮,添加 HTML 页面文件。然后会自动打开一个名为 HtmlPage1.html 的文件,如下图:
修改文件内容如下:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>JavaScript 调用 C++ 函数</title>
<script>
function CallCppFunc() { //该函数名不能与 window 对象挂接的 C++ 函数名相同
var info = document.getElementById("txtInfo").value;
if (info == "") {
alert("请输入要发送给 C++ 的数据!");
return;
}
window.ShowJsInfo(info); //C++ 函数,挂接在 window 对象上
}
</script>
</head>
<body>
<input id="txtInfo" type="text" />
<button id="btnCallCppFunc" onclick="CallCppFunc();">调用 C++ 函数</button>
</body>
</html>
注意,这里的 JavaScript 函数名与刚才在 C++ 里注册的函数名不能相同。
修改完毕后,点击工具栏中的“保存”按钮,此时会打开“另存文件为”对话框,保持默认文件名不变,将文件保存到 D:\CefMfcDemo3 目录下。
应用 CSimpleApp
打开 CefMfcDemoDlg.cpp 文件,在顶部添加 #include "SimpleApp.h"
。
找到 OnInitDialog() 函数,将 CefRefPtr<CefApp> cefApp;
行替换为 CefRefPtr<CSimpleApp> cefApp(new CSimpleApp());
。
设置默认打开的网页
在 CefMfcDemoDlg.cpp 文件里,找到 OnInitDialog() 函数。在 CefBrowserSettings browserSettings;
行上面添加如下代码:
CString strPath;
GetModuleFileName(NULL, strPath.GetBufferSetLength(MAX_PATH + 1), MAX_PATH);
strPath.ReleaseBuffer();
int pos = strPath.ReverseFind('\\');
strPath = strPath.Left(pos);
CString strUrl = _T("file://") + strPath + _T("/HtmlPage1.html");
然后再将 CefBrowserHost::CreateBrowser(winInfo, client, _T("http://www.baidu.com"), browserSettings, NULL);
修改为 CefBrowserHost::CreateBrowser(winInfo, client, CefString(strUrl), browserSettings, NULL);
。
编译运行程序
运行程序之前,先把刚才添加的网页从 D:\CefMfcDemo3\HtmlPage1.html
复制到 D:\CefMfcDemo3\Debug
和 D:\CefMfcDemo3\Release
目录下面。
点击菜单 [调试(D)->开始执行(不调试)(H)],编译并运行程序,运行结果如下图:
注:提示框可能不会自动弹到窗口前面来,会显示在窗口后面,这是由于 CEF 是多进程的,浏览器窗口与弹出的提示框处于两个不同的进程,需要点一下任务栏上的“C++ 提示框”才能显示到前面来。
第二部分
到上面这一步为止,其实已经实现了 Web 页面与 C++ 代码的通信,但假如想把 Web 页面发来的信息显示到界面上,应该怎么办呢?刚才说了,CEF 是多进程的,接收 Web 页面信息的代码与 MFC 窗口处于不同的进程,要想把接收到的信息显示到界面上,只能通过进程间通信的办法。
下面使用命名管道来实现这个功能。
添加信息显示框
点击菜单 [视图(V)->其他窗口(E)->资源视图(R)],打开“资源视图”窗口,依次展开“CefMfcDemo->CefMfcDemo.rc->Dialog”节点,双击 IDD_CEFMFCDEMO_DIALOG,打开对话框界面编辑窗口。
在对话框顶部添加一个 Edit Control,修改 Edit Control 的 ID 为 IDC_EDIT_INFO,完成后的界面如下图:
添加 CNamedPipeServer 类
在“解决方案资源管理器”里,在项目名称“CefMfcDemo”上点击鼠标右键,然后在弹出菜单上选择“添加(D)->类(C)…”项,打开“添加类”窗口,在左侧选择“已安装->Visual C++ ->C++”,再选择中间的“C++ 类”,然后点击 [添加(A)] 按钮,进入“一般 C++ 类向导”窗口。在“类名(L)”里输入 CNamedPipeServer,然后点击 [完成] 按钮,生成 CNamedPipeServer 类。
修改 NamedPipeServer.h 文件内容如下:
#pragma once
#include <afxwin.h>
#include <tchar.h>
#include <stdio.h>
#include <wtypes.h>
class IPipeObserver
{
public:
virtual void OnData(LPCTSTR strData) = 0;
};
class CNamedPipeServer
{
public:
CNamedPipeServer();
~CNamedPipeServer();
bool OpenPipe();
void AttachObserver(IPipeObserver* pObserver) { m_pObserver = pObserver; };
private:
static IPipeObserver* m_pObserver;
static DWORD WINAPI ConnectionThread(LPVOID lpvParam);
static DWORD WINAPI InstanceThread(LPVOID lpvParam);
};
打开 NamedPipeServer.cpp 文件,在里面添加如下三个函数:
bool CNamedPipeServer::OpenPipe()
{
DWORD dwThreadId = 0;
HANDLE hThread = NULL;
hThread = CreateThread(
NULL, // no security attribute
0, // default stack size
ConnectionThread, // thread proc
(LPVOID)this, // thread parameter
0, // not suspended
&dwThreadId); // returns thread ID
if (hThread == NULL)
{
TRACE(TEXT("CreateThread failed, GLE=%d.\n"), GetLastError());
return false;
}
return true;
}
DWORD WINAPI CNamedPipeServer::ConnectionThread(LPVOID lpvParam)
{
BOOL fConnected = FALSE;
DWORD dwThreadId = 0;
HANDLE hPipe = INVALID_HANDLE_VALUE, hThread = NULL;
LPTSTR lpszPipename = TEXT("\\\\.\\pipe\\cefnamedpipe");
// The main loop creates an instance of the named pipe and
// then waits for a client to connect to it. When the client
// connects, a thread is created to handle communications
// with that client, and this loop is free to wait for the
// next client connect request. It is an infinite loop.
for (;;)
{
TRACE(TEXT("\nPipe Server: Main thread awaiting client connection on %s\n"), lpszPipename);
hPipe = CreateNamedPipe(
lpszPipename, // pipe name
PIPE_ACCESS_DUPLEX, // read/write access
PIPE_TYPE_MESSAGE | // message type pipe
PIPE_READMODE_MESSAGE | // message-read mode
PIPE_WAIT, // blocking mode
PIPE_UNLIMITED_INSTANCES, // max. instances
BUFSIZE, // output buffer size
BUFSIZE, // input buffer size
0, // client time-out
NULL); // default security attribute
if (hPipe == INVALID_HANDLE_VALUE)
{
TRACE(TEXT("CreateNamedPipe failed, GLE=%d.\n"), GetLastError());
return -1;
}
// Wait for the client to connect; if it succeeds,
// the function returns a nonzero value. If the function
// returns zero, GetLastError returns ERROR_PIPE_CONNECTED.
fConnected = ConnectNamedPipe(hPipe, NULL) ?
TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
if (fConnected)
{
TRACE("Client connected, creating a processing thread.\n");
// Create a thread for this client.
hThread = CreateThread(
NULL, // no security attribute
0, // default stack size
InstanceThread, // thread proc
(LPVOID)hPipe, // thread parameter
0, // not suspended
&dwThreadId); // returns thread ID
if (hThread == NULL)
{
TRACE(TEXT("CreateThread failed, GLE=%d.\n"), GetLastError());
return -1;
}
else CloseHandle(hThread);
}
else
// The client could not connect, so close the pipe.
CloseHandle(hPipe);
}
return 0;
}
DWORD WINAPI CNamedPipeServer::InstanceThread(LPVOID lpvParam)
// This routine is a thread processing function to read from and reply to a client
// via the open pipe connection passed from the main loop. Note this allows
// the main loop to continue executing, potentially creating more threads of
// of this procedure to run concurrently, depending on the number of incoming
// client connections.
{
HANDLE hHeap = GetProcessHeap();
TCHAR* pchRequest = (TCHAR*)HeapAlloc(hHeap, 0, BUFSIZE * sizeof(TCHAR));
TCHAR* pchReply = (TCHAR*)HeapAlloc(hHeap, 0, BUFSIZE * sizeof(TCHAR));
DWORD cbBytesRead = 0, cbReplyBytes = 0, cbWritten = 0;
BOOL fSuccess = FALSE;
HANDLE hPipe = NULL;
// Do some extra error checking since the app will keep running even if this
// thread fails.
if (lpvParam == NULL)
{
TRACE("\nERROR - Pipe Server Failure:\n");
TRACE(" InstanceThread got an unexpected NULL value in lpvParam.\n");
TRACE(" InstanceThread exitting.\n");
if (pchReply != NULL) HeapFree(hHeap, 0, pchReply);
if (pchRequest != NULL) HeapFree(hHeap, 0, pchRequest);
return (DWORD)-1;
}
if (pchRequest == NULL)
{
TRACE("\nERROR - Pipe Server Failure:\n");
TRACE(" InstanceThread got an unexpected NULL heap allocation.\n");
TRACE(" InstanceThread exitting.\n");
if (pchReply != NULL) HeapFree(hHeap, 0, pchReply);
return (DWORD)-1;
}
if (pchReply == NULL)
{
TRACE("\nERROR - Pipe Server Failure:\n");
TRACE(" InstanceThread got an unexpected NULL heap allocation.\n");
TRACE(" InstanceThread exitting.\n");
if (pchRequest != NULL) HeapFree(hHeap, 0, pchRequest);
return (DWORD)-1;
}
// Print verbose messages. In production code, this should be for debugging only.
TRACE("InstanceThread created, receiving and processing messages.\n");
// The thread's parameter is a handle to a pipe object instance.
hPipe = (HANDLE)lpvParam;
// Loop until done reading
while (1)
{
// Read client requests from the pipe. This simplistic code only allows messages
// up to BUFSIZE characters in length.
fSuccess = ReadFile(
hPipe, // handle to pipe
pchRequest, // buffer to receive data
BUFSIZE * sizeof(TCHAR), // size of buffer
&cbBytesRead, // number of bytes read
NULL); // not overlapped I/O
if (!fSuccess || cbBytesRead == 0)
{
if (GetLastError() == ERROR_BROKEN_PIPE)
{
TRACE(TEXT("InstanceThread: client disconnected.\n"));
}
else
{
TRACE(TEXT("InstanceThread ReadFile failed, GLE=%d.\n"), GetLastError());
}
break;
}
TRACE(TEXT("Client Request String:\"%s\"\n"), pchRequest);
// Process the incoming message.
if (m_pObserver != nullptr)
{
m_pObserver->OnData(pchRequest);
}
}
// Flush the pipe to allow the client to read the pipe's contents
// before disconnecting. Then disconnect the pipe, and close the
// handle to this pipe instance.
FlushFileBuffers(hPipe);
DisconnectNamedPipe(hPipe);
CloseHandle(hPipe);
HeapFree(hHeap, 0, pchRequest);
HeapFree(hHeap, 0, pchReply);
TRACE("InstanceThread exitting.\n");
return 1;
}
实现 IPipeObserver 接口
打开 CefMfcDemoDlg.h 文件,在顶部添加 #include "NamedPipeServer.h"
,然后为 CCefMfcDemoDlg 类添加 IPipeObserver
基类。
再为 CCefMfcDemoDlg 类添加下面两个成员:
CNamedPipeServer m_PipeServer;
virtual void OnData(LPCTSTR strData);
然后打开 CefMfcDemoDlg.cpp 文件,在 OnInitDialog() 函数的最后添加下面两行:
m_PipeServer.AttachObserver(this);
m_PipeServer.OpenPipe();
再在文件底部添加 OnData() 函数的定义:
void CCefMfcDemoDlg::OnData(LPCTSTR strData)
{
this->SetDlgItemText(IDC_EDIT_INFO, strData);
CString strInfo(_T("这是 JavaScript 发来的数据:"));
strInfo += strData;
MessageBox(strInfo);
}
修改 CSimpleV8Handler 类
打开 SimpleV8Handler.h 文件,添加如下函数声明:
void SendData(std::wstring strData);
再打开 SimpleV8Handler.cpp 文件,添加如下函数定义:
void CSimpleV8Handler::SendData(std::wstring strData)
{
HANDLE hPipe;
LPCTSTR lpvMessage = TEXT("Default message from client.");
BOOL fSuccess = FALSE;
DWORD cbToWrite, cbWritten, dwMode;
LPTSTR lpszPipename = TEXT("\\\\.\\pipe\\cefnamedpipe");
lpvMessage = strData.c_str();
// Try to open a named pipe; wait for it, if necessary.
while (1)
{
hPipe = CreateFile(
lpszPipename, // pipe name
GENERIC_READ | // read and write access
GENERIC_WRITE,
0, // no sharing
NULL, // default security attributes
OPEN_EXISTING, // opens existing pipe
0, // default attributes
NULL); // no template file
// Break if the pipe handle is valid.
if (hPipe != INVALID_HANDLE_VALUE)
break;
// Exit if an error other than ERROR_PIPE_BUSY occurs.
if (GetLastError() != ERROR_PIPE_BUSY)
{
return;
}
// All pipe instances are busy, so wait for 20 seconds.
if (!WaitNamedPipe(lpszPipename, 20000))
{
printf("Could not open pipe: 20 second wait timed out.");
return;
}
}
// The pipe connected; change to message-read mode.
dwMode = PIPE_READMODE_MESSAGE;
fSuccess = SetNamedPipeHandleState(
hPipe, // pipe handle
&dwMode, // new pipe mode
NULL, // don't set maximum bytes
NULL); // don't set maximum time
if (!fSuccess)
{
return;
}
// Send a message to the pipe server.
cbToWrite = (lstrlen(lpvMessage) + 1) * sizeof(TCHAR);
fSuccess = WriteFile(
hPipe, // pipe handle
lpvMessage, // message
cbToWrite, // message length
&cbWritten, // bytes written
NULL); // not overlapped
if (!fSuccess)
{
return;
}
CloseHandle(hPipe);
}
最后修改 Execute() 函数如下:
bool CSimpleV8Handler::Execute(const CefString& name, CefRefPtr<CefV8Value> object, const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval, CefString& exception)
{
if (name == CefString("ShowJsInfo"))
{
if (arguments.size() == 1 && arguments[0]->IsString())
{
std::wstring wstrData;
wstrData = arguments[0]->GetStringValue().ToWString();
SendData(wstrData);
return true;
}
}
return false;
}
重新编译运行程序
点击菜单 [调试(D)->开始执行(不调试)(H)],编译并运行程序,运行结果如下图:
源码下载:http://download.csdn.net/download/blackwoodcliff/10171521