この記事は、Windows & Microsoft技術 基礎 Advent Calendar 2015の24日目の記事です。Windows APIプログラミングのあんまり基礎っぽくない内容です。
Windows 8.1には、「リーダー」というPDFビューアー(ストアアプリ)があります。ということは、当然PDFを描画するAPIも追加されています:WinRT/Metro TIPS:Windows 8.1の新機能、PDFを表示するには?[Windows 8.1ストア・アプリ開発] – @IT。これをネイティブC++のデスクトップアプリから使ってみました。
上記記事ではPdfPage.RenderToStreamAsyncで描画してますが、このほかにもIPdfRendererNativeというインタフェースがあります。これには、ID2D1DeviceContextやIDXGISurfaceに描画するメソッドがあるんです。今回、ID2D1DeviceContextに描画するRenderPageToDeviceContextメソッドを使いました。以下、サンプルコードです。
#define UNICODE #define _UNICODE #include <cstdlib> #include <utility> #include <windows.h> #include <shcore.h> #include <d3d11_1.h> #include <dxgi1_2.h> #include <d2d1_2.h> #include <windows.storage.h> #include <windows.storage.streams.h> #include <windows.data.pdf.h> #include <windows.data.pdf.interop.h> #include <wrl/client.h> #include <wrl/event.h> #include <atlbase.h> #include <atlwin.h> #include <atltypes.h> #pragma comment(lib, "shcore.lib") #pragma comment(lib, "runtimeobject.lib") #pragma comment(lib, "d3d11.lib") #pragma comment(lib, "d2d1.lib") #pragma comment(lib, "windows.data.pdf.lib") using namespace ABI::Windows::Data::Pdf; using namespace ABI::Windows::Foundation; using namespace ABI::Windows::Storage; using namespace ABI::Windows::Storage::Streams; using namespace Microsoft::WRL; using namespace Microsoft::WRL::Wrappers; class TestWindow : public ATL::CWindowImpl<TestWindow> { public: DECLARE_WND_CLASS_EX(L"Test Window Class", 0, -1); BEGIN_MSG_MAP(TestWindow) MESSAGE_HANDLER(WM_SIZE, OnSize) //MSG_WM_SIZE(OnSize) MESSAGE_HANDLER(WM_PAINT, OnPaint) //MSG_WM_PAINT(OnPaint) MESSAGE_HANDLER(WM_CREATE, OnCreate) //MSG_WM_CREATE(OnCreate) MESSAGE_HANDLER(WM_DESTROY, OnDestroy) //MSG_WM_DESTROY(OnDestroy) END_MSG_MAP() private: //void OnSize(UINT type, SIZE size) LRESULT OnSize(UINT, WPARAM type, LPARAM lp, BOOL&) { SIZE size{ GET_X_LPARAM(lp), GET_Y_LPARAM(lp) }; if (type == SIZE_MINIMIZED || type == SIZE_MAXHIDE) return 0; DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; auto hr = resource.m_dxgiSwapChain->GetDesc1(&swapChainDesc); if (SUCCEEDED(hr) && swapChainDesc.Width == size.cx && swapChainDesc.Height == size.cy) { return 0; } resource.m_d2dDeviceContext->SetTarget(nullptr); hr = resource.m_dxgiSwapChain->ResizeBuffers( swapChainDesc.BufferCount, 0, 0, DXGI_FORMAT_UNKNOWN, 0); if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { HandleDeviceLost(); } InitializeRenderTarget(); BOOL b{}; OnPaint(0, 0, 0, b); // WTL使用時はOnPaint(nullptr); return 0; } //void OnPaint(HDC) LRESULT OnPaint(UINT, WPARAM, LPARAM, BOOL&) { OnRender(); ValidateRect(nullptr); return 0; } void OnRender() { resource.m_d2dDeviceContext->BeginDraw(); OnRenderCore(); auto hr = resource.m_d2dDeviceContext->EndDraw(); if (hr == D2DERR_RECREATE_TARGET) { HandleDeviceLost(); } else if (SUCCEEDED(hr)) { hr = resource.m_dxgiSwapChain->Present(1, 0); if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { HandleDeviceLost(); } } } void OnRenderCore() { resource.m_d2dDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::Black)); if (m_document != nullptr) { Microsoft::WRL::ComPtr<ABI::Windows::Data::Pdf::IPdfPage> page; auto hr = m_document->GetPage(0, &page); if (SUCCEEDED(hr)) { auto params = PdfRenderParams(); resource.m_pdfRenderer->RenderPageToDeviceContext( page.Get(), resource.m_d2dDeviceContext.Get(), ¶ms); } } } //LRESULT OnCreate(const CREATESTRUCT*) LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&) { if (FAILED(CreateDeviceIndependentResources())) return -1; if (FAILED(CreateDeviceResources())) return -1; LoadPdf(); return 0; } void HandleDeviceLost() { //MessageBox(L"デバイスロストが発生しました。"); OutputDebugStringW(L"デバイスロストが発生しました。\r\n"); ReleaseDeviceResources(); CreateDeviceResources(); // いったんメッセージループに戻したくて、OnRenderを直接呼ばず、こうしている。 Invalidate(); } //void OnDestroy() LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL&) { PostQuitMessage(0); RevokeDragDrop(m_hWnd); ReleaseDeviceResources(); return 0; } void ReleaseDeviceResources() { resource = {}; } HRESULT Direct3DCreateDevice( D3D_DRIVER_TYPE driverType, _COM_Outptr_ ID3D11Device** ppDevice) { UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; #ifdef _DEBUG //flags |= D3D11_CREATE_DEVICE_DEBUG; #endif const D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_2, D3D_FEATURE_LEVEL_9_1, }; return D3D11CreateDevice(nullptr, driverType, nullptr, flags, featureLevels, ARRAYSIZE(featureLevels), D3D11_SDK_VERSION, ppDevice, nullptr, nullptr); } HRESULT Direct3DCreateDevice(_COM_Outptr_ ID3D11Device** ppDevice) { auto hr = Direct3DCreateDevice(D3D_DRIVER_TYPE_HARDWARE, ppDevice); if (SUCCEEDED(hr)) return hr; return Direct3DCreateDevice(D3D_DRIVER_TYPE_WARP, ppDevice); } HRESULT CreateDeviceResources() { ComPtr<ID3D11Device> device; auto hr = Direct3DCreateDevice(&device); if (FAILED(hr)) return hr; ComPtr<IDXGIDevice2> dxgiDevice; hr = device.As(&dxgiDevice); if (FAILED(hr)) return hr; ComPtr<ID2D1Device> d2d1Device; hr = m_d2dFactory->CreateDevice(dxgiDevice.Get(), &d2d1Device); if (FAILED(hr)) return hr; hr = d2d1Device->CreateDeviceContext( D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &resource.m_d2dDeviceContext); if (FAILED(hr)) return hr; ComPtr<IDXGIAdapter> dxgiAdapter; hr = dxgiDevice->GetAdapter(&dxgiAdapter); if (FAILED(hr)) return hr; ComPtr<IDXGIFactory2> dxgiFactory; hr = dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory)); if (FAILED(hr)) return hr; DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; swapChainDesc.Width = 0; swapChainDesc.Height = 0; swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; swapChainDesc.Stereo = FALSE; swapChainDesc.SampleDesc.Count = 1; swapChainDesc.SampleDesc.Quality = 0; swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swapChainDesc.BufferCount = 2; swapChainDesc.Scaling = DXGI_SCALING_NONE; swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; hr = dxgiFactory->CreateSwapChainForHwnd( device.Get(), m_hWnd, &swapChainDesc, nullptr, nullptr, &resource.m_dxgiSwapChain); if (FAILED(hr)) return hr; (void)dxgiDevice->SetMaximumFrameLatency(1); hr = InitializeRenderTarget(); if (FAILED(hr)) return hr; hr = PdfCreateRenderer(dxgiDevice.Get(), &resource.m_pdfRenderer); if (FAILED(hr)) return hr; return S_OK; } HRESULT InitializeRenderTarget() { ComPtr<ID2D1Bitmap1> d2d1RenderTarget; ComPtr<IDXGISurface2> dxgiSurface; auto hr = resource.m_dxgiSwapChain->GetBuffer( 0, IID_PPV_ARGS(&dxgiSurface)); if (FAILED(hr)) return hr; float dpiX, dpiY; m_d2dFactory->GetDesktopDpi(&dpiX, &dpiY); hr = resource.m_d2dDeviceContext->CreateBitmapFromDxgiSurface( dxgiSurface.Get(), D2D1::BitmapProperties1( D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, D2D1::PixelFormat( DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)), &d2d1RenderTarget); if (FAILED(hr)) return hr; resource.m_d2dDeviceContext->SetTarget(d2d1RenderTarget.Get()); return S_OK; } HRESULT LoadPdf() { ComPtr<IRandomAccessStream> s; auto hr = CreateRandomAccessStreamOnFile( __wargv[1], FileAccessMode_Read, IID_PPV_ARGS(&s)); if (FAILED(hr)) return hr; return LoadPdf(s.Get()); } HRESULT LoadPdf(_In_ IRandomAccessStream* s) { ComPtr<IPdfDocumentStatics> pdfDocumentsStatics; HStringReference className(RuntimeClass_Windows_Data_Pdf_PdfDocument); // 以下はRoGetActivationFactoryのラッパー auto hr = Windows::Foundation::GetActivationFactory( className.Get(), &pdfDocumentsStatics); if (FAILED(hr)) return hr; ComPtr<IAsyncOperation<PdfDocument*>> async; hr = pdfDocumentsStatics->LoadFromStreamAsync(s, &async); if (FAILED(hr)) return hr; auto callback = Callback<IAsyncOperationCompletedHandler<PdfDocument*>>( [this](_In_ IAsyncOperation<PdfDocument*>* async, AsyncStatus status) { if (status != AsyncStatus::Completed) return S_FALSE; ComPtr<IPdfDocument> doc; auto hr = async->GetResults(&doc); if (SUCCEEDED(hr)) { m_document = std::move(doc); Invalidate(); } return hr; }); hr = async->put_Completed(callback.Get()); if (FAILED(hr)) return hr; return S_OK; } HRESULT CreateDeviceIndependentResources() { D2D1_FACTORY_OPTIONS options = {}; #ifdef _DEBUG options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION; #endif auto hr = D2D1CreateFactory( D2D1_FACTORY_TYPE_MULTI_THREADED, options, m_d2dFactory.GetAddressOf()); if (FAILED(hr)) return hr; return S_OK; } // デバイス非依存リソース ComPtr<ID2D1Factory1> m_d2dFactory; // デバイス依存リソース struct DeviceResources { ComPtr<IDXGISwapChain1> m_dxgiSwapChain; ComPtr<ID2D1DeviceContext> m_d2dDeviceContext; ComPtr<IPdfRendererNative> m_pdfRenderer; } resource; // その他 ComPtr<IPdfDocument> m_document; }; int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int cmdShow) { CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); TestWindow wnd; if (!wnd.Create(nullptr, ATL::CWindow::rcDefault, TEXT("Hello, world"), WS_OVERLAPPEDWINDOW)) { std::quick_exit(-1); } wnd.ShowWindow(cmdShow); wnd.UpdateWindow(); MSG msg; while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } std::quick_exit(static_cast<int>(msg.wParam)); } |
このプログラムは、コマンドライン引数に与えられたPDFファイルを読み込み、その1ページ目(固定)を描画する内容になっています。
- PdfCreateRenderer関数を呼び出し、IPdfRendererNativeへのポインタを得る。
- PdfDocumentへのポインタを得る。
- CreateRandomAccessStreamOnFile関数を呼び出し、ファイルパスからIRandomAccessStreamへのポインタを得る。
- PdfDocument.LoadFromStreamAsync静的メソッドに上記のストリームを渡し、IPdfDocumentへのポインタを受け取る。
- 描画する。
- PdfDocument.GetPageメソッドでPdfPage(ソースコード上はIPdfPageへのポインタ)を得る。
- IPdfRendererNative.RenderPageToDeviceContextメソッドに、先ほど得たPdfPageを渡し、描画してもらう。
このサンプルではまるで何も指定していませんが、描画座標や拡大・縮小などはRenderPageToDeviceContextメソッドで指定可能です。
Direct2Dアプリとしてのコードは、前日の記事D2Dデバイスコンテキストを使う最小サンプルを元にしています。ただし、以下の点を変更しました。
- DXGI_SWAP_EFFECT_FLIP_SEQUENTIALおよびDXGI_SCALING_NONEを採用。Windows 8.1以上を前提として良いため。
- Direct3DとDirect2Dをマルチスレッドで使用する指定に変更。MSDNライブラリのどこにも書いていないけど、こうしないと不意にクラッシュする。
- D3D11_CREATE_DEVICE_SINGLETHREADEDを外す。
- D2D1_FACTORY_TYPE_SINGLE_THREADEDをD2D1_FACTORY_TYPE_MULTI_THREADEDに変える。
PdfDocument.LoadFromStreamAsyncメソッドは非同期なので、やや苦労しました。ネイティブC++では、WinRT APIの非同期の処理にMicrosoft::WRL::Callback関数を使います。ソースコード上、PdfDocumentと書くべきところとIPdfDocumentと書くべきところが混在しており、コンパイルが通るコードが出来上がるまで時間がかかりました。次はこれをC++/CXで書き直したものも作ってみたいと思います。
以上、C++製デスクトップアプリからPDF描画のAPIを叩いてみる話でした。PdfCreateRendererとCreateRandomAccessStreamOnFileというマイナーな関数2つの紹介となりました。
スポンサード リンク |
[…] [1]. Document management – Portable document format – Part 1: PDF 1.7, http://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/PDF32000_2008.pdf%5B2%5D. Dumb fuzzing XSLT engines in a smart way, http://www.nosuchcon.org/talks/2013/D1_04_Nicolas_Gregoire_XSLT_Fuzzing.pdf%5B3%5D. Abusing Adobe Reader’s JavaScript APIs,https://media.defcon.org/DEF%20CON%2023/DEF%20CON%2023%20presentations/DEFCON-23-Hariri-Spelman-Gorenc-Abusing-Adobe-Readers-JavaScript-APIs.pdf%5B4%5D. Abusing the Reader’s embedded XFA engine for reliable Exploitation,https://www.syscan360.org/slides/2016_SG_Sebastian_Apelt_Pwning_Adobe_Reader-Abusing_the_readers_embedded_XFA_engine_for_reliable_Exploitation.pdf%5B5%5D. ISO 32000-1:2008, https://www.iso.org/standard/51502.html%5B6%5D. JavaScript for Acrobat API Reference,http://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/js_api_reference.pdf%5B7%5D. XML Forms Architecture (XFA) Specification, http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.364.2157&rep=rep1&type=pdf%5B8%5D. FormCalc User Reference, http://help.adobe.com/en_US/livecycle/es/FormCalc.pdf%5B9%5D. Zero Day Initiative’s published advisories, http://www.zerodayinitiative.com/advisories/published/%5B10%5D. Chromium issue tracker,https://bugs.chromium.org/p/chromium/issues/list?can=1&q=Type=%22Bug-Security%22%5B11%5D. Adobe Security Bulletins and Advisories, https://helpx.adobe.com/security.html#acrobat%5B12%5D. Official fuzzers for PDFium, https://pdfium.googlesource.com/pdfium/+/refs/heads/master/testing/libfuzzer/%5B13%5D. OpenJPEG data, https://github.com/uclouvain/openjpeg-data%5B14%5D. Seclists, http://seclists.org/oss-sec/2016/q2/623%5B15%5D. LibTIFF Issues Lead To Code Execution, http://blog.talosintelligence.com/2016/10/LibTIFF-Code-Execution.html%5B16%5D. Using WinRT API to render PDF, http://dev.activebasic.com/egtra/2015/12/24/853/ […]