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関数を使う例)ので、その流れでこれを書くことにしたというわけです。
スポンサード リンク |