C++ ネタです。久しく触ってない言語なのと、へましたらメモリリークやらなんやらしやすい言語なので、そういうまずいところ見たら教えてください!
ではやっていきます。
はじめに
先日、以下の記事を書きました。
最初に触った言語が C/C++ なので普通の C++ で書けるというのだけで凄くいいなって思ったのでもう少しだけ触ってみました。 多くの人が C++ で Windows アプリを開発している場合には Windows 7 以降(場合によってはそれ以前も???)を対象にしていると思います。
そんな中で Windows 10 対応とかして Windows 7, 8.1 で動かなくなったんじゃぁ元も子もないということになります。 そうならないようにする方法を紹介しようと思います。
スタート地点
MFC アプリケーションで新規作成した、どうしてこうなったというくらい色んなものが入っている(と個人的に思ってる)アプリの雛形をスタート視点としたいと思います。
とりあえず、ここのヘルプの下の「アプリ名について」というメニューを押したときに UWP として動くときは UWP の機能を呼び出して、そうじゃないときは既存の動作をそのままさせてみたいと思います。
Windows アプリケーション パッケージ プロジェクトでパッケージング
Windows 10 の API は、普通にダイレクトに呼べる奴もあるのですが appx 形式にパッケージングしておかないと呼べない奴もあります。今回はトーストの API を使おうかなと思ってるのですが、それも appx じゃないと動かないものなのでさくっと固めてしまいます。固めるのは簡単で、Windows アプリケーション パッケージ プロジェクトを作って、MFC のアプリを追加するだけです。
実行した結果は変わり映えしないけど
スタートメニューを見ると追加されてるのは UWP っぽい雰囲気になってます。右クリックからさくっとクリーンにアンインストールできます。
Windows 10 の API を呼び出す DLL を作る
ということで、今回は UWP の API を呼び出す DLL を作って UWP のほうにはそれを同梱して、既存のほうには、それを同梱しないという感じで行ってみたいと思います。 C++ のダイナミック リンク ライブラリ (DLL) を作成します。私は UWPFeatures という名前で作りました。 このままビルドすると exe と同じ場所に dll が出力されるようになってるので UWPFeatures プロジェクトのプロパティの出力ディレクトリを以下の値に変更します(すべての構成/すべてのプラットフォームにしてね)
$(SolutionDir)UWPFeatures\$(PlatformTarget)\$(Configuration)\
つぎに、appx に UWPFeatures.dll が含まれるようにします。これはちょっとイレギュラーな方法なのですが以下のようにしてやりました。
- 一度ビルドする
- Windows アプリケーション パッケージ プロジェクトに既存の項目を追加で UWPFeature.dll をリンクとして追加する
- Windows アプリケーション パッケージ プロジェクトを右クリックしてアンロード
- アンロードしたら右クリックして編集
- UWP Features.dll のタグを以下のように編集
編集前
<Content Include="..\UWPFeatures\x86\Debug\UWPFeatures.dll"><Link>UWPFeatures.dll</Link></Content>
編集後
<Content Include="$(SolutionDir)UWPFeatures\$(PlatformTarget)\$(Configuration)\UWPFeatures.dll"><Link>UWPFeatures.dll</Link></Content>
- プロジェクトを右クリックして再読込
これで、プロジェクトの構成に応じて最適な dll が appx に含まれるようになります。正しい手順かは知らないけど、今のところこれしか手が無いように思います。
次に、C++/WinRT を使う用に設定していきます。
UWPFeatures プロジェクトのプロパティで C/C++ -> 言語
の C++ 言語標準
を ISO C++17 Standard (/std:c++17)
にします。
そして、準拠モード
をいいえ
にします。
そして、リンカー -> 入力
の追加の依存ファイル
を編集して windowsapp.lib
を追加します。
UWPFeatures プロジェクトの stdafx.h によく使うヘッダーファイルを追加しておきます。
#include "winrt/base.h"#include "winrt/Windows.Foundation.h"
では UWPFeatures に適当にヘッダーファイルを追加しましょう。私は Notification.h という名前で追加しました。そして以下のように編集します。
#pragma once#include "stdafx.h"extern"C" __declspec(dllexport) void ShowNotification(constwchar_t* text);
続けて実装も。Notification.cpp を追加して以下のようにします。
#include "stdafx.h"#include "Notification.h"#include "winrt/Windows.UI.Notifications.h"#include "winrt/Windows.Data.Xml.Dom.h"usingnamespace winrt; usingnamespace Windows::Foundation; usingnamespace Windows::UI::Notifications; extern"C"void ShowNotification(constwchar_t* text) { // 今から 2 時間有効なトースト通知を出すauto notificationManager = ToastNotificationManager::GetDefault(); auto toastXml = ToastNotificationManager::GetTemplateContent(ToastTemplateType::ToastText01); auto textNode = toastXml.GetElementsByTagName(L"text").Item(0); textNode.AppendChild(toastXml.CreateTextNode(text)); auto toast = ToastNotification(toastXml); toast.ExpirationTime(winrt::clock::now() + std::chrono::hours() * 2); notificationManager.CreateToastNotifier().Show(toast); }
ビルドしてビルドエラーがないことを確認しましょう。
次に MFC アプリのほうに行きます。プロジェクト名App.cpp
というファイルの中に CWinRTMFCAppApp::OnAppAbout
というメソッドがあります。(あれ、今思ったけど C++ だとメンバー関数っていうんでしたっけという記憶がよみがえってきた)
これを以下のように書き換えます。
void CWinRTMFCAppApp::OnAppAbout() { // DLL の有無で処理切り分けauto hmodule = LoadLibrary(L"UWPFeatures.dll"); if (hmodule == NULL) { CAboutDlg aboutDlg; aboutDlg.DoModal(); return; } // トーストを出す関数を取得auto showNotification = (void (*)(constwchar_t*))GetProcAddress(hmodule, "ShowNotification"); if (showNotification == NULL) { // エラーがあったらエラーの情報を出す LPVOID lpMsgBuf; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL ); MessageBox(NULL, (LPCTSTR)lpMsgBuf, L"Error", MB_OK | MB_ICONINFORMATION); LocalFree(lpMsgBuf); return; } showNotification(L"This message is from MFC app"); FreeLibrary(hmodule); }
では実行してみましょう!MFC アプリをスタートアッププロジェクトにしてメニューを選択してみます。
今まで通りですね!次は Windows アプリケーション パッケージ プロジェクトをスタートアッププロジェクトにして実行してみます。
通知が出ましたね!
まとめ
こんな感じで作れば既存の処理のほうには、あまり手を入れずに UWP 機能の詰まった dll の LoadLibrary が成功したときだけ Windows 10 っぽくふるまうということも出来るかなと思います。Windows アプリケーション パッケージ プロジェクトで作るとインストール・アンインストールを繰り返してもレジストリを汚さないというメリットがあるので個人的には好きです。
ストアからも配れるし、ストアから配らない場合にもちゃんとした証明書で署名する or 信頼されたルート証明機関あたりに証明書突っ込めば(企業内だとポリシーとかで)インストール出来ます。
ソースコードは以下のリポジトリにあげています。
多分、C++/WinRT の VISX 入れて Windows 10 SDK も入れてないと動かないと思います。ヘッダーとかが SDK に入ってるみたいなので。
- C++/WinRT の VSIX はここから。
- Windows 10 の SDK は Visual Studio Installer から
- C++/WinRT のドキュメントはこちら。例によって機械翻訳なので英語で見た方がいいかも。日本語のほうは誰かプルリク投げた方がいいくらいの出来栄えに見える…。