引き続き、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を実装する
  • ⇒ ATL::IDispEventSimpleImplでIDispatchを実装する

前回の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を使ってみる
  • ⇒ CreateDispTypeInfoとATL::IDispatchImplを使ってみる

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を使ってみる
  • ⇒ CreateDispTypeInfoとCreateStdDispatchを使ってみる

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コントロールを使ってみる
  • ⇒ ATL::CAxWindowでWebBrowserコントロールを使ってみる
  • ⇒ ATL::CAxWindowでWebBrowserコントロールを使ってみる

私、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を使わない書き方

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メタデータファイルを埋め込むツールを作った

Boost.勉強会 #20 東京でこんな発表をしました: エクストリームC++11/14プログラミング

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

「自分はこうしている」という話が意外と聞けたことが大変有益でした。

なお、std名前空間の中に利用者が定義を書くことは、ごく限られた状況に限り許されています。詳しい解説がstd名前空間にテンプレートの特殊化を追加する – Cry’s Diaryにあります。

この記事のカテゴリ

  • ⇒ Boost.勉強会 #20で発表
  • ⇒ Boost.勉強会 #20で発表

boost::make_iterator_range_n関数の話です。これを最近見つけました。これは、先頭要素を指すイテレータと要素数を実引数に渡すとRangeオブジェクトを返してくれる関数です。調べてみるとBoost 1.56.0からのものでした。

C言語のAPIでよくある、配列要素へのポインタと要素数という組み合わせをRange化するのに便利です。

#include <array>
#include <iostream>
#include <boost/range/iterator_range.hpp>
#include <windows.h>
#include <wtsapi32.h>
 
#pragma comment(lib, "wtsapi32.lib")
 
int main()
{
  WTS_SESSION_INFO* sessionInfoArray;
  DWORD count;
  if (!WTSEnumerateSessions(
    WTS_CURRENT_SERVER_HANDLE, 0, 1, &sessionInfoArray, &count))
  {
    std::cerr << "Failed" << std::endl;
    std::quick_exit(1);
  }
  // sessionInfoArray[0]からsessionInfoArray[count - 1]に
  // データが書き込まれている。
 
  for (const WTS_SESSION_INFO& info
    : boost::make_iterator_range_n(sessionInfoArray, count))
  {
    std::cout << info.SessionId
      << ' ' << info.pWinStationName << std::endl;
  }
  std::quick_exit(0);
  // WTSFreeMemoryは省略
}

main関数の仮引数argcとargvに対しても使えます。

#include <iostream>
#include <boost/range/iterator_range.hpp>
 
int main(int argc, char** argv)
{
  // なお、argv[0]を除外したければ
  // boost::make_iterator_range_n(argv + 1, argc - 1)とすれば良い。
  auto r = boost::make_iterator_range_n(argv, argc);
  for (auto arg : r)
  {
    std::cout << arg << std::endl;
  }
}

以前より、先頭と終端(の次)のイテレータ2つからRangeを作ってくれるboost::make_iterator_range関数がありました。これを使って、boost::make_iterator_range(sessionInfoArray, sessionInfoArray + count)やmake_iterator_range(argv, argv + argc)というコードを良く書いていました。そのため、こういう関数が欲しいなあと思っていたところだったので、すでに存在していて嬉しいです。

この記事のカテゴリ

  • ⇒ 配列要素へのポインタと要素数からRangeを作る

ありそうで見当たらなかったので、そういうサンプルコードを書いておこうと思いました。

  • 同期モードです。非同期モードは少しコードの分量が増えるので見送りました。
  • #import使っているので、Visual C++限定です。
#define UNICODE
#define _UNICODE
 
#include <iostream>
#include <locale>
 
#import <msxml6.dll>
 
int main()
{
  std::locale loc(std::locale::classic(), "", std::locale::ctype);
  std::wcout.imbue(loc);
  std::wcerr.imbue(loc);
 
  try
  {
    _com_util::CheckError(CoInitializeEx(
      nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE));
 
    MSXML2::IXMLHTTPRequestPtr xhr(__uuidof(MSXML2::XMLHTTP60));
    xhr->open(
      L"GET",
      L"https://www.google.co.jp/robots.txt",
      VARIANT_FALSE);
    xhr->send();
 
    auto status = xhr->status;
    if (200 <= status && status <= 299)
    {
      std::wcout << xhr->responseText << std::endl;
    }
    else
    {
      std::wcerr << xhr->status << L' ' << xhr->statusText << std::endl;
    }
 
    CoUninitialize(); // RAII化していないのは手抜き。
  }
  catch (const _com_error& e)
  {
    std::wcerr << e.Error() << L' ' << e.ErrorMessage() << std::endl;
  }
}

対比して理解できるよう、VBScriptとJavaScript(共にWSH)で書いたものも置いてきます。C++版のcatch節に相当する部分を欠いているのはご容赦ください。

Set xhr = CreateObject("Msxml2.XMLHTTP.6.0")
xhr.open "GET", "https://www.google.co.jp/robots.txt", False
xhr.send
If 200 <= xhr.status And xhr.status <= 299 Then
  WScript.Echo xhr.responseText
Else
  WScript.Echo xhr.status & " " & xhr.statusText
End If
var xhr = new ActiveXObject("Msxml2.XMLHTTP.6.0")
xhr.open("GET", "https://www.google.co.jp/robots.txt", false);
xhr.send();
if (200 <= xhr.status && xhr.status <= 299) {
  WScript.Echo(xhr.responseText);
} else {
  WScript.Echo(xhr.status + " " + xhr.statusText);
}

HTTPステータスコードが200番台以外だった場合(else側)はhttpstat.usで試しました。こういう、任意のHTTPステータスコードを試せるウェブサービスはほかにもあるようです。

細かい制御が要らないなら、意外と短いコードになるので、Visual C++で使うのもアリではないかと思いました。

MSDNライブラリのリファレンス: IXMLHTTPRequest Members

この記事のカテゴリ

  • ⇒ XMLHttpRequestをVC++で使う例
  • ⇒ XMLHttpRequestをVC++で使う例
  • ⇒ XMLHttpRequestをVC++で使う例
  • ⇒ XMLHttpRequestをVC++で使う例

初めにお断りしておきます。タイトルは少し嘘です。正確には、WinPcapのデバイスドライバnpf.sysを使わずにWiresharkを動かします。wpcap.dllは使います。

以前書いた記事、netsh traceと同じようにパケットキャプチャーするプログラムを作ったの続きです。

今回作ったもののソースコードはegtra/ndiscap-packet (GitHub)にあります。

前回のあらすじ

Windows 7より、OSにパケットキャプチャ機能が搭載されており、netsh traceコマンドで呼び出せます。これを他のアプリから使う方法が公開されていないようだったので、なんのAPIを使っているのか調べました。そして、以下のソースコードが出来上がりました。

Gist: “netsh trace start caputre=yes traceFile=D:\packet.etl”の再現 · GitHub

今回やったこと

前回のコードを基に、WinPcap互換のDLLを作ろうと考えました。WinPcapを使う代表的なアプリケーションとしてWireshark(Wireshark – 窓の杜)を対象とし、Wiresharkがとりあえず動くところまで作りました。

動機

始めたきっかけは、近頃のWindowsとWinPcapのnpf.sysの組み合わせにおける安定性への疑問からです。npf.sysなしでWinPcapを使えたら良いと思っていました。

もっとも、現状ではWin10Pcap – WinPcap for Windows 10が良い解決策でしょう。私も普段はWin10Pcapを使っています。

動かし方

  1. Githubのリポジトリegtra/ndiscap-packetにあるソースコードをVisual Studio 2015でビルドする。packet.dllが出来上がる。
  2. WinPcap · DownloadからWinPcapをダウンロードし、wpcap.dllを取り出す。WinPcapのインストールは不要。
  3. C:\Windows\System32など、パスの通るところにwpcap.dllとビルドしたpacket.dllを置く。
  4. WiresharkをWinPcap抜きでインストールする。
  5. Wiresharkを「管理者として実行」する。packet.dllの影響で起動に時間がかかる(私の場合、10秒ほど)ので、少々待つ。
  6. 「NdisCapPacket」というインタフェースが1つ存在するはずなので、それに対してキャプチャーを開始。

ndiscap-packetがWiresharkで認識されている様子

メニューバーの「キャプチャ」→「オプション」をクリックするとこういう表示になります。もちろん、このウィンドウを呼び出さず、メインウィンドウからキャプチャを開始しても問題ありません。

WinPcapのAPI

WinPcap互換のものを作ろうとしているので、作るにあたってWinPcapのおさらいをしました。

WinPcap: WinPcap internalsで説明されているように、WinPcapは以下のような階層になっています。

Application Wpcap.dll Packet.dll Npf.sys

Wpcap.dllはlibpcapとほぼ互換の高水準なAPI、Packet.dllは低水準なAPIを提供しています。アプリからWpcap.dllが呼び出され、Packet.dllが呼び出され、デバイスドライバNpf.sysに到達するという階層です。

余談ですが、Win10PcapのWin10Pcap.sysは、Npf.sysと互換となっているのですね。

というわけで、Wpcap.dllまたはPacket.dllのいずれかと互換性のあるDLLを作れば良いというわけです。そこで、双方のAPIを見て回り、作るのが簡単そうだったPacket.dllの互換品を作ることにしました。

まとめ

WinPcapのデバイスドライバnpf.sysあるいはその代替となるWin10Pcapを使うことなく、WinPcapを使うアプリケーションを動作させたいと考えました。

突貫工事で作ったので、まだ実用になる状態ではありません。プルリク歓迎です。無線LANなどいろいろな環境で試したり、実装をサボっているAPIをまじめに実装したりしないと使い物にならないでしょう。

この記事のカテゴリ

  • ⇒ WiresharkをWinPcap抜きで動かす
  • ⇒ WiresharkをWinPcap抜きで動かす

次ページへ »