由于時間倉促,之前貼的代碼中有些許錯誤,請大家見諒,現已更正,并附上運作結果!
----------------我是開始分割線-----------------
在上一篇利用InjectedBundle定制自己的Webkit(一)中,我們完成了一個自己的InjectedBundle,接下來我們就要在Webkit中加載我們自己的InjectedBundle。
為了測試友善先給出一個示例的InjectedBundle代碼,項目名稱MyInjectedBundle。
#include <WebKit2/WKBundleInitialize.h>
#include <WebKit2/WKBundleFrame.h>
#include <Webkit2/WKBundlePage.h>
#include <WebKit2/WKBundle.h>
#include <Webkit2/WKUrl.h>
#include <Webkit2/WKString.h>
#include <windows.h>
#include <string.h>
#include <wchar.h>
#pragma comment(lib, "WebKit.lib")
// 架構加載完畢後調用
void didFinishLoadForFrame(WKBundlePageRef, WKBundleFrameRef frame, WKTypeRef*, const void*)
{
if (WKBundleFrameIsMainFrame(frame)) // 如果是主架構
{
WKURLRef url = WKBundleFrameCopyURL(frame); // 獲得URL
WKStringRef str = WKURLCopyString(url); // 得到URL字元串
size_t length = WKStringGetLength(str);
wchar_t* urlBuffer = new wchar_t[length + 1];
size_t size = WKStringGetCharacters(str, urlBuffer, length); // 轉成wchar_t*
urlBuffer[length] = 0;
WKRect rect = WKBundleFrameGetContentBounds(frame); // 得到邊界範圍
wchar_t info[1024];
swprintf(info, L"url: %s\nx=%f, y=%f, width=%f, height=%f",
urlBuffer, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); // 生成結果
::MessageBoxW(0, info, L"FrameBound", MB_OK); // 顯示
delete[] urlBuffer;
}
}
// page建立完後調用
void didCreatePage(WKBundleRef bundle, WKBundlePageRef page, const void*)
{
WKBundlePageLoaderClient loaderClient = { 0 };
loaderClient.version = kWKBundlePageLoaderClientCurrentVersion;
loaderClient.didFinishLoadForFrame = didFinishLoadForFrame; // 注冊回調
WKBundlePageSetPageLoaderClient(page, &loaderClient);
}
extern "C" __declspec(dllexport)
void WKBundleInitialize(WKBundleRef bundle, WKTypeRef initializationUserData)
{
WKBundleClient client = { 0 };
client.version = kWKBundleClientCurrentVersion;
client.didCreatePage = didCreatePage;
WKBundleSetClient(bundle, &client);
}
以上InjectedBundle首先注冊page建立後的回調didCreatePage,然後在page建立之後注冊架構加載完畢的回調didFinishLoadForFrame,當架構加載完畢之後再獲得架構的url和區域坐标顯示出來。
在InjectedBundle編譯生成完畢之後得到MyInjectedBundle.dll,之後我們開始寫客戶程序調用的代碼。
建立一個空項目WebkitDemo,準備工作同InjectedBundle,不過配置類型是應用程式(.exe)
然後開始寫代碼
首先需要建立一個讓View對象依附的Window,Window是怎樣的不重要,主要是需要一個載體和消息隊列,是以這裡直接不顯示Window了。
#include <Windows.h>
#include <Webkit2/WKType.h>
#include <WebKit2/WKStringCF.h>
#include <Webkit2/WKString.h>
#include <Webkit2/WKUrl.h>
#include <Webkit2/WKView.h>
#include <Webkit2/WKContext.h>
#include <Webkit2/WKPage.h>
#include <shlwapi.h>
#include <WebKit2/WKURLCF.h>
#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "CoreFoundation.lib")
#pragma comment(lib, "WebKit.lib")
LRESULT CALLBACK runLoopProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_CREATE)
{
createView(window); // 建立view這裡之後介紹
return 0;
}
return ::DefWindowProc(window, message, wParam, lParam);
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpstrCmdLine, int nCmdShow)
{
MSG msg;
BOOL bRet;
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = runLoopProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon((HINSTANCE) NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = 0;
wc.lpszClassName = L"MyWebkitToolRunLoop";
if (!RegisterClass(&wc))
return 0;
HWND hwnd = ::CreateWindow(L"MyWebkitToolRunLoop", L"MyWebkitTool",
WS_OVERLAPPED, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, 0);
if (!hwnd)
return 0;
// 開始消息循環
while ( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
if (bRet == -1)
{
break;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return 0;
}
然後在WM_CREATE事件的響應中建立view,建立view需要一個WKContextRef參數,這個參數能夠指定view在什麼上下文中建立,即利用哪個WebProcess程序來建立。是以我們就可以在WKContextRef中設定使用我們的InjectedBundle。
WKStringRef getBundlePath()
{
const wchar_t* THIS_FILE_NAME = L"WebkitDemo.exe"; // 客戶程式檔案名
const wchar_t* BUNDLE_FILE_NAME = L"MyInjectedBundle.dll"; // InjectedBundle檔案名
HMODULE thisModule = ::GetModuleHandleW(THIS_FILE_NAME);
if (!thisModule)
return 0;
WCHAR pathStr[MAX_PATH];
if (!::GetModuleFileNameW(thisModule, pathStr, (DWORD)wcslen(pathStr))) // 得到客戶程式檔案路徑
return 0;
::PathRemoveFileSpecW(pathStr); // 去掉檔案名
if (!::PathAppendW(pathStr, BUNDLE_FILE_NAME)) // 加上InjectedBundle檔案名
return 0;
CFStringRef cf = CFStringCreateWithCharacters(0, (const UniChar*)pathStr, (CFIndex)wcslen(pathStr));
WKStringRef res = WKStringCreateWithCFString(cf);
CFRelease(cf);
return res;
}
void goToURL(const wchar_t* wcurl, WKViewRef view)
{
CFStringRef string = CFStringCreateWithCharacters(0, (const UniChar*)wcurl, (CFIndex)wcslen(wcurl));
CFStringRef escapedString = CFURLCreateStringByAddingPercentEscapes(0, string, 0, 0, kCFStringEncodingUTF8);
CFRelease(string);
CFURLRef cfURL = CFURLCreateWithString(0, escapedString, 0);
CFRelease(escapedString);
WKURLRef url = WKURLCreateWithCFURL(cfURL); // 建立一個URL對象
CFRelease(cfURL);
WKPageRef page = WKViewGetPage(view);
WKPageLoadURL(page, url); // 開始加載
WKRelease(url);
}
void createView(HWND window)
{
RECT webViewRect = { 0, 0, 0, 0 };
WKStringRef path = getBundlePath();
WKContextRef context = WKContextCreateWithInjectedBundlePath(path); // 利用InjectedBundle建立context對象
WKRelease(path);
WKViewRef view = WKViewCreate(webViewRect, context, 0, window); // 建立view對象
WKViewSetIsInWindow(view, true);
// 這裡可以用WKPageSetXXXClient注冊一些回調函數
goToURL(L"http://www.baidu.com", view);
}
我們需要給将WKContextCreateWithInjectedBundlePath傳入bundle檔案的路徑,為友善我們将MyInjectedBundle.dll和用戶端程式放在一起,利用getBundlePath()得到bundle檔案的路徑。在建立完成之後調用goToURL讓Webkit加載頁面,這裡以百度為例。
以上代碼用到了Shlwapi和CoreFoundation,是以加上頭檔案和連結庫。
編譯運作,大功告成了!

之後附上源碼檔案InjectedBundleDemo.zip
小小嘗試,歡迎大家多提意見和建議!下一期準備為大家介紹利用定制的Webkit來爬取動态頁面内容和動态生成的連結。