由于网站开启HTTPS导致客户端使用Windows的WebBrowser控件在XP系统下无法显示页面,查阅了浏览器内核相关资料后选择了开源小巧的Miniblink(官方主页https://weolar.github.io/miniblink)。

基于Miniblink封装了一个WebBrowser控件CXMBWebCtrl,基于控件CXMBWebCtrl写了一个简单的浏览器Demo,Demo代码下载地址:https://download.csdn.net/download/werocpp/11127189。

使用过程中遇到一些问题,记录如下:

  1. 作为MFC对话框子控件使用时接收不到键盘消息,这个只需要在控件的窗口处理过程函数中处理消息WM_GETDLGCODE即可;
  2. 超链接在新窗口中打开事件中有些URL获取不到,对于这种情况只能在本进程内打开此页面;
  3. 网页超链接启动本地应用程序,需要自己在回调函数中实现;
  4. 网页超链接下载文件需要自己实现,这里没有实现;
  5. cookies及localstoreage最好使用进程ID进行拼接存放路径,这里代码没有进行修改;
  6. wkeInitialize只需要调用一次,wkeFinalize不需要调用,这里代码没有进行修改;

控件类CXMBWebCtrl头文件如下:


/********************************************************************
    created: 17:4:2019  13:14
    author: zhangweifang
    
    purpose: Miniblink封装Web控件
	homepage: http://www.keepthink.cn/
********************************************************************/
#ifndef _XMB_WEB_CTRL_H_FOXWQ_
#define _XMB_WEB_CTRL_H_FOXWQ_
//////////////////////////////////////////////////////////////////////////
#include <Windows.h>
#include <string>
using namespace std;
//////////////////////////////////////////////////////////////////////////
// 定义回调函数类型,用户自定义处理新窗口浏览URL
// 页面在新窗口中打开,有的可以获取到URL,有的获取不到URL
// 可获取到URL的可以任意处理,比如在新的进程中打开
// 获取不到URL的只能在本进程内打开,页面可以在新的CXMBWebCtrl中打开,需要给出一个CXMBWebCtrl地址
// 返回TRUE表示自定义处理了新窗口打开事件,FALSE表示未处理将使用默认方式处理
class CXMBWebCtrl;
typedef CXMBWebCtrl* PCXMBWebCtrl;
typedef BOOL (CALLBACK* XMBWebCtrl_CustomNewWindowNavigateCallback)(
	OUT PCXMBWebCtrl &newXMBWebCtrlAddr,        // 输出新打开页面的CXMBWebCtrl地址,当在本进程内其他CXMBWebCtrl中打开时,需要给出CXMBWebCtrl地址
	IN  LPCWSTR lpURL,                          // URL,NULL表示当前获取不到URL,只能在本进程内打开页面,需要newXMBWebCtrlAddr给出CXMBWebCtrl地址
	IN  LPVOID lpParameter                      // 回调函数参数
	);

// 定义回调函数类型,处理数据及状态改变
typedef void (CALLBACK* XMBWebCtrl_DataAndStatusChangedCallback)(
	IN  LPCWSTR lpTitle,                        // 不为NULL时表示标题改变了,用于显示在窗口标题上的内容
	IN  LPCWSTR lpURL,                          // 不为NULL时表示URL改变了,用于显示在地址栏上的内容
	IN  const BOOL *pbCanGoBack,                // 不为NULL时表示是否客户回退
	IN  const BOOL *pbCanGoForward,             // 不为NULL时表示是否可以前进
	IN  const BOOL *pbCanReload,                // 不为NULL时表示是否可以重新加载
	IN  LPVOID lpParameter                      // 回调函数参数
	);

//////////////////////////////////////////////////////////////////////////
// CXMBWebCtrl
class CXMBWebCtrl
{
public:
	CXMBWebCtrl();
	~CXMBWebCtrl();

public:
	// 创建窗口
	BOOL Create(
		IN  HWND hParent,            // 父窗口
		IN  int nX,                  // 相对于父窗口X坐标
		IN  int nY,                  // 相对于父窗口Y坐标
		IN  int nWidth,              // 宽度
		IN  int nHeight,             // 高度		
		IN  XMBWebCtrl_CustomNewWindowNavigateCallback lpCustomNewWindowNavigateCallback = NULL, // 新窗口打开网页自定义处理函数,NULL表示使用默认的方式处理
		IN  LPVOID lpCustomNewWindowNavigateCallbackParameter = NULL,                            // 新窗口打开网页自定义处理函数参数
        IN  XMBWebCtrl_DataAndStatusChangedCallback lpDataAndStatusChangedCallback = NULL,       // 数据及状态改变回调函数
		IN  LPVOID lpDataAndStatusChangedCallbackParameter = NULL                                // 数据及状态改变回调函数参数
		); 

	// 取得窗口句柄,返回NULL表示窗口无效
	HWND GetSafeHwnd(void) const;

	// 移动窗口
	BOOL MoveWindow(
		IN  int nX,                  // 相对于父窗口X坐标
		IN  int nY,                  // 相对于父窗口Y坐标
		IN  int nWidth,              // 宽度
		IN  int nHeight              // 高度
		);

	// 销毁窗口
	void Destroy(void);

	// 打开网址
	BOOL Navigate(
		IN LPCWSTR lpUrl             // 待打开的网址
		);

	// 取得URL,返回空表示没有URL
	wstring GetURL(void) const; 

	// 执行回退
	BOOL DoGoBack(void);

	// 执行前进
	BOOL DoGoForward(void);

	// 执行重新加载
	BOOL DoReload(void);


private: // 防止使用拷贝构造和赋值函数
	CXMBWebCtrl(const CXMBWebCtrl &other);
	CXMBWebCtrl& operator=(const CXMBWebCtrl &other);

public:
	HANDLE m_hHandle;
};

//////////////////////////////////////////////////////////////////////////
#endif

控件类CXMBWebCtrl源文件如下:

/********************************************************************
    created: 17:4:2019  13:14
    author: zhangweifang
    
    purpose: Miniblink封装Web控件
	homepage: http://www.keepthink.cn/
********************************************************************/
#include "XMBWebCtrl.h"
#include "wke.h"
//////////////////////////////////////////////////////////////////////////
// 不需要提供接口的函数在此处实现
// 获取显示缩放比例,返回0表示失败,缩放比 = DPI*100/96,150表示150%
int XMBWCIN_GetDisplayScale(void)
{
	// 获取DC
	HDC hDC = ::GetDC(NULL);
	if (hDC == NULL)
	{
		return 100;
	}

	// 取得Y方向DPI,并释放DC
	int nDPI = ::GetDeviceCaps(hDC, LOGPIXELSY);
	::ReleaseDC(NULL, hDC);

	// 返回缩放比例
	return (nDPI * 100) / 96;
}

// UTF8转UNICODE
wstring	XMBWCIN_Utf8ToUnicode(
	IN  const char* lpUtf8Str                    // UTF8编码字符串
	)
{
	// 参数有效性
	if (lpUtf8Str == NULL)
	{
		return L"";
	}

	// 计算字符数
	int nNeedChars = ::MultiByteToWideChar(CP_UTF8, 0, lpUtf8Str, -1, NULL, 0);
	if (nNeedChars <= 0)
	{
		return L"";
	}

	// 申请空间
	wchar_t *pWCharBuf = new wchar_t[nNeedChars];
	if (pWCharBuf == NULL)
	{
		return L"";
	}
	memset(pWCharBuf, 0, nNeedChars*sizeof(wchar_t));

	// 转换
	::MultiByteToWideChar(CP_UTF8, 0, lpUtf8Str, -1, pWCharBuf, nNeedChars);

	// 得到结果
	wstring wstrUnicode(pWCharBuf);

	// 释放空间
	if (pWCharBuf != NULL)
	{
		delete[] pWCharBuf;
		pWCharBuf = NULL;
	}

	// 返回UNICODE编码字符串
	return wstrUnicode;
}

// 判断文件路径是否有效,根据文件属性判断
BOOL XMBWCIN_PathEffective(
	IN LPCWSTR lpPathName
	)
{
	// 属性是否无效
	return (   lpPathName == NULL
		    || ::GetFileAttributesW(lpPathName) == INVALID_FILE_ATTRIBUTES
			) ? FALSE : TRUE;
}

// 创建路径上所有层次的文件目录,若目录层次错误则返回失败
BOOL XMBWCIN_CreateAllDirInPath(
	IN  LPCWSTR lpszPath             // 路径
	)
{
	// 参数有效性
	wstring strPath = (lpszPath!=NULL) ? wstring(lpszPath) : L"";	
	if (strPath.length() == 0)
	{
		return FALSE;
	}

	// 从根目录开始依次创建目录层次
	wstring strTmpDir = L"";
	wstring::size_type szPos = 0;
	while (szPos < strPath.length()) { if (strPath[szPos]==L'\\' && szPos>0)
		{
			// 得到目录路径
			strTmpDir = strPath.substr(0, szPos);

			// 路径不存在则创建
			if (XMBWCIN_PathEffective(strTmpDir.c_str()) == FALSE)
			{
				::CreateDirectoryW(strTmpDir.c_str(), NULL);
			}			
		}

		// 查询下一个字符
		szPos += 1;
	}

	// 判断路径是否有效
	return XMBWCIN_PathEffective(strTmpDir.c_str());
}

// 取得临时目录
wstring XMBWCIN_GetTempDirPath(void)
{
	wchar_t wcTmpBuf[1024] = {0};
	::GetTempPathW(sizeof(wcTmpBuf)/sizeof(wchar_t), wcTmpBuf);
	return wstring(wcTmpBuf);
}

// 取得XMBWebCtrl临时目录
wstring XMBWCIN_GetXMBWebCtrlTempDirPath(void)
{
	// 拼接路径
	wstring wstrTmpDir = XMBWCIN_GetTempDirPath() + L"\\XMBWebCtrl";

	// 拼接一个文件名,方便调用创建路径层次文件夹
	wstring wstrDirFile = wstrTmpDir + L"\\XXX.tmp";
	XMBWCIN_CreateAllDirInPath(wstrDirFile.c_str());

	// 返回路径
	return wstrDirFile;
}

// 取得临时本地存放目录
wstring XMBWCIN_GetXMBWebCtrlLocalStorageDirPath(void)
{
	// 拼接路径
	wstring wstrLocalPath = XMBWCIN_GetXMBWebCtrlTempDirPath() + L"\\LocalStorage";

	// 拼接一个文件名,方便调用创建路径层次文件夹
	wstring wstrDirFile = wstrLocalPath + L"\\XXX.tmp";
	XMBWCIN_CreateAllDirInPath(wstrDirFile.c_str());

	// 返回路径
	return wstrLocalPath;
}

// 获得当前EXE所在的目录路径
wstring XMBWCIN_GetExeDirPath(void)
{
	// 得到实例绝对路径
	wchar_t appPathName[1024] = {0};
	::GetModuleFileNameW(NULL, appPathName, sizeof(appPathName)/sizeof(wchar_t));

	// 查找最后'\'位置确定路径
	wstring wstrPath = wstring(appPathName);
	wstrPath = wstrPath.substr(0, wstrPath.rfind(L'\\'));
	return wstrPath;
}

// 取得node.dll所在路径
wstring XMBWCIN_GetNodeDllPath(void)
{
	wstring wstrNodeDllPath = XMBWCIN_GetExeDirPath() + L"\\node.dll";
	return wstrNodeDllPath;
}

// 启动一个可执行程序
BOOL XMBWCIN_StartUpExeFile(
	IN  LPCWSTR lpExeFilePathName,                   // EXE文件路径名
	IN  LPWSTR lpCmdLine,                            // 命令行参数
	OUT PDWORD pProcessID                            // 输出进程ID
	)
{
	// 参数有效性
	if (lpExeFilePathName == NULL)
	{
		return FALSE;
	}

	// 启动应用程序
	STARTUPINFOW si; 
	ZeroMemory(&si, sizeof(si)); 
	si.cb = sizeof(si); 
	PROCESS_INFORMATION processInfo;
	BOOL bSuccess = ::CreateProcessW(lpExeFilePathName, lpCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &processInfo);

	// 是否启动成功
	if (bSuccess == TRUE)
	{
		// 是否获取进程信息
		if (pProcessID != NULL)
		{
			*pProcessID = processInfo.dwProcessId;
		}	

		// 释放资源
		::CloseHandle(processInfo.hProcess);
		::CloseHandle(processInfo.hThread);
	}

	// 返回结果
	return bSuccess;
}

// 获得注册表项的值(字节缓冲区)
BOOL XMBWCIN_RegGetValueBuffer(
	IN  HKEY hKey,             // 根项(HKEY_CLASSES_ROOT、HKEY_CURRENT_USER、HKEY_LOCAL_MACHINE、HKEY_USERS)
	IN  LPCWSTR lpSubKey,      // 子项(ControlPanel\Volume)
	IN  LPCWSTR lpKeyName,     // 键名(Mute)
	OUT LPDWORD lpType,        // 带出类型
	OUT LPBYTE lpData,         // 存放缓冲区地址
	IN OUT LPDWORD lpcbData	   // 存放缓冲区大小
	)
{
	HKEY  hkResult;
	// opens the specified registry key. Note that key names are not case sensitive.
	if(ERROR_SUCCESS != ::RegOpenKeyExW(hKey, lpSubKey, 0, KEY_QUERY_VALUE, &hkResult))		
	{
		return FALSE;
	}

	// retrieves the type and data for a specified value name associated with an open registry key
	if(ERROR_SUCCESS != ::RegQueryValueExW(hkResult, lpKeyName, NULL, lpType, lpData, lpcbData))	
	{		
		::RegCloseKey(hkResult);				// releases a handle to the specified registry key
		return FALSE;
	}

	::RegCloseKey(hkResult);					// releases a handle to the specified registry key
	return TRUE;
}

// 获得注册表项的值(字符串)
BOOL XMBWCIN_RegGetValue(
	IN  HKEY hKey,             // 根项(HKEY_CLASSES_ROOT、HKEY_CURRENT_USER、HKEY_LOCAL_MACHINE、HKEY_USERS)
	IN  LPCWSTR lpSubKey,      // 子项(ControlPanel\Volume)
	IN  LPCWSTR lpKeyName,     // 键名(Mute)
	OUT wstring &strValue      // 带出读取到的键值
	)
{
	// 临时存放取得的注册表键值
	wchar_t resultValue[1024] = {0};
	memset(resultValue, 0, sizeof(resultValue));
	DWORD dwType;
	DWORD dwReadLen = sizeof(resultValue);
	if(XMBWCIN_RegGetValueBuffer(hKey, lpSubKey, lpKeyName, &dwType, (LPBYTE)resultValue, &dwReadLen) == FALSE)
	{		
		return FALSE;	
	}

	// 转换为字符串
	strValue = wstring(resultValue);
	return TRUE;
}

// 执行自定义协议
// 返回TRUE表示执行了,返回FALSE表示未处理
BOOL XMBWCIN_DoCustomURLProtocol(
	IN  LPCWSTR lpURL                           // URL
	)
{
	// 参数有效性
	wstring wstrUrl = (lpURL!=NULL) ? wstring(lpURL) : L"";
	if (wstrUrl.length() == 0)
	{
		return FALSE;
	}
	
	// 查找":",取得协议头
	wstring wstrSplitter = L":";
	size_t szPos = wstrUrl.find(wstrSplitter);
	if (szPos == wstring::npos)
	{
		return FALSE;
	}

	// 取得分隔符之前的串为协议头,判断是否常用协议
	wstring wstrProtocol = wstrUrl.substr(0, szPos);		
	if (   _wcsicmp(wstrProtocol.c_str(), L"http") == 0
		|| _wcsicmp(wstrProtocol.c_str(), L"https") == 0
		|| _wcsicmp(wstrProtocol.c_str(), L"ftp") == 0
		)
	{
		return FALSE;
	}

	// 其余情况在注册表中查找
	// 取得协议对应EXE路径
	wstring wstrExePath = L"";
	if (XMBWCIN_RegGetValue(HKEY_CLASSES_ROOT, wstrProtocol.c_str(), L"URL Protocol", wstrExePath) == FALSE)
	{
		return FALSE;
	}

	// 拼接命令行参数
	wstring wstrCmdLine = L"\"";
	wstrCmdLine += wstrExePath + L"\" \"";
	wstrCmdLine += wstrUrl + L"\"";

	// 启动应用程序
	return XMBWCIN_StartUpExeFile(wstrExePath.c_str(), (LPWSTR)wstrCmdLine.c_str(), NULL);
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// 初始化Miniblink
static LONG XMBWCING_lObjectCount = 0;
BOOL XMBWCIN_InitMiniblink(void)
{
	// 已经初始化则累加计数
	if (XMBWCING_lObjectCount > 0)
	{
		XMBWCING_lObjectCount += 1;
		return TRUE;
	}

	// 初始化Miniblink,这里以绝对路径方式使用EXE同目录下的node.dll
	// 必须以变量先保存路径,把变量作为参数,因为函数内部只保存了地址,需要确保该地址在调用wkeInitialize时有效
	// 直接以函数调用作为参数时,函数返回后地址就被销毁无效了
	wstring wstrNodeDllPath = XMBWCIN_GetNodeDllPath();
	wkeSetWkeDllPath(wstrNodeDllPath.c_str());
	if (wkeInitialize() == 0)
	{
		wstring wstrErrorMsg = wstrNodeDllPath + L" not exist!";
		::MessageBoxW(NULL, wstrErrorMsg.c_str(), L"wkeInitialize error", 0);
		return FALSE;
	}

	// 初始化成功则累加计数
	XMBWCING_lObjectCount = 1;
	return TRUE;	
}

// 释放Miniblink
void XMBWCIN_CleanUpMiniblink(void)
{
	// 未初始化
	if (XMBWCING_lObjectCount <= 0)
	{
		return;
	}

	// 计数减少
	XMBWCING_lObjectCount -= 1;

	// 计数为0则真正释放Miniblink
	if (XMBWCING_lObjectCount <= 0) { wkeFinalize(); XMBWCING_lObjectCount = 0; } } // Miniblink是否初始化 BOOL XMBWCIN_IsMiniblinkInited(void) { return (XMBWCING_lObjectCount > 0) ? TRUE : FALSE;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// 句柄数据类
class XMBWebCtrlHandleDataIn_t
{
public:
	XMBWebCtrlHandleDataIn_t()
		: m_wkeWebView(NULL)
		, m_WebViewOldWndProc(NULL)
		, m_lpDataAndStatusChangedCallback(NULL)
		, m_lpDataAndStatusChangedCallbackParameter(NULL)
		, m_lpCustomNewWindowNavigateCallback(NULL)
		, m_lpCustomNewWindowNavigateCallbackParameter(NULL)
	{
		// 初始化Miniblink
		XMBWCIN_InitMiniblink();
	}

	~XMBWebCtrlHandleDataIn_t()
	{
		// 销毁窗口
		Destroy();

		// 释放Miniblink
		XMBWCIN_CleanUpMiniblink();
	}

	// 取得句柄
	HWND GetSafeHwnd(void) const
	{
		// 参数有效性
		if (   m_wkeWebView == NULL
			|| XMBWCIN_IsMiniblinkInited() == FALSE
			)
		{
			return NULL;
		}

		// 取得窗口句柄
		return wkeGetWindowHandle(m_wkeWebView);
	}

	// 销毁窗口
	void Destroy(void)
	{
		// 窗口未创建
		if (GetSafeHwnd() == NULL)
		{
			return;
		}

		// 销毁窗口
		wkeDestroyWebWindow(m_wkeWebView);
		m_wkeWebView = NULL;
		m_WebViewOldWndProc = NULL;

		// 清除数据
		m_lpDataAndStatusChangedCallback = NULL;
		m_lpDataAndStatusChangedCallbackParameter = NULL;
		m_lpCustomNewWindowNavigateCallback = NULL;
		m_lpCustomNewWindowNavigateCallbackParameter = NULL;
	}


public:
	wkeWebView m_wkeWebView;
	WNDPROC m_WebViewOldWndProc;
	XMBWebCtrl_DataAndStatusChangedCallback m_lpDataAndStatusChangedCallback;
	LPVOID m_lpDataAndStatusChangedCallbackParameter;
	XMBWebCtrl_CustomNewWindowNavigateCallback m_lpCustomNewWindowNavigateCallback;
	LPVOID m_lpCustomNewWindowNavigateCallbackParameter;
};

//////////////////////////////////////////////////////////////////////////
// Miniblink回调函数
// a标签将弹出新窗口触发的回调函数
static wkeWebView WKE_CALL_TYPE XMBWCBK_wkeCreateViewCallback(wkeWebView webView, void* param, wkeNavigationType navType, const wkeString url, const wkeWindowFeatures* features)
{
	// 取得URL
	const wchar_t *pUtf18Url = wkeGetStringW(url);
	wstring wstrURL = (pUtf18Url!=NULL) ? wstring(pUtf18Url) : L"";
	const wchar_t *pNewURL = (wstrURL.length()==0) ? NULL : wstrURL.c_str();
	PCXMBWebCtrl newXMBWebCtrlAddr = NULL;

	// 得到窗口句柄数据,执行回调函数
	CXMBWebCtrl *pXMBWebCtrl = (CXMBWebCtrl*)param;
	XMBWebCtrlHandleDataIn_t *pThisData = (pXMBWebCtrl!=NULL) ? (XMBWebCtrlHandleDataIn_t*)pXMBWebCtrl->m_hHandle : NULL;
	if (   pThisData != NULL
		&& pThisData->m_lpCustomNewWindowNavigateCallback != NULL
		&& pThisData->m_lpCustomNewWindowNavigateCallback(newXMBWebCtrlAddr, pNewURL, pThisData->m_lpCustomNewWindowNavigateCallbackParameter) == TRUE
		)
	{		
		// 非本进程内打开新的页面
		if (newXMBWebCtrlAddr == NULL)
		{
			return NULL;
		}

		// 取得本进程其他CXMBWebCtrl打开页面时的wkeWebView
		XMBWebCtrlHandleDataIn_t *pNewCtrlData = (newXMBWebCtrlAddr!=NULL) ? (XMBWebCtrlHandleDataIn_t*)newXMBWebCtrlAddr->m_hHandle : NULL;
		return (pNewCtrlData!=NULL) ? pNewCtrlData->m_wkeWebView : NULL;	
	}	

	// 使用默认方式新窗口打开
	wkeWebView newWindow = wkeCreateWebWindow(WKE_WINDOW_TYPE_POPUP, NULL, features->x, features->y, features->width, features->height);
	wkeSetWindowTitleW(newWindow, L"XMBWebCtrl New Browser Window");
	wkeShowWindow(newWindow, true);
	return newWindow;
}

// 网页开始浏览将触发的回调函数
static bool WKE_CALL_TYPE XMBWCBK_wkeNavigationCallback(wkeWebView webView, void* param, wkeNavigationType navigationType, const wkeString url)
{	
	// 执行了自定义协议则不处理
	if (XMBWCIN_DoCustomURLProtocol(wkeGetStringW(url)) == TRUE)
	{
		return false;
	}

	// 返回true表示可以继续进行浏览,返回false表示阻止本次浏览
	return true;
}

static void WKE_CALL_TYPE XMBWCBK_wkeTitleChangedCallback(wkeWebView webView, void* param, const wkeString title)
{
	// 得到窗口句柄数据,执行回调函数
	CXMBWebCtrl *pXMBWebCtrl = (CXMBWebCtrl*)param;
	XMBWebCtrlHandleDataIn_t *pThisData = (pXMBWebCtrl!=NULL) ? (XMBWebCtrlHandleDataIn_t*)pXMBWebCtrl->m_hHandle : NULL;
	if (   pThisData != NULL
		&& pThisData->m_lpDataAndStatusChangedCallback != NULL
		)
	{		
		pThisData->m_lpDataAndStatusChangedCallback(wkeGetStringW(title), NULL, NULL, NULL, NULL, pThisData->m_lpDataAndStatusChangedCallbackParameter);
	}
}

static void WKE_CALL_TYPE XMBWCBK_wkeURLChangedCallback2(wkeWebView webView, void* param, wkeWebFrameHandle frameId, const wkeString url)
{
	// 得到窗口句柄数据,执行回调函数,只处理MainFrame
	CXMBWebCtrl *pXMBWebCtrl = (CXMBWebCtrl*)param;
	XMBWebCtrlHandleDataIn_t *pThisData = (pXMBWebCtrl!=NULL) ? (XMBWebCtrlHandleDataIn_t*)pXMBWebCtrl->m_hHandle : NULL;
	if (   pThisData != NULL
		&& pThisData->m_lpDataAndStatusChangedCallback != NULL
		&& wkeIsMainFrame(webView, frameId) == true
		)
	{
		BOOL bCanGoBack = (wkeCanGoBack(webView) == true) ? TRUE : FALSE;
		BOOL bCanGoForward = (wkeCanGoForward(webView) == true) ? TRUE : FALSE;
		BOOL bCanReload = TRUE;
		pThisData->m_lpDataAndStatusChangedCallback(NULL, wkeGetStringW(url), &bCanGoBack, &bCanGoForward, &bCanReload, pThisData->m_lpDataAndStatusChangedCallbackParameter);
	}
}

//////////////////////////////////////////////////////////////////////////
// 窗口过程处理函数
LRESULT CALLBACK XMBWebCtrl_WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	// 得到窗口句柄数据
	CXMBWebCtrl *pXMBWebCtrl = (CXMBWebCtrl*)::GetWindowLongW(hWnd, GWL_USERDATA);
	XMBWebCtrlHandleDataIn_t *pThisData = (pXMBWebCtrl!=NULL) ? (XMBWebCtrlHandleDataIn_t*)pXMBWebCtrl->m_hHandle : NULL;
	if (   pThisData == NULL
		|| pThisData->m_WebViewOldWndProc == NULL
		)
	{
		return ::DefWindowProc(hWnd, message, wParam, lParam);
	}

	// 处理消息
	switch (message)
	{
	case WM_GETDLGCODE: // 使得MB控件作为对话框子窗口时可接收到键盘消息
		{
			return DLGC_WANTARROWS|DLGC_WANTALLKEYS|DLGC_WANTCHARS;
		}
		break;

	default:
		return pThisData->m_WebViewOldWndProc(hWnd, message, wParam, lParam);
	}  

	return 0;
}

//////////////////////////////////////////////////////////////////////////
// CXMBWebCtrl
CXMBWebCtrl::CXMBWebCtrl()
	: m_hHandle(NULL)
{
	// 申请句柄内存
	m_hHandle = new XMBWebCtrlHandleDataIn_t();
}

CXMBWebCtrl::~CXMBWebCtrl()
{
	// 释放句柄内存
	if (m_hHandle != NULL)
	{
		delete (XMBWebCtrlHandleDataIn_t*)m_hHandle;
		m_hHandle = NULL;
	}
}

// 创建窗口
BOOL CXMBWebCtrl::Create(
	IN  HWND hParent,            // 父窗口
	IN  int nX,                  // 相对于父窗口X坐标
	IN  int nY,                  // 相对于父窗口Y坐标
	IN  int nWidth,              // 宽度
	IN  int nHeight,             // 高度
	IN  XMBWebCtrl_CustomNewWindowNavigateCallback lpCustomNewWindowNavigateCallback, // 新窗口打开网页自定义处理函数,NULL表示使用默认的方式处理
	IN  LPVOID lpCustomNewWindowNavigateCallbackParameter,                            // 新窗口打开网页自定义处理函数参数
	IN  XMBWebCtrl_DataAndStatusChangedCallback lpDataAndStatusChangedCallback,       // 数据及状态改变回调函数
	IN  LPVOID lpDataAndStatusChangedCallbackParameter                                // 数据及状态改变回调函数参数	
	)
{
	// 参数有效性
	XMBWebCtrlHandleDataIn_t *pThisData = (XMBWebCtrlHandleDataIn_t*)m_hHandle;
	if (   GetSafeHwnd() != NULL
		|| hParent == NULL 
		|| ::IsWindow(hParent) == FALSE
		|| nWidth <= 0 
		|| nHeight <= 0 || pThisData == NULL || XMBWCIN_IsMiniblinkInited() == FALSE ) { return FALSE; } // 创建子窗口控件 pThisData->m_wkeWebView = wkeCreateWebWindow(WKE_WINDOW_TYPE_CONTROL, hParent, nX, nY, nWidth, nHeight);
	if (pThisData->m_wkeWebView == NULL)
	{
		return FALSE;
	}

	// 是否创建成功
	HWND hWebWnd = wkeGetWindowHandle(pThisData->m_wkeWebView);
	BOOL bSuccess = (hWebWnd!=NULL && ::IsWindow(hWebWnd)==TRUE) ? TRUE : FALSE;

	// 创建成功需要设置一些回调,并显示窗口
	if (bSuccess == TRUE)
	{
		// 记录数据及状态改变回调函数及参数地址
		pThisData->m_lpDataAndStatusChangedCallback = lpDataAndStatusChangedCallback;
		pThisData->m_lpDataAndStatusChangedCallbackParameter = lpDataAndStatusChangedCallbackParameter;

		// HOOK窗口过程处理函数,解决MFC对话框内MB作为子窗口时收不到键盘消息的问题
		pThisData->m_WebViewOldWndProc = (WNDPROC)::SetWindowLongW(hWebWnd, GWL_WNDPROC, (LONG)XMBWebCtrl_WindowProc);
		::SetWindowLongW(hWebWnd, GWL_USERDATA, (LONG)this);

		// 设置Cookie
		wkeSetCookieEnabled(pThisData->m_wkeWebView, true);
		wkeSetCookieJarPath(pThisData->m_wkeWebView, XMBWCIN_GetXMBWebCtrlTempDirPath().c_str());

		// 设置本地存储位置
		wkeSetLocalStorageFullPath(pThisData->m_wkeWebView, XMBWCIN_GetXMBWebCtrlLocalStorageDirPath().c_str());

		// 记录设置的回调函数及参数,设置a标签将弹出新窗口,在设置的回调函数中处理
		pThisData->m_lpCustomNewWindowNavigateCallback = lpCustomNewWindowNavigateCallback;
		pThisData->m_lpCustomNewWindowNavigateCallbackParameter = lpCustomNewWindowNavigateCallbackParameter;
		wkeSetNavigationToNewWindowEnable(pThisData->m_wkeWebView, true);
		wkeOnCreateView(pThisData->m_wkeWebView, XMBWCBK_wkeCreateViewCallback, this);

		// 设置网页开始浏览将触发回调
		wkeOnNavigation(pThisData->m_wkeWebView, XMBWCBK_wkeNavigationCallback, this);

		// 设置标题改变触发回调,用于更新窗口标题
		wkeOnTitleChanged(pThisData->m_wkeWebView, XMBWCBK_wkeTitleChangedCallback, this);

		// 设置URL改变回调,用于更新地址框中的URL
		wkeOnURLChanged2(pThisData->m_wkeWebView, XMBWCBK_wkeURLChangedCallback2, this);

		// 如果当前为放大显示则设置缩放系数
		int nDisplayScale = XMBWCIN_GetDisplayScale();
		if (nDisplayScale > 100)
		{
			wkeSetZoomFactor(pThisData->m_wkeWebView, ((float)nDisplayScale)/100);
		}		

		// 显示子窗口
		wkeShowWindow(pThisData->m_wkeWebView, TRUE);
	}

	// 返回窗口是否创建成功
	return bSuccess;
}

// 取得窗口句柄,返回NULL表示窗口无效
HWND CXMBWebCtrl::GetSafeHwnd(void) const
{
	XMBWebCtrlHandleDataIn_t *pThisData = (XMBWebCtrlHandleDataIn_t*)m_hHandle;
	return (pThisData != NULL) ? pThisData->GetSafeHwnd() : NULL;
}

// 移动窗口
BOOL CXMBWebCtrl::MoveWindow(
	IN  int nX,                  // 相对于父窗口X坐标
	IN  int nY,                  // 相对于父窗口Y坐标
	IN  int nWidth,              // 宽度
	IN  int nHeight              // 高度
	)
{
	// 参数有效性
	XMBWebCtrlHandleDataIn_t *pThisData = (XMBWebCtrlHandleDataIn_t*)m_hHandle;
	if (   pThisData == NULL
		|| GetSafeHwnd() == NULL
		)
	{
		return FALSE;
	}

	// 移动窗口
	wkeMoveWindow(pThisData->m_wkeWebView, nX, nY, nWidth, nHeight);
	return TRUE;
}

// 销毁窗口
void CXMBWebCtrl::Destroy(void)
{
	XMBWebCtrlHandleDataIn_t *pThisData = (XMBWebCtrlHandleDataIn_t*)m_hHandle;
	if (pThisData != NULL)
	{
		pThisData->Destroy();
	}
}

// 打开网址
BOOL CXMBWebCtrl::Navigate(
	IN LPCWSTR lpUrl             // 待打开的网址
	)
{
	// 参数有效性
	XMBWebCtrlHandleDataIn_t *pThisData = (XMBWebCtrlHandleDataIn_t*)m_hHandle;
	if (   pThisData == NULL
		|| lpUrl == NULL
		|| GetSafeHwnd() == NULL
		)
	{
		return FALSE;
	}

	// 加载URL
	wkeLoadW(pThisData->m_wkeWebView, lpUrl);
	return TRUE;
}

// 取得URL,返回空表示没有URL
wstring CXMBWebCtrl::GetURL(void) const
{
	// 参数有效性
	XMBWebCtrlHandleDataIn_t *pThisData = (XMBWebCtrlHandleDataIn_t*)m_hHandle;
	if (   pThisData == NULL
		|| GetSafeHwnd() == NULL
		)
	{
		return L"";
	}
	
	// 取得URL
	return XMBWCIN_Utf8ToUnicode(wkeGetURL(pThisData->m_wkeWebView));	
}

// 执行回退
BOOL CXMBWebCtrl::DoGoBack(void)
{
	// 参数有效性
	XMBWebCtrlHandleDataIn_t *pThisData = (XMBWebCtrlHandleDataIn_t*)m_hHandle;
	if (   pThisData == NULL
		|| GetSafeHwnd() == NULL
		|| wkeCanGoBack(pThisData->m_wkeWebView) == false
		)
	{
		return FALSE;
	}

	// 执行回退
	return (wkeGoBack(pThisData->m_wkeWebView) == true) ? TRUE : FALSE;
}

// 执行前进
BOOL CXMBWebCtrl::DoGoForward(void)
{
	// 参数有效性
	XMBWebCtrlHandleDataIn_t *pThisData = (XMBWebCtrlHandleDataIn_t*)m_hHandle;
	if (   pThisData == NULL
		|| GetSafeHwnd() == NULL
		|| wkeCanGoForward(pThisData->m_wkeWebView) == false
		)
	{
		return FALSE;
	}

	// 执行前进
	return (wkeGoForward(pThisData->m_wkeWebView) == true) ? TRUE : FALSE;
}

// 执行重新加载
BOOL CXMBWebCtrl::DoReload(void)
{
	// 参数有效性
	XMBWebCtrlHandleDataIn_t *pThisData = (XMBWebCtrlHandleDataIn_t*)m_hHandle;
	if (   pThisData == NULL
		|| GetSafeHwnd() == NULL
		|| GetURL().length() == 0
		)
	{
		return FALSE;
	}

	// 执行重新加载
	wkeReload(pThisData->m_wkeWebView);
	return TRUE;
}