Quantcast
Viewing all 123 articles
Browse latest View live

PDFにXMPメタデータファイルを埋め込むツールを作った

PDFに埋め込めるメタデータとしてXMP (Extensible Metadata Platform)というものがあります。

XMPファイルは、creativecommons.orgのライセンス付与のページ(Choose a License)で、作れるようになっています。具体的には、「他の人があなたへのクレジットを表示するのを助けてください! (Help others attribute you!)」のところで、各情報を入力し、「ライセンス・マーク (License mark)」でXMPをを選択すると、ダウンロードのリンクが現れます。

ただ、このXMPファイルをPDFに埋め込むツールが意外と全然見当たりません。XMPファイルを埋め込むことだけを行えるアプリが見つからないんです。そこで作ってみました: egtra/embed-xmp (GitHub)。実行ファイルのダウンロードはReleases · egtra/embed-xmpです。

コマンドラインアプリで、こんな感じに引数を指定します。

EmbedXMP (入力PDFファイル) (入力XMPファイル) (出力XMPファイル)

実行には.NET Framework 4.5.2が必要です。PDFファイルの操作にはiTextSharpを使いました。

参考: XMP Specification日本語版 (Adobe)

PDFにXMPメタデータファイルを埋め込むツールを作った is a post from: イグトランスの頭の中


WTL::CAppModuleを使わない書き方

私、WTLでそんなに大きなアプリケーションを作ったことがないので、WTL::CAppModuleの必要性を感じたことがないんです。あと、基底クラスであるATL::CComModuleは、ATL 7より非推奨になっています。それも、なんとなく避けたい理由の1つです。

では、WTL::CMessageFilterやWTL::CIdleHandlerを使いたいときはどうするかというと、こうです。WinMain関数かどこかで定義したWTL::CMessageLoopのオブジェクトを直接使います。WTL::CAppModuleのAddMessageLoopとGetMessageLoopのメンバー関数を使わない、それだけのことです。

以下のサンプルプログラムでは、WTL::CMessageFilterだけしか登場しませんが、WTL::CIdleHandlerも同じ要領です。

#define UNICODE
#define _UNIDODE
 
#define WINVER 0x0600
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
 
#include <windows.h>
 
#include <atlbase.h>
#include <atlwin.h>
#include <atltypes.h>
 
#include <atlapp.h>
#include <atlcrack.h>
#include <atlctrls.h>
 
// https://msdn.microsoft.com/ja-jp/library/bb531404.aspx
#ifdef UNICODE
  #if defined _M_IX86
    #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")
  #elif defined _M_IA64
    #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"")
  #elif defined _M_X64
    #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"")
  #else
    #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
  #endif
#endif
 
class TestWindow :
  public ATL::CWindowImpl<TestWindow>,
  public WTL::CMessageFilter
{
public:
  DECLARE_WND_CLASS(TEXT("Test Window Class"));
 
  bool Initialize(int cmdShow, _Inout_ WTL::CMessageLoop& msgLoop)
  {
    if (!Create(nullptr, CRect(0, 0, 200, 100),
      TEXT("Hello, world"), WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU))
    {
      return false;
    }
 
    ShowWindow(cmdShow);
    UpdateWindow();
 
    msgLoop.AddMessageFilter(this);
    return true;
  }
 
  BOOL PreTranslateMessage(_In_ MSG* msg) override
  {
    return IsDialogMessage(msg);
  }
 
  BEGIN_MSG_MAP(TestWindow)
    COMMAND_ID_HANDLER_EX(IDOK, OnOK)
    COMMAND_ID_HANDLER_EX(IDCANCEL, OnCancel)
    MSG_WM_CREATE(OnCreate)
    MSG_WM_DESTROY(OnDestroy)
  END_MSG_MAP()
 
private:
  void OnOK(UINT, int, HWND)
  {
    SendMessage(WM_CLOSE);
  }
 
  void OnCancel(UINT, int, HWND)
  {
    SendMessage(WM_CLOSE);
  }
 
  LRESULT OnCreate(const CREATESTRUCT*)
  {
    constexpr DWORD ButtonStyle =
      WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON;
    constexpr DWORD ButtonExStyle = WS_EX_NOPARENTNOTIFY;
    m_buttonOK.Create(
      *this, CRect(20, 20, 80, 50), L"OK",
      ButtonStyle, ButtonExStyle, IDOK);
    m_buttonCancel.Create(
      *this, CRect(100, 20, 160, 50), L"Cancel",
      ButtonStyle, ButtonExStyle, IDCANCEL);
 
    m_font = WTL::AtlCreateControlFont();
 
    m_buttonOK.SetFont(m_font);
    m_buttonCancel.SetFont(m_font);
 
    return 0;
  }
 
  void OnDestroy()
  {
    PostQuitMessage(0);
  }
 
  WTL::CFont m_font;
  WTL::CButton m_buttonOK;
  WTL::CButton m_buttonCancel;
};
 
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int cmdShow)
{
  if (!WTL::AtlInitCommonControls(ICC_STANDARD_CLASSES))
  {
    return -1;
  }
 
  WTL::CMessageLoop msgLoop;
  TestWindow wnd;
  if (!wnd.Initialize(cmdShow, msgLoop))
  {
    return -1;
  }
 
  return msgLoop.Run();
}

このコードは、Visual C++ 2015とWTL 9.1.5321 Finalでコンパイル・動作確認しました。

なお、ATL 7で導入されたATL::CAtlExeModuleTやATL::CAtlDllModuleTを活用しているプログラムに、あとからWTLを導入しようという場合、WTL::CAppModuleの入る隙がありません。そんなときも、この書き方が適用できます。

そんなわけで、邪道かもしれない、WTL::CAppModuleを使わないWTLプログラミングの話でした。ちなみに、昔書いた空のウィンドウを表示するだけのWindowsアプリケーション (WTL)でも、WTL::CAppModuleを使っていません。当時(それ以前)から、この書き方をずっとやっていたわけです。

WTL::CAppModuleを使わない書き方 is a post from: イグトランスの頭の中

ATL::CAxWindowでWebBrowserコントロールを使ってみる

C++コードだけで、なおかつATL::CAxWindowクラス(MSDNライブラリ)を使って、WebBrowserコントロールを使うプログラムを作ってみました。というのも、ATLでWebBrowserコントロールを使うコードとして、

  • ダイアログで作るもの
  • AtlAxWinInit関数とAtlAxWinウィンドウクラス(正確にはATLのバージョンによって少し異なる名称)を使うもの

しか見当たらなかったので、自分で書いてみることにしました。

ATL::CAxWindowは、ATL::CWindowから派生して、いくつかメンバー関数が増えたクラスです。その点では、WTL::CButtonやWTL::CEditなどと同様な存在です。ウィンドウクラスAtlAxWinのウィンドウを持ち、また、その中でActiveXコントロールを動かす機能を持っています。

そんなわけで、ATL/WTLを使って作るプログラムにおいて、ウィンドウ内にActiveXコントロールを載せようという場合、まずATL::CAxWindowを使ってみれば良いというわけです。

#define UNICODE
#define _UNIDODE
 
#define WINVER 0x0501
#define _ATL_XP_TARGETING
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
 
#include <cstdlib>
 
#include <windows.h>
 
#include <atlbase.h>
#include <atlwin.h>
#include <atltypes.h>
 
#include <atlapp.h>
#include <atlcrack.h>
 
class Module : public ATL::CAtlExeModuleT<Module>
{
};
 
Module module;
 
class TestWindow :
  public ATL::CWindowImpl<TestWindow>,
  public WTL::CMessageFilter
{
public:
  int Run(int cmdShow, _Inout_ WTL::CMessageLoop& msgLoop)
  {
    if (!Create(nullptr, ATL::CWindow::rcDefault,
      TEXT("Hello, world"), WS_OVERLAPPEDWINDOW))
    {
      return -1;
    }
 
    ShowWindow(cmdShow);
    UpdateWindow();
 
    msgLoop.AddMessageFilter(this);
    return msgLoop.Run();
  }
 
  BOOL PreTranslateMessage(_In_ MSG* pmsg) override
  {
    if (m_webBrowser.SendMessage(
      WM_FORWARDMSG, 0, reinterpret_cast<LPARAM>(pmsg)))
    {
      return TRUE;
    }
    return IsDialogMessage(pmsg);
  }
 
  DECLARE_WND_CLASS(TEXT("Test Window Class"));
 
  BEGIN_MSG_MAP(TestWindow)
    MSG_WM_SIZE(OnSize)
    MSG_WM_SETFOCUS(OnSetFocus)
    MSG_WM_CREATE(OnCreate)
    MSG_WM_DESTROY(OnDestroy)
  END_MSG_MAP()
 
private:
  void OnSize(UINT, SIZE size)
  {
    m_webBrowser.ResizeClient(size.cx, size.cy);
  }
 
  void OnSetFocus(HWND)
  {
    m_webBrowser.SetFocus();
  }
 
  LRESULT OnCreate(const CREATESTRUCT*)
  {
    constexpr DWORD IDC_WEBBROWSER_CONTROL = 1;
    RECT empty{};
    m_webBrowser.Create(m_hWnd, empty, L"https://www.google.co.jp",
      WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | WS_GROUP, 0,
      IDC_WEBBROWSER_CONTROL);
 
    return 0;
  }
 
  void OnDestroy()
  {
    PostQuitMessage(0);
  }
 
  ATL::CAxWindow m_webBrowser;
};
 
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int cmdShow)
{
  if (FAILED(OleInitialize(nullptr)))
  {
    std::quick_exit(-1);
  }
 
  WTL::CMessageLoop msgLoop;
  TestWindow wnd;
  std::quick_exit(wnd.Run(cmdShow, msgLoop));
}

WebBrowserコントロールのCLSIDなどを指定している箇所が全くありませんが、Createメンバー関数でURLを指定しているためです。なお、ProgIDやCLSIDを文字列で指定すれば、他のコントロールを作成できます。

なお、IWebBrowser2へのポインタを得るには、次のようにQueryControlメンバー関数を使用します。

ATL::CComPtr<IWebBrowser2> wb;
m_webBrowser.QueryControl(&wb);

PreTranslateMessage関数の実装が特徴的です。これはどうしても分からなかったので、ATL::CAxDialogImplの実装を参考にしました。2016年9月22日追記: 自分が最初に書いたコードが動かない原因が分かった(単純な誤りだった)ので、PreTranslateMessage関数の実装を単純なものに変えました。

以上、ATL::CAxWindowを使ってWebBrowserコントロールを使ってみるサンプルプログラムでした。これは最低限の実装なので、実際活用するにはここからいろいろ書き足していくことになると思います。

ところで、IEコントロールという呼び方もよく見かけますし、私も使いますが、この記事ではWebBrowserコントロールに統一しました。ExDisp.idlでcoclass WebBrowser (CLSID_WebBrowser)と定義されていて、MSDNライブラリでもWebBrowser Controlと表記されているためです。

ATL::CAxWindowでWebBrowserコントロールを使ってみる is a post from: イグトランスの頭の中

CreateDispTypeInfoとCreateStdDispatchを使ってみる

OBJREFモニカーなど、過去複数の記事でIDisptachを自前実装しました。が、自前実装しないで済むならそれに越したことはないと思っています。そこで、今回はCreateDispTypeInfo関数とCreateStdDispatch関数を使うプログラムを作ってみることにしました。

というわけで、上記過去記事で作ったHogeクラスを基に書き換えます。今回は単独で動作を見られるよう、VBScriptのプログラムを起動する処理を廃止しています。

#define _ATL_NO_AUTOMATIC_NAMESPACE
 
#include <iostream>
#include <windows.h>
#include <atlbase.h>
#include <atlcom.h>
#include <atlutil.h>
#include <boost/implicit_cast.hpp>
 
class Module : public ATL::CAtlExeModuleT<Module> {};
Module module;
 
interface DECLSPEC_UUID("6ae160da-9e37-4f46-83bd-bf166a82f871") IFunc :
  IUnknown
{
  virtual void STDMETHODCALLTYPE Func() = 0;
};
 
class ATL_NO_VTABLE Hoge
  : public ATL::CComObjectRootEx<ATL::CComMultiThreadModel>
  , public ATL::CComCoClass<Hoge>
  , public IFunc
{
public:
  BEGIN_COM_MAP(Hoge)
    COM_INTERFACE_ENTRY(IFunc)
    COM_INTERFACE_ENTRY_AGGREGATE(__uuidof(IDispatch), m_pdisp.p)
  END_COM_MAP()
 
  HRESULT FinalConstruct()
  {
    auto hrTI = InitializeTypeInfo();
    ATLENSURE_RETURN_HR(SUCCEEDED(hrTI), hrTI);
    // 集成に対応させるなら、DECLARE_GET_CONTROLLING_UNKNOWNを使用した上で、
    // GetUnknown()をGetControllingUnknown()に変更する。
    return CreateStdDispatch(
      GetUnknown(), boost::implicit_cast<IFunc*>(this),
      s_typeInfo, &m_pdisp);
  }
 
  void STDMETHODCALLTYPE Func() override
  {
    std::cout << "Hoge::Invoke" << std::endl;
  }
 
private:
  ATL::CComPtr<IUnknown> m_pdisp;
 
  static HRESULT InitializeTypeInfo()
  {
    if (s_typeInfo != nullptr)
    {
      return S_OK;
    }
    static METHODDATA md[]
    {
      {
        L"Func",
        nullptr,
        DISPID_VALUE,
        3, // 0~2はIUnknownの各メソッドのため、Funcのインデックスは3となる。
        CC_STDCALL,
        0,
        DISPATCH_METHOD,
        VT_EMPTY
      },
    };
    static INTERFACEDATA id
    {
      md, ARRAYSIZE(md),
    };
    return CreateDispTypeInfo(&id, LOCALE_SYSTEM_DEFAULT, &s_typeInfo);
  }
 
  static ATL::CComPtr<ITypeInfo> s_typeInfo;
};
 
ATL::CComPtr<ITypeInfo> Hoge::s_typeInfo;
 
int main()
{
  try
  {
    ATLENSURE_SUCCEEDED(CoInitializeEx(
      nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE));
 
    ATL::CComPtr<IDispatch> hoge;
    ATLENSURE_SUCCEEDED(Hoge::CreateInstance(&hoge));
 
    hoge.Invoke0(static_cast<DISPID>(DISPID_VALUE));
  }
  catch (const ATL::CAtlException& e)
  {
    std::clog << std::hex << std::showbase;
    std::clog << e.m_hr << ' ';
    std::clog << ATL::AtlGetErrorDescription(e) << std::endl;
  }
 
  CoUninitialize();
}

INTERFACEDATAとMETHODDATAの組み立てが面倒です。なお、仮引数を持たせるなら、PARAMDATAも必要です。C++自体のメタプログラミング能力が向上して、全自動で組み立てられるようになれば良いのですが。

以下、MSDNライブラリの関連する項目へのリンクです。

CreateDispTypeInfoとCreateStdDispatchを使ってみる is a post from: イグトランスの頭の中

CreateDispTypeInfoとATL::IDispatchImplを使ってみる

前回のCreateDispTypeInfoとCreateStdDispatchを使ってみるを書いた後、ATL::IDispatchImplとの組み合わせでも作れるのではないかと思い、やってみることにしました。

#define _ATL_NO_AUTOMATIC_NAMESPACE
 
#include <iostream>
#include <windows.h>
#include <atlbase.h>
#include <atlcom.h>
#include <atlutil.h>
 
class Module : public ATL::CAtlExeModuleT<Module> {};
Module module;
 
// ダミー
// {34F91FCB-7237-4EA7-B5E5-3FB7FDDD87AC}
static constexpr CLSID CLSID_Hoge = { 0x34f91fcb, 0x7237, 0x4ea7, { 0xb5, 0xe5, 0x3f, 0xb7, 0xfd, 0xdd, 0x87, 0xac } };
 
interface DECLSPEC_UUID("6ae160da-9e37-4f46-83bd-bf166a82f871") IFunc2
  : IDispatch
{
  virtual void STDMETHODCALLTYPE Func() = 0;
};
 
class ATL_NO_VTABLE Hoge
  : public ATL::CComObjectRootEx<ATL::CComMultiThreadModel>
  , public ATL::CComCoClass<Hoge>
  , public ATL::IDispatchImpl<IFunc2, &__uuidof(IFunc2)>
{
public:
  BEGIN_COM_MAP(Hoge)
    COM_INTERFACE_ENTRY(IFunc2)
    COM_INTERFACE_ENTRY(IDispatch)
  END_COM_MAP()
 
  DECLARE_NO_REGISTRY()
 
  static void WINAPI ObjectMain(bool starting)
  {
    if (!starting)
    {
      _tihclass::Cleanup(reinterpret_cast<DWORD_PTR>(&_tih));
    }
  }
 
  HRESULT FinalConstruct()
  {
    return InitializeTypeInfo();
  }
 
  void STDMETHODCALLTYPE Func() override
  {
    std::cout << "Hoge::Invoke" << std::endl;
  }
 
  DECLARE_NOT_AGGREGATABLE(Hoge)
 
private:
  static HRESULT InitializeTypeInfo()
  {
    auto& ti = _tih.m_pInfo;
    if (ti != nullptr)
    {
      return S_OK;
    }
    static METHODDATA md[]
    {
      {
        const_cast<OLECHAR*>(L"Func"),
        nullptr,
        DISPID_VALUE,
        7, // 0~2はIUnknownの各メソッド、3~6はIDispatchの各メソッドのため。
        CC_STDCALL,
        0,
        DISPATCH_METHOD,
        VT_EMPTY
      },
    };
    static INTERFACEDATA id
    {
      md, ARRAYSIZE(md),
    };
    return CreateDispTypeInfo(&id, LOCALE_SYSTEM_DEFAULT, &ti);
  }
};
 
OBJECT_ENTRY_NON_CREATEABLE_EX_AUTO(CLSID_Hoge, Hoge)
 
int main()
{
  try
  {
    ATLENSURE_SUCCEEDED(CoInitializeEx(
      nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE));
 
    ATL::CComPtr<IDispatch> hoge;
    ATLENSURE_SUCCEEDED(Hoge::CreateInstance(&hoge));
 
    hoge.Invoke0(static_cast<DISPID>(DISPID_VALUE));
  }
  catch (const ATL::CAtlException& e)
  {
    std::clog << std::hex << std::showbase;
    std::clog << e.m_hr << ' ';
    std::clog << ATL::AtlGetErrorDescription(e) << std::endl;
  }
 
  CoUninitialize();
}

コード中に現れるtihclass_tihは、ATL::IDispatchImplのprotectedメンバーです。

後始末をObjectMain関数でやることにしたため、OBJECT_ENTRY_NON_CREATEABLE_EX_AUTOを使用しています。

実験としてやってみたことです。実用性は考えていません。特段、コード量が減るわけでもないので、私は前回の方法でいいやと思いました。

CreateDispTypeInfoとATL::IDispatchImplを使ってみる is a post from: イグトランスの頭の中

ATL::IDispEventSimpleImplでIDispatchを実装する

引き続き、C++でIDispatchを実装する話です。

ATLにおいて、簡易的なIDispatchの実装クラスとしてATL::IDispEventSimpleImplがあります。本来の目的は、IConnectionPointなどによるイベント処理の実装です。イベントの通知に使われるインタフェース(アウトゴーイングインタフェース)がIDispatchベース (dispinterface)であることがよくあり、その実装のためのものです。

しかし、今回はただIDispatch (dispinterface)実装のためだけに使います。今回はコードを2つ掲載します。

まず、コード例1つ目です。ATL::CComObjectRootExを使わない場合です。参照カウントが不要ならATL::CComObjectRootExすら不要なのです。ATL::IDispEventSimpleImpl内部では、常に1を返すAddRefとReleaseが定義されているためです。

#include <iostream>
#include <cstdlib>
#include <windows.h>
#include <atlbase.h>
#include <atlcom.h>
#include <boost/implicit_cast.hpp>
 
constexpr UINT ID_Hoge = 0;
 
class Hoge :
  public ATL::IDispEventSimpleImpl<ID_Hoge, Hoge, &IID_NULL>
{
public:
  static auto GetFuncInfo()
  {
    static ATL::_ATL_FUNC_INFO FuncInfo = { CC_STDCALL, VT_EMPTY, 0, {} };
    return &FuncInfo;
  }
 
  BEGIN_SINK_MAP(Hoge)
    SINK_ENTRY_INFO(ID_Hoge, IID_NULL, DISPID_VALUE, &Hoge::Func, GetFuncInfo())
  END_SINK_MAP()
 
  void STDMETHODCALLTYPE Func()
  {
    std::cout << "Hoge::Invoke" << std::endl;
  }
 
  IDispatch* GetDispatch()
  {
    return reinterpret_cast<IDispatch*>(
      boost::implicit_cast<IDispEventSimpleImpl*>(this));
  }
};
 
int main()
{
  if (FAILED(OleInitialize(nullptr)))
  {
    std::quick_exit(-1);
  }
 
  Hoge obj;
  ATL::CComPtr<IDispatch> hoge = obj.GetDispatch();
  auto hr = hoge.Invoke0(static_cast<DISPID>(DISPID_VALUE));
  if (FAILED(hr))
  {
    std::clog << "Failed: " << std::hex << hr << std::endl;
  }
  std::quick_exit(0);
}

2つ目は、ATL::CComObjectRootExと併用する例です。

#include <iostream>
#include <cstdlib>
#include <windows.h>
#include <atlbase.h>
#include <atlcom.h>
 
constexpr UINT ID_Hoge = 0;
 
class Hoge :
  public ATL::CComObjectRootEx<ATL::CComSingleThreadModel>,
  public ATL::IDispEventSimpleImpl<ID_Hoge, Hoge, &IID_NULL>
{
public:
  static auto GetFuncInfo()
  {
    static ATL::_ATL_FUNC_INFO FuncInfo = { CC_STDCALL, VT_EMPTY, 0, {} };
    return &FuncInfo;
  }
 
  BEGIN_COM_MAP(Hoge)
    COM_INTERFACE_ENTRY_IID(IID_IDispatch, IDispEventSimpleImpl)
  END_COM_MAP()
 
  BEGIN_SINK_MAP(Hoge)
    SINK_ENTRY_INFO(ID_Hoge, IID_NULL, DISPID_VALUE, &Hoge::Func, GetFuncInfo())
  END_SINK_MAP()
 
  void STDMETHODCALLTYPE Func()
  {
    std::cout << "Hoge::Invoke" << std::endl;
  }
};
 
int main()
{
  if (FAILED(OleInitialize(nullptr)))
  {
    std::quick_exit(-1);
  }
 
  ATL::CComObjectStackEx<Hoge> obj;
  ATL::CComQIPtr<IDispatch> hoge = obj.GetUnknown();
  auto hr = hoge.Invoke0(static_cast<DISPID>(DISPID_VALUE));
  if (FAILED(hr))
  {
    std::clog << "Failed: " << std::hex << hr << std::endl;
  }
  std::quick_exit(0);
}

いずれの例でも、BEGIN_SINK_MAP~END_SINK_MAPで、DISPIDと受け付ける関数の対応を記述しています。

ATL::IDispEventSimpleImplの目的外利用という、邪道な感じのIDispatch実装方法の話でした。

ATL::IDispEventSimpleImplでIDispatchを実装する is a post from: イグトランスの頭の中

WebBrowserコントロールでJavaScriptほか色々禁止する (1)

少し前に書いたATL::CAxWindowでWebBrowserコントロールを使ってみるの続きです。ATL::CAxWindowでWebBrowserコントロールを使っている場合に、JavaScriptなどを禁止する方法です。まずはIInternetSecurityManagerなどを使う方法その1です。

IInternetSecurityManagerおよびそこから派生するインタフェースは、インターネットオプションのセキュリティゾーンの設定を表現するインタフェースです。セキュリティゾーンの設定を見たことがないなら、IE のセキュリティ ゾーンについて – Japan IE Support Team Blogなどを読んでみてください。

IInternetSecurityManagerを自分で実装すると、インターネットオプションの設定より優先して、URLごとに、ゾーンやゾーン内の個々の項目について選択できます。

WebBrowserコンポーネントにIInternetSecurityManagerを渡すには、IServiceProviderを使います。そのIServiceProviderを渡すやり方は2つあります。

  • サイトオブジェクトに対してQueryInterfaceしてくるので、そこでオブジェクトを返す。
  • IProfferServiceでIServiceProviderを提供する。

今回は、1つ目の方法です。さて、そのQueryInterfaceを受け付けるのは誰かというと、ATL::CAxWindowの内部で作成されるATL::CAxHostWindowクラスのオブジェクトです。そこに手を入れられないか調べたところ、ATL::CAxHostWindowはIObjectWithSiteを実装しており、ここにIServiceProviderを実装したオブジェクトへのポインタを渡せば良いということが分かりました。

#define UNICODE
#define _UNICODE
 
#define WINVER 0x0501
#define _ATL_XP_TARGETING
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
 
#include <cstdlib>
 
#include <windows.h>
 
#include <atlbase.h>
#include <atltypes.h>
#include <atlwin.h>
 
#include <atlapp.h>
#include <atlcrack.h>
 
class Module : public ATL::CAtlExeModuleT<Module>
{
};
 
Module module;
 
class ATL_NO_VTABLE UntrustedSecurityManager :
  public ATL::CComObjectRootEx<ATL::CComSingleThreadModel>,
  public IInternetSecurityManagerEx2
{
public:
  BEGIN_COM_MAP(UntrustedSecurityManager)
    COM_INTERFACE_ENTRY(IInternetSecurityManager)
    COM_INTERFACE_ENTRY(IInternetSecurityManagerEx)
    COM_INTERFACE_ENTRY(IInternetSecurityManagerEx2)
  END_COM_MAP()
 
  IFACEMETHOD(SetSecuritySite)(
    _In_opt_ IInternetSecurityMgrSite* /*pSite*/) override
  {
    return INET_E_DEFAULT_ACTION;
  }
 
  IFACEMETHOD(GetSecuritySite)(
    _Out_ IInternetSecurityMgrSite** /*ppSite*/) override
  {
    return INET_E_DEFAULT_ACTION;
  }
 
  IFACEMETHOD(MapUrlToZone)(
    _In_ LPCWSTR /*pwszUrl*/,
    _Out_ DWORD* pdwZone,
    DWORD /*dwFlags*/) override
  {
    *pdwZone = URLZONE_UNTRUSTED;
    return S_OK;
  }
 
  IFACEMETHOD(GetSecurityId)(
    _In_ LPCWSTR /*pwszUrl*/,
    _Out_writes_bytes_to_(MAX_SIZE_SECURITY_ID, *pcbSecurityId) BYTE* /*pbSecurityId*/,
    _Inout_ _At_(*pcbSecurityId, _In_range_(>= , MAX_SIZE_SECURITY_ID) _Out_range_(0, MAX_SIZE_SECURITY_ID)) DWORD* /*pcbSecurityId*/,
    _In_ DWORD_PTR /*dwReserved*/) override
  {
    return INET_E_DEFAULT_ACTION;
  }
 
  IFACEMETHOD(ProcessUrlAction)(
    _In_ LPCWSTR pwszUrl,
    DWORD dwAction,
    _Out_writes_(cbPolicy) BYTE* pPolicy,
    DWORD cbPolicy,
    _In_opt_ BYTE* pContext,
    DWORD cbContext,
    DWORD dwFlags,
    DWORD dwReserved) override
  {
    DWORD flags;
    return ProcessUrlActionEx(pwszUrl, dwAction, pPolicy, cbPolicy, pContext, cbContext, dwFlags, dwReserved, &flags);
  }
 
  IFACEMETHOD(QueryCustomPolicy)(
    _In_ LPCWSTR /*pwszUrl*/,
    _In_ REFGUID /*guidKey*/,
    /*__RPC__deref_out_ecount_full_opt(*pcbPolicy)*/ BYTE** /*ppPolicy*/,
    _Out_ DWORD* /*pcbPolicy*/,
    _In_ BYTE* /*pContext*/,
    DWORD /*cbContext*/,
    DWORD /*dwReserved*/) override
  {
    return INET_E_DEFAULT_ACTION;
  }
 
  IFACEMETHOD(SetZoneMapping)(
    DWORD /*dwZone*/,
    _In_ LPCWSTR /*lpszPattern*/,
    DWORD /*dwFlags*/) override
  {
    return INET_E_DEFAULT_ACTION;
  }
 
  IFACEMETHOD(GetZoneMappings)(
    DWORD /*dwZone*/,
    __RPC__deref_out_opt IEnumString** /*ppenumString*/,
    DWORD /*dwFlags*/) override
  {
    return INET_E_DEFAULT_ACTION;
  }
 
  IFACEMETHOD(ProcessUrlActionEx)(
    _In_ LPCWSTR /*pwszUrl*/,
    DWORD /*dwAction*/,
    __RPC__out_ecount_full(cbPolicy) BYTE* pPolicy,
    DWORD cbPolicy,
    _In_ BYTE* /*pContext*/,
    DWORD /*cbContext*/,
    DWORD /*dwFlags*/,
    DWORD /*dwReserved*/,
    _Out_ DWORD* pdwOutFlags) override
  {
    if (pdwOutFlags != nullptr)
    {
      *pdwOutFlags = PUAFOUT_DEFAULT;
    }
    if (cbPolicy == sizeof(DWORD))
    {
      *reinterpret_cast<DWORD*>(pPolicy) = URLPOLICY_DISALLOW;
      return S_FALSE;
    }
    return INET_E_DEFAULT_ACTION;
  }
 
  IFACEMETHOD(MapUrlToZoneEx2)(
    _In_ IUri* /*pUri*/,
    _Out_ DWORD* pdwZone,
    DWORD /*dwFlags*/,
    _Outptr_opt_ LPWSTR* ppwszMappedUrl,
    _Out_opt_ DWORD* pdwOutFlags) override
  {
    if (ppwszMappedUrl != nullptr)
    {
      *ppwszMappedUrl = nullptr;
    }
    if (pdwOutFlags != nullptr)
    {
      *pdwOutFlags = 0;
    }
    if (pdwZone != nullptr)
    {
      *pdwZone = URLZONE_UNTRUSTED;
    }
    return S_OK;
  }
 
  IFACEMETHOD(ProcessUrlActionEx2)(
    _In_ IUri* /*pUri*/,
    DWORD dwAction,
    __RPC__out_ecount_full(cbPolicy) BYTE* pPolicy,
    DWORD cbPolicy,
    _In_opt_ BYTE* pContext,
    DWORD cbContext,
    DWORD dwFlags,
    DWORD_PTR dwReserved,
    _Out_ DWORD* pdwOutFlags) override
  {
    return ProcessUrlActionEx(L"", dwAction, pPolicy, cbPolicy, pContext, cbContext, dwFlags, dwReserved, pdwOutFlags);
  }
 
  IFACEMETHOD(GetSecurityIdEx2)(
    _In_ IUri* /*pUri*/,
    /*_Out_writes_bytes_to_(MAX_SIZE_SECURITY_ID, *pcbSecurityId)*/ BYTE* /*pbSecurityId*/,
    /*_Inout_ _At_(*pcbSecurityId, _In_range_(>= , MAX_SIZE_SECURITY_ID) _Out_range_(0, MAX_SIZE_SECURITY_ID))*/ DWORD* /*pcbSecurityId*/,
    _In_ DWORD_PTR /*dwReserved*/) override
  {
    return INET_E_DEFAULT_ACTION;
  }
 
  IFACEMETHOD(QueryCustomPolicyEx2)(
    _In_ IUri* /*pUri*/,
    _In_ REFGUID /*guidKey*/,
    __RPC__deref_out_ecount_full_opt(*pcbPolicy) BYTE** /*ppPolicy*/,
    _Out_ DWORD* /*pcbPolicy*/,
    _In_ BYTE* /*pContext*/,
    DWORD /*cbContext*/,
    DWORD_PTR /*dwReserved*/) override
  {
    return INET_E_DEFAULT_ACTION;
  }
};
 
class ATL_NO_VTABLE WebBrowserServiceProvider :
  public ATL::CComObjectRootEx<ATL::CComSingleThreadModel>,
  public IServiceProvider
{
public:
  BEGIN_COM_MAP(WebBrowserServiceProvider)
    COM_INTERFACE_ENTRY(IServiceProvider)
  END_COM_MAP()
 
public:
  IFACEMETHOD(QueryService)(
    _In_ REFGUID guidService,
    _In_ REFIID riid,
    _COM_Outptr_ void** ppv) override
  {
    if (guidService == SID_SInternetSecurityManager
      || guidService == SID_SInternetSecurityManagerEx
      || guidService == SID_SInternetSecurityManagerEx2)
    {
      return m_untrustedSecurityManager.QueryInterface(riid, ppv);
    }
    return E_NOINTERFACE;
  }
 
private:
  ATL::CComObjectStackEx<UntrustedSecurityManager> m_untrustedSecurityManager;
};
 
ATL::CComObjectStackEx<WebBrowserServiceProvider> g_webBrowserServiceProvider;
 
class TestWindow :
  public ATL::CWindowImpl<TestWindow>,
  public WTL::CMessageFilter
{
public:
  int Run(int cmdShow, _Inout_ WTL::CMessageLoop& msgLoop)
  {
    if (!Create(nullptr, ATL::CWindow::rcDefault,
      TEXT("Hello, world"), WS_OVERLAPPEDWINDOW))
    {
      return -1;
    }
 
    ShowWindow(cmdShow);
    UpdateWindow();
 
    msgLoop.AddMessageFilter(this);
    return msgLoop.Run();
  }
 
  BOOL PreTranslateMessage(_In_ MSG* pmsg) override
  {
    if (m_webBrowser.IsWindow()
      && m_webBrowser.SendMessage(
      WM_FORWARDMSG, 0, reinterpret_cast<LPARAM>(pmsg)))
    {
      return TRUE;
    }
    return IsDialogMessage(pmsg);
  }
 
  DECLARE_WND_CLASS(TEXT("Test Window Class"));
 
  BEGIN_MSG_MAP(TestWindow)
    MSG_WM_SIZE(OnSize)
    MSG_WM_SETFOCUS(OnSetFocus)
    MSG_WM_CREATE(OnCreate)
    MSG_WM_DESTROY(OnDestroy)
  END_MSG_MAP()
 
private:
  void OnSize(UINT, SIZE size)
  {
    m_webBrowser.ResizeClient(size.cx, size.cy);
  }
 
  void OnSetFocus(HWND)
  {
    m_webBrowser.SetFocus();
  }
 
  LRESULT OnCreate(const CREATESTRUCT*)
  {
    constexpr DWORD IDC_WEBBROWSER_CONTROL = 1;
    RECT empty{};
    m_webBrowser.Create(m_hWnd, empty, L"",
      WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | WS_GROUP | WS_VSCROLL | WS_HSCROLL, 0,
      IDC_WEBBROWSER_CONTROL);
 
    ATL::CComPtr<IObjectWithSite> host;
    if (SUCCEEDED(m_webBrowser.QueryHost(&host)))
    {
      ATLENSURE_RETURN_VAL(SUCCEEDED(
        host->SetSite(&g_webBrowserServiceProvider)), -1);
    }
 
    ATLENSURE_RETURN_VAL(SUCCEEDED(
      m_webBrowser.CreateControl(L"http://enable-javascript.com/ja/")), -1);
 
    return 0;
  }
 
  void OnDestroy()
  {
    PostQuitMessage(0);
  }
 
  ATL::CAxWindow m_webBrowser;
};
 
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int cmdShow)
{
  if (FAILED(OleInitialize(nullptr)))
  {
    std::quick_exit(-1);
  }
 
  WTL::CMessageLoop msgLoop;
  TestWindow wnd;
  std::quick_exit(wnd.Run(cmdShow, msgLoop));
}

IServiceProviderはTestWindowクラスに実装しても良かったのですが、その理解しやすいかと思い、今回は分けてみました。

特徴的な点は以下のところです。

  • セキュリティゾーンの実装(UntrustedSecurityManagerクラス)。
    • IInternetSecurityManagerのMapUrlToZoneとProcessUrlAction
    • IInternetSecurityManagerExのProcessUrlActionEx
    • IInternetSecurityManagerEx2のMapUrlToZoneEx2とProcessUrlActionEx2

    MapUrlToZoneなどでは「制限付きサイト」ゾーンのURLZONE_UNTRUSTEDを返しています。また、念のため、ProcessUrlActionのほうではURLPOLICY_DISALLOWを返し、あらゆる機能を禁止する指定としています。

  • UntrustedSecurityManagerを提供するIServiceProvider実装(WebBrowserServiceProviderクラス。
  • WebBrowserコントロールからのQueryServiceにWebBrowserServiceProviderを使ってもらうよう、ATL::CAxHostWindowに設定する処理。
    ATL::CComPtr<IObjectWithSite> host;
    if (SUCCEEDED(m_webBrowser.QueryHost(&host)))
    {
      ATLENSURE_RETURN_VAL(SUCCEEDED(
        host->SetSite(&g_webBrowserServiceProvider)), -1);
    }

実はこのサンプル、IInternetSecurityManagerExやIInternetSecurityManagerEx2の機能を使っていないので、その2つのインタフェースの実装を削除(SID_SInternetSecurityManagerExとSID_SInternetSecurityManagerEx2も削除)しても動作します。サンプルコードとして、一応含めることにしました。

IProfferServiceを使う方法は次回にします。

MSDNライブラリへのリンク:

WebBrowserコントロールでJavaScriptほか色々禁止する (1) is a post from: イグトランスの頭の中

WebBrowserコントロールでJavaScriptほか色々禁止する (2)

今回は、前回予告した、IProfferServiceを使い、WebBrowserコントロールにIInternetSecurityManagerを提供する方法の話です。

WebBrowserコントロールでのIProfferServiceの使い方はこんな感じでした。

  1. WebBrowserコントロールのオブジェクトに対し、IServiceProvider.QueryServiceでSID_SProfferServiceを問い合わせ、IProfferServiceへのポインタを得る。
  2. IProfferService.ProfferServiceで、サービスのGUIDと自分が作成したIServiceProviderへのポインタを登録する。

その部分のコードです。

// class TestWindow内
 
LRESULT OnCreate(const CREATESTRUCT*)
{
  constexpr DWORD IDC_WEBBROWSER_CONTROL = 1;
  RECT empty{};
  m_webBrowser.Create(m_hWnd, empty,
    L"{8856F961-340A-11D0-A96B-00C04FD705A2}",
    WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | WS_GROUP, 0,
    IDC_WEBBROWSER_CONTROL);
 
  ATL::CComPtr<IServiceProvider> sp;
  if (SUCCEEDED(m_webBrowser.QueryControl(&sp)))
  {
    ATL::CComPtr<IProfferService> ps;
    if (SUCCEEDED(sp->QueryService(SID_SProfferService, &ps)))
    {
      DWORD cookie;
      ATLENSURE_RETURN_VAL(
        SUCCEEDED(ps->ProfferService(
          SID_SInternetSecurityManagerEx2,
          &g_webBrowserServiceProvider,
          &cookie)),
        -1);
      m_webBrowserServiceCookie.push_back(cookie);
      ATLENSURE_RETURN_VAL(
        SUCCEEDED(ps->ProfferService(
          SID_SInternetSecurityManagerEx,
          &g_webBrowserServiceProvider,
          &cookie)),
        -1);
      m_webBrowserServiceCookie.push_back(cookie);
      ATLENSURE_RETURN_VAL(
        SUCCEEDED(ps->ProfferService(
          SID_SInternetSecurityManager,
          &g_webBrowserServiceProvider,
          &cookie)),
        -1);
      m_webBrowserServiceCookie.push_back(cookie);
    }
  }
 
  ATL::CComQIPtr<IWebBrowser2> wb = sp;
  ATL::CComVariant url(L"http://enable-javascript.com/ja/");
  wb->Navigate2(&url, nullptr, nullptr, nullptr, nullptr);
 
  return 0;
}
 
void OnDestroy()
{
  ATL::CComPtr<IServiceProvider> sp;
  if (SUCCEEDED(m_webBrowser.QueryControl(&sp)))
  {
    ATL::CComPtr<IProfferService> ps;
    if (SUCCEEDED(sp->QueryService(SID_SProfferService, &ps)))
    {
      for (auto cookie : m_webBrowserServiceCookie)
      {
        ps->RevokeService(cookie);
      }
    }
  }
  PostQuitMessage(0);
}
 
ATL::CAxWindow m_webBrowser;
// boostを使いたくないなら、std::vector<DWORD>に変更する。
boost::container::static_vector<DWORD, 3> m_webBrowserServiceCookie;

ここ以外は、前回と全く同じコードです。そのコード全部を次に載せます。

#define UNICODE
#define _UNICODE
 
#define WINVER 0x0501
#define _ATL_XP_TARGETING
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
 
#include <cstdlib>
 
#include <windows.h>
#include <shobjidl.h>
 
#include <atlbase.h>
#include <atltypes.h>
#include <atlwin.h>
 
#include <atlapp.h>
#include <atlcrack.h>
 
// boostを使いたくないなら、#include <vector>に変更する。
#include <boost/container/static_vector.hpp>
 
class Module : public ATL::CAtlExeModuleT<Module>
{
};
 
Module module;
 
class ATL_NO_VTABLE UntrustedSecurityManager :
  public ATL::CComObjectRootEx<ATL::CComSingleThreadModel>,
  public IInternetSecurityManagerEx2
{
public:
  BEGIN_COM_MAP(UntrustedSecurityManager)
    COM_INTERFACE_ENTRY(IInternetSecurityManager)
    COM_INTERFACE_ENTRY(IInternetSecurityManagerEx)
    COM_INTERFACE_ENTRY(IInternetSecurityManagerEx2)
  END_COM_MAP()
 
  IFACEMETHOD(SetSecuritySite)(
    _In_opt_ IInternetSecurityMgrSite* /*pSite*/) override
  {
    return INET_E_DEFAULT_ACTION;
  }
 
  IFACEMETHOD(GetSecuritySite)(
    _Out_ IInternetSecurityMgrSite** /*ppSite*/) override
  {
    return INET_E_DEFAULT_ACTION;
  }
 
  IFACEMETHOD(MapUrlToZone)(
    _In_ LPCWSTR /*pwszUrl*/,
    _Out_ DWORD* pdwZone,
    DWORD /*dwFlags*/) override
  {
    *pdwZone = URLZONE_UNTRUSTED;
    return S_OK;
  }
 
  IFACEMETHOD(GetSecurityId)(
    _In_ LPCWSTR /*pwszUrl*/,
    _Out_writes_bytes_to_(MAX_SIZE_SECURITY_ID, *pcbSecurityId) BYTE* /*pbSecurityId*/,
    _Inout_ _At_(*pcbSecurityId, _In_range_(>= , MAX_SIZE_SECURITY_ID) _Out_range_(0, MAX_SIZE_SECURITY_ID)) DWORD* /*pcbSecurityId*/,
    _In_ DWORD_PTR /*dwReserved*/) override
  {
    return INET_E_DEFAULT_ACTION;
  }
 
  IFACEMETHOD(ProcessUrlAction)(
    _In_ LPCWSTR pwszUrl,
    DWORD dwAction,
    _Out_writes_(cbPolicy) BYTE* pPolicy,
    DWORD cbPolicy,
    _In_opt_ BYTE* pContext,
    DWORD cbContext,
    DWORD dwFlags,
    DWORD dwReserved) override
  {
    DWORD flags;
    return ProcessUrlActionEx(pwszUrl, dwAction, pPolicy, cbPolicy, pContext, cbContext, dwFlags, dwReserved, &flags);
  }
 
  IFACEMETHOD(QueryCustomPolicy)(
    _In_ LPCWSTR /*pwszUrl*/,
    _In_ REFGUID /*guidKey*/,
    /*__RPC__deref_out_ecount_full_opt(*pcbPolicy)*/ BYTE** /*ppPolicy*/,
    _Out_ DWORD* /*pcbPolicy*/,
    _In_ BYTE* /*pContext*/,
    DWORD /*cbContext*/,
    DWORD /*dwReserved*/) override
  {
    return INET_E_DEFAULT_ACTION;
  }
 
  IFACEMETHOD(SetZoneMapping)(
    DWORD /*dwZone*/,
    _In_ LPCWSTR /*lpszPattern*/,
    DWORD /*dwFlags*/) override
  {
    return INET_E_DEFAULT_ACTION;
  }
 
  IFACEMETHOD(GetZoneMappings)(
    DWORD /*dwZone*/,
    __RPC__deref_out_opt IEnumString** /*ppenumString*/,
    DWORD /*dwFlags*/) override
  {
    return INET_E_DEFAULT_ACTION;
  }
 
  IFACEMETHOD(ProcessUrlActionEx)(
    _In_ LPCWSTR /*pwszUrl*/,
    DWORD /*dwAction*/,
    __RPC__out_ecount_full(cbPolicy) BYTE* pPolicy,
    DWORD cbPolicy,
    _In_ BYTE* /*pContext*/,
    DWORD /*cbContext*/,
    DWORD /*dwFlags*/,
    DWORD /*dwReserved*/,
    _Out_ DWORD* pdwOutFlags) override
  {
    if (pdwOutFlags != nullptr)
    {
      *pdwOutFlags = PUAFOUT_DEFAULT;
    }
    if (cbPolicy == sizeof(DWORD))
    {
      *reinterpret_cast<DWORD*>(pPolicy) = URLPOLICY_DISALLOW;
      return S_FALSE;
    }
    return INET_E_DEFAULT_ACTION;
  }
 
  IFACEMETHOD(MapUrlToZoneEx2)(
    _In_ IUri* /*pUri*/,
    _Out_ DWORD* pdwZone,
    DWORD /*dwFlags*/,
    _Outptr_opt_ LPWSTR* ppwszMappedUrl,
    _Out_opt_ DWORD* pdwOutFlags) override
  {
    if (ppwszMappedUrl != nullptr)
    {
      *ppwszMappedUrl = nullptr;
    }
    if (pdwOutFlags != nullptr)
    {
      *pdwOutFlags = 0;
    }
    if (pdwZone != nullptr)
    {
      *pdwZone = URLZONE_UNTRUSTED;
    }
    return S_OK;
  }
 
  IFACEMETHOD(ProcessUrlActionEx2)(
    _In_ IUri* /*pUri*/,
    DWORD dwAction,
    __RPC__out_ecount_full(cbPolicy) BYTE* pPolicy,
    DWORD cbPolicy,
    _In_opt_ BYTE* pContext,
    DWORD cbContext,
    DWORD dwFlags,
    DWORD_PTR dwReserved,
    _Out_ DWORD* pdwOutFlags) override
  {
    return ProcessUrlActionEx(L"", dwAction, pPolicy, cbPolicy, pContext, cbContext, dwFlags, dwReserved, pdwOutFlags);
  }
 
  IFACEMETHOD(GetSecurityIdEx2)(
    _In_ IUri* /*pUri*/,
    /*_Out_writes_bytes_to_(MAX_SIZE_SECURITY_ID, *pcbSecurityId)*/ BYTE* /*pbSecurityId*/,
    /*_Inout_ _At_(*pcbSecurityId, _In_range_(>= , MAX_SIZE_SECURITY_ID) _Out_range_(0, MAX_SIZE_SECURITY_ID))*/ DWORD* /*pcbSecurityId*/,
    _In_ DWORD_PTR /*dwReserved*/) override
  {
    return INET_E_DEFAULT_ACTION;
  }
 
  IFACEMETHOD(QueryCustomPolicyEx2)(
    _In_ IUri* /*pUri*/,
    _In_ REFGUID /*guidKey*/,
    __RPC__deref_out_ecount_full_opt(*pcbPolicy) BYTE** /*ppPolicy*/,
    _Out_ DWORD* /*pcbPolicy*/,
    _In_ BYTE* /*pContext*/,
    DWORD /*cbContext*/,
    DWORD_PTR /*dwReserved*/) override
  {
    return INET_E_DEFAULT_ACTION;
  }
};
 
class ATL_NO_VTABLE WebBrowserServiceProvider :
  public ATL::CComObjectRootEx<ATL::CComSingleThreadModel>,
  public IServiceProvider
{
public:
  BEGIN_COM_MAP(WebBrowserServiceProvider)
    COM_INTERFACE_ENTRY(IServiceProvider)
  END_COM_MAP()
 
public:
  IFACEMETHOD(QueryService)(
    _In_ REFGUID guidService,
    _In_ REFIID riid,
    _COM_Outptr_ void** ppv) override
  {
    if (guidService == SID_SInternetSecurityManager
      || guidService == SID_SInternetSecurityManagerEx
      || guidService == SID_SInternetSecurityManagerEx2)
    {
      return m_untrustedSecurityManager.QueryInterface(riid, ppv);
    }
    return E_NOINTERFACE;
  }
 
private:
  ATL::CComObjectStackEx<UntrustedSecurityManager> m_untrustedSecurityManager;
};
 
ATL::CComObjectStackEx<WebBrowserServiceProvider> g_webBrowserServiceProvider;
 
class TestWindow :
  public ATL::CWindowImpl<TestWindow>,
  public WTL::CMessageFilter
{
public:
  int Run(int cmdShow, _Inout_ WTL::CMessageLoop& msgLoop)
  {
    if (!Create(nullptr, ATL::CWindow::rcDefault,
      TEXT("Hello, world"), WS_OVERLAPPEDWINDOW))
    {
      return -1;
    }
 
    ShowWindow(cmdShow);
    UpdateWindow();
 
    msgLoop.AddMessageFilter(this);
    return msgLoop.Run();
  }
 
  BOOL PreTranslateMessage(_In_ MSG* pmsg) override
  {
    if (m_webBrowser.IsWindow()
      && m_webBrowser.SendMessage(
        WM_FORWARDMSG, 0, reinterpret_cast<LPARAM>(pmsg)))
    {
      return TRUE;
    }
    return IsDialogMessage(pmsg);
  }
 
  DECLARE_WND_CLASS(TEXT("Test Window Class"));
 
  BEGIN_MSG_MAP(TestWindow)
    MSG_WM_SIZE(OnSize)
    MSG_WM_SETFOCUS(OnSetFocus)
    MSG_WM_CREATE(OnCreate)
    MSG_WM_DESTROY(OnDestroy)
  END_MSG_MAP()
 
private:
  void OnSize(UINT, SIZE size)
  {
    m_webBrowser.ResizeClient(size.cx, size.cy);
  }
 
  void OnSetFocus(HWND)
  {
    m_webBrowser.SetFocus();
  }
 
  LRESULT OnCreate(const CREATESTRUCT*)
  {
    constexpr DWORD IDC_WEBBROWSER_CONTROL = 1;
    RECT empty{};
    m_webBrowser.Create(m_hWnd, empty,
      L"{8856F961-340A-11D0-A96B-00C04FD705A2}",
      WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | WS_GROUP, 0,
      IDC_WEBBROWSER_CONTROL);
 
    ATL::CComPtr<IServiceProvider> sp;
    if (SUCCEEDED(m_webBrowser.QueryControl(&sp)))
    {
      ATL::CComPtr<IProfferService> ps;
      if (SUCCEEDED(sp->QueryService(SID_SProfferService, &ps)))
      {
        DWORD cookie;
        ATLENSURE_RETURN_VAL(
          SUCCEEDED(ps->ProfferService(
            SID_SInternetSecurityManagerEx2,
            &g_webBrowserServiceProvider,
            &cookie)),
          -1);
        m_webBrowserServiceCookie.push_back(cookie);
        ATLENSURE_RETURN_VAL(
          SUCCEEDED(ps->ProfferService(
            SID_SInternetSecurityManagerEx,
            &g_webBrowserServiceProvider,
            &cookie)),
          -1);
        m_webBrowserServiceCookie.push_back(cookie);
        ATLENSURE_RETURN_VAL(
          SUCCEEDED(ps->ProfferService(
            SID_SInternetSecurityManager,
            &g_webBrowserServiceProvider,
            &cookie)),
          -1);
        m_webBrowserServiceCookie.push_back(cookie);
      }
    }
 
    ATL::CComQIPtr<IWebBrowser2> wb = sp;
    ATL::CComVariant url(L"http://enable-javascript.com/ja/");
    wb->Navigate2(&url, nullptr, nullptr, nullptr, nullptr);
 
    return 0;
  }
 
  void OnDestroy()
  {
    ATL::CComPtr<IServiceProvider> sp;
    if (SUCCEEDED(m_webBrowser.QueryControl(&sp)))
    {
      ATL::CComPtr<IProfferService> ps;
      if (SUCCEEDED(sp->QueryService(SID_SProfferService, &ps)))
      {
        for (auto cookie : m_webBrowserServiceCookie)
        {
          ps->RevokeService(cookie);
        }
      }
    }
    PostQuitMessage(0);
  }
 
  ATL::CAxWindow m_webBrowser;
  // boostを使いたくないなら、std::vector<DWORD>に変更する。
  boost::container::static_vector<DWORD, 3> m_webBrowserServiceCookie;
};
 
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int cmdShow)
{
  if (FAILED(OleInitialize(nullptr)))
  {
    std::quick_exit(-1);
  }
 
  WTL::CMessageLoop msgLoop;
  TestWindow wnd;
  std::quick_exit(wnd.Run(cmdShow, msgLoop));
}

前回より少し手順が複雑です。その代わり、ATL::CAxWindowやATL::CAxHostWindowなどの内部実装を知る必要が無いのが利点です。

今回のIProfferServiceを使う方法は、C# Tips -AxWebBrowserのIInternetSecurityManager-で知りました。この方法をC++でやってみたということになります。

MSDNライブラリの項目へのリンクです: IProfferService interface

WebBrowserコントロールでJavaScriptほか色々禁止する (2) is a post from: イグトランスの頭の中


WebBrowserコントロールでJavaScriptほか色々禁止する (3) DISPID_AMBIENT_DLCONTROLその1

引き続き、ATLでWebBrowserコントロールで使用する場合にJavaScriptその他を禁止する方法の話です。3回目からは、ちょっと違うやり方、DISPID_AMBIENT_DLCONTROLです。

インターネット上で検索すると、DISPID_AMBIENT_DLCONTROLを使うやり方のほうが多く見受けられるのですが、ちょっと問題があって後回しにしていました。その理由は、「Visual C++ 2013以降だと、ATL::CAxWindowと組み合わせての利用が少し困難だから」です。

というわけで、今回のプログラムは、Visual C++ 2012で動作確認しました。

#define UNICODE
#define _UNICODE
 
#define WINVER 0x0501
#define _ATL_DLL
#define _ATL_XP_TARGETING
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
 
#include <cstdlib>
 
#include <windows.h>
#include <mshtmdid.h>
 
#include <atlbase.h>
#include <atltypes.h>
#include <atlwin.h>
 
#include <atlapp.h>
#include <atlcrack.h>
 
#include <boost/implicit_cast.hpp>
 
#define quick_exit exit
#define constexpr const
 
class Module : public ATL::CAtlExeModuleT<Module>
{
};
 
Module module;
 
constexpr UINT ID_WEBBROWSER_EVENTS = 1;
 
class WebBrowserAmbient :
  public ATL::IDispEventSimpleImpl<ID_WEBBROWSER_AMBIENT, WebBrowserAmbient, &IID_NULL>
{
  static ATL::_ATL_FUNC_INFO* GetDlControlInfo()
  {
    static ATL::_ATL_FUNC_INFO GetDlControlInfo = { CC_STDCALL, VT_I4, 0, {} };
    return &GetDlControlInfo;
  }
 
public:
  BEGIN_SINK_MAP(WebBrowserAmbient)
    SINK_ENTRY_INFO(ID_WEBBROWSER_AMBIENT, IID_NULL, DISPID_AMBIENT_DLCONTROL, &WebBrowserAmbient::GetDlControl, GetDlControlInfo())
  END_SINK_MAP()
 
public:
  int __stdcall GetDlControl()
  {
    return DLCTL_NO_SCRIPTS
      | DLCTL_NO_JAVA
      | DLCTL_NO_RUNACTIVEXCTLS
      | DLCTL_NO_DLACTIVEXCTLS
      | DLCTL_DOWNLOADONLY
      | DLCTL_NO_FRAMEDOWNLOAD
      | DLCTL_NO_BEHAVIORS
      | DLCTL_NOFRAMES
      | DLCTL_SILENT;
  }
 
  IDispatch* GetDispatch()
  {
    return reinterpret_cast<IDispatch*>(
      boost::implicit_cast<IDispEventSimpleImpl*>(this));
  }
};
 
class TestWindow :
  public ATL::CWindowImpl<TestWindow>,
  public WTL::CMessageFilter
{
public:
  int Run(int cmdShow, _Inout_ WTL::CMessageLoop& msgLoop)
  {
    if (!Create(nullptr, ATL::CWindow::rcDefault,
      TEXT("Hello, world"), WS_OVERLAPPEDWINDOW))
    {
      return -1;
    }
 
    ShowWindow(cmdShow);
    UpdateWindow();
 
    msgLoop.AddMessageFilter(this);
    return msgLoop.Run();
  }
 
  BOOL PreTranslateMessage(_In_ MSG* pmsg) override
  {
    if (m_webBrowser.IsWindow()
      && m_webBrowser.SendMessage(
        WM_FORWARDMSG, 0, reinterpret_cast<LPARAM>(pmsg)))
    {
      return TRUE;
    }
    return IsDialogMessage(pmsg);
  }
 
  DECLARE_WND_CLASS(TEXT("Test Window Class"));
 
  BEGIN_MSG_MAP(TestWindow)
    MSG_WM_SIZE(OnSize)
    MSG_WM_SETFOCUS(OnSetFocus)
    MSG_WM_CREATE(OnCreate)
    MSG_WM_DESTROY(OnDestroy)
  END_MSG_MAP()
 
private:
  void OnSize(UINT, SIZE size)
  {
    m_webBrowser.ResizeClient(size.cx, size.cy);
  }
 
  void OnSetFocus(HWND)
  {
    m_webBrowser.SetFocus();
  }
 
  LRESULT OnCreate(const CREATESTRUCT*)
  {
    constexpr DWORD IDC_WEBBROWSER_CONTROL = 1;
    RECT empty = {};
    ATLENSURE_RETURN_VAL(
      SUCCEEDED(m_webBrowser.Create(m_hWnd, empty, L"",
        WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | WS_GROUP, 0,
        IDC_WEBBROWSER_CONTROL)),
      -1);
 
    ATL::CComPtr<IAxWinAmbientDispatchEx> ambientDispatch;
    ATLENSURE_RETURN_VAL(
      SUCCEEDED(m_webBrowser.QueryHost(&ambientDispatch)),
      -1);
    ATLENSURE_RETURN_VAL(
      SUCCEEDED(ambientDispatch->SetAmbientDispatch(m_ambient.GetDispatch())),
      -1);
 
    ATLENSURE_RETURN_VAL(
      SUCCEEDED(m_webBrowser.CreateControl(L"http://enable-javascript.com/ja/")),
      -1);
 
    return 0;
  }
 
  void OnDestroy()
  {
    PostQuitMessage(0);
  }
 
  WebBrowserAmbient m_ambient;
  ATL::CAxWindow m_webBrowser;
};
 
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int cmdShow)
{
  if (FAILED(OleInitialize(nullptr)))
  {
    std::quick_exit(-1);
  }
 
  WTL::CMessageLoop msgLoop;
  TestWindow wnd;
  std::quick_exit(wnd.Run(cmdShow, msgLoop));
}

このプログラムは、次のようなことをやっています。

一番大事なのがWebBrowserAmbientクラスのGetDlControl関数で、ID_WEBBROWSER_AMBIENTのgetに対する処理を書いているところです。ここを変えればWebBrowserコントロールの挙動が変化します。


ここから先は、atl110.dllを使うようにした理由の話です。

ActiveXコントロールからのアンビエントプロパティの取得を受け付けるのはATL::CAxHostWindowです。このクラスのInvoke関数の実装を読みました。すると、SetAmbientDispatchで設定したIDispatchオブジェクトに処理が渡されるには、ちょっとした条件を満たしている必要があることが分かりました。その条件とは、

  • モジュール自身のリソースとしてタイプライブラリがある。
  • そのタイプライブラリの中にIAxWinAmbientDispatchExの情報が含まれている。

atl110.dllには、ATL::CAxHostWindowの実装が含まれており、しかもこの条件を満たしています。そのため、atl110.dllを使うのが簡単だと考えました。

今回、Visual C++ 2012を使った理由もそこにあります。Visual C++ 2013以降、ATLのDLLは廃止されたので、この方法は使えなくなりました: MSDNライブラリのATL アプリケーションの再配布


次回は、VC++ 2013以降でATL::CAxHostWindowを使いつつ、SetAmbientDispatchを使う方法のコードを載せます。

WebBrowserコントロールでJavaScriptほか色々禁止する (3) DISPID_AMBIENT_DLCONTROLその1 is a post from: イグトランスの頭の中

WebBrowserコントロールでJavaScriptほか色々禁止する (4) DISPID_AMBIENT_DLCONTROLその2

前回(WebBrowserコントロールでJavaScriptほか色々禁止する (3) DISPID_AMBIENT_DLCONTROLその1)の続きです。ATLのDLLが廃止されたVisual C++ 2013以降を使いつつ、ATL::CAxWindowでDISPID_AMBIENT_DLCONTROLやその他のアンビエントプロパティを使う方法です。


分かってしまえば簡単なことでした。IAxWinAmbientDispatchExを自身のタイプライブラリに含めてしまえば良いのです。というわけで、早速コードを書いておきます。

まずは、以下のようなIDLファイルを作ります。

import "atliface.idl";
 
[
  uuid(c595583a-0d92-4490-94fe-42e3ab446071)
]
library Test
{
  interface IAxWinAmbientDispatchEx;
}

これをmidl Test.idlとしてコンパイルし、Test.tlbを得ます。

次にリソーススクリプトを作ります。

1 TYPELIB "Test.tlb"

こちらもrc Test.rcとしてコンパイルします。

そうしたら、次はソースコードです。前回のものと比べ、_ATL_DLLとquick_exitとconstexprのdefineを削除しただけです。Visual C++ 2015にはquick_exitもconstexprもあります。

#define UNICODE
#define _UNICODE
 
#define WINVER 0x0501
#define _ATL_XP_TARGETING
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
 
#include <cstdlib>
 
#include <windows.h>
#include <mshtmdid.h>
 
#include <atlbase.h>
#include <atltypes.h>
#include <atlwin.h>
 
#include <atlapp.h>
#include <atlcrack.h>
 
#include <boost/implicit_cast.hpp>
 
class Module : public ATL::CAtlExeModuleT<Module>
{
};
 
Module module;
 
constexpr UINT ID_WEBBROWSER_AMBIENT = 0;
 
class WebBrowserAmbient :
  public ATL::IDispEventSimpleImpl<ID_WEBBROWSER_AMBIENT, WebBrowserAmbient, &IID_NULL>
{
  static ATL::_ATL_FUNC_INFO* GetDlControlInfo()
  {
    static ATL::_ATL_FUNC_INFO GetDlControlInfo = { CC_STDCALL, VT_I4, 0, {} };
    return &GetDlControlInfo;
  }
 
public:
  BEGIN_SINK_MAP(WebBrowserAmbient)
    SINK_ENTRY_INFO(ID_WEBBROWSER_AMBIENT, IID_NULL, DISPID_AMBIENT_DLCONTROL, &WebBrowserAmbient::GetDlControl, GetDlControlInfo())
  END_SINK_MAP()
 
public:
  int __stdcall GetDlControl()
  {
    return DLCTL_NO_SCRIPTS
      | DLCTL_NO_JAVA
      | DLCTL_NO_RUNACTIVEXCTLS
      | DLCTL_NO_DLACTIVEXCTLS
      | DLCTL_DOWNLOADONLY
      | DLCTL_NO_FRAMEDOWNLOAD
      | DLCTL_NO_BEHAVIORS
      | DLCTL_NOFRAMES
      | DLCTL_SILENT;
  }
 
  IDispatch* GetDispatch()
  {
    return reinterpret_cast<IDispatch*>(
      boost::implicit_cast<IDispEventSimpleImpl*>(this));
  }
};
 
class TestWindow :
  public ATL::CWindowImpl<TestWindow>,
  public WTL::CMessageFilter
{
public:
  int Run(int cmdShow, _Inout_ WTL::CMessageLoop& msgLoop)
  {
    if (!Create(nullptr, ATL::CWindow::rcDefault,
      TEXT("Hello, world"), WS_OVERLAPPEDWINDOW))
    {
      return -1;
    }
 
    ShowWindow(cmdShow);
    UpdateWindow();
 
    msgLoop.AddMessageFilter(this);
    return msgLoop.Run();
  }
 
  BOOL PreTranslateMessage(_In_ MSG* pmsg) override
  {
    if (m_webBrowser.IsWindow()
      && m_webBrowser.SendMessage(
        WM_FORWARDMSG, 0, reinterpret_cast<LPARAM>(pmsg)))
    {
      return TRUE;
    }
    return IsDialogMessage(pmsg);
  }
 
  DECLARE_WND_CLASS(TEXT("Test Window Class"));
 
  BEGIN_MSG_MAP(TestWindow)
    MSG_WM_SIZE(OnSize)
    MSG_WM_SETFOCUS(OnSetFocus)
    MSG_WM_CREATE(OnCreate)
    MSG_WM_DESTROY(OnDestroy)
  END_MSG_MAP()
 
private:
  void OnSize(UINT, SIZE size)
  {
    m_webBrowser.ResizeClient(size.cx, size.cy);
  }
 
  void OnSetFocus(HWND)
  {
    m_webBrowser.SetFocus();
  }
 
  LRESULT OnCreate(const CREATESTRUCT*)
  {
    constexpr DWORD IDC_WEBBROWSER_CONTROL = 1;
    RECT empty = {};
    ATLENSURE_RETURN_VAL(
      SUCCEEDED(m_webBrowser.Create(m_hWnd, empty, L"",
        WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | WS_GROUP, 0,
        IDC_WEBBROWSER_CONTROL)),
      -1);
 
    ATL::CComPtr<IAxWinAmbientDispatchEx> ambientDispatch;
    ATLENSURE_RETURN_VAL(
      SUCCEEDED(m_webBrowser.QueryHost(&ambientDispatch)),
      -1);
    ATLENSURE_RETURN_VAL(
      SUCCEEDED(ambientDispatch->SetAmbientDispatch(m_ambient.GetDispatch())),
      -1);
 
    ATLENSURE_RETURN_VAL(
      SUCCEEDED(m_webBrowser.CreateControl(L"http://enable-javascript.com/ja/")),
      -1);
 
    return 0;
  }
 
  void OnDestroy()
  {
    PostQuitMessage(0);
  }
 
  WebBrowserAmbient m_ambient;
  ATL::CAxWindow m_webBrowser;
};
 
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int cmdShow)
{
  if (FAILED(OleInitialize(nullptr)))
  {
    std::quick_exit(-1);
  }
 
  WTL::CMessageLoop msgLoop;
  TestWindow wnd;
  std::quick_exit(wnd.Run(cmdShow, msgLoop));
}

これをcl test.cpp test.resなど、先ほど作ったリソースを含めてコンパイルします。すると、前回のVisual C++ 2012対象に書いたコードと同じように動作します。


これを明らかになるまで調べた結果を簡潔に書きます。

ATL::CAxHostWindowは、基底クラスにIDispatchImpl<IAxWinAmbientDispatchEx, &__uuidof(IAxWinAmbientDispatchEx), &CAtlModule::m_libid, 0xFFFF, 0xFFFF>があります。また、ATL::CAxHostWindow::Invokeが以下のように実装されています。

STDMETHOD(Invoke)(
	_In_ DISPID dispIdMember,
	_In_ REFIID riid,
	_In_ LCID lcid,
	_In_ WORD wFlags,
	_In_ DISPPARAMS *pDispParams,
	_Out_opt_ VARIANT *pVarResult,
	_Out_opt_ EXCEPINFO *pExcepInfo,
	_Out_opt_ UINT *puArgErr)
{
	HRESULT hr = IDispatchImpl<IAxWinAmbientDispatchEx, &__uuidof(IAxWinAmbientDispatchEx), &CAtlModule::m_libid, 0xFFFF, 0xFFFF>::Invoke
		(dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
	if ((hr == DISP_E_MEMBERNOTFOUND || hr == TYPE_E_ELEMENTNOTFOUND) && m_spAmbientDispatch != NULL)
	{
		hr = m_spAmbientDispatch->Invoke(dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
		if (SUCCEEDED(hr) && (wFlags & DISPATCH_PROPERTYPUT) != 0)
		{
			hr = FireAmbientPropertyChange(dispIdMember);
		}
	}
	return hr;
}

m_spAmbientDispatchに、SetAmbientDispatchで渡したオブジェクトが入っています。したがって、IDispatchImplのInvokeがDISP_E_MEMBERNOTFOUNDかTYPE_E_ELEMENTNOTFOUNDを返さないと、m_spAmbientDispatchが使われることはないのです。

タイプライブラリがリソースに入っていなかったり、タイプライブラリはあるけどIAxWinAmbientDispatchExが入っていなかったりする場合、エラーコードはそのどちらでもないので、うまくいきません。そのため、IAxWinAmbientDispatchExを含むタイプライブラリを含めるという方法に辿り着きました。


DISPID_AMBIENT_DLCONTROLを使う話、無事終わりました。なお、ウェブブラウザコントロールの話はまだ続きます。

WebBrowserコントロールでJavaScriptほか色々禁止する (4) DISPID_AMBIENT_DLCONTROLその2 is a post from: イグトランスの頭の中

WebBrowserコントロールでコンテキストメニューとページ遷移とその他制限を加える

WebBrowserコントロールの機能を制限する話、これで最後です。

今回は、これまでやっていなかったこと、まとめてコードを書きました。

  • ページ遷移の禁止: DWebBrowserEvents2のBeforeNavigate2イベントを受信しての処理
  • コンテキストメニュー(右クリックなど)の禁止: IAxWinAmbientDispatchのput_AllowContextMenu
  • その他: IAxWinAmbientDispatchのput_AllowShowUI、IWebBrowser2のput_RegisterAsBrowserとput_RegisterAsDropTargetとput_Silent

ソースコードを載せます。

#define UNICODE
#define _UNICODE
 
#define WINVER 0x0501
#define _ATL_XP_TARGETING
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
 
#include <cstdlib>
 
#include <windows.h>
#include <exdisp.h>
#include <exdispid.h>
 
#include <atlbase.h>
#include <atltypes.h>
#include <atlwin.h>
 
#include <atlapp.h>
#include <atlcrack.h>
 
class Module : public ATL::CAtlExeModuleT<Module>
{
};
 
Module module;
 
constexpr UINT ID_WEBBROWSER_EVENTS = 1;
 
class TestWindow :
  public ATL::CWindowImpl<TestWindow>,
  public WTL::CMessageFilter,
  public ATL::IDispEventImpl<ID_WEBBROWSER_EVENTS, TestWindow, &__uuidof(DWebBrowserEvents2), &LIBID_SHDocVw, 1, 1>
{
public:
  int Run(int cmdShow, _Inout_ WTL::CMessageLoop& msgLoop)
  {
    if (!Create(nullptr, ATL::CWindow::rcDefault,
      TEXT("Hello, world"), WS_OVERLAPPEDWINDOW))
    {
      return -1;
    }
 
    ShowWindow(cmdShow);
    UpdateWindow();
 
    msgLoop.AddMessageFilter(this);
    return msgLoop.Run();
  }
 
  BOOL PreTranslateMessage(_In_ MSG* pmsg) override
  {
    if (m_webBrowser.IsWindow()
      && m_webBrowser.SendMessage(
        WM_FORWARDMSG, 0, reinterpret_cast<LPARAM>(pmsg)))
    {
      return TRUE;
    }
    return IsDialogMessage(pmsg);
  }
 
  DECLARE_WND_CLASS(TEXT("Test Window Class"));
 
  BEGIN_MSG_MAP(TestWindow)
    MSG_WM_SIZE(OnSize)
    MSG_WM_SETFOCUS(OnSetFocus)
    MSG_WM_CREATE(OnCreate)
    MSG_WM_DESTROY(OnDestroy)
  END_MSG_MAP()
 
  BEGIN_SINK_MAP(TestWindow)
    SINK_ENTRY_EX(ID_WEBBROWSER_EVENTS, __uuidof(DWebBrowserEvents2), DISPID_BEFORENAVIGATE2, &TestWindow::BeforeNavigate2)
  END_SINK_MAP()
 
private:
  void __stdcall BeforeNavigate2(
    _In_ IDispatch* /*disp*/,
    _In_ VARIANT* /*url*/,
    _In_ VARIANT* /*flags*/,
    _In_ VARIANT* /*targetFrameName*/,
    _In_ VARIANT* /*postData*/,
    _In_ VARIANT* /*headers*/,
    _Inout_ VARIANT_BOOL* cancel)
  {
    if (cancel != nullptr)
    {
      *cancel = VARIANT_TRUE;
    }
  }
 
  void OnSize(UINT, SIZE size)
  {
    m_webBrowser.ResizeClient(size.cx, size.cy);
  }
 
  void OnSetFocus(HWND)
  {
    m_webBrowser.SetFocus();
  }
 
  LRESULT OnCreate(const CREATESTRUCT*)
  {
    constexpr DWORD IDC_WEBBROWSER_CONTROL = 1;
    RECT empty{};
    m_webBrowser.Create(m_hWnd, empty, L"https://www.google.co.jp/",
      WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | WS_GROUP | WS_VSCROLL | WS_HSCROLL, 0,
      IDC_WEBBROWSER_CONTROL);
 
    ATL::CComPtr<IAxWinAmbientDispatch> ambient;
    m_webBrowser.QueryHost(&ambient);
    ATLENSURE_RETURN_VAL(
      SUCCEEDED(ambient->put_AllowContextMenu(VARIANT_FALSE)),
      -1);
    ATLENSURE_RETURN_VAL(
      SUCCEEDED(ambient->put_AllowShowUI(VARIANT_FALSE)),
      -1);
 
    ATLENSURE_RETURN_VAL(
      SUCCEEDED(InitializeWebBrowserControl()),
      -1);
    ATLENSURE_RETURN_VAL(
      SUCCEEDED(ATL::AtlAdviseSinkMap(this, true)),
      -1);
 
    return 0;
  }
 
  HRESULT InitializeWebBrowserControl()
  {
    ATL::CComPtr<IWebBrowser2> wb;
    if (SUCCEEDED(m_webBrowser.QueryControl(&wb)))
    {
      auto hrRB = wb->put_RegisterAsBrowser(VARIANT_FALSE);
      ATLENSURE_RETURN_HR(SUCCEEDED(hrRB), hrRB);
      auto hrRDT = wb->put_RegisterAsDropTarget(VARIANT_TRUE);
      ATLENSURE_RETURN_HR(SUCCEEDED(hrRDT), hrRDT);
      auto hrSl = wb->put_Silent(VARIANT_TRUE);
      ATLENSURE_RETURN_HR(SUCCEEDED(hrSl), hrSl);
    }
    return S_OK;
  }
 
  void OnDestroy()
  {
    PostQuitMessage(0);
  }
 
  ATL::CAxWindow m_webBrowser;
};
 
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int cmdShow)
{
  if (FAILED(OleInitialize(nullptr)))
  {
    std::quick_exit(-1);
  }
 
  WTL::CMessageLoop msgLoop;
  TestWindow wnd;
  std::quick_exit(wnd.Run(cmdShow, msgLoop));
}

コンテキストメニュー(いわゆる右クリックメニュー)の禁止は、本来IDocHostUIHandlerのShowContextMenuで制御します。ところが、ATL::CAxHostWindowがIDocHostUIHandlerを実装していて、ATL::CAxWindowを使う側ではAllowContextMenuプロパティで簡単に制御できるようになっています。AllowShowUIも同じです。

DWebBrowserEvents2のイベントの受信のコードは、BEGIN_SINK_MAPやAtlAdviseSinkMap関数などをオーソドックスに使って書き上げています。BeforeNavigate2で、cancelにVARIANT_TRUEを書き込むことで、リンクをクリックしてもページ遷移を実行させないようにしています。

ここまで、単なるHTMLビューアーとしてWebBroserコントロールを使いたいと思って、いろいろ試してきました。これでだいたいやり尽くしたのではないかと考えています。

WebBrowserコントロールでコンテキストメニューとページ遷移とその他制限を加える is a post from: イグトランスの頭の中

Visual C++コード分析を支えるSALという題で話をしてきました

Boost.勉強会 #21 札幌でLTとして話をしてきました。Visual C++のSAL (source code annotation language)についてです。

このスライドはCreative Commons — 表示 – 継承 4.0 国際 — CC BY-SA 4.0です。ダウンロードはこちらです。

その後、尋ねられた質問の回答を以下書いておきます。

こういう低水準な処理はラップして隠蔽するものでは?

はい、そう思います。実際、C++プログラムにおいて、SALを適用する箇所は少ない実感があります。

Visual C++以外でも使用する予定のコードがある。空に置き換えるマクロ定義が用意されていないか?

無さそうです。実際、C++ REST SDKでは、cpprest/details/nosal.hにて自前で作っています。このように自身で作るしかないと思います。

_In_reads_(n)など配列の場合で、要素数が変数であるのにコンパイル時に範囲外アクセスを検出できるのか?

はい、可能なものもあると思います。MSDNライブラリのSAL についてというページには、こういう例があります。

wchar_t * wmemcpy(
  _Out_writes_all_(count) wchar_t *dest, 
  _In_reads_(count) const wchar_t *src, 
  size_t count)
{
  size_t i;
  for (i = 0; i <= count; i++) { // BUG: off-by-one error
    dest[i] = src[i];
  }
  return dest;
}

src[count - 1]までしか読み取れないはずなのに、iは最大countに達する可能性があります。

機械的に検出できそうな感じがするでしょう。でもこれ、Visual C++の/analyzeでは何も言われないという残念なオチでした: c++ – How Microsoft SAL can prevent off-by-one error – Stack Overflow。すみません、私もこれを書いているたった今知りました。


そんなわけで、抜けもありますが、自分はこれでバグを見つけてもらったことが何度かあるので、SALを自分の書くコードにも使っています。ここ最近このブログに載せているコードも全部そうです。今回紹介しきれなかったものも、今後紹介していきたいと思っています。

Visual C++コード分析を支えるSALという題で話をしてきました is a post from: イグトランスの頭の中

ATL::CAtlExeModuleT::WinMain関数を使う例

ATLのCAtlExeModuleTクラステンプレートには、WinMain関数があります。これを使うウィンドウアプリのサンプルコードを書いてみようと思いました。

以前書いた空のウィンドウを表示するだけのWindowsアプリケーション (WTL)をもとに書いています。

#define UNICODE
#define _UNICODE
 
#define WINVER 0x0501
#define _ATL_XP_TARGETING
#define _ATL_NO_COM_SUPPORT
#define _ATL_APARTMENT_THREADED
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
 
#include <cstdlib>
 
#include <windows.h>
 
#include <atlbase.h>
#include <atltypes.h>
#include <atlwin.h>
 
#include <atlapp.h>
#include <atlcrack.h>
 
class TestWindow : public ATL::CWindowImpl<TestWindow>
{
public:
  DECLARE_WND_CLASS(TEXT("Test Window Class"));
 
private:
  BEGIN_MSG_MAP(TestWindow)
    MSG_WM_DESTROY(OnDestroy)
  END_MSG_MAP()
 
  void OnDestroy()
  {
    PostQuitMessage(0);
  }
};
 
class Module : public ATL::CAtlExeModuleT<Module>
{
public:
  bool ParseCommandLine(_In_ PCTSTR /*cmdLine*/, _Out_ HRESULT* phr)
  {
    *phr = S_OK;
    return true;
  }
 
  HRESULT PreMessageLoop(int cmdShow)
  {
    if (!m_wnd.Create(nullptr, ATL::CWindow::rcDefault,
      TEXT("Hello, world"), WS_OVERLAPPEDWINDOW))
    {
      return HRESULT_FROM_WIN32(GetLastError());
    }
    m_wnd.ShowWindow(cmdShow);
    m_wnd.UpdateWindow();
    return S_OK;
  };
 
private:
  TestWindow m_wnd;
};
 
Module module;
 
int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int cmdShow)
{
  std::quick_exit(module.WinMain(cmdShow));
}

CAtlExeModuleT::WinMainは、以下の関数を呼び出します。Run含め、すべてCRTPになっています。つまり、派生クラスでメンバ関数を作ることで、動作をオーバーライドできます。

  1. InitializeCom
  2. ParseCommandLine
  3. Run
    1. PreMessageLoop
    2. RunMessageLoop
    3. PostMessageLoop
  4. UninitializeCom

そのままだと、アウトプロセスのCOMサーバー用のための実装(/RegServerコマンドライン引数を解釈したり、CoRegisterClassObject関数を呼び出したり)が多いです。そのため、不要な処理をある程度取り除いています。具体的には、_ATL_NO_COM_SUPPORTを定義したり、何もしないParseCommandLine関数を定義したりしました。

RunMessageLoop関数がGetMessage関数などによるメッセージループを実装しているため、ソースコードが少し短くなりました。場合によってはアリではないでしょうか。

参考: CAtlExeModuleT クラス(MSDNライブラリ)

ATL::CAtlExeModuleT::WinMain関数を使う例 is a post from: イグトランスの頭の中

ATL::CAtlExeModuleTのバージョン非互換を見つけた話

ATL::CAtlExeModuleTを使ったコードを様々なバージョンのVisual C++でコンパイルしていたところ、非互換な変更を見つけました。

問題を見つけたコードとその症状

次のコード、Visual C++のバージョンを上げると動かないという現象に遭遇しました。

#include <iostream>
#include <windows.h>
#include <atlbase.h>
 
#define nullptr NULL
 
class Module : public ATL::CAtlExeModuleT<Module>
{
public:
  HRESULT Run(int showCmd)
  {
    ATL::CComPtr<IUnknown> obj;
    HRESULT hr = CoCreateInstance(CLSID_GlobalOptions, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&obj));
    std::cout << std::showbase << std::hex << hr << std::endl;
    return hr;
  }
 
  int WinMain(int showCmd)
  {
    // 実際には、これ以外にも前処理や後処理がある。
    return static_cast<int>(Run(showCmd));
  }
};
 
Module module;
 
int main()
{
  return module.WinMain(SW_SHOW);
}

Visual C++ 2008までなら、CoCreateInstanceは成功します。ところが、これをVisual C++ 2010以降でコンパイルすると0x800401f0、すなわちCO_E_NOTINITIALIZEDとなってしまいました。

原因・考察

CAtlExeModuleTのソースを見たところ、Visual C++ 2008から2010の間で変更されていることが分かりました。

  • VC++ 2008まで: CAtlExeModuleTのコンストラクタ・デストラクタで、メンバー関数InitializeComとUninitializeComを呼び出している。
  • VC++ 2010から: CAtlExeModuleTのメンバー関数WinMainで、InitializeComとUninitializeComを呼び出している。

以下のようなわけで、この変更は好ましいと考えています。

  • メンバー関数InitializeComとUninitializeComが中で呼び出している、CoInitializeExおよびCoUninitialize関数は、スレッド単位の初期化・終了処理の関数だからです。
  • 最初に提示したコードのように、CAtlExeModuleT(の派生クラス)は、ほとんどの場合グローバル変数として使われます。main関数を実行したスレッド以外でstd::exit関数を呼び出すと、そのスレッドでデストラクタが実行されます。

したがって、Visual C++ 2008までのCAtlExeModuleT実装では、CoInitializeExを実行したことと異なるスレッドでCoUninitializeを実行してしまう可能性がありました。Visual C++ 2010以降は、メンバー関数WinMainにこの処理が移動したので、この問題が解消されたわけです。

修正後のコード

というわけで、上記のコードは次のように修正することとしました。

#include <iostream>
#include <windows.h>
#include <atlbase.h>
 
class Module : public ATL::CAtlExeModuleT<Module>
{
public:
  HRESULT Run(_In_ int nShowCmd)
  {
    ATL::CComPtr<IUnknown> obj;
    HRESULT hr = CoCreateInstance(CLSID_GlobalOptions, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&obj));
    std::cout << std::showbase << std::hex << hr << std::endl;
    return hr;
  }
 
  int WinMain(int showCmd)
  {
    // 実際には、これ以外にも前処理や後処理がある。
    return ATL::CAtlExeModuleT<Module>::WinMain(showCmd);
  }
};
 
Module module;
 
int main()
{
  return module.WinMain(SW_SHOW);
}

というわけで、CAtlExeModuleTの非互換な変更についての話でした。これ自体は結構前から見つけていて、そのうちブログに書こうと思っていました。前回、それとは無関係にCAtlExeModuleTについて取り上げた(ATL::CAtlExeModuleT::WinMain関数を使う例)ので、その流れでこれを書くことにしたというわけです。

ATL::CAtlExeModuleTのバージョン非互換を見つけた話 is a post from: イグトランスの頭の中

Boostで型名をデマングルする

GCCやClangでtypeinfo::name()の結果を分かりやすい名前に変換(デマングル)するにあたり、Boostに便利なクラス・関数があります。

まずは、scoped_demangled_nameクラスです。std::type_infoから構築し、getメンバー関数でデマングルした文字列が得られるというものです。

#include <iostream>
#include <typeinfo>
#include <boost/core/demangle.hpp>
 
int main()
{
  // コンストラクタには、const std::type_info&型の仮引数が1つある。
  boost::core::scoped_demangled_name name(typeid(int).name());
  if (name.get())
  {
    std::cout << name.get() << std::endl;
  }
  else
  {
    std::cout << "不明" << std::endl;
  }
}

このほか、std::string型で返すboost::core::demangle関数もあります。こっちは条件判定が要りません。変換失敗時は元の文字列をそのままstd::stringに変換する実装になっています。もちろん、std::stringコンストラクタがstd::bad_allocなどを投げる可能性はあります。

#include <iostream>
#include <typeinfo>
#include <boost/core/demangle.hpp>
 
int main()
{
  // demangle関数もconst std::type_info&型の仮引数が1つある。
  std::cout << boost::core::demangle(typeid(int).name()) << std::endl;
}

内部の実装は、もちろんabi::__cxa_demangleです。

  • RAII化されており、freeを忘れる心配不要(自作するだけでなく、Boostを使うという選択肢が生まれた)。
  • GCCおよびClang以外とのポータビリティが得られる。その他の環境では、実引数で与えられたポインタをそのまま返す実装となっている。そのため、Visual C++などでも、上記のコードがそのまま実行可能。

現在の形になったのはBoost 1.56からです。ドキュメントは最近までありませんでしたが、現在はdemangleにあります。

Boostで型名をデマングルする is a post from: イグトランスの頭の中


VC++のwstring_convertやwbuffer_convertがちょっと変

std::wstring_convertとstd::wstring_bufferって、std::localeと組み合わて使うのが難しいです。そんなわけで、その名前とは裏腹にstd::wstring (wchar_t)との組み合わせには使いづらいなあと思っています。

が、しかし、Visual C++はその辺、規格をいい感じにやっちゃって(無視して)いて、使えるようになっていることに気付いてしまいました。

このコード、コンパイルできてしまいました。もちろん、実行すればちゃんと動きます。

#include <codecvt>
#include <locale>
#include <string>
#include <iostream>
 
int main()
{
  using codecvt_wchar = std::codecvt<wchar_t, char, std::mbstate_t>;
 
  std::locale loc("");
  std::wstring_convert<codecvt_wchar> cv(
    &std::use_facet<codecvt_wchar>(loc));
 
  std::string message = "あいうえお";
 
  std::wstring wcs = cv.from_bytes(message);
  std::string mbs = cv.to_bytes(wcs);
 
  std::cout << mbs << std::endl;
}

wbuffer_convertも同様にコンパイルできました。

#include <codecvt>
#include <locale>
#include <string>
#include <iostream>
#include <sstream>
 
int main()
{
  using codecvt_wchar = std::codecvt<wchar_t, char, std::mbstate_t>;
 
  std::locale loc("");
 
  std::wstring_convert<codecvt_wchar> cv(
    &std::use_facet<codecvt_wchar>(loc));
 
  std::wbuffer_convert<codecvt_wchar> buf(
    std::cout.rdbuf(),
    &std::use_facet<codecvt_wchar>(loc));
  std::wostream my_wcout(&buf);
 
  std::wstring wcs = cv.from_bytes("かきくけこ");
  my_wcout << wcs << std::endl;
}

「こういう風に書くとダメなんですよー」という例を出すつもりで書き始めたら、逆にコンパイルエラーが出なくて、驚きました。

  • wstring_convertおよびwstring_bufferのコンストラクタがコンパイルできている。標準規格では、仮引数の型が非constなポインタ型となっているのに、VC++ではconstなポインタ型となっている。use_facetの戻り値の型はconstな参照なのだが、そのせいでコンパイルが通る。
  • wstring_convertおよびwstring_bufferのデストラクタがコンパイルできている。デストラクタでは、コンストラクタで渡したcodecvtへのポインタをdeleteすることになっているのだが、std::codecvtのデストラクタはprotected。VC++のwstring_convertとwbuffer_convertは、内部でstd::localeオブジェクトにcodecvtを持たせることで、この問題を回避している。

GCC (libstdc++)やClang (libc++)だといずれもコンパイルエラーです。

この実装、私がこういう風に使えたら良かったと考えていた感じです。その点では素晴らしいのですが、現実VC++だけが標準規格に沿っていないという現状では、あまり有用に使えないのですよね。

VC++のwstring_convertやwbuffer_convertがちょっと変 is a post from: イグトランスの頭の中

wstring_convertやwbuffer_convertでwchar_tとcharとを変換する

wstring_convertやwbuffer_convertを使ってワイド文字列とマルチバイト文字列との変換を実現する方法を考えました。

wstring_convertやwbuffer_convertは、前回(VC++のwstring_convertやwbuffer_convertがちょっと変)書いたように、その名前に反してワイド文字との変換に使えるようになっていません。std::use_facetの戻り値を使えるように作られていないためです。

そこをどうにかできないかとずっと考えていて、やっと思いつきました。use_facetで得たcodecvtに移譲するクラスを作るという手法です。それをwstring_convertやwbuffer_convertに渡せばいいわけです。

#include <array>
#include <iostream>
#include <locale>
#include <codecvt>
#include <string>
 
template<typename CodeCvt>
class codecvt_forwarder : public CodeCvt
{
public:
  //using std::codecvt_base::result; // ← Visual C++ 2015でダメだった。
  using typename CodeCvt::result;
  using typename CodeCvt::state_type;
  using typename CodeCvt::intern_type;
  using typename CodeCvt::extern_type;
 
  explicit codecvt_forwarder(const std::locale& loc)
    : CodeCvt(), m_loc(loc), m_cvt(std::use_facet<CodeCvt>(m_loc))
  {
  }
 
  ~codecvt_forwarder() {}
 
  result do_out(
    state_type& state,
    const intern_type* from,
    const intern_type* from_end,
    const intern_type*& from_next,
    extern_type* to,
    extern_type* to_end,
    extern_type*& to_next) const override
  {
    return m_cvt.out(state, from, from_end, from_next, to, to_end, to_next);
  }
  result do_unshift(
    state_type& state,
    extern_type* to,
    extern_type* to_end,
    extern_type*& to_next) const override
  {
    return m_cvt.unshift(state, to, to_end, to_next);
  }
  result do_in(
    state_type& state,
    const extern_type* from,
    const extern_type* from_end,
    const extern_type*& from_next,
    intern_type* to,
    intern_type* to_end,
    intern_type*& to_next) const override
  {
    return m_cvt.in(state, from, from_end, from_next, to, to_end, to_next);
  }
  int do_encoding() const noexcept override
  {
    return m_cvt.encoding();
  }
  bool do_always_noconv() const noexcept override
  {
    return m_cvt.always_noconv();
  }
  int do_length(
    state_type& state,
    const extern_type* from,
    const extern_type* from_end,
    std::size_t max) const override
  {
    return m_cvt.length(state, from, from_end, max);
  }
  int do_max_length() const noexcept override
  {
    return m_cvt.max_length();
  }
 
private:
  std::locale m_loc;
  const CodeCvt& m_cvt;
};
 
class wstring_convert_by_locale
  : public std::wstring_convert<
    codecvt_forwarder<std::codecvt<wchar_t, char, std::mbstate_t>>, wchar_t>
{
public:
  wstring_convert_by_locale(const std::locale& loc)
    : wstring_convert(
      new codecvt_forwarder<std::codecvt<wchar_t, char, std::mbstate_t>>(loc))
  {
  }
};
 
class wbuffer_convert_by_locale
  : public std::wbuffer_convert<
    codecvt_forwarder<std::codecvt<wchar_t, char, std::mbstate_t>>, wchar_t>
{
public:
  wbuffer_convert_by_locale(std::streambuf* buf, const std::locale& loc)
    : wbuffer_convert(
      buf,
      new codecvt_forwarder<std::codecvt<wchar_t, char, std::mbstate_t>>(loc))
  {
  }
};
 
int main()
{
  std::locale loc("");
  std::locale::global(loc); // setlocaleを呼び出すことを意図している。
 
  wstring_convert_by_locale cv(loc);
 
  wbuffer_convert_by_locale buf(std::cin.rdbuf(), loc);
  std::wistream my_wcin(&buf);
 
  std::wstring input;
  std::getline(my_wcin, input);
 
  std::array<char, 256> input_mbs;
  wcstombs(input_mbs.data(), input.c_str(), input_mbs.size());
 
  std::wstring wcs = cv.from_bytes(input_mbs.data());
  std::string mbs = cv.to_bytes(wcs);
 
  std::cout << mbs << std::endl;
}

クラステンプレートcodecvt_forwarderは、codecvtの全virtualメンバー関数をオーバーライドしています。いずれも、すべてメンバー変数m_cvtのpublicメンバー関数に処理を丸投げしています。m_cvtはuse_facetで得たオブジェクトです。

変換に使うcodecvtについては、std::codecvt_byname<char, wchar_t, std::mbstate_t>を使う方法も考えられます。一応汎用性を重視して、上記プログラムではstd::localeとstd::use_fasetの組み合わせとしました。

FreeBSDでこんな感じで動きました。

$ echo あいうえお | iconv -t EUC-JP | env LANG=ja_JP.eucJP ./a.out | iconv -f EUC-JP
あいうえお
$ echo あいうえお | iconv -t Shift_JIS | env LANG=ja_JP.SJIS ./a.out | iconv -f Shift_JIS
あいうえお
$ echo あいうえお | iconv -t UTF-8 | env LANG=ja_JP.UTF-8 ./a.out | iconv -f UTF-8

Visual C++ 2015でも同じように動きます(試したのはコードページ932でだけです)。

c++ – libc++ vs VC++: Can non-UTF conversions be done with wstring_convert? – Stack Overflowの回答の1つにある、std::codecvt(またはstd::codecvt_byname)の派生クラスを作る方法よりまともな方法が出来上がったと自負しています。

wstring_convertやwbuffer_convertでwchar_tとcharとを変換する is a post from: イグトランスの頭の中

十六進数で文字列に変換する (Boostのhex関数)

この記事は、初心者C++er Advent Calendar 2016の4日目の記事です。


C++で、数値を文字列に変換する関数といえば、まずはstd::to_string (cpprefjp)です。ところが、この関数は、十進法で結果が出てきます。ほかによく使うものと言えば、十六進法です。というわけで、十六進法で文字列に変換する方法の話です。

標準ライブラリにいい感じのものがないので、Boostに頼ります。使いますのは、hexという単純な名前の関数です。

#include <iostream>
#include <string>
#include <iterator>
#include <cstdint>
#include <boost/algorithm/hex.hpp>
 
int main()
{
  std::uint32_t x = 0xdeadbeaf;
  std::string s;
  boost::algorithm::hex(&x, &x + 1, std::back_inserter(s));
  std::cout << s << std::endl;
}

このプログラムを実行すると、変数sの中身は”DEADBEAF”となり、それが出力されます。

hex関数は、「イテレータ2つまたはRangeで複数要素を指定し、一気に変換する関数」という設計になっています。ところが、このように1要素だけ変換するのに使うこともできるんです。

というわけで、boost::algorithm::hex関数を使い、数値オブジェクト1要素を文字列に変換する、という内容でした。こういう使い方、意外と見かけなかったので、今回紹介することにしました。

リファレンス: The Boost Algorithm Library – hex

十六進数で文字列に変換する (Boostのhex関数) is a post from: イグトランスの頭の中

配列でないオブジェクトに対するポインタ演算

この記事は、C言語 Advent Calendar 2016の11日目の記事です。


前回の記事、十六進数で文字列に変換する (Boostのhex関数)では、uint32_t型の変数xを定義し、&x + 1というポインタ演算を行うC++のコードを載せました。xは配列でもなんでもありません。果たしてこんなことして良いのでしょうか、というのが今回の話です。Cにも当てはまる話なので、C言語 Advent Calendarに乗っかせていただくことにしました。

結論を述べると、問題ありません。C11ドラフトであるISO/IEC 9899:201x – n1548.pdfの6.5.6 Additive operatorsの7に、1要素の配列と同じよう取り扱う旨が書かれています。なお、Additive operatorsという見出しですが、2項演算子の+と-両方について書かれているところです。

For the purposes of these operators, a pointer to an object that is not an element of an array behaves the same as a pointer to the first element of an array of length one with the type of the object as its element type.

なお、C++でも同様の規定があります。C++14ドラフトのC++ International Standard – n4296.pdfでは、5.3.1 Unary operators [expr.unary.op]の3の途中に、以下の文章があります。

For purposes of pointer arithmetic (5.7) and comparison (5.9, 5.10), an object that is not an array element whose address is taken in this way is considered to belong to an array with one element of type T.

このように、CおよびC++でそれぞれ、&x + 1は、配列の最後の要素の次を指すポインタと同様、*で参照剥がしさえしなければ問題ありません。

こんなコードは全然問題ないわけです。

#include <stdio.h>
 
void test1() // 例
{
  int x = 500;
  int* end = &x + 1;
  for (int* p = &x; p != end; p++)
  {
    printf("%d\n", *p);
  }
}
 
void test2() // 比較用に配列を使ったもの
{
  int a[] = {
    1,
    2,
    3,
  };
  int* end = a + sizeof a / sizeof a[0];
  for (int* p = a; p != end; p++)
  {
    printf("%d\n", *p);
  }
}
 
int main()
{
  test1();
  puts("--------");
  test2();
}

ちなみに、Stack overflowにもIs the “one-past-the-end” pointer of a non-array type a valid concept in C++?という質問がありました。

というわけで、C++のコードを書いていて気になったことですが、Cにも当てはまることだったので、C言語 Advent Calendarのネタといたしました。

配列でないオブジェクトに対するポインタ演算 is a post from: イグトランスの頭の中

Boost.Threadの排他制御を補助する関数

この記事は、C++ Advent Calendar 2016の12日目の記事です。


std::lock_guardクラステンプレートは、std::mutexなどのlockとunlockをRAII化するものとして欠かせません。

std::mutex m;
std::lock_guard<std::mutex> guard(m);

しかし、このようにテンプレート実引数としてミューテックスの型を指定しなければならないのが少しです。そこで、Boost.Thraedの中からこの点少し便利になったものを2つ紹介します。どれもヘッダーのみ、ビルド不要で使えます。

make_lock_guard

まずは、boost::make_lock_guard関数テンプレートです。std::make_pairなどのように、よくある関数テンプレートです。ただし、ミューテックスの要件(コンセプト)でコピー・ムーブ可能となっていないため、このように参照で受ける必要があります。

std::mutex m;
auto&& guard = boost::make_lock_guard(m);
// 以下も可。
// const auto& guard = boost::make_lock_guard(m);

なお、boost::make_unique_lockとboost::make_unique_locksもあります。boost::make_unique_locksは複数個のミューテックスを一挙にロックします。内部では、boost::lock関数を呼び出しています。

std::mutex m1
std::recursive_mutex m2;
auto locks = boost::make_unique_locks(m1, m2);
// locksの型はstd::tuple<std::mutex, std::recursive_mutex>

なお、boost::adopt_lockを実引数として受け取るboost::make_lock_guardや、boost::adopt_lock, boost::defer_lock, boost::try_to_lockのそれぞれを実引数として受け取るboost::make_unique_lockもあります。

以下、Boostのリファレンスページへのリンクです。

with_lock_guard

次は、boost::with_lock_guard関数テンプレートです。これは、ミューテックスをロックしてから、指定の関数オブジェクトを呼び出すというものです。もちろん、関数オブジェクトから脱出したら、ロック解除されます。

std::mutex m;
int x = boost::with_lock_guard(m, [&]{
  // ロックされている状態。
  // いろんな処理を実行する。
  return 1;
});
// xには関数オブジェクトの戻り値1が入る。
 
boost::with_lock_guard(m, [](int n) {}, 2);
// with_lock_guardの3番目以降の実引数は、関数オブジェクトに渡される。

boost::with_lock_guardの良いところは、排他制御する範囲を明確にできる点です。戻り値の受け渡しが可能なので、関数内に{}でスコープを作ってstd::lock_guardを使うコードでは代用が難しい場合でも、スマートに書ける可能性があります。

class hoge
{
public:
  // デフォルトコンストラクタがないクラス。
  explicit hoge(int) {}
  hoge(hoge&&) = default;
};
 
std::mutex m;
 
void f1()
{
  // ここで前処理
 
  hoge obj = boost::with_lock_guard(m, []
  {
    hoge tmp(3);
    // ここで何か処理
    return tmp;
  };
 
  // ここで残りの処理
}
 
// こういうコードは書けない、という例
void f2()
{
  // ここで前処理
 
  hoge obj; // デフォルトコンストラクタがないので無理!
  {
    std::lock_guard<std::mutex> guard(m);
    hoge tmp(3);
    // ここで何か処理
    obj = std::move(tmp);
  });
 
  // ここで残りの処理
}

boost::with_lock_guardを使うには、<boost/thread/with_lock_guard.hpp>をインクルードします。

以下、Boostのリファレンスページへのリンクです。


なお、boost::make_lock_guardは、C++1zで不要になります(参考:C++1z クラステンプレートのテンプレート引数推論 – Faith and Brave – C++で遊ぼう)。

今回は、ロックの補助関数に焦点を当てることにしました。そのため、synchronized_value(boost::synchronized_valueクラス – yohhoyの日記)は取り上げていません。

std::lock_guardだけでなく、これらも使って、楽してコードを書いていきましょう。

Boost.Threadの排他制御を補助する関数 is a post from: イグトランスの頭の中

Viewing all 123 articles
Browse latest View live