CEF:JavaScript 调用 C++ 函数 Demo(VS2013)

  上一篇 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\DebugD:\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

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值