Quantcast
Channel: イグトランスの頭の中
Viewing all 123 articles
Browse latest View live

FreeBSD 10.0はstd::localeが使えるようになった(FreeBSD 10.0-RELEASE登場記念)

$
0
0

先日、FreeBSD 10.0-RELEASEが登場しました(参考: SourceForge.JP Magazine)。今回、大きな変更点としてClangとlibc++の標準採用があります。これにより、以下のプログラムが動作するようになりました。

// c++ helloworld.cppまたはclang++ helloworld.cpp
 
#include <iostream>
 
int main()
{
  std::wcout.imbue(std::locale(""));
  std::wcout << L"こんにちは、世界" << std::endl;
}

ただし、これが動くのはLANG=ja_JP.UTF-8の場合のみでした。ja_JP.eucJPでは、コンパイル時に“error: illegal character encoding in string literal”と言われ、それを回避しても実行時にセグメンテーションフォルトになります。

今まで使われてきたlibstdc++は、std::localeが”C”しか使えませんでした。libstdc++でロケールが完全に使えるのはglibc使用時のみ、すなわち実質的にGNU/Linuxくらいのものでした。

これに対し、libc++はxlocale(jmanxxx_l系関数)などを使用しています。このため、UTF-8でしか動かないということは原理上なく、今後直す余地があります。

以上、FreeBSD 10.0からは標準でstd::localeが使えるようになるという話でした。

FreeBSD 10.0はstd::localeが使えるようになった(FreeBSD 10.0-RELEASE登場記念) is a post from: イグトランスの頭の中(のかけら)


ATLでCOMインタフェースを実装するコード例

$
0
0

ATLを使ってCOMオブジェクトを作る最小コードの例です。1アプリケーションの中でCOMオブジェクトの作成が必要になったという想定です。すなわち、この例にクラスファクトリやOBJECT_ENTRY_AUTOは登場しません。

#define _ATL_NO_AUTOMATIC_NAMESPACE
 
#include <iostream>
#include <atlbase.h>
#include <atlcom.h>
 
MIDL_INTERFACE("0f9a78ce-4f58-4160-9889-e3bd4485c92d") ITest : IUnknown
{
  virtual HRESULT STDMETHODCALLTYPE Test() = 0;
};
 
class ATL_NO_VTABLE TestImpl
  : public ATL::CComObjectRootEx<ATL::CComMultiThreadModel>
  , public ITest
{
  DECLARE_NOT_AGGREGATABLE(TestImpl)
 
  BEGIN_COM_MAP(TestImpl)
    COM_INTERFACE_ENTRY(ITest)
  END_COM_MAP()
 
public:
  HRESULT FinalConstruct() throw()
  {
    std::cout << "TestImpl::FinalConstruct" << std::endl;
    return S_OK;
  }
 
  HRESULT FinalRelease() throw()
  {
    std::cout << "TestImpl::FinalRelease" << std::endl;
    return S_OK;
  }
 
  virtual HRESULT STDMETHODCALLTYPE Test() throw() override
  {
    std::cout << "TestImpl::Test" << std::endl;
    return S_OK;
  }
 
  void Test2()
  {
    std::cout << "TestImpl::Test2 (non-virtual)" << std::endl;
  }
};
 
class Module : public ATL::CAtlExeModuleT<Module> {};
Module module;
 
int main()
{
  try
  {
    ATL::CComObject<TestImpl>* impl = nullptr;
    auto hr = ATL::CComObject<TestImpl>::CreateInstance(&impl);
    ATLENSURE_SUCCEEDED(hr);
    ATL::CComPtr<ITest> test(impl); // ここでAddRefが呼ばれる
    impl->Test2();
 
    test->Test();
  }
  catch (const ATL::CAtlException& e)
  {
    std::cerr << std::hex << std::showbase << "Failed: " << e << std::endl;
  }
}

この例では、オブジェクトの作成にATL::CComObject<>::CreateInstanceを使っています。これは、ATLのオブジェクト作成の関数において、実装クラスへのポインタを返す唯一の存在です(これ以外はみんなインタフェースへのポインタを返します)。そのことを示す意図を込めて、上記コードでも、どのインタフェースのメソッドでもないTest2メンバ関数を呼び出しています。

ただし、ATL::CComObject<>::CreateInstanceには注意点があります。それは、この関数から返った直後のオブジェクトは原則として参照カウントが0ということです。上のコードのように、すぐにAddRef(あるいはQueryInterface)を呼びましょう。

ATLでCOMインタフェースを実装するコード例 is a post from: イグトランスの頭の中(のかけら)

ATLでCOMオブジェクトを作る関数を作る

$
0
0

前回(ATLでCOMインタフェースを実装するコード例)の続きです。ATL::CComObject<>::CreateInstanceに注目していきます。

ATL::CComObject<>::CreateInstanceのラッパー関数を作る

少し複雑なので、オブジェクトの作成部分を関数に切り出したいと思います。戻り値はオブジェクトへのポインタとし、失敗したら前回同様ATLENSURE_SUCCEEDEDで例外を投げることにします。

単純に作るとこうなりますが、実は少し問題があります。

template<typename T>
ATL::CComPtr<T> CreateComObject()
{
  ATL::CComObject<T>* obj = nullptr;
  auto hr = ATL::CComObject<T>::CreateInstance(&obj);
  ATLENSURE_SUCCEEDED(hr);
  return ATL::CComPtr<T>(obj);
}

ATL::CComObject<>::CreateInstanceの問題

その問題は、CComObjectのCreateInstanceの実装を見ると分かります。

// Visual C++ 2013のatlcom.h
template <class Base>
HRESULT WINAPI CComObject<Base>::CreateInstance(
  _COM_Outptr_ CComObject<Base>** pp) throw()
{
  ATLASSERT(pp != NULL);
  if (pp == NULL)
    return E_POINTER;
  *pp = NULL;
 
  HRESULT hRes = E_OUTOFMEMORY;
  CComObject<Base>* p = NULL;
  ATLTRY(p = _ATL_NEW CComObject<Base>())
  if (p != NULL)
  {
    p->SetVoid(NULL);
    p->InternalFinalConstructAddRef();
    hRes = p->_AtlInitialConstruct();
    if (SUCCEEDED(hRes))
      hRes = p->FinalConstruct();
    if (SUCCEEDED(hRes))
      hRes = p->_AtlFinalConstruct();
    p->InternalFinalConstructRelease();
    if (hRes != S_OK)
    {
      delete p;
      p = NULL;
    }
  }
  *pp = p;
  return hRes;
}

お分かりでしょうか。

ATLTRY(p = _ATL_NEW CComObject<Base>())

ATLTRYは、try{…;} catch(…) {}に展開されます。つまり、コンストラクタで何か例外を投げてもすべてE_OUTOFMEMORYにされてしまうのです。HRESULTを返すインタフェースメソッドの実装中などであればそれでもよいのですが、例外を活用する前提でC++のコードを書いているときには少し困ります。

// atldef.h
 
#ifndef _ATL_DISABLE_NOTHROW_NEW
#include <new.h>
#define _ATL_NEW		new(std::nothrow)
#else
#define _ATL_NEW		new
#endif
 
// ……
 
#define ATLTRYALLOC(x) __pragma(warning(push)) __pragma(warning(disable: 4571)) try{x;} catch(...) {} __pragma(warning(pop))
 
// ……
 
#define ATLTRY(x) ATLTRYALLOC(x)

解決方法

結論として自前で関数を作りました。CComObject<>::CreateInstanceとCComCreator<>::CreateInstanceの2つを参考にしています。

template<typename T>
ATL::CComPtr<T> CreateComObject()
{
  std::unique_ptr<ATL::CComObject<T>> p(new ATL::CComObject<T>());
  p->SetVoid(nullptr);
  p->InternalFinalConstructAddRef();
  HRESULT hRes = p->_AtlInitialConstruct();
  if (SUCCEEDED(hRes))
    hRes = p->FinalConstruct();
  if (SUCCEEDED(hRes))
    hRes = p->_AtlFinalConstruct();
  p->InternalFinalConstructRelease();
  return hRes == S_OK
    ? p.release()
    : nullptr;
}

もちろん、自分で作らずに済む方法がないか、他のATLクラスのCreateInstance関数も当たってみました。しかし、やはりどれも同じようにダメだったので、自分で作る道を選びました。

もちろん、例外を使わないコードを書いている場合など、CComObject<>::CreateInstanceが最適な場合はそのまま使うべきです。


2014年2月16日追記:CraeteComObjectからCreateComObjectに書き間違いを直しました。

ATLでCOMオブジェクトを作る関数を作る is a post from: イグトランスの頭の中(のかけら)

ATLでコンストラクタに引数を渡しつつCOMオブジェクトを作る

$
0
0

ATLでCOMオブジェクトを作る話の続きです。今回で一応終わります。


ATLでCOMインタフェースを実装するC++クラスを作っていて遭遇した、次なる壁は引数を持つコンストラクタでした。

ATL::CComObjectRootExから派生したクラスで、引数を取るコンストラクタを持たせたいと思いました。しかし、そんなことができるようにはなっていません。

これがATL::CComObjectのコンストラクタです。基底クラス(CComObjectRoot(Ex)から派生した自作クラス)にコンストラクタ引数を渡す余地はありません。

// CComObject内
CComObject(_In_opt_ void* = NULL)
{
  _pAtlModule->Lock();
}

仕方がないのでATL::CComObjectに代わるクラスを自作してしまいました。それが今回の内容です。

変更点はコンストラクタを以下のように変えたこと、それだけです(実際には、CreateInstanceやテンプレート版QueryInterfaceなど一部を面倒なので除外しています)。

// 自作CComObject
template<typename... Args>
CComObject(Args&&... args) : Base(std::forward<Args>(args)...) {}

以下コード全体です。前回作ったCreateComObject関数も可変長引数に修正してあります。

#define _ATL_NO_AUTOMATIC_NAMESPACE
 
#include <iostream>
#include <memory>
#include <atlbase.h>
#include <atlcom.h>
 
MIDL_INTERFACE("0f9a78ce-4f58-4160-9889-e3bd4485c92d") ITest : IUnknown
{
  virtual HRESULT STDMETHODCALLTYPE Test() = 0;
};
 
class ATL_NO_VTABLE TestImpl
  : public ATL::CComObjectRootEx<ATL::CComMultiThreadModel>
  , public ITest
{
  DECLARE_NOT_AGGREGATABLE(TestImpl)
 
  BEGIN_COM_MAP(TestImpl)
    COM_INTERFACE_ENTRY(ITest)
  END_COM_MAP()
 
public:
  explicit TestImpl(std::unique_ptr<int> p) : p(std::move(p)) {}
 
  HRESULT FinalConstruct() throw()
  {
    std::cout << "TestImpl::FinalConstruct" << std::endl;
    return S_OK;
  }
 
  HRESULT FinalRelease() throw()
  {
    std::cout << "TestImpl::FinalRelease" << std::endl;
    return S_OK;
  }
 
  virtual HRESULT STDMETHODCALLTYPE Test() throw() override
  {
    std::cout << "TestImpl::Test *p = " << *p << std::endl;
    return S_OK;
  }
 
  void Test2()
  {
    std::cout << "TestImpl::Test2 (non-virtual)" << std::endl;
  }
 
private:
  std::unique_ptr<int> p;
};
 
namespace egtra
{
 
template <class Base>
class CComObject :
  public Base
{
public:
  typedef Base _BaseClass;
  template<typename... Args>
  CComObject(Args&&... args) : Base(std::forward<Args>(args)...) {}
  virtual ~CComObject()
  {
    m_dwRef = -(LONG_MAX/2);
    FinalRelease();
#if defined(_ATL_DEBUG_INTERFACES) && !defined(_ATL_STATIC_LIB_IMPL)
    ATL::_AtlDebugInterfacesModule.DeleteNonAddRefThunk(_GetRawUnknown());
#endif
    ATL::_pAtlModule->Unlock();
  }
  STDMETHOD_(ULONG, AddRef)()
  {
    return InternalAddRef();
  }
  STDMETHOD_(ULONG, Release)()
  {
    ULONG l = InternalRelease();
    if (l == 0)
    {
      ATL::ModuleLockHelper lock;
      delete this;
    }
    return l;
  }
  STDMETHOD(QueryInterface)(REFIID iid, _COM_Outptr_ void** ppvObject) throw()
  {
    return _InternalQueryInterface(iid, ppvObject);
  }
};
 
template<typename T, typename... Args>
ATL::CComPtr<T> CreateComObject(Args&&... args)
{
  std::unique_ptr<CComObject<T>> p(
    new CComObject<T>(std::forward<Args>(args)...));
  p->SetVoid(nullptr);
  p->InternalFinalConstructAddRef();
  HRESULT hRes = p->_AtlInitialConstruct();
  if (SUCCEEDED(hRes))
    hRes = p->FinalConstruct();
  if (SUCCEEDED(hRes))
    hRes = p->_AtlFinalConstruct();
  p->InternalFinalConstructRelease();
  return hRes == S_OK
    ? p.release()
    : nullptr;
}
 
} // namespace egtra
 
class Module : public ATL::CAtlExeModuleT<Module> {};
Module module;
 
int main()
{
  using namespace egtra;
  try
  {
    auto impl = CreateComObject<TestImpl>(std::make_unique<int>(1000));
    impl->Test2();
 
    ATL::CComPtr<ITest> test(impl);
    test->Test();
  }
  catch (const ATL::CAtlException& e)
  {
    std::cerr << std::hex << std::showbase << "Failed: " << e << std::endl;
  }
  catch (const std::exception& e)
  {
    std::cerr << e.what() << std::endl;
  }
}

CreateComObjectの中でstd::make_uniqueを使っていないのは、過去のバージョンのVisual C++で使うことを一応考えているためです。もっとも、その場合には可変長引数にも対応していない(make_uniqueとともにVisual C++ 2013からです)ので、まずはそっちの対処を考えなければならないのですが。

なお、コンストラクタの時点ではAddRef/Release/QueryInterfaceは使用できません。ATLのCComObjectなどでも同じです。注意してください。IUnknownとしての操作が必要なら、DECLARE_PROTECT_FINAL_CONSTRUCTを定義して、FinalConstruct関数で書きましょう。

なんだか、だんだんATLに頼らないでコードを書くという結果になってしまいました。


2014年2月16日追記:CraeteComObjectからCreateComObjectに書き間違いを直しました。

ATLでコンストラクタに引数を渡しつつCOMオブジェクトを作る is a post from: イグトランスの頭の中(のかけら)

CComPtrのバグが直っていた

$
0
0

以前書いたCComPtrをCOMインタフェースでないクラスに対して使うのは要注意について、Visual C++ 2012でこの問題が直っていました。

ソースコードを見たところ、代入演算子の実装が改善されていました。すなわち、問題だったAtlComPtrAssignを使用しないようになっていました。

T* operator=(_Inout_ const CComPtr<T>& lp) throw()
{
  if(*this!=lp)
  {
    CComPtr(lp).Swap(*this);
  }
  return *this;
}

コピーコンストラクタとSwapの組み合わせという、きわめて一般的な実装に変更されています。参照カウントがあるので、thisとの比較も妥当だと思います。

これで安心してCComPtrが使用できます。また、このバージョンからムーブコンストラクタ・ムーブ代入演算子も実装されていて満足です。

CComPtrのバグが直っていた is a post from: イグトランスの頭の中(のかけら)

COMで弱参照を実現する

$
0
0

WinRTには弱参照のCOMインタフェースがあります。WinRTに限らず使いたいのですが、あいにくWinRT専用の作りだったので、代替品を自作しました。

これと同名のインタフェースとメソッド(ただし引数の型が少し異なるもの)を作ったわけです。そして、それに対する実装も作りました。

インタフェース

弱参照を使用できるオブジェクトはIWeakReferenceSourceを実装します。

MIDL_INTERFACE("de2988fe-a6b7-4e3d-923e-7463ce0e1040")
IWeakReferenceSource : IUnknown
{
public:
  virtual HRESULT STDMETHODCALLTYPE GetWeakReference(
    /* [retval][out] */ __RPC__deref_out IWeakReference** weakReference) = 0;
};

GetWeakReferenceメソッドで弱参照を取得できるというわけです。

IWeakReferenceインタフェースが弱参照を表すインタフェースです。
こちらは、強参照のオブジェクトを取得するResolveメソッドを持っています。

MIDL_INTERFACE("bdcb7ca6-376d-481a-8652-dfd69f723ecc")
IWeakReference : IUnknown
{
public:
  virtual HRESULT STDMETHODCALLTYPE Resolve(
    /* [in] */ __RPC__in REFIID riid,
    /* [iid_is][out] */ __RPC__deref_out void** objectReference) = 0;
};

対象のオブジェクトがすでに存在しない場合、成功値を返しますが*objectReferenceにはnullptrが格納されます。その場合の戻り値はWinRTだとS_OKでしたが、私の好みはS_FALSEです。

WinRT APIでは2番目の引数がIInspectable**でしたので、非WinRTなCOMインタフェースでは使用不可能でした。そこで、こちらはvoid**にしています。そのため、上記のIWeakReferenceSourceとIWeakReferenceはWinRTのものと異なるIIDにしています。

使用例

このインタフェースを実装するクラス、WeakReferenceImplとWeakReferenceSourceImpl、オブジェクト作成関数CreateComObjectWithWeakRefを作りました。今回はシングルスレッド専用です。

int main()
{
  ATLENSURE_RETURN_VAL(SUCCEEDED(CoInitializeEx(nullptr, COINIT::COINIT_APARTMENTTHREADED)), 1);
 
  {
    ATL::CComPtr<egtra::IWeakReference> weak;
    {
      auto x = CreateComObjectWithWeakRef<TestImpl>();
      ATL::CComQIPtr<egtra::IWeakReferenceSource> s(x);
      ATLENSURE_SUCCEEDED(s->GetWeakReference(&weak));
 
      ATL::CComPtr<IUnknown> u;
      weak->Resolve(IID_PPV_ARGS(&u));
      std::cout << implicit_cast<IUnknown*>(u) << std::endl;
    }
 
    ATL::CComPtr<IUnknown> u;
    weak->Resolve(IID_PPV_ARGS(&u));
    std::cout << implicit_cast<IUnknown*>(u) << std::endl;
  }
 
  CoUninitialize();
  return 0;
}

これを実行すると、1つ目のuは非ヌルポインタである値が出力され、2つ目のuはヌルポインタを取得していることが分かります。

ソースコード

最後に、ソースコード全体です。シングルスレッド限定のため、弱参照の実現方法は単純です。

  • FinalConstructで弱参照用に生のポインタをセット
  • FinalReleaseで弱参照から辿れないようnullptrをセット
#include <iostream>
#include <memory>
#include <intrin.h>
#include <atlbase.h>
#include <atlcom.h>
#include <boost/implicit_cast.hpp>
 
using boost::implicit_cast;
 
class Module : public ATL::CAtlExeModuleT<Module> {};
Module module;
 
namespace egtra
{
  MIDL_INTERFACE("bdcb7ca6-376d-481a-8652-dfd69f723ecc")
  IWeakReference : IUnknown
  {
  public:
    virtual HRESULT STDMETHODCALLTYPE Resolve(
      /* [in] */ __RPC__in REFIID riid,
      /* [iid_is][out] */ __RPC__deref_out void** objectReference) = 0;
  };
 
  MIDL_INTERFACE("de2988fe-a6b7-4e3d-923e-7463ce0e1040")
  IWeakReferenceSource : IUnknown
  {
  public:
    virtual HRESULT STDMETHODCALLTYPE GetWeakReference(
      /* [retval][out] */ __RPC__deref_out IWeakReference** weakReference) = 0;
  };
}
 
template<typename T>
ATL::CComPtr<T> CreateComObject()
{
  auto p = std::make_unique<ATL::CComObject<T>>();
  p->SetVoid(nullptr);
  p->InternalFinalConstructAddRef();
  HRESULT hRes = p->_AtlInitialConstruct();
  if (SUCCEEDED(hRes))
    hRes = p->FinalConstruct();
  if (SUCCEEDED(hRes))
    hRes = p->_AtlFinalConstruct();
  p->InternalFinalConstructRelease();
  return hRes == S_OK
    ? p.release()
    : nullptr;
}
 
class WeakReferenceImpl
  : public ATL::CComObjectRootEx<ATL::CComSingleThreadModel>
  , public egtra::IWeakReference
{
  BEGIN_COM_MAP(WeakReferenceImpl)
    COM_INTERFACE_ENTRY(egtra::IWeakReference)
  END_COM_MAP()
 
public:
  virtual HRESULT STDMETHODCALLTYPE Resolve(
    /* [in] */ _In_ REFIID riid,
    /* [iid_is][out] */ _COM_Outptr_result_maybenull_ void** ppv) throw() override
  {
    ATLENSURE_RETURN_HR(ppv != nullptr, E_POINTER);
    *ppv = nullptr;
    if (m_obj == nullptr)
    {
      return S_FALSE;
    }
    return m_obj->QueryInterface(riid, ppv);
  }
 
  void SetObject(IUnknown* obj)
  {
    m_obj = obj;
  }
 
private:
  IUnknown* m_obj = nullptr;
};
 
template<class T>
class WeakReferenceSourceImpl
  : public T
  , public egtra::IWeakReferenceSource
{
  static_assert(
    std::is_same<typename T::_ThreadModel, ATL::CComSingleThreadModel>::value,
    "Only ATL::CComSingleThreadModel is supported.");
 
  BEGIN_COM_MAP(WeakReferenceSourceImpl)
    COM_INTERFACE_ENTRY(egtra::IWeakReferenceSource)
    COM_INTERFACE_ENTRY_CHAIN(T)
  END_COM_MAP()
 
public:
  virtual HRESULT STDMETHODCALLTYPE GetWeakReference(
    /* [retval][out] */ _COM_Outptr_ egtra::IWeakReference** weakReference) throw() override
  {
    ATLENSURE_RETURN_HR(weakReference != nullptr, E_POINTER);
    *weakReference = m_weakRef;
    (*weakReference)->AddRef();
    return S_OK;
  }
 
  HRESULT FinalConstruct()
  {
    // このアップキャストはGetControllingUnknown()の代わり
    m_weakRef->SetObject(implicit_cast<egtra::IWeakReferenceSource*>(this));
    return T::FinalConstruct();
  }
 
  void FinalRelease()
  {
    m_weakRef->SetObject(nullptr);
    T::FinalRelease();
  }
 
private:
  ATL::CComPtr<WeakReferenceImpl> m_weakRef
    = CreateComObject<WeakReferenceImpl>();
};
 
template<typename T>
ATL::CComPtr<T> CreateComObjectWithWeakRef()
{
  return implicit_cast<T*>(CreateComObject<WeakReferenceSourceImpl<T>>());
}
 
MIDL_INTERFACE("0f9a78ce-4f58-4160-9889-e3bd4485c92d") ITest : IUnknown
{
  virtual HRESULT STDMETHODCALLTYPE Test() = 0;
};
 
class ATL_NO_VTABLE TestImpl
  : public ATL::CComObjectRootEx<ATL::CComSingleThreadModel>
  , public ITest
{
  DECLARE_NOT_AGGREGATABLE(TestImpl)
 
  BEGIN_COM_MAP(TestImpl)
    COM_INTERFACE_ENTRY(ITest)
  END_COM_MAP()
 
public:
  HRESULT FinalConstruct() throw()
  {
    std::cout << "TestImpl::FinalConstruct" << std::endl;
    return S_OK;
  }
 
  HRESULT FinalRelease() throw()
  {
    std::cout << "TestImpl::FinalRelease" << std::endl;
    return S_OK;
  }
 
  virtual HRESULT STDMETHODCALLTYPE Test() throw() override
  {
    std::cout << "TestImpl::Test" << std::endl;
    return S_OK;
  }
 
  void Test2()
  {
    std::cout << "TestImpl::Test2 (non-virtual)" << std::endl;
  }
};
 
int main()
{
  ATL::CComPtr<egtra::IWeakReference> weak;
  {
    auto x = CreateComObjectWithWeakRef<TestImpl>();
    ATL::CComQIPtr<egtra::IWeakReferenceSource> s(x);
    ATLENSURE_SUCCEEDED(s->GetWeakReference(&weak));
 
    ATL::CComPtr<IUnknown> u;
    weak->Resolve(IID_PPV_ARGS(&u));
    std::cout << implicit_cast<IUnknown*>(u) << std::endl;
  }
 
  ATL::CComPtr<IUnknown> u;
  weak->Resolve(IID_PPV_ARGS(&u));
  std::cout << implicit_cast<IUnknown*>(u) << std::endl;
}

次回はマルチスレッド対応版の予定です。→書きました:COMで弱参照を実現する(マルチスレッド対応)

COMで弱参照を実現する is a post from: イグトランスの頭の中(のかけら)

COMで弱参照を実現する(マルチスレッド対応)

$
0
0

前回(COMで弱参照を実現する)の続きで弱参照の実装、マルチスレッド対応編です。今回のコードは前回からがらりと変わっています。

今回はクラスObjectWithWeakReferenceにすべてまとめました。

  • 対象オブジェクトのメモリ領域
  • IWeakReferenceSourceの実装
  • IWeakReferenceの実装

対象オブジェクト

対象オブジェクトはATL::CComContainedObjectを使い、aligned_storageで確保した領域に構築させています。C++11のunionが使えればよいのですが、Visual C++にはまだありません。

typedef ATL::CComContainedObject<T> Obj;
typename std::aligned_storage<
  sizeof (Obj), std::alignment_of<Obj>::value>::type m_contained;

m_contained上のオブジェクトはObjectWithWeakReferenceのコンストラクタで構築します。そしてm_strongRefが0になったら破棄します。

std::atomic<ULONG> m_strongRef = 1;
 
ObjectWithWeakReference()
{
  ::new(&m_contained) Obj(
    static_cast<IUnknown*>(GetWeakReferenceSource()));
}
 
STDMETHOD_(ULONG, ObjectRelease)() override
{
  auto p = GetContainedObject();
  auto c = --m_strongRef;
  if (c == 0)
  {
    p->FinalRelease();
    p->~Obj();
 
    // FinalConstructでInternalAddRefしたものに対応する。
    InternalRelease();
  }
  return c;
}
 
Obj* GetContainedObject()
{
  assert(m_strongRef > 0);
  return reinterpret_cast<Obj*>(&m_contained);
}

GetWeakReferenceSource()は小細工入りのアップキャストで、IWeakReferenceSource*を返します。

なお、このObjectWithWeakReferenceでは、強参照がなくなっても弱参照がなくなるまでオブジェクトの大きさ分メモリを占有し続けます。こうした理由は、弱参照を長期間放置するような使い方は希だろうと判断したためです。ちなみに、boost::make_sharedも同じ挙動です。

IWeakReferenceSource

VTableの順番さえあっていればOK、というのがCOMです。IUnknownのメソッドを別名にしたIWeakReferenceSourceImplから派生させることで、弱参照用のIUnknownと強参照用のIUnknownを1クラスに共存させています。C++的に綺麗な実装方法もあるのですが、このほうが簡単なのです。

この手法はATLでも_IDispEvent::_LocDEQueryInterfaceで使用されています。

class DECLSPEC_NOVTABLE IWeakReferenceSourceImpl
{
public:
  STDMETHOD(ObjectQueryInterface)(
    _In_ REFIID riid, _COM_Outptr_ void** ppv) = 0;
  STDMETHOD_(ULONG, ObjectAddRef)() = 0;
  STDMETHOD_(ULONG, ObjectRelease)() = 0;
  STDMETHOD(GetWeakReference)(
    _COM_Outptr_ egtra::IWeakReference** weakReference) = 0;
};
 
template<class T>
class ObjectWithWeakReference
  : public ATL::CComObjectRootEx<ATL::CComMultiThreadModelNoCS>
  , public egtra::IWeakReference
  , public IWeakReferenceSourceImpl
{
public:
  virtual HRESULT STDMETHODCALLTYPE GetWeakReference(
    /* [retval][out] */ _COM_Outptr_ egtra::IWeakReference** weakReference
    ) throw() override
  {
    ATLENSURE_RETURN_HR(weakReference != nullptr, E_POINTER);
    *weakReference = this;
    AddRef();
    return S_OK;
  }
 
  egtra::IWeakReferenceSource* GetWeakReferenceSource()
  {
    return reinterpret_cast<egtra::IWeakReferenceSource*>(
      static_cast<IWeakReferenceSourceImpl*>(this));
  }

IWeakReference

Resolveメソッドでは、強参照のカウンタを見てオブジェクトを返して良いか判定しています。1クラスにまとめて実装した理由もここにあり、すなわち強参照のカウンタを読み書きできる必要があるからです。

virtual HRESULT STDMETHODCALLTYPE Resolve(
  /* [in] */ _In_ REFIID riid,
  /* [iid_is][out] */ _COM_Outptr_result_maybenull_ void** ppv
  ) throw() override
{
  ATLENSURE_RETURN_HR(ppv != nullptr, E_POINTER);
  *ppv = nullptr;
  if (!TryAddRef())
  {
    return S_FALSE;
  }
  auto hr = ObjectQueryInterface(riid, ppv);
  ObjectRelease(); // TryAddRefの分
  return hr;
}
 
bool TryAddRef()
{
  for (;;)
  {
    auto c = m_strongRef.load(std::memory_order_relaxed);
    if (c == 0)
    {
      return false;
    }
    if (m_strongRef.compare_exchange_weak(
      c, c + 1, std::memory_order_relaxed))
    {
      return true;
    }
    _mm_pause();
  }
}

ソースコード

以下、ソースコード全体です。参照カウントに対して、積極的にmemory_order_relaxedを使ってみました。

#include <atomic>
#include <iostream>
#include <memory>
#include <cassert>
#include <intrin.h>
#include <atlbase.h>
#include <atlcom.h>
 
class Module : public ATL::CAtlExeModuleT<Module> {};
Module module;
 
namespace egtra
{
  MIDL_INTERFACE("bdcb7ca6-376d-481a-8652-dfd69f723ecc")
  IWeakReference : IUnknown
  {
  public:
    virtual HRESULT STDMETHODCALLTYPE Resolve(
      /* [in] */ __RPC__in REFIID riid,
      /* [iid_is][out] */ __RPC__deref_out void** objectReference) = 0;
  };
 
  MIDL_INTERFACE("de2988fe-a6b7-4e3d-923e-7463ce0e1040")
  IWeakReferenceSource : IUnknown
  {
  public:
    virtual HRESULT STDMETHODCALLTYPE GetWeakReference(
      /* [retval][out] */ __RPC__deref_out IWeakReference** weakReference
      ) = 0;
  };
}
 
template<typename T>
ATL::CComPtr<T> CreateComObject()
{
  auto p = std::make_unique<ATL::CComObject<T>>();
  p->SetVoid(nullptr);
  p->InternalFinalConstructAddRef();
  HRESULT hRes = p->_AtlInitialConstruct();
  if (SUCCEEDED(hRes))
    hRes = p->FinalConstruct();
  if (SUCCEEDED(hRes))
    hRes = p->_AtlFinalConstruct();
  p->InternalFinalConstructRelease();
  return hRes == S_OK
    ? p.release()
    : nullptr;
}
 
class DECLSPEC_NOVTABLE IWeakReferenceSourceImpl
{
public:
  STDMETHOD(ObjectQueryInterface)(
    _In_ REFIID riid, _COM_Outptr_ void** ppv) = 0;
  STDMETHOD_(ULONG, ObjectAddRef)() = 0;
  STDMETHOD_(ULONG, ObjectRelease)() = 0;
  STDMETHOD(GetWeakReference)(
    _COM_Outptr_ egtra::IWeakReference** weakReference) = 0;
};
 
template<class T>
class ObjectWithWeakReference
  : public ATL::CComObjectRootEx<ATL::CComMultiThreadModelNoCS>
  , public egtra::IWeakReference
  , public IWeakReferenceSourceImpl
{
  typedef ATL::CComContainedObject<T> Obj;
 
  BEGIN_COM_MAP(ObjectWithWeakReference)
    COM_INTERFACE_ENTRY(egtra::IWeakReference)
  END_COM_MAP()
 
public:
  DECLARE_PROTECT_FINAL_CONSTRUCT()
 
  ObjectWithWeakReference()
  {
    ::new(&m_contained) Obj(
      static_cast<IUnknown*>(GetWeakReferenceSource()));
  }
 
  HRESULT _AtlInitialConstruct()
  {
    HRESULT hr = GetContainedObject()->_AtlInitialConstruct();
    ATLENSURE_RETURN_HR(SUCCEEDED(hr), hr);
    return __super::_AtlInitialConstruct();
  }
 
  HRESULT FinalConstruct()
  {
    InternalAddRef();
    auto hr = __super::FinalConstruct();
    ATLENSURE_RETURN_HR(SUCCEEDED(hr), hr);
    return GetContainedObject()->FinalConstruct();
  }
 
  void FinalRelease()
  {
    auto c = m_strongRef.load(std::memory_order_relaxed);
    assert(c == 0 || c == 1);
    if (c == 1)
    {
      ObjectRelease();
    }
    __super::FinalRelease();
  }
 
  virtual HRESULT STDMETHODCALLTYPE ObjectQueryInterface(
    _In_ REFIID riid, _COM_Outptr_ void** ppv) throw() override
  {
    ATLENSURE_RETURN_HR(ppv != nullptr, E_POINTER);
    if (riid == __uuidof(egtra::IWeakReferenceSource))
    {
      *ppv = GetWeakReferenceSource();
      ObjectAddRef();
      return S_OK;
    }
    return GetContainedObject()->_InternalQueryInterface(riid, ppv);
  }
 
  virtual ULONG STDMETHODCALLTYPE ObjectAddRef() throw() override
  {
    // 簡単にするためインクリメント前の値をそのまま返す。
    return m_strongRef.fetch_add(1, std::memory_order_relaxed);
  }
 
  virtual ULONG STDMETHODCALLTYPE ObjectRelease() throw() override
  {
    auto p = GetContainedObject();
    // FinalRelease前にはmemory_order_seq_cstを入れようと思い、
    // デクリメント演算子を使用した。
    auto c = --m_strongRef;
    if (c == 0)
    {
      // 上記をmemory_order_relaxedにして
      // ここでstd::atomic_thread_fence(std::memory_order_seq_cst)
      // という手もあるが、コードが複雑になるので採用しなかった。
      p->FinalRelease();
      p->~Obj();
      InternalRelease();
    }
    return c;
  }
 
  virtual HRESULT STDMETHODCALLTYPE GetWeakReference(
    /* [retval][out] */ _COM_Outptr_ egtra::IWeakReference** weakReference
    ) throw() override
  {
    ATLENSURE_RETURN_HR(weakReference != nullptr, E_POINTER);
    *weakReference = this;
    AddRef();
    return S_OK;
  }
 
  virtual HRESULT STDMETHODCALLTYPE Resolve(
    /* [in] */ _In_ REFIID riid,
    /* [iid_is][out] */ _COM_Outptr_result_maybenull_ void** ppv
    ) throw() override
  {
    ATLENSURE_RETURN_HR(ppv != nullptr, E_POINTER);
    *ppv = nullptr;
    if (!TryAddRef())
    {
      return S_FALSE;
    }
    auto hr = ObjectQueryInterface(riid, ppv);
    ObjectRelease(); // TryAddRefの分
    return hr;
  }
 
  Obj* GetContainedObject()
  {
    assert(m_strongRef > 0);
    return reinterpret_cast<Obj*>(&m_contained);
  }
 
private:
  bool TryAddRef()
  {
    for (;;)
    {
      auto c = m_strongRef.load(std::memory_order_relaxed);
      if (c == 0)
      {
        return false;
      }
      if (m_strongRef.compare_exchange_weak(
        c, c + 1, std::memory_order_relaxed))
      {
        return true;
      }
      _mm_pause();
    }
  }
 
  egtra::IWeakReferenceSource* GetWeakReferenceSource()
  {
    return reinterpret_cast<egtra::IWeakReferenceSource*>(
      static_cast<IWeakReferenceSourceImpl*>(this));
  }
 
  typename std::aligned_storage<
    sizeof (Obj), std::alignment_of<Obj>::value>::type m_contained;
  std::atomic<ULONG> m_strongRef = 1;
};
 
template<typename T>
ATL::CComPtr<T> CreateComObjectWithWeakRef()
{
  if (auto weak = CreateComObject<ObjectWithWeakReference<T>>())
  {
    ATL::CComPtr<T> obj;
    obj.Attach(weak->GetContainedObject());
    return obj;
  }
  else
  {
    return nullptr;
  }
}
 
MIDL_INTERFACE("0f9a78ce-4f58-4160-9889-e3bd4485c92d") ITest : IUnknown
{
  virtual HRESULT STDMETHODCALLTYPE Test() = 0;
};
 
class ATL_NO_VTABLE TestImpl
  : public ATL::CComObjectRootEx<ATL::CComMultiThreadModel>
  , public ITest
{
  DECLARE_NOT_AGGREGATABLE(TestImpl)
 
  BEGIN_COM_MAP(TestImpl)
    COM_INTERFACE_ENTRY(ITest)
  END_COM_MAP()
 
public:
  HRESULT FinalConstruct() throw()
  {
    std::cout << "TestImpl::FinalConstruct" << std::endl;
    return S_OK;
  }
 
  HRESULT FinalRelease() throw()
  {
    std::cout << "TestImpl::FinalRelease" << std::endl;
    return S_OK;
  }
 
  virtual HRESULT STDMETHODCALLTYPE Test() throw() override
  {
    std::cout << "TestImpl::Test" << std::endl;
    return S_OK;
  }
 
  void Test2()
  {
    std::cout << "TestImpl::Test2 (non-virtual)" << std::endl;
  }
};
 
int main()
{
  ATL::CComPtr<egtra::IWeakReference> weak;
  {
    auto x = CreateComObjectWithWeakRef<TestImpl>();
    ATL::CComQIPtr<egtra::IWeakReferenceSource> s(x);
    ATLENSURE_SUCCEEDED(s->GetWeakReference(&weak));
 
    ATL::CComPtr<IUnknown> u;
    weak->Resolve(IID_PPV_ARGS(&u));
    std::cout << static_cast<IUnknown*>(u) << std::endl;
  }
 
  ATL::CComPtr<IUnknown> u;
  weak->Resolve(IID_PPV_ARGS(&u));
  std::cout << static_cast<IUnknown*>(u) << std::endl;
}

利用例(main関数)がマルチスレッドな感じになっていないのは申し訳ありません。

これを実現するに当たって、boost::shared_ptrとlibc++のstd::shared_ptrのソースを参考にしました。強参照が生きている間、弱参照のカウントを+1しておくことや、弱参照から強参照を得る際のCASなどはそこから着想を得ました。

COMで弱参照を実現する(マルチスレッド対応) is a post from: イグトランスの頭の中(のかけら)

アパートメント(スレッド)を越えても安全に持ち運びできるVARIANTラッパーを作った

$
0
0

アパートメントを越えてVARIANTを持ち運びするためのクラスを書きました。名前はFreeThreadedVariantです。インタフェースへのポインタにおけるATL::CComGITPtr(リンク先:MSDNライブラリ)のように使えるものが欲しくて、これを作りました。

こんなコードが書けるようになります。

std::cout << "マーシャリング不要な型" << std::endl;
auto f1 = std::async(std::launch::async, []
{
  CoInitilaizer coInit;
  return FreeThreadedVariant(_variant_t(1));
});
std::cout << static_cast<int>(f1.get().Get()) << std::endl;
// GetはFreeThreadedVariantのメンバ関数
 
std::cout << "マーシャリングが行われる場合" << std::endl;
auto f2 = std::async(std::launch::async, []
{
  CoInitilaizer coInit;
  IUnknownPtr p(CLSID_ShellWindows);
  std::cout << p.GetInterfacePtr() << std::endl;
  return FreeThreadedVariant(_variant_t(p.GetInterfacePtr()));
});
std::cout << static_cast<IUnknown*>(f2.get().Get()) << std::endl;
 
std::cout << "マーシャリングが行われない場合" << std::endl;
auto f3 = std::async(std::launch::async, []
{
  CoInitilaizer coInit;
  IUnknownPtr p(__uuidof(FreeThreadedDOMDocument60));
  std::cout << p.GetInterfacePtr() << std::endl;
  return FreeThreadedVariant(_variant_t(p.GetInterfacePtr()));
});
std::cout << static_cast<IUnknown*>(f3.get().Get()) << std::endl;

実装は簡単でした。VT_I4など、マーシャリング不要な型と、COMインタフェースを含む型(VT_UNKNOWN, VT_DISPATCH, VT_RECORD)に分けます。マーシャリングが必要な型については、別のCOMオブジェクトでラップして、そこから取り出すという形にします。ラップしたオブジェクトからの取り出しをプロパティ(IDispatch.Invoke)にして、そこでマーシャリングを実行させています。

詳しくは以下のソースコード全文を読んでみてください。

#include <iostream>
#include <future>
#include <utility> // for std::swap
#include <exdisp.h>
#include <msxml6.h>
#include <atlbase.h>
#include <atlcom.h>
#include <comdef.h>
 
class Module : public ATL::CAtlExeModuleT<Module> {};
Module module;
 
// ここから
// http://dev.activebasic.com/egtra/2014/02/08/640/
namespace egtra
{
 
  template <class Base>
  class CComObject :
    public Base
  {
  public:
    typedef Base _BaseClass;
    template<typename... Args>
    CComObject(Args&&... args) : Base(std::forward<Args>(args)...) {}
    virtual ~CComObject()
    {
      m_dwRef = -(LONG_MAX / 2);
      FinalRelease();
#if defined(_ATL_DEBUG_INTERFACES) && !defined(_ATL_STATIC_LIB_IMPL)
      ATL::_AtlDebugInterfacesModule.DeleteNonAddRefThunk(_GetRawUnknown());
#endif
      ATL::_pAtlModule->Unlock();
    }
    STDMETHOD_(ULONG, AddRef)()
    {
      return InternalAddRef();
    }
    STDMETHOD_(ULONG, Release)()
    {
      ULONG l = InternalRelease();
      if (l == 0)
      {
        ATL::ModuleLockHelper lock;
        delete this;
      }
      return l;
    }
    STDMETHOD(QueryInterface)(REFIID iid, _COM_Outptr_ void** ppvObject)
    {
      return _InternalQueryInterface(iid, ppvObject);
    }
  };
 
  template<typename T, typename... Args>
  ATL::CComPtr<T> CreateComObject(Args&&... args)
  {
    std::unique_ptr<CComObject<T>> p(
      new CComObject<T>(std::forward<Args>(args)...));
    p->SetVoid(nullptr);
    p->InternalFinalConstructAddRef();
    HRESULT hRes = p->_AtlInitialConstruct();
    if (SUCCEEDED(hRes))
      hRes = p->FinalConstruct();
    if (SUCCEEDED(hRes))
      hRes = p->_AtlFinalConstruct();
    p->InternalFinalConstructRelease();
    return hRes == S_OK
      ? p.release()
      : nullptr;
  }
 
} // namespace egtra
 
// http://dev.activebasic.com/egtra/2014/02/08/640/
// ここまで
 
class ATL_NO_VTABLE VariantHolder
  : public ATL::CComObjectRootEx<ATL::CComMultiThreadModelNoCS>
  , public IDispatch
{
  friend class FreeThreadedVariant;
 
  BEGIN_COM_MAP(VariantHolder)
    COM_INTERFACE_ENTRY(IDispatch)
  END_COM_MAP()
 
public:
  VariantHolder(const _variant_t& v)
  {
    ATLENSURE_SUCCEEDED(::VariantCopyInd(&m_value, &v));
  }
 
  virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(
    /* [out] */ __RPC__out UINT*) override
  {
    return E_NOTIMPL;
  }
 
  virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(
    /* [in] */ UINT,
    /* [in] */ LCID,
    /* [out] */ ITypeInfo**) override
  {
    return E_NOTIMPL;
  }
 
  virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(
    /* [in] */ REFIID,
    /* [size_is][in] */ LPOLESTR*,
    /* [range][in] */UINT,
    /* [in] */ LCID ,
    /* [size_is][out] */DISPID*) override
  {
    return E_NOTIMPL;
  }
 
  virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke(
    /* [annotation][in] */
    _In_ DISPID dispIdMember,
    /* [annotation][in] */
    _In_ REFIID riid,
    /* [annotation][in] */
    _In_ LCID,
    /* [annotation][in] */
    _In_ WORD wFlags,
    _In_ DISPPARAMS *pDispParams,
    _Out_opt_ VARIANT*pVarResult,
    _Out_opt_ EXCEPINFO*,
    _Out_opt_ UINT*) override
  {
    if (dispIdMember != DISPID_VALUE || wFlags != DISPATCH_PROPERTYGET)
    {
      return DISP_E_MEMBERNOTFOUND;
    }
    if (riid != IID_NULL)
    {
      return DISP_E_UNKNOWNINTERFACE;
    }
    if (pDispParams->cArgs != 0)
    {
      return DISP_E_BADPARAMCOUNT;
    }
    if (pDispParams->cNamedArgs != 0)
    {
      return DISP_E_NONAMEDARGS;
    }
    VariantInit(pVarResult);
    return VariantCopy(pVarResult, &m_value);
  }
 
private:
  _variant_t m_value;
 
  VariantHolder(const VariantHolder&) = delete;
  VariantHolder& operator=(const VariantHolder&) = delete;
};
 
class FreeThreadedVariant
{
public:
  FreeThreadedVariant() = default;
  FreeThreadedVariant(FreeThreadedVariant&& x)
  {
    std::swap(m_value.GetVARIANT(), x.m_value.GetVARIANT());
    m_getter = std::move(x.m_getter);
  }
  FreeThreadedVariant(const FreeThreadedVariant&) = default;
  FreeThreadedVariant(const VARIANT& value) { Set(value); }
 
  FreeThreadedVariant& operator=(FreeThreadedVariant&& x)
  {
    std::swap(m_value.GetVARIANT(), x.m_value.GetVARIANT());
    m_getter = std::move(x.m_getter);
    return *this;
  }
  FreeThreadedVariant& operator=(const FreeThreadedVariant&) = default;
  FreeThreadedVariant& operator=(const VARIANT& value)
  {
    Set(value);
    return *this;
  }
 
  ~FreeThreadedVariant() = default;
 
  operator _variant_t() const
  {
    return Get();
  }
 
  void Set(const VARIANT& value)
  {
    if (IsNeedToMarshal(value))
    {
      m_getter = egtra::CreateComObject<VariantHolder>(value);
    }
    else
    {
      ATLENSURE_SUCCEEDED(VariantCopyInd(&m_value, &value));
    }
  }
 
  _variant_t Get() const
  {
    if (!m_getter)
    {
      return m_value;
    }
    ATL::CComPtr<IDispatch> obj;
    ATLENSURE_SUCCEEDED(m_getter.CopyTo(&obj));
    _variant_t ret;
    ATLENSURE_SUCCEEDED(obj.GetProperty(DISPID_VALUE, &ret));
    return ret;
  }
 
private:
  static bool IsNeedToMarshal(const VARIANT& v)
  {
    switch (v.vt & ~(VT_ARRAY | VT_BYREF))
    {
    case VT_EMPTY:
    case VT_NULL:
    case VT_I1:
    case VT_I2:
    case VT_I4:
    case VT_I8:
    case VT_R4:
    case VT_R8:
    case VT_CY:
    case VT_DATE:
    case VT_BSTR:
    case VT_ERROR:
    case VT_BOOL:
    case VT_DECIMAL:
    case VT_UI1:
    case VT_UI2:
    case VT_UI4:
    case VT_UI8:
    case VT_INT:
    case VT_UINT:
      return false;
    default:
      // SAFEARRAYの場合の判定をサボっている。
      if (v.vt == VT_UNKNOWN || v.vt == VT_DISPATCH)
      {
        return ATL::CComQIPtr<IAgileObject>(v.punkVal) == nullptr;
      }
      else
      {
        return true;
      }
    }
  }
 
  _variant_t m_value;
  ATL::CComGITPtr<IDispatch> m_getter;
};
 
struct CoInitilaizer
{
  CoInitilaizer()
  {
    ATLENSURE_SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED));
  }
  ~CoInitilaizer()
  {
    CoUninitialize();
  }
private:
  CoInitilaizer(const CoInitilaizer&) = delete;
  CoInitilaizer& operator =(const CoInitilaizer&) = delete;
};
 
int main()
{
  try
  {
    ATLENSURE_SUCCEEDED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED));
    CO_MTA_USAGE_COOKIE mtaCookie = {};
    ATLENSURE_SUCCEEDED(CoIncrementMTAUsage(&mtaCookie));
 
    std::cout << "マーシャリング不要な型" << std::endl;
    auto f1 = std::async(std::launch::async, []
    {
      CoInitilaizer coInit;
      return FreeThreadedVariant(_variant_t(1));
    });
    std::cout << static_cast<int>(f1.get().Get()) << std::endl;
 
    std::cout << "マーシャリングが行われる場合" << std::endl;
    auto f2 = std::async(std::launch::async, []
    {
      CoInitilaizer coInit;
      IUnknownPtr p(CLSID_ShellWindows);
      std::cout << p.GetInterfacePtr() << std::endl;
      return FreeThreadedVariant(_variant_t(p.GetInterfacePtr()));
    });
    std::cout << static_cast<IUnknown*>(f2.get().Get()) << std::endl;
 
    std::cout << "マーシャリングが行われない場合" << std::endl;
    auto f3 = std::async(std::launch::async, []
    {
      CoInitilaizer coInit;
      IUnknownPtr p(__uuidof(FreeThreadedDOMDocument60));
      std::cout << p.GetInterfacePtr() << std::endl;
      return FreeThreadedVariant(_variant_t(p.GetInterfacePtr()));
    });
    std::cout << static_cast<IUnknown*>(f3.get().Get()) << std::endl;
 
    ATLENSURE_SUCCEEDED(CoDecrementMTAUsage(mtaCookie));
  }
  catch (const ATL::CAtlException& e)
  {
    std::cerr << "ATL::CAtlException" << std::hex << e.m_hr << std::endl;
  }
  catch (const std::exception& e)
  {
    std::cerr << "std::exception" << e.what() << std::endl;
  }
  catch (const _com_error& e)
  {
    std::cerr << "_com_error" << std::hex << e.Error() << std::endl;
  }
  CoUninitialize();
}
  • ATLでコンストラクタに引数を渡しつつCOMオブジェクトを作るのコードを利用しています。
  • IAgileObjectはマーシャリング不要であることを示すインタフェースです。XPターゲットだと宣言されませんので、その場合は自分でソースコードに宣言を書いてしまいましょう。

    MIDL_INTERFACE("94ea2b94-e9cc-49e0-c0ff-ee64ca8f5b90")
    IAgileObject : IUnknown {};
  • VT_ARRAYかつVT_UNKNOWN, VT_DISPATCHの場合、すべての要素がIAgileObjectをQueryIterface
    できるかどうか調べるべきです。しかし、VT_ARRAYをそんなによく使うわけではないので今回はサボっています。
  • VT_ARRAY | VT_VARIANTのときも、マーシャリング不要な場合があるはずですが、同様にサボっています。
  • VT_RECORDは、IRecordInfo*があるためマーシャリングが必要という扱いにしています。

アパートメント(スレッド)を越えても安全に持ち運びできるVARIANTラッパーを作った is a post from: イグトランスの頭の中(のかけら)


goto fail、到達しないコードにVC++で警告を出す

$
0
0

ここ最近、Appleの例のバグで大盛り上がりですね(Apple史上最悪のセキュリティバグか、iOSとOS XのSSL接続に危険すぎる脆弱性が発覚──原因はタイプミス? | アプリオ)。

さて問題になったようなコード、Visual C++でも警告は出るよね、という確認です。

int ReadyHash(void*, void*);
int SSLHashSHA1_update(void*, void*);
int SSLHashSHA1_final(void*, void*);
int sslRawVerify();
 
void f()
{
  int SSLHashSHA1;
  int hashCtx;
  int clientRandom, serverRandom, signedParams;
  int hashOut;
 
  int err;
  if((err = ReadyHash(&SSLHashSHA1, &hashCtx)) != 0)
    goto fail;
  if((err = SSLHashSHA1_update(&hashCtx, &clientRandom)) != 0)
    goto fail;
  if((err = SSLHashSHA1_update(&hashCtx, &serverRandom)) != 0)
    goto fail;
  if((err = SSLHashSHA1_update(&hashCtx, &signedParams)) != 0)
    goto fail;
    goto fail;
  if((err = SSLHashSHA1_final(&hashCtx, &hashOut)) != 0)
    goto fail;
 
  sslRawVerify();
 
fail:
  ;
}

それっぽい、しかし適当に省略したコードを準備しました。

PS T:\> cl /c /W4 a.cpp
t:\a.cpp(23) : warning C4702: 制御が渡らないコードです。
t:\a.cpp(26) : warning C4702: 制御が渡らないコードです。

というわけで、/W4で出ました。なお、Visual Studioプロジェクトのデフォルトは/W3なので、そこは注意してください。

なお、GCCでは-Wunreachable-codeオプションを使えば良いそうです: AppleがiOS7.0.6で修正したSSLバグの簡単な解説 – Qiita

goto fail、到達しないコードにVC++で警告を出す is a post from: イグトランスの頭の中(のかけら)

Visual C++ 2005でとを使う

$
0
0

今回の裏テーマ、「Boost.AtomicをVisual C++ 2005で使用する」です。

Visual C++ 2005でそこそこ新しいWindows SDKを併用すると、<windows.h>と<intrin.h>で衝突して使えない問題の解決法です。

このように、#defineでいい感じの宣言になるように細工してからincludeします、めでたしめでたし。

#include <windows.h>
 
#define _interlockedbittestandset(a, b) _interlockedbittestandset(volatile long*, long)
#define _interlockedbittestandreset(a, b) _interlockedbittestandreset(volatile long*, long)
#define _interlockedbittestandset64(a, b) _interlockedbittestandset(volatile long long*, long long)
#define _interlockedbittestandreset64(a, b) _interlockedbittestandreset(volatile long long*, long long)
#include <intrin.h>
#undef _interlockedbittestandset
#undef _interlockedbittestandreset
#undef _interlockedbittestandset64
#undef _interlockedbittestandreset64
 
#incldue <boost/atomic.hpp>

今まで<intrin.h>をインクルードしない(必要な関数は個別に宣言する)ようにしていました。ところが、Boost.Atomicが内部で<intrin.h>をインクルードするため、そういうわけにはいかなくなってしまったのです。そのため、こんな回避策を考えました。

もうこんなエラーとはおさらばです。

C:\Program Files (x86)\Microsoft Visual Studio 8\VC\INCLUDE\intrin.h(944) : error C2733: オーバーロードされた関数 '_interlockedbittestandset' の C リンケージの 2 回以上の宣言は許されません。
        C:\Program Files (x86)\Microsoft Visual Studio 8\VC\INCLUDE\intrin.h(944) : '_interlockedbittestandset' の宣言を確認してください。
C:\Program Files (x86)\Microsoft Visual Studio 8\VC\INCLUDE\intrin.h(945) : error C2733: オーバーロードされた関数 '_interlockedbittestandreset' の C リンケージ の 2 回以上の宣言は許されません。
        C:\Program Files (x86)\Microsoft Visual Studio 8\VC\INCLUDE\intrin.h(945) : '_interlockedbittestandreset' の宣言を確認してください。

Visual C++ 2005で<windows.h>と<intrin.h>を使う is a post from: イグトランスの頭の中(のかけら)

ATLENSURE_SUCCEEDEDにはVC++ 2012から副作用のある式も置ける

$
0
0

ATLにATLENSURE_SUCCEEDEDというマクロがあります。これはHRESULT値を1つ引数に取り、その値が失敗を表すものだったら例外を投げるというものです(例外を投げるはデフォルトの挙動であり、変更可能)。

HRESULT hr = CoInitializeEx(
    nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
ATLENSURE_SUCCEEDED(hr);

さて、以前はこのマクロに副作用のある式を置けませんでした。具体的には、Visual C++ 2010までがダメで、2012からOKになりました。それぞれ以下のように定義されています (atldef.h)。

Visual C++ 2005~2010
#define ATLENSURE_SUCCEEDED(hr) ATLENSURE_THROW(SUCCEEDED(hr), hr)
Visual C++ 2012
#define ATLENSURE_SUCCEEDED(hrExpr)								\
do {															\
	HRESULT __atl_hresult = (hrExpr);							\
	ATLENSURE_THROW(SUCCEEDED(__atl_hresult), __atl_hresult);   \
} while (0)
Visual C++ 2013
#define ATLENSURE_SUCCEEDED(hrExpr)								\
do {															\
	HRESULT __atl_hresult = (hrExpr);							\
	ATLENSURE_THROW(SUCCEEDED(__atl_hresult), __atl_hresult);   \
} __pragma(warning(suppress:4127)) while (0)

(注:このpragmaでは、条件式が定数であることの警告を抑制しています: コンパイラの警告 (レベル 4) C4127

このように、Visual C++ 2012から1度変数に代入することで、1度きりの評価で済まされるようになりました。なお、いずれのバージョンでも、ATLENSURE_THROWで各実引数の評価は1度だけです。

つまり、Visual C++ 2012からはこのように書いても問題ありません。逆に言えば、それ以前ではこのように書いてはいけません。

ATLENSURE_SUCCEEDED(CoInitializeEx(
    nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE));

ATLENSURE_SUCCEEDEDにはVC++ 2012から副作用のある式も置ける is a post from: イグトランスの頭の中(のかけら)

RAII以外にもある後処理の書き方

$
0
0

言ってしまえば、高階関数は便利だねという話でもあるわけです、それに加えてC++特有の事情があるだけで。


C++視点からのRuby紹介 // Speaker Deckの52ページ目の「ブロックを使ったRAIIっぽい機能」より。

ブロックを使ったRAIIっぽい機能

File.open('some_file') do |file|
  # 処理
end
  • File.openメソッド
    • ファイルを開いた後、受け取ったブロックを実行
    • 例外の発生有無にかかわらず最後にファイルを閉じる
  • スコープを抜けると自答的にリソースが閉じられる
    • RAIIと共通している

このような書き方はC++でも有用です。なぜなら、C++には事実上「デストラクタで例外を投げてはいけない」ためです(cf. More Effective C++ 項目11 デストラクタで発生した例外を抑える)。

そこで、ブロック引数のように関数(オブジェクト)を実引数に取る関数という方法です。後処理に失敗する可能性のある処理を抽象化する方法として有力な方法の1つです。

例として、RubyのFile.openに相当する関数をC++で作ってみます。

#include <iostream>
#include <fstream>
#include <string>
#include <exception>
#include <type_traits>
#include <functional>
//#include <boost/range/algorithm/copy.hpp>
 
template<typename Char>
inline void on_failure(std::basic_fstream<Char>& fs) {
  fs.exceptions(std::ios_base::iostate());
  fs.close();
  if (!fs) {
    std::throw_with_nested(std::ios_base::failure("close"));
  } else {
    throw;
  }
}
 
template<typename F, typename Char = char>
auto file_open(const char* filename, std::ios_base::openmode mode, F&& f)
  -> typename std::enable_if<
    !std::is_same<decltype(f(std::declval<std::fstream&>())), void>::value,
    decltype(f(std::declval<std::fstream&>()))>::type
{
  std::basic_fstream<Char> fs(filename, mode);
  fs.exceptions(std::ios_base::failbit | std::ios_base::badbit);
  try {
    auto result = f(fs);
    fs.close();
    return result;
  } catch (...) {
    on_failure(fs);
  }
}
 
template<typename F, typename Char = char>
auto file_open(const char* filename, std::ios_base::openmode mode, F&& f)
  -> typename std::enable_if<
    std::is_same<decltype(f(std::declval<std::fstream&>())),
    void>::value, decltype(f(std::declval<std::fstream&>()))>::type
{
  std::basic_fstream<Char> fs(filename, mode);
  try {
    fs.exceptions(std::ios_base::failbit | std::ios_base::badbit);
    f(fs);
  } catch (...) {
    on_failure(fs);
  }
}
 
int main() {
  auto s = file_open("src.txt", std::ios_base::in, [](std::istream& is) {
    return std::string(
      std::istreambuf_iterator<char>(is),
      std::istreambuf_iterator<char>());
  });
  std::cout << s << std::endl;
  file_open("dst.txt", std::ios_base::out, [&](std::ostream& os) {
    std::copy(s.cbegin(), s.cend(), std::ostreambuf_iterator<char>(os));
    //boost::copy(s, std::ostreambuf_iterator<char>(os));
  });
}

このように、closeでの失敗をちゃんと拾って呼び出し元へthrowする処理が書けました。

まとめです。

  • 一般的に後処理で失敗の可能性がある処理は、デストラクタに任すわけにはいかない。
  • その制約を超える手段として、関数オブジェクトを引数に取る関数(Rubyならブロック引数を取るメソッド)を作るという手段がある。

ファイルIO(特に失敗の検出・エラー処理)については、気が向いたら別の記事として書くかもしれません。


ファイルは閉じるまでがIOです(「家に帰るまでが遠足です」調で)。

Man page of CLOSE

close() の返り値のチェックはよく省略されるが、 これは深刻なプログラミングエラーである。 前の write(2) 処理に関するエラーが最後の close() のときになって初めて通知される場合がありうる。 ファイルクローズの際に返り値をチェックしないと、 気付かないうちにデータを失ってしまうかもしれない。 これは特に NFS やディスククォータを使用した場合に見られる。

RAII以外にもある後処理の書き方 is a post from: イグトランスの頭の中(のかけら)

friendとtemplateによるVisual C++でしか動かないコード

$
0
0

今日のテーマは、Visual C++だけでコンパイル・実行できる不思議なコードです。(nullptrさえ直せば)2005から2013までいずれでも通用します。

#include <iostream>
 
void f() { std::cout << "f" << std::endl; }
void g() { std::cout << "g" << std::endl; }
 
template<class T>
class TypeF
{
  friend void InvokeImpl(T*)
  {
    f();
  }
};
 
template<class T>
class TypeG
{
  friend void InvokeImpl(T*)
  {
    g();
  }
};
 
template<class T>
class Selector
{
  friend void InvokeImpl(T*);
 
public:
  static void Invoke()
  {
    return InvokeImpl(static_cast<T*>(nullptr));
  }
};
 
// ここから利用例
 
struct Hoge;
struct Piyo;
struct Fuga;
 
template class TypeF<Hoge>;
template class TypeG<Piyo>;
template class TypeF<Fuga>;
 
int main()
{
  Selector<Hoge>::Invoke();
  Selector<Piyo>::Invoke();
  Selector<Fuga>::Invoke();
}

これを実行するとこういう出力になります。

f
g
f

このコードは「型によって関数fとgを呼び分ける」というものです。

利用者は、自分の作った型を実引数にTypeFまたはTypeGを明示的実体化します。Selector::Invokeに型を与えて呼び出すと、明示的実体化したほうのInvokeImplが呼び出されるというからくりです。このtemplateとfriendの組み合わせ方がGCCやClangではうまくいきませんでした。

これに相当することをやるだけなら、ほかにやりよう(GCCでもClangでもできる方法)はあります。単にこういう方法もあるという紹介でした。

friendとtemplateによるVisual C++でしか動かないコード is a post from: イグトランスの頭の中(のかけら)

Visual C++の不思議なfriend(その2)

$
0
0

間が空きましたが、前回(friendとtemplateによるVisual C++でしか動かないコード)の続きです。

今度は分割コンパイルです。前回消化した謎の挙動とテンプレートの明示的実体化を使って、オブジェクトファイル間の依存を断ち切るコードを書きます。

まずは、2ファイル中1つ目、実装を書く側です。

#include <iostream>
 
void f() { std::cout << "f" << std::endl; }
void g() { std::cout << "g" << std::endl; }
 
template<class T>
class TypeF
{
  friend void InvokeImpl(T*)
  {
    f();
  }
 
public:
  // 前回からの追加部分
  void Invoke()
  {
    InvokeImpl(static_cast<T*>(nullptr));
  };
};
 
template<class T>
class TypeG
{
  friend void InvokeImpl(T*)
  {
    g();
  }
 
public:
  // 前回からの追加部分
  void Invoke()
  {
    InvokeImpl(static_cast<T*>(nullptr));
  };
};
 
struct Hoge;
struct Piyo;
struct Fuga;
 
template class TypeF<Hoge>;
template class TypeG<Piyo>;
template class TypeF<Fuga>;

そしてもう1つのファイル、それを使う側です。

template<class T>
class Selector
{
  friend void InvokeImpl(T*);
 
public:
  static void Invoke()
  {
    return InvokeImpl(static_cast<T*>(nullptr));
  }
};
 
// ここから利用例
 
struct Hoge;
struct Piyo;
struct Fuga;
 
int main()
{
  Selector<Hoge>::Invoke();
  Selector<Piyo>::Invoke();
  Selector<Fuga>::Invoke();
}

前回との違いはメンバ関数Invokeです。テンプレートTypeFとTypeGの実体化でInvokeが実体化、Invoke内で呼び出すフレンド関数InvokeImplも実体化されるという流れです。

これの特長はオブジェクトファイルの依存関係を絶縁(分離)できることです。それを利用する側(この例ではmain関数)の翻訳単位は、InvokeImplの実装を全く知らずコンパイルできます。

たとえば、template class TypeF<Fuga>;をtemplate class TypeG<Fuga>;に変えても利用する側の再コンパイルは不要というわけです。

というわけで、Visual C++の不思議な挙動その2でした。なお、この手法を推奨しているわけではありません。念のため。

Visual C++の不思議なfriend(その2) is a post from: イグトランスの頭の中(のかけら)

IMEを無効にすることとスクリーンキーボード

$
0
0

今日は、特定のWin32コントロール(ウィンドウ)でIMEを無効化する方法についてです。

近頃はタブレットも珍しくない存在になりました。そこで、タブレットにおいてなくてはならないWindowsのスクリーンキーボードを意識してみたという話です。

従来の方法とその問題点

これまで、IMEを無効にするにはマイクロソフトのKB171154(特定のウィンドウを IME を無効にする方法)に書かれている方法、つまりImmAssociateContextにヌルを渡すことがセオリーでした。

しかし、タブレットを触っていると、これだけでは不十分ではないかと思いました。なぜなら、スクリーンキーボードにIMEをオン・オフするキー(スペースの左側にある「あ」、押すと「A」と交互に変わる。以下「あ/Aキー」と表記)が表示されるためです。IME無効の場合、押しても何も起こらないだけなので、存在が気になります。

On-Screen-keyboard-normal

今回やってみた手法

幸いなことに、パスワード入力の場面で現るスクリーンキーボードは「あ/Aキー」がないことにすぐに気がつきました。これを通常のエディットコントロールに適用できれば良さそうです。

そこで、WTLでそのような処理を行うクラスCEditWithoutImeを作りました。こちらがソースコードです: Gist: CEditWithoutIme.h

IAccessible::get_accStateメンバ関数をオーバーライドし、そこで定数STATE_SYSTEM_PROTECTEDを加えるのがミソでした。これによりパスワード用のスクリーンキーボードになります。

IFACEMETHOD(get_accState)(
  VARIANT varChild, __RPC__out VARIANT* pvarState) override
{
  ATLENSURE_RETURN_HR(pvarState != nullptr, E_POINTER);
  ATL::CComVariant state;
  auto hr = m_base->get_accState(varChild, &state);
  ATLENSURE_RETURN_HR(SUCCEEDED(hr), hr);
  ATL::CComVariant additionalState(STATE_SYSTEM_PROTECTED);
  return VarOr(&state, &additionalState, pvarState);
}

On-Screen-keyboard-protected

もちろん、ImmAssociateContextにヌルを渡す方法も併用しています。

その使用法

使い方は、このようにエディットコントロールをサブクラス化またはスーパークラス化するだけです。ダイアログなら、WM_INITDIALOG内でサブクラス化すれば大丈夫です。そのほかはWTL::CEditと同じです。

#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
 
#include "CEditWithoutIme.h"
 
#pragma comment(lib, "imm32.lib")
#pragma comment(lib, "oleacc.lib")
 
#pragma comment(linker, "\"/manifestdependency:type='Win32' "\
  "name='Microsoft.Windows.Common-Controls' version='6.0.0.0' "\
  "processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
 
class Module : public ATL::CAtlExeModuleT<Module> {};
Module module;
 
class TestWindow : public ATL::CWindowImpl<TestWindow>
{
public:
  DECLARE_WND_CLASS_EX(TEXT("Test Window Class"), 0, COLOR_3DFACE);
 
private:
  BEGIN_MSG_MAP(TestWindow)
    MSG_WM_CREATE(OnCreate)
    MSG_WM_DESTROY(OnDestroy)
  END_MSG_MAP()
 
  LRESULT OnCreate(const CREATESTRUCT* pcs)
  {
    m_font = WTL::AtlCreateControlFont();
 
    // 代わりにm_edit.Create(…)を呼び出せばスーパークラス化になる。
    if (auto hwnd = CreateWindowEx(
      WS_EX_CLIENTEDGE, TEXT("EDIT"), nullptr, WS_CHILD | WS_VISIBLE,
      10, 10, 300, 30, *this, nullptr, nullptr, nullptr))
    {
      m_edit.SubclassWindow(hwnd);
    }
    else
    {
      return -1;
    }
    m_edit.SetFont(m_font);
 
    return 0;
  }
 
  void OnDestroy()
  {
    PostQuitMessage(0);
  }
 
  CEditWithoutIme m_edit;
  WTL::CFont m_font;
};
 
int Run(int cmdShow)
{
  TestWindow wnd;
  RECT rc = { 0, 0, 360, 100 };
  ATLENSURE_RETURN_VAL(wnd.Create(
    nullptr, rc, TEXT("Test"), WS_OVERLAPPEDWINDOW), -1);
 
  wnd.ShowWindow(cmdShow);
  wnd.UpdateWindow();
 
  WTL::CMessageLoop msgLoop;
  return msgLoop.Run();
}
 
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int cmdShow)
{
  InitCommonControls();
 
  ATLENSURE_RETURN_VAL(SUCCEEDED(CoInitializeEx(
    nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)), -1);
  int ret = Run(cmdShow);
  CoUninitialize();
  return ret;
}

Special Thanks


IMEを無効にすることとスクリーンキーボード is a post from: イグトランスの頭の中(のかけら)


ダイアログをメイリオなどで表示する

$
0
0

Visual Studioのリソースエディタで特に何もせずダイアログを作る(※)と、かな漢字の表示には最新のWindows 8.1でもMS UI Gochicが使用されます。これをメイリオやMeiryo UIなどにしようというのが今日の内容です。

※ なお、何も考えずに作るダイアログはDS_SHELLFONTかつ”MS Shell Dlg”ということです。MS Shell DlgがTahomaになり、TahomaからのフォントリンクでMS UI Gothicが使われます。

今回もWTLを使ってそのようなことを実現するクラスを作り、Gistに置いています: gist: CDialogWithThemedFontImpl.h

フォントの取得には、以下のようにTheme APIを使用しています。TEXT_CONTROLLABELがこの用途に最もふさわしいものであるという確証を得られなかったのがちょっと気になるところです(その名前でいいだろうと判断しました)。

HTHEME hTheme = ::OpenThemeData(nullptr, VSCLASS_TEXTSTYLE);
// ……
HRESULT hr = ::GetThemeFont(hTheme, nullptr, TEXT_CONTROLLABEL, 0, TMT_FONT, &tmp);

この辺のAPIが失敗を返したら、もとのリソーススクリプトの内容そのままを使用するようにしています。Windows XPでも動きます。

フォントを取得したら、ダイアログテンプレートをメモリ上で書き換えて、あとはDialogBoxIndirectParamやCreateDialogIndirectParamで表示するだけです(実際にはWTL::CIndirectDialogImplを使っています)。

これを利用するサンプルコードは以下です。

#define WINVER 0x0501
#define _WIN32_WINNT 0x0501
 
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
 
#include <windows.h>
 
#include <atlbase.h>
#include <atlwin.h>
 
#include <atlapp.h>
#include <atlcrack.h>
 
#include "CDialogWithThemedFontImpl.h"
 
#pragma comment(linker, "\"/manifestdependency:type='Win32' "\
  "name='Microsoft.Windows.Common-Controls' version='6.0.0.0' "\
  "processorArchitecture='*' publicKeyToken='6595b64144ccf1df' "\
  "language='*'\"")
 
#define IDD_TEST 1
 
// WTL::CThemeが必要としている
class Module : public ATL::CAtlExeModuleT<Module> {};
Module module;
 
class TestDialog
#if 1
  : public CDialogWithThemedFontImpl<TestDialog>
#else
  : public ATL::CDialogImpl<TestDialog>
#endif
{
public:
  static const UINT IDD = IDD_TEST;
 
private:
  BEGIN_MSG_MAP(TestDialog)
    MSG_WM_INITDIALOG(OnInitDialog)
    COMMAND_ID_HANDLER_EX(IDOK, OnExit)
    COMMAND_ID_HANDLER_EX(IDCANCEL, OnExit)
  END_MSG_MAP()
 
  BOOL OnInitDialog(...)
  {
    return TRUE;
  }
 
  void OnExit(UINT uNotifyCode, int nID, HWND wndCtl)
  {
    EndDialog(nID);
  }
};
 
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
  InitCommonControls();
 
  TestDialog dlg;
  return dlg.DoModal();
}

また、ダイアログを定義するリソーススクリプトはこちらになります。

#include 
#include 
#define IDD_TEST 1
IDD_TEST DIALOGEX 0, 0, 120, 80
STYLE DS_SHELLFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
	DEFPUSHBUTTON "OK", IDOK, 8, 38, 50, 14
	PUSHBUTTON "キャンセル", IDCANCEL, 60, 38, 50, 14
	LTEXT "テキストのテスト", IDC_STATIC, 36, 19, 60, 8
END

これを使うに当たっては、ちゃんと表示されることの確認が大変になることの覚悟をお願いいたします。日本語版Windowsだけに限っても、Meiryo UI、メイリオ、MS UI Gochicと3種類になります。

ダイアログリソースをメモリ上で加工する例は少ないので、苦労しました。ATLとMFCのソースコード、Old New Thingsを参考に書き上げました。フォント名のところのパディングは、起こらない場合(メイリオ)と起こる場合(游ゴシック)両方で動くことを確認済みです。

ダイアログをメイリオなどで表示する is a post from: イグトランスの頭の中(のかけら)

HTTPステータスコードをHRESULTで表現する

$
0
0

Winerror.hなどを見ていると、HTTPステータスコードを表現するHRESULT値があることに気付きます。

最初にネタばらししてしまうと、すべてMAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, ステータスコード)で組み立てられるのです。それでは、一覧してみましょう。

定数名
BG_E_HTTP_ERROR_1000×80190064
BG_E_HTTP_ERROR_1010×80190065
BG_E_HTTP_ERROR_2000x801900C8
BG_E_HTTP_ERROR_2010x801900C9
BG_E_HTTP_ERROR_2020x801900CA
BG_E_HTTP_ERROR_2030x801900CB
BG_E_HTTP_ERROR_2040x801900CC
BG_E_HTTP_ERROR_2050x801900CD
BG_E_HTTP_ERROR_2060x801900CE
BG_E_HTTP_ERROR_3000x8019012C
HTTP_E_STATUS_AMBIGUOUS
BG_E_HTTP_ERROR_3010x8019012D
HTTP_E_STATUS_MOVED
BG_E_HTTP_ERROR_3020x8019012E
HTTP_E_STATUS_REDIRECT
BG_E_HTTP_ERROR_3030x8019012F
HTTP_E_STATUS_REDIRECT_METHOD
BG_E_HTTP_ERROR_3040×80190130
HTTP_E_STATUS_NOT_MODIFIED
BG_E_HTTP_ERROR_3050×80190131
HTTP_E_STATUS_USE_PROXY
BG_E_HTTP_ERROR_3070×80190133
HTTP_E_STATUS_REDIRECT_KEEP_VERB
BG_E_HTTP_ERROR_4000×80190190
HTTP_E_STATUS_BAD_REQUEST
BG_E_HTTP_ERROR_4010×80190191
HTTP_E_STATUS_DENIED
BG_E_HTTP_ERROR_4020×80190192
HTTP_E_STATUS_PAYMENT_REQ
BG_E_HTTP_ERROR_4030×80190193
HTTP_E_STATUS_FORBIDDEN
BG_E_HTTP_ERROR_4040×80190194
HTTP_E_STATUS_NOT_FOUND
BG_E_HTTP_ERROR_4050×80190195
HTTP_E_STATUS_BAD_METHOD
BG_E_HTTP_ERROR_4060×80190196
HTTP_E_STATUS_NONE_ACCEPTABLE
BG_E_HTTP_ERROR_4070×80190197
HTTP_E_STATUS_PROXY_AUTH_REQ
BG_E_HTTP_ERROR_4080×80190198
HTTP_E_STATUS_REQUEST_TIMEOUT
BG_E_HTTP_ERROR_4090×80190199
HTTP_E_STATUS_CONFLICT
BG_E_HTTP_ERROR_4100x8019019A
HTTP_E_STATUS_GONE
BG_E_HTTP_ERROR_4110x8019019B
HTTP_E_STATUS_LENGTH_REQUIRED
BG_E_HTTP_ERROR_4120x8019019C
HTTP_E_STATUS_PRECOND_FAILED
BG_E_HTTP_ERROR_4130x8019019D
HTTP_E_STATUS_REQUEST_TOO_LARGE
BG_E_HTTP_ERROR_4140x8019019E
HTTP_E_STATUS_URI_TOO_LONG
BG_E_HTTP_ERROR_4150x8019019F
HTTP_E_STATUS_UNSUPPORTED_MEDIA
BG_E_HTTP_ERROR_4160x801901A0
HTTP_E_STATUS_RANGE_NOT_SATISFIABLE
BG_E_HTTP_ERROR_4170x801901A1
HTTP_E_STATUS_EXPECTATION_FAILED
BG_E_HTTP_ERROR_4490x801901C1
BG_E_HTTP_ERROR_5000x801901F4
HTTP_E_STATUS_SERVER_ERROR
BG_E_HTTP_ERROR_5010x801901F5
HTTP_E_STATUS_NOT_SUPPORTED
BG_E_HTTP_ERROR_5020x801901F6
HTTP_E_STATUS_BAD_GATEWAY
BG_E_HTTP_ERROR_5030x801901F7
HTTP_E_STATUS_SERVICE_UNAVAIL
BG_E_HTTP_ERROR_5040x801901F8
HTTP_E_STATUS_GATEWAY_TIMEOUT
BG_E_HTTP_ERROR_5050x801901F9
HTTP_E_STATUS_VERSION_NOT_SUP

HTTP_E_STATUS系はwinerror.hにあります。ただし、比較的新しいWindows SDKからのようです。Windows SDK 7.1では存在しませんでした。一方、BG_E系はBitsMsg.hに結構前から存在するようです。

なお、FACILITY_HTTPの中にはステータスコードそのままではない値も定義されています。つまり、HRESULからHTTPステータスコードを取り出すときには、FACILITY_HTTPかつHRESULT_CODEの結果が100~599の範囲という条件を課すと良いのではないかと思います。

HTTP_E_STATUS_UNEXPECTED0×80190001
HTTP_E_STATUS_UNEXPECTED_REDIRECTION0×80190003
HTTP_E_STATUS_UNEXPECTED_CLIENT_ERROR0×80190004
HTTP_E_STATUS_UNEXPECTED_SERVER_ERROR0×80190005

HTTP_E_STATUS_UNEXPECTEDは予期せぬステータスコード、後の3つは、それぞれ300番台、400番台、500番台すべてを意味する値のようです。winerror.hには以下のように書かれています。

//
// MessageId: HTTP_E_STATUS_UNEXPECTED
//
// MessageText:
//
// Unexpected HTTP status code.
//
#define HTTP_E_STATUS_UNEXPECTED         _HRESULT_TYPEDEF_(0x80190001L)
 
//
// MessageId: HTTP_E_STATUS_UNEXPECTED_REDIRECTION
//
// MessageText:
//
// Unexpected redirection status code (3xx).
//
#define HTTP_E_STATUS_UNEXPECTED_REDIRECTION _HRESULT_TYPEDEF_(0x80190003L)
 
//
// MessageId: HTTP_E_STATUS_UNEXPECTED_CLIENT_ERROR
//
// MessageText:
//
// Unexpected client error status code (4xx).
//
#define HTTP_E_STATUS_UNEXPECTED_CLIENT_ERROR _HRESULT_TYPEDEF_(0x80190004L)
 
//
// MessageId: HTTP_E_STATUS_UNEXPECTED_SERVER_ERROR
//
// MessageText:
//
// Unexpected server error status code (5xx).
//
#define HTTP_E_STATUS_UNEXPECTED_SERVER_ERROR _HRESULT_TYPEDEF_(0x80190005L)

200番台に対して失敗扱い(SEVERITY_ERROR: 最上位ビットを1)とするのは違和感が拭えないですが、400番台・500番台なら使ってもよいと思いました。

HTTPステータスコードをHRESULTで表現する is a post from: イグトランスの頭の中(のかけら)

サービスから昇格した権限のプロセスを起動する

$
0
0

Windowsサービス内から、UACダイアログを表示せず昇格した権限を持つプロセスを起動することはできないだろうかと試してみました。結果、以下の条件の下で実際に可能であることを確認しました。

  • 自身(起動する側)Local Systemアカウントのもとで動くプロセス
  • 対象となる管理者ユーザーはログオンしている

調査を始める前、以下の手順でできるのではないかと考えました。

  1. WTSQueryUserTokenで通常状態のトークンを取得。
  2. GetTokenInformationのTokenLinkedTokenで昇格状態のトークンを取得。
  3. 必要ならDuplicateTokenExでプライマリトークンを作成。
  4. そのトークンをCreateProcessAsUserに渡して起動。

やってみたところ、一応起動しました。ただ、ユーザープロファイルのディレクトリが認識されていないなどの挙動が見られたため、LoadUserProfileとCreateEnvironmentBlockの呼び出しを追加しました。

こちらが一応の完成版です。

// RunAsAdminTest.cpp
 
#define UNICODE
#define WINVER 0x0600
#define _WIN32_WINNT 0x0600
 
#include <iostream>
#include <memory>
#include <windows.h>
#include <wtsapi32.h>
#include <userenv.h>
 
#pragma comment(lib, "wtsapi32.lib")
#pragma comment(lib, "userenv.lib")
 
template<typename F>
class ScopeExitHolder
{
public:
  ScopeExitHolder(F f) : deleter(f) {}
 
  ~ScopeExitHolder()
  {
    deleter();
  }
  // VC++2013はムーブのdefault/deleteに非対応
  //ScopeExitHolder(ScopeExitHolder&&) = default;
  ScopeExitHolder(const ScopeExitHolder&) = default;
  //ScopeExitHolder& operator=(ScopeExitHolder&&) = delete;
  ScopeExitHolder& operator=(const ScopeExitHolder&) = delete;
 
private:
  F deleter;
 
};
enum MakeScopeExitTag {};
template<typename F>
inline ScopeExitHolder<F> operator*(MakeScopeExitTag, F&& f)
{
  return std::move(f);
}
 
void OutputError(const char* function, DWORD error)
{
  std::wcout << function << ": " << error << std::endl;
}
 
#define SCOPE_EXIT_ID2(n) ScopeExit ## n
#define SCOPE_EXIT_ID(n) SCOPE_EXIT_ID2(n)
#define SCOPE_EXIT auto SCOPE_EXIT_ID(__LINE__) \
  = MakeScopeExitTag() * [&]()
 
int wmain(int argc, wchar_t** argv)
{
  if (argc <= 1)
  {
    return 1;
  }
 
  try
  {
    WTS_SESSION_INFO* sessionInfo;
    DWORD count;
    if (!WTSEnumerateSessions(
      WTS_CURRENT_SERVER_HANDLE, 0, 1, &sessionInfo, &count))
    {
      OutputError("WTSEnumerateSessions", GetLastError());
    }
    SCOPE_EXIT{ WTSFreeMemory(sessionInfo); };
    for (DWORD i = 0; i < count; ++i)
    {
      if (sessionInfo[i].State != WTSActive)
      {
        continue;
      }
      if (sessionInfo[i].SessionId == 0)
      {
        break;
      }
      std::cout << "Session: " << sessionInfo[i].SessionId << std::endl;
      HANDLE hTokenBase = nullptr;
      if (!WTSQueryUserToken(sessionInfo[i].SessionId, &hTokenBase))
      {
        OutputError("WTSQueryUserToken", GetLastError());
      }
      SCOPE_EXIT{ CloseHandle(hTokenBase); };
 
      // 本当はここでGetTokenInformation(TokenElevationType)を使い、
      // 昇格済みトークンの取得処理が必要かどうか判断すべき。
 
      TOKEN_LINKED_TOKEN tlt = {};
      DWORD length = {};
      if (!GetTokenInformation(
        hTokenBase, TokenLinkedToken, &tlt, sizeof tlt, &length))
      {
        OutputError(
          "GetTokenInformation(TokenLinkedToken)", GetLastError());
      }
      SCOPE_EXIT{ CloseHandle(tlt.LinkedToken); };
 
      HANDLE hToken;
      if (!DuplicateTokenEx(tlt.LinkedToken, MAXIMUM_ALLOWED,
        nullptr, SecurityImpersonation, TokenPrimary, &hToken))
      {
        OutputError("DuplicateTokenEx", GetLastError());
      }
      SCOPE_EXIT{ CloseHandle(hToken); };
 
      DWORD tokenUserLength;
      if (!GetTokenInformation(hToken, TokenUser,
        nullptr, 0, &tokenUserLength)
        && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
      {
        OutputError("GetTokenInformation(TokenUser) 1", GetLastError());
      }
      std::unique_ptr<BYTE[]> buffer(new BYTE[tokenUserLength]);
      if (!GetTokenInformation(hToken, TokenUser,
        buffer.get(), tokenUserLength, &tokenUserLength))
      {
        OutputError("GetTokenInformation(TokenUser) 2", GetLastError());
      }
      auto tokenUser = reinterpret_cast<const TOKEN_USER*>(buffer.get());
 
      DWORD nameLength = 0;
      DWORD domainLength = 0;
      SID_NAME_USE nameUse;
      if (!LookupAccountSid(nullptr, tokenUser->User.Sid,
        nullptr, &nameLength, nullptr, &domainLength, &nameUse)
        && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
      {
        OutputError("LookupAccountSid 1", GetLastError());
      }
      std::unique_ptr<TCHAR[]> name(new TCHAR[nameLength]);
      std::unique_ptr<TCHAR[]> domain(new TCHAR[domainLength]);
      if (!LookupAccountSid(nullptr, tokenUser->User.Sid,
        name.get(), &nameLength, domain.get(), &domainLength, &nameUse))
      {
        OutputError("LookupAccountSid 2", GetLastError());
      }
 
      std::wcout << "User: " << name.get() << std::endl;
      std::wcout << "Domain: " << domain.get() << std::endl;
 
      // 移動プロファイルのときが不安
      PROFILEINFO profileInfo = { sizeof profileInfo };
      profileInfo.lpUserName = name.get();
      if (!LoadUserProfile(hToken, &profileInfo))
      {
        OutputError("LoadUserProfile", GetLastError());
      }
      SCOPE_EXIT{ UnloadUserProfile(hToken, profileInfo.hProfile); };
 
      void* environment;
      if (!CreateEnvironmentBlock(&environment, hToken, FALSE))
      {
        OutputError("CreateEnvironmentBlock", GetLastError());
      }
      SCOPE_EXIT{ DestroyEnvironmentBlock(environment); };
 
      STARTUPINFOW si = { sizeof si };
      PROCESS_INFORMATION pi = {};
      if (!CreateProcessAsUser(hToken, nullptr, argv[1],
        nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT,
        environment, nullptr, &si, &pi))
      {
        OutputError("CreateProcessAsUser", GetLastError());
      }
      CloseHandle(pi.hThread);
      WaitForSingleObject(pi.hProcess, INFINITE);
      CloseHandle(pi.hProcess);
 
      break;
    }
  }
  catch (const std::exception& e)
  {
    std::cerr << "std::exception: " << e.what() << std::endl;
  }
}

これをコンパイルしてRunAsAdminTest.exeを作ったら、以下のようにPsExecを使ってLocal Systemアカウントのもとで実行します。1番目の引数にCreateProcessAsUserに渡すコマンドラインを指定します。

PsExec -s (絶対パス)RunAsAdminTest.exe C:\Windows\System32\notepad.exe

あるいは、以下のように新しいウィンドウでコマンドプロンプトを起動すると、タイトルバーが「管理者: C:\Windows\system32\cmd.exe」となるので分かりやすいでしょう。

PsExec -s (絶対パス)RunAsAdminTest.exe "cmd /c start cmd"

もちろん、ユーザーのいるセッション上で強力な権限を持つプロセスを起動したいだけなら、ほかにも方法はあります。ユーザーアカウントに拘らなければ、自身のプロセスのトークン(Local System)を使う方法もあります。別にUACダイアログを表示して昇格したプロセスが存在するなら、そのトークンを受け渡しする方法だって考えられます。

今回の方法は、自身がLocal Systemアカウントで動作している(必要な特権が使える)ことと対象のユーザーがログオンしていることだけを必要な条件としていることが特徴です。

やってみて「Local System強い、なんでもできる」という感想を改めて抱きました。

サービスから昇格した権限のプロセスを起動する is a post from: イグトランスの頭の中(のかけら)

Visual C++でしか動かないコード:明示的特殊化

$
0
0

Visual C++でしかコンパイルできないコードその2です。今回は「メンバーテンプレートの明示的特殊化をクラス定義内に書く」です。

class hoge
{
  template<typename T>
  class piyo {};
  template<>
  class piyo<int> {};
};

GCCやClangではコンパイルエラーになります。一方、Visual C++は2013でも通してしまいます。

正しくは、このようにクラス定義の外に書きます。

class hoge
{
  template<typename T>
  class piyo {};
};
 
template<>
class hoge::piyo<int> {};

たまにGCCやClangでコンパイルすると、こういう発見があって面白いです。これを読んでくださっている皆様は、ぜひこのようなコードを書かないようにしましょう。

Visual C++でしか動かないコード:明示的特殊化 is a post from: イグトランスの頭の中(のかけら)

Boostのunique_ptrをstd::unique_ptrのようにする

$
0
0

今日は、Boost.Interprocessに存在するunique_ptrをstd::unique_ptrのように使う方法です。

Boostにもunique_ptrが存在します: Boost に std::unique_ptr の移植がないかな、と思ったら有った。 – 野良C++erの雑記帳。しかし、このboost::interprocess::unique_ptrは削除子(Deleter)を指定する必要があるのが、std::unique_ptrと違って面倒です。

#include <boost/interprocess/smart_ptr/unique_ptr.hpp>
#include <boost/checked_delete.hpp>
 
int main()
{
  boost::interprocess::unique_ptr<int, boost::checked_deleter<int>> p(
    new int);
  // std::unique_ptrならこれだけで良い。
  // std::unique_ptr<int> p(new int);
}

そこで逆転の発想です。こちらからデフォルト実引数を指定すれば良いのです。

#include <boost/checked_delete.hpp>
 
namespace boost
{
  namespace interprocess
  {
    template<typename T>
    struct default_delete : checked_deleter<T> {};
    template<typename T>
    struct default_delete<T[]> : checked_array_deleter<T> {};
 
    // これ。デフォルト実引数を付けて先に宣言してしまう。
    template<typename T, typename D = default_delete<T>>
    class unique_ptr;
  }
}
 
#include <boost/interprocess/smart_ptr/unique_ptr.hpp>
 
int main()
{
  boost::interprocess::unique_ptr<int> p(new int);
}

決してお行儀が良い方法とは言えないのは承知の上です。std::unique_ptrに乗り移るまでの暫定処置としていかがでしょうか。

Boostのunique_ptrをstd::unique_ptrのようにする is a post from: イグトランスの頭の中(のかけら)

Viewing all 123 articles
Browse latest View live