wxWidgetsはメッセージループを隠蔽している。これを加工する。
wxGUIEventLoopを継承して、必要な機能のみ実装する。
#ifndef WX_PRECOMP #include <wx/wx.h> #endif #include <wx/gdicmn.h> // wxPointに必要 #include <wx/frame.h> // wxFrameに必要 #include <wx/evtloop.h> ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// #include <string> class MyCustomEventLoop : public wxGUIEventLoop { public: MyCustomEventLoop() = default; virtual ~MyCustomEventLoop() = default; protected:
// Yieldが必要な場合 virtual void DoYieldFor(long eventsToProcess) override { // デフォルトの実装 wxGUIEventLoop::DoYieldFor(eventsToProcess); }
// メッセージループの各イテレーション開始時に呼ばれるフック virtual void OnNextIteration() override { ////////////////////////////////// static int counter = 0; counter++; char ss[64]; snprintf(ss, sizeof(ss), "OnNextIteration called: %d", counter); wxLogDebug(ss); ////////////////////////////////// // デフォルトの実装 wxGUIEventLoop::OnNextIteration(); }
////////////////////////////////////////////// // メッセージを処理する bool Dispatch() override { MSG msg;// メッセージループの処理はOS依存 if (!::GetMessage(&msg, NULL, 0, 0)) return false; // WM_QUIT if (msg.message == WM_LBUTTONDOWN)// 処理例:WM_LBUTTONDOWNを無効化 { wxLogDebug("Ignored in custom Dispatch"); // WM_LBUTTONDOWN このメッセージを処理しないためにここでtrueを返す return true; } ProcessMessage(&msg); return true; } //////////////////////////////////////////////
// イベント処理のメインループ // *メッセージループ中に何かしたいだけなら、OnNextIteration() をオーバーライドするだけでよい // *メッセージを横取りするような用途では、Dispatch() をオーバーライドするだけでよい // より細かい制御が必要なら DoRun() をオーバーライドすることもできる #define IF_YOU_NEED_DORUN #if defined( IF_YOU_NEED_DORUN ) virtual int DoRun() override { for (;;) { // 1) ループ開始フック OnNextIteration(); // 2) キュー内の全イベントを処理 while (!m_shouldExit && Pending()) { // Dispatch() が false を返したらループ終了 if (!Dispatch()) break; } if (m_shouldExit) break; // 3) アイドル処理:戻り値 true なら更にアイドルイベントを送り続ける if (ProcessIdle()) { continue; } // 4) 何もやることがなければ少し待機 wxThread::Sleep(10); } // 返り値は Exit() でセットされたコード return m_exitcode; } #endif // IF_YOU_NEED
}; class MyFrame : public wxFrame { public: MyFrame() : wxFrame(nullptr, wxID_ANY, "メッセージループ") { Bind(wxEVT_CLOSE_WINDOW, &MyFrame::OnClose, this); // クリックイベント Bind(wxEVT_LEFT_DOWN, [](wxMouseEvent& event) { wxLogMessage("Left mouse button clicked at (%d, %d)", event.GetX(), event.GetY()); }); } private: void OnClose(wxCloseEvent& evt) {
if (wxEventLoopBase::GetActive()) wxEventLoopBase::GetActive()->Exit();// プログラム終了
evt.Skip(); // フレーム自体も閉じる } }; class MyApp : public wxApp { public: bool OnInit() override { auto frame = new MyFrame(); frame->Show(); return true; } int OnRun() override { // wxGUIEventLoop を使って自前ループを構築 MyCustomEventLoop* loop = new MyCustomEventLoop; wxEventLoopBase::SetActive(loop); // アクティブなループとして登録 int retval = loop->Run(); // ここでメッセージループ開始 delete loop; // ループ終了後に解放 return retval; } }; // WinMainをマクロで定義 wxIMPLEMENT_APP(MyApp);
CEFをwxWidgetsの上に貼り付けてみる。注意点として、配布されたdllではdebugモードで実行できないらしい。必ずReleaseモードで実行する。
#ifndef WX_PRECOMP #include <wx/wx.h> #endif #include <wx/gdicmn.h> // wxPointに必要 #include <wx/frame.h> // wxFrameに必要 #include <include/cef_app.h> #pragma comment(lib, "D:\\cmmon\\cef_binary_138.0.15+gd0f1f64+chromium-138.0.7204.50_windows64\\Release\\libcef.lib") #pragma comment(lib, "D:\\cmmon\\MD\\Release\\libcef_dll_wrapper.lib") ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// #include <string>
class MyHandler : public CefClient, public CefLifeSpanHandler { public: MyHandler() {} CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; } void OnAfterCreated(CefRefPtr<CefBrowser> browser) override { m_Browser = browser; } void OnBeforeClose(CefRefPtr<CefBrowser> browser) override { m_Browser = nullptr; } void CloseAllBrowsers(bool force_close) { if (m_Browser) { m_Browser->GetHost()->CloseBrowser(force_close); } } IMPLEMENT_REFCOUNTING(MyHandler); private: CefRefPtr<CefBrowser> m_Browser; };
// ウィンドウ作成 class MyFrame : public wxFrame {
CefRefPtr<MyHandler> g_handler;
public: void PostCreate() {
g_handler = CefRefPtr<MyHandler>(new MyHandler); CefBrowserSettings browser_settings; CefWindowInfo window_info; CefRect cefRect( 0, 0, 400, 400); HWND hwnd = (HWND)this->GetHandle(); window_info.SetAsChild(hwnd, cefRect); CefBrowserHost::CreateBrowser( window_info, g_handler, "https://www.google.com", browser_settings, nullptr, nullptr);
this->Layout(); // レイアウトの更新 } void OnClose(wxCloseEvent& event) {
// CEFのブラウザを閉じる g_handler->CloseAllBrowsers(true);
event.Skip(); // デフォルトの処理を実行 } MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame(NULL, wxID_ANY, title, pos, size) { // CallAfter : 現在処理中のイベントが終わったらPostCreateを実行 // コンストラクタはウィンドウ生成イベント扱い CallAfter(&MyFrame::PostCreate); } private: }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // wxWidgetsのアプリケーション作成 class MyApp : public wxApp { public: virtual bool OnInit() {
HINSTANCE hInstance = ::GetModuleHandle(NULL); CefMainArgs main_args(hInstance); int exit_code = CefExecuteProcess(main_args, nullptr, nullptr); if (exit_code >= 0) return false; // CEFの設定 CefSettings settings; settings.no_sandbox = true; // マルチスレッドメッセージループを有効にする settings.multi_threaded_message_loop = true; CefInitialize(main_args, settings, nullptr, nullptr);
/////////////////////////////////////////////////// MyFrame* frame = new MyFrame("Hello World", wxPoint(50, 50), wxSize(450, 340)); frame->Show(true); return true; } virtual int OnExit() {
// CEFのシャットダウン CefShutdown();
return wxApp::OnExit(); } }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // WinMainをマクロで定義 wxIMPLEMENT_APP(MyApp);
Win32apiのCreateWindowのhwndにChromeを張り付ける。
CefRunMessageLoopを使用すると、メッセージループにCEFのものを使う。
#include <windows.h> #include <iostream> #include <include/cef_app.h> #ifdef _DEBUG #pragma comment(lib, "D:\\cmmon\\cef_binary_138.0.15+gd0f1f64+chromium-138.0.7204.50_windows64\\Debug\\libcef.lib") #pragma comment(lib, "D:\\cmmon\\MD\\Debug\\libcef_dll_wrapper.lib") #else #pragma comment(lib, "D:\\cmmon\\cef_binary_138.0.15+gd0f1f64+chromium-138.0.7204.50_windows64\\Release\\libcef.lib") #pragma comment(lib, "D:\\cmmon\\MD\\Release\\libcef_dll_wrapper.lib") #endif
class MyHandler : public CefClient , public CefLifeSpanHandler { public: MyHandler() {} CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; } void OnAfterCreated(CefRefPtr<CefBrowser> browser) override { m_Browser = browser; } void OnBeforeClose(CefRefPtr<CefBrowser> browser) override { m_Browser = nullptr; } void CloseAllBrowsers(bool force_close) { if (m_Browser) { m_Browser->GetHost()->CloseBrowser(force_close); } } IMPLEMENT_REFCOUNTING(MyHandler); private: CefRefPtr<CefBrowser> m_Browser; };
class MyApp : public CefApp { IMPLEMENT_REFCOUNTING(MyApp); };
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { MyHandler* handler; LPCREATESTRUCT pcs; switch (msg) { case WM_CLOSE:
handler = (MyHandler*)GetWindowLongPtr(hwnd, GWLP_USERDATA); if (handler) { handler->CloseAllBrowsers(true); }
DestroyWindow(hwnd); return 0; case WM_CREATE:
pcs = (LPCREATESTRUCT)lp; handler = (MyHandler *)pcs->lpCreateParams; SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)(handler));
return 0; case WM_DESTROY:
CefQuitMessageLoop();
PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, msg, wp, lp); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { /////////////////////////////////////////////////////////////////////////
CefMainArgs main_args(hInstance); CefRefPtr<MyApp> app(new MyApp); // MyAppのインスタンス用のポインタ // サブプロセス処理 int exit_code = CefExecuteProcess(main_args, app, nullptr); if (exit_code >= 0) return exit_code; // CEFの設定 CefSettings settings; settings.no_sandbox = true; CefInitialize(main_args, settings, app, nullptr);
///////////////////////////////////////////////////////////////////////// HWND hwnd; MSG msg; WNDCLASS winc; winc.style = CS_HREDRAW | CS_VREDRAW; winc.lpfnWndProc = WndProc; winc.cbClsExtra = winc.cbWndExtra = 0; winc.hInstance = hInstance; winc.hIcon = LoadIcon(NULL, IDI_APPLICATION); winc.hCursor = LoadCursor(NULL, IDC_ARROW); winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); winc.lpszMenuName = NULL; winc.lpszClassName = TEXT("SZL-WND"); if (!RegisterClass(&winc)) return -1;
CefRefPtr<MyHandler> g_handler = CefRefPtr<MyHandler>(new MyHandler);
hwnd = CreateWindow( TEXT("SZL-WND"), TEXT("CEF test"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, g_handler.get() ); if (hwnd == NULL) return -1;
CefRefPtr<CefClient> client = g_handler; // 型は CefClient で渡す CefBrowserSettings browser_settings; CefWindowInfo window_info; RECT rect; GetClientRect(hwnd, &rect); // 親ウィンドウのクライアント領域 CefRect cefRect( rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); window_info.SetAsChild(hwnd, cefRect); CefBrowserHost::CreateBrowser( window_info, g_handler, "https://www.google.com", browser_settings, nullptr, nullptr); CefRunMessageLoop(); // メッセージループ // settings.multi_threaded_message_loop = true;の時はコメントアウトすること CefShutdown(); // CEF終了処理
return 0; }
CefRunMessageLoopを使わない場合、メッセージループは自分で書き、CEFのメッセージを処理するためにループ内で CefDoMessageLoopWork() を呼び出す。
また、CefDoMessageLoopWork内でPeekMessageを呼んでいる関係でWM_QUITを検知できなくなるので、GetMessageを使わずにMsgWaitForMultipleObjectsとPeekMessageでメッセージループを管理する。
// CefRunMessageLoop(); // メッセージループ // 注意 GetMessageは使わない // CefDoMessageLoopWorkは中でPeekMessageを使っている // PeekMessage は強制的にWM_QUITを取り出してしまうので、 // こちら側でGetMessageをつかうと先にWM_QUITを取り出されて // 終了を感知できなくなりループから抜け出せなくなる // **CefDoMessageLoopWorkは内部でタイマーイベントなどを多数キューに投げているので、 // **GetMessageがタイマーを補足し、次に入っていたWM_QUITはCefDoMessageLoopWorkが取り出すという // **現象が起こる // bool should_quit = false; while (!should_quit) { // MsgWaitForMultipleObjects = メッセージが来るまでスレッドをスリープ DWORD result = MsgWaitForMultipleObjects(0, NULL, FALSE, INFINITE, QS_ALLINPUT); // もし何かメッセージが来たら処理 if (result == WAIT_OBJECT_0) { while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) { should_quit = true; } else { TranslateMessage(&msg); DispatchMessage(&msg); } } } if (!should_quit) { CefDoMessageLoopWork(); } }
multi_threaded_message_loopをtrueに設定すると、CEFが専用スレッドで動く。この場合、CefRunMessageLoop(),CefShutdown()を使用してはいけない。
メッセージループは自プログラムのメッセージだけを処理する。
CefSettings settings; settings.no_sandbox = true; settings.multi_threaded_message_loop = true; // を指定して専用スレッドでCEFを動かす場合、 // CefRunMessageLoop()も CefDoMessageLoopWork() も CefShutdown() も使ってはいけない /* ... */ while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } // 使わない CefRunMessageLoop(); // settings.multi_threaded_message_loop = true;の時はコメントアウトすること // 使わない CefShutdown(); return 0;// msg.wParam; }
CEFはアプリケーションにChromeを組み込むためのフレームワークである(辞書的説明)。用は自アプリにChromeの機能を埋め込むためのライブラリ。
以下からビルド済みライブラリをダウンロード:
https://cef-builds.spotifycdn.com/index.html
ライブラリ自体はビルド済みだが、C++から使用するにはwrapper dllをビルドした方がよい。このためにCMakeする。
使用時、/MD でリンクしたいなら、必ずUSE_SANDBOX=OFFにする。
これはCEFが持っているsandboxという機能が/MTでビルドされているからで、この/MDバージョンは用意されていない。使う場合はCEFのビルドそのものをやらないといけなくなる。sandboxを使わないとセキュリティが下がるらしいが、とりあえず当分配布するものは作らないのでsandboxをOFFにする。
libcef_dll_wrapperをビルドする。他のものはサンプルなので無視してもいい。
今回はlibcef_dll_wrapperを/MDに設定して、libcef_dll_wrapperを右クリック→ビルド でビルドする。
・libcef_dll_wrapper/Release/libcef_dll_wrapper.lib
・libcef_dll_wrapper/Debug/libcef_dll_wrapper.lib
が生成される。
一応、ALL_BUILDしたcefsimpleを実行してみる。
上ではlibcef_dll_wrapperだけビルドする話をした。当然ALL_BUILDもできるが全てのプロジェクトで/MDまたは/MTに統一する必要がある。
あと、cefsimple.exeは二つ起動すると二つ目がChromeのウィンドウになるという謎動作をする。
pyenv-virtualenvを使用すれば、Python 環境のバージョンを含めた作業環境の切り替えが出来る。
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
.vcxprojとcsprojはxmlなので、pugixmlで内容を解析できる。例えばプロジェクト名を変えてみる。
#include <iostream> #include <fstream> #include "pugixml.hpp"
// vcxprojファイルへのアクセスと編集 void vcxproj(std::string vcxproj_path) { pugi::xml_document doc; doc.load_file(vcxproj_path.c_str()); pugi::xml_node property_group; pugi::xml_node project = doc.child("Project"); for (pugi::xml_node node : project.children("PropertyGroup")) { if (node.attribute("Label") && std::string(node.attribute("Label").value()) == "Globals") { property_group = node; break; } } // プロジェクト名を取得 pugi::xml_node root_namespace = property_group.child("RootNamespace"); std::cout << root_namespace.child_value();
// プロジェクト名を編集 root_namespace.text().set("MyNewProjectName"); doc.save_file(vcxproj_path.c_str()); }
// csprojファイルへのアクセスと編集 void csproj(std::string csproj_path) { pugi::xml_document doc; doc.load_file(csproj_path.c_str()); pugi::xml_node project = doc.child("Project"); pugi::xml_node root_namespace; for (pugi::xml_node node : project.children("PropertyGroup")) { pugi::xml_node candidate = node.child("RootNamespace"); if (candidate) { root_namespace = candidate; break; } } // プロジェクト名を取得 std::cout << root_namespace.text().as_string() << std::endl; // プロジェクト名を編集 root_namespace.text().set("MyNewProjectName"); doc.save_file(csproj_path.c_str()); }
int main() { std::string cs_proj_path = R"(C:\test\ConsoleApp1-SCharpProj\ConsoleApp1-SCharpProj.csproj)"; std::string vc_proj_path = R"(C:\test\ConsoleAppl-CPPProj\ConsoleAppl-CPPProj.vcxproj)"; csproj(cs_proj_path); vcxproj(vc_proj_path); }
VC++の.slnファイルを解析するよい方法が見つからないので書いてみた。
.slnファイルは独自形式だが、同時に単純かつ厳密なフォーマットらしいので割と簡単に書ける。
https://suzulang.com/wp-content/uploads/2025/06/winslnrw.zip
#include <iostream> #include <list> #include <memory> #include <string> #include <vector> #include <fstream> #include <unordered_map> #include <stack> #include <regex> /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// // std::stringから先頭の空白を削除する関数 std::string trimLeadingWhitespace(const std::string& str) { size_t start = str.find_first_not_of(" \t\n\r"); return (start == std::string::npos) ? "" : str.substr(start); } // std::stringから、冒頭のアルファベットのみの文字列を取得。 std::string getLeadingAlphabet(const std::string& str) { size_t end = str.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); return (end == std::string::npos) ? str : str.substr(0, end); } bool isSectionMarks(const std::string& str) { // セクションの開始を示す文字列かどうかを判定 // ここでは例として "Project" と "Global" をセクションの開始とする return str == "Project" || str == "ProjectSection" || str == "EndProjectSection" || str == "EndProject" || str == "Global" || str == "GlobalSection" || str == "EndGlobalSection" || str == "EndGlobal" ; }
// slnファイルの各行を表すクラス class LineData { protected: std::string line; public: void setLine(const std::string& text) { line = text; } const std::string& getLine() const { return line; } std::string& getLine() { return line; } };
// ヘッダはセクション扱いとはしないで各行を独自の情報とみなす class HeaderLine :public LineData { protected: std::regex pattern; public: HeaderLine(std::string pat) :pattern(pat) { } virtual std::string getVersion() const{ std::string text = getLine(); std::smatch match; std::regex_search(text, match, pattern); return text.substr(match.position(1), match.length(1)); } virtual void setVersion(const std::string& newvalue) { std::string text = getLine(); std::smatch match; std::regex_search(text, match, pattern); text.replace(match.position(1), match.length(1), newvalue); setLine(text); } };
// Visual Studioのバージョン情報を保持するクラス class VisualStudioVersion : public HeaderLine { public: VisualStudioVersion(): HeaderLine(R"(^VisualStudioVersion\s*=\s*([0-9.]+))") { } };
// 最小Visual Studioのバージョン情報を保持するクラス class MinimumVisualStudioVersion : public HeaderLine { public: MinimumVisualStudioVersion(): HeaderLine(R"(^MinimumVisualStudioVersion\s*=\s*([0-9.]+))") { } };
// ソリューションファイルのフォーマットバージョンを保持するクラス class FormatVersion : public HeaderLine { public: FormatVersion(): HeaderLine(R"(Format Version ([0-9.]+))") { } };
enum class HeaderLineTypeT { HL_VisualStudioVersion, HL_MinimumVisualStudioVersion, HL_FormatVersion, HL_ERROR }; HeaderLineTypeT HeaderLineType(const std::string& line) { std::regex format_version_regex(R"(Format Version ([0-9.]+))"); std::regex visual_studio_version_regex(R"(^VisualStudioVersion\s*=\s*([0-9.]+))"); std::regex minimum_visual_studio_version_regex(R"(^MinimumVisualStudioVersion\s*=\s*([0-9.]+))"); std::smatch match; if (std::regex_search(line, match, format_version_regex)) { return HeaderLineTypeT::HL_FormatVersion; } else if (std::regex_search(line, match, visual_studio_version_regex)) { return HeaderLineTypeT::HL_VisualStudioVersion; } else if (std::regex_search(line, match, minimum_visual_studio_version_regex)) { return HeaderLineTypeT::HL_MinimumVisualStudioVersion; } return HeaderLineTypeT::HL_ERROR; }
using SlnFileLineSPtr = std::shared_ptr<LineData>;
class Section : public std::enable_shared_from_this<Section> { protected: std::list< SlnFileLineSPtr > ::iterator head; std::list< SlnFileLineSPtr > ::iterator tail; std::string _sectionType; public: std::list < std::shared_ptr<Section> > subSections; // サブセクションのリスト std::shared_ptr<Section> This() { return shared_from_this(); } std::list < std::shared_ptr<Section> >& getSubSections(){ // グローバルセクションのサブセクションを取得するメソッド return subSections; } virtual void update() {} Section(std::string sectionname, std::list< SlnFileLineSPtr >::iterator shead) { head = shead; _sectionType = sectionname; } std::string getSectionType()const { return _sectionType; } void setSectionTail(std::list< SlnFileLineSPtr >::iterator stail) { tail = stail; } std::list< SlnFileLineSPtr > ::iterator getSectionBegin() const { return head; } std::list< SlnFileLineSPtr > ::iterator getSectionTail() const { return tail; } std::list< SlnFileLineSPtr > ::iterator getSectionEnd() const { auto itr = tail; itr++; return itr; } };
class GlobalSection : public Section { protected: std::string* sectionHeadLine() { return & (*getSectionBegin())->getLine(); } const std::string* sectionHeadLine() const{ return & (*getSectionBegin())->getLine(); } std::regex Pattern; public: GlobalSection(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line):Section(sectionname,line) { Pattern = std::regex(R"(^\s*GlobalSection\((\w+)\)\s*=\s*(preSolution|postSolution))"); } std::string getSectionName()const{ std::string slice; std::smatch match; if (std::regex_match(*sectionHeadLine(), match, Pattern)) { // セクション名をスライスで保存 slice = sectionHeadLine()->substr(match.position(1), match.length(1)); } return slice; } void setSectionName(const std::string& newvalue) { std::string slice; std::smatch match; if (std::regex_match(*sectionHeadLine(), match, Pattern)) { sectionHeadLine()->replace(match.position(1), match.length(1), newvalue); } } void setSectionTiming(const std::string& newvalue) { std::string slice; std::smatch match; if (std::regex_match(*sectionHeadLine(), match, Pattern)) { sectionHeadLine()->replace(match.position(2), match.length(2), newvalue); } } std::string getSectionTiming() const { std::string slice; std::smatch match; if (std::regex_match(*sectionHeadLine(), match, Pattern)) { slice = sectionHeadLine()->substr(match.position(2), match.length(2)); } return slice; } };
class SolutionConfigurationPlatforms : public GlobalSection { public: SolutionConfigurationPlatforms(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line) : GlobalSection(sectionname,line) {} }; class ProjectConfigurationPlatforms : public GlobalSection { public: ProjectConfigurationPlatforms(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line) : GlobalSection(sectionname,line) {} }; class SolutionProperties: public GlobalSection { public: SolutionProperties(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line) : GlobalSection(sectionname,line) {} }; class ExtensibilityGlobals : public GlobalSection { public: ExtensibilityGlobals(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line) : GlobalSection(sectionname,line) {} };
class Header { std::list< SlnFileLineSPtr > lines; std::weak_ptr<VisualStudioVersion> vs_ver; std::weak_ptr<MinimumVisualStudioVersion> minivs_ver; std::weak_ptr<FormatVersion> f_ver; public: std::weak_ptr<VisualStudioVersion> getVisualStudioVersion(){ return vs_ver; } std::weak_ptr<MinimumVisualStudioVersion> getMinimumVisualStudioVersion(){ return minivs_ver; } std::weak_ptr<FormatVersion> getFormatVersion(){ return f_ver; } SlnFileLineSPtr push_back(SlnFileLineSPtr line) { SlnFileLineSPtr ptr; switch (HeaderLineType(line->getLine())) { case HeaderLineTypeT::HL_FormatVersion: { auto ptmp = std::make_shared<FormatVersion>(); ptmp->setLine(line->getLine()); f_ver = ptmp; ptr = ptmp; break; } case HeaderLineTypeT::HL_VisualStudioVersion: { auto ptmp = std::make_shared<VisualStudioVersion>(); ptmp->setLine(line->getLine()); vs_ver = ptmp; ptr = ptmp; break; } case HeaderLineTypeT::HL_MinimumVisualStudioVersion: { auto ptmp = std::make_shared<MinimumVisualStudioVersion>(); ptmp->setLine(line->getLine()); minivs_ver = ptmp; ptr = ptmp; break; } default: ptr = line; } lines.push_back(ptr); return ptr; } std::list< SlnFileLineSPtr > getLines() { return lines; } };
class Project :public Section{ std::string* sectionHeadLine() { return &getSectionBegin()->get()->getLine(); } const std::string* sectionHeadLine() const { return &getSectionBegin()->get()->getLine(); } std::regex projectPattern; std::string getSubstr(const int pos)const { std::smatch match; std::regex_search(*sectionHeadLine(), match, projectPattern); return sectionHeadLine()->substr(match.position(pos), match.length(pos)); } void setReplace(const int pos,const std::string& newvalue) { std::smatch match; std::regex_search(*sectionHeadLine(), match, projectPattern); sectionHeadLine()->replace(match.position(pos), match.length(pos), newvalue); } public: Project(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line):Section(sectionname,line) { projectPattern = std::regex(R"xxx(Project\("\{([A-F0-9\-]+)\}"\)\s*=\s*"([^"]+)",\s*"([^"]+)",\s*"\{([A-F0-9\-]+)\}")xxx", std::regex::ECMAScript | std::regex::icase); } // プロジェクトの種類をGUID形式で取得するメソッド std::string getProjectTypeGUID() const {return getSubstr(1);} // プロジェクト名を取得するメソッド std::string getProjectName() const {return getSubstr(2);} // プロジェクトのパスを取得するメソッド std::string getProjectPath() const {return getSubstr(3);} // プロジェクトのGUIDを取得するメソッド std::string getProjectGUID() const {return getSubstr(4);} //////////////////////////////////////////////////////////////////////// void setProjectTypeGUID(const std::string& newvalue) {setReplace(1, newvalue);} void setProjectName(const std::string& newvalue) {setReplace(2, newvalue);} void setProjectPath(const std::string& newvalue) {setReplace(3, newvalue);} void setProjectGUID(const std::string& newvalue) {setReplace(4, newvalue);} //////////////////////////////////////////////////////////////////////// };
class Global :public Section { std::unordered_map<std::string , std::weak_ptr<GlobalSection> > subSectionsMap; // サブセクションのマップ public: Global(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line) :Section(sectionname,line) { } virtual void update()override { auto& subsections = getSubSections(); for (auto s : subsections) { std::shared_ptr<GlobalSection> gs = std::static_pointer_cast<GlobalSection>(s); subSectionsMap[gs->getSectionName()] = gs; } } auto getGlobalSections() { return getSubSections(); } std::weak_ptr<SolutionConfigurationPlatforms> getSolutionConfigurationPlatforms(){ auto it = subSectionsMap.find("SolutionConfigurationPlatforms"); if (it == subSectionsMap.end()) return std::weak_ptr<SolutionConfigurationPlatforms>(); return std::dynamic_pointer_cast<SolutionConfigurationPlatforms>((*it).second.lock()); } std::weak_ptr<ProjectConfigurationPlatforms> getProjectConfigurationPlatforms() { auto it = subSectionsMap.find("ProjectConfigurationPlatforms"); if (it == subSectionsMap.end()) return std::weak_ptr<ProjectConfigurationPlatforms>(); return std::dynamic_pointer_cast<ProjectConfigurationPlatforms>((*it).second.lock()); } std::weak_ptr<SolutionProperties> getSolutionProperties() { auto it = subSectionsMap.find("SolutionProperties"); if (it == subSectionsMap.end()) return std::weak_ptr<SolutionProperties>(); return std::dynamic_pointer_cast<SolutionProperties>((*it).second.lock()); } std::weak_ptr<ExtensibilityGlobals> getExtensibilityGlobals() { auto it = subSectionsMap.find("ExtensibilityGlobals"); if (it == subSectionsMap.end()) return std::weak_ptr<ExtensibilityGlobals>(); return std::dynamic_pointer_cast<ExtensibilityGlobals>((*it).second.lock()); } };
// slnファイルを管理するクラス。 // slnファイルフォーマットは原則余計な場所で改行されないので、1行単位で保存 class SlnFileLines { std::list< SlnFileLineSPtr > _lines; std::unordered_map<std::string,std::string> _sectionMarks = { {"Project", "EndProject"}, {"ProjectSection", "EndProjectSection"}, {"Global", "EndGlobal"}, {"GlobalSection", "EndGlobalSection"}, }; std::string _slnFilename; public: std::shared_ptr<Header> header; // ヘッダー部分 std::vector< std::shared_ptr<Project> > projects; // プロジェクトセクションのポインタ std::shared_ptr<Global> global; // グローバルセクションのポインタ std::list< SlnFileLineSPtr > getLines() const { return _lines; } std::vector< std::shared_ptr<Project> > getProjects() const { // プロジェクトセクションのポインタを返す return projects; } SlnFileLines() { // コンストラクタでセクションマークを初期化 _sectionMarks = { {"Project", "EndProject"}, {"ProjectSection", "EndProjectSection"}, {"Global", "EndGlobal"}, {"GlobalSection", "EndGlobalSection"}, }; header = std::make_shared<Header>(); // ヘッダー部分を初期化 } std::shared_ptr<Section> createNewSection(std::list< SlnFileLineSPtr >::iterator line_itr,std::string linehead) { std::shared_ptr<Section> newsection; // 各セクションへのショートカット if (linehead == "Project") { std::shared_ptr<Project> newproject= std::make_shared<Project>(linehead,line_itr); projects.push_back(newproject); newsection = newproject; } else if (linehead == "Global") { std::shared_ptr<Global> newglobal = std::make_shared<Global>(linehead,line_itr); global = newglobal; newsection = newglobal; } else if (linehead == "GlobalSection") { std::regex pattern(R"(^\s*GlobalSection\((\w+)\)\s*=\s*(preSolution|postSolution))"); std::smatch match; std::string name; if (std::regex_match((*line_itr)->getLine(), match, pattern)) { name = match.str(1); } if (name == "SolutionConfigurationPlatforms") { newsection = std::make_shared<SolutionConfigurationPlatforms>(linehead,line_itr); } else if (name == "ProjectConfigurationPlatforms") { newsection = std::make_shared<ProjectConfigurationPlatforms>(linehead, line_itr); } else if (name == "SolutionProperties") { newsection = std::make_shared<SolutionProperties>(linehead, line_itr); } else if (name == "ExtensibilityGlobals") { newsection = std::make_shared<ExtensibilityGlobals>(linehead, line_itr); } else { newsection = std::make_shared<GlobalSection>(linehead, line_itr); } } else { newsection = std::make_shared<Section>(linehead, line_itr); } return newsection; } void Load(const std::string& filePath) { _slnFilename = filePath; // ファイルオープン std::ifstream file(_slnFilename); if (!file.is_open()) { std::cerr << "Error opening file: " << _slnFilename << std::endl; return; } ///////////////////////////////////////////////////////////////////////////////// // ファイルから行を読み込み、リストに追加 std::string line; while (std::getline(file, line)) { if (!line.empty()) { // 空行は無視 SlnFileLineSPtr lineData = std::make_shared<LineData>(); lineData->setLine(line); _lines.push_back(lineData); } } ///////////////////////////////////////////////////////////////////////////////// // ヘッダー部分の抽出 auto line_itr = _lines.begin(); for (; line_itr != _lines.end(); ++line_itr) { std::string linehead = getLeadingAlphabet(trimLeadingWhitespace((*line_itr)->getLine())); if( isSectionMarks(linehead) == false) { // ヘッダー部分に追加 *line_itr = header->push_back(*line_itr); } else { break; } } ///////////////////////////////////////////////////////////////////////////////// // セクションの開始 std::stack< std::shared_ptr<Section> > currentSection; std::shared_ptr<Section> root = std::make_shared<Section>("root",_lines.end()); // ルートセクションを初期化 currentSection.push(root); // ルートセクションをスタックに追加 for (; line_itr != _lines.end(); ++line_itr) { std::string linehead = getLeadingAlphabet(trimLeadingWhitespace((*line_itr)->getLine())); if (_sectionMarks.find(linehead) != _sectionMarks.end()) { // セクションの開始 std::shared_ptr<Section> newSection = createNewSection(line_itr,linehead); currentSection.top()->subSections.push_back(newSection); currentSection.push(newSection); } else { std::string endSectionMark = _sectionMarks[currentSection.top()->getSectionType()]; if (linehead == endSectionMark) { // セクションの終了 if (!currentSection.empty()) { currentSection.top()->setSectionTail(line_itr); currentSection.top()->update(); currentSection.pop(); } } } } global->update(); } void Save() { // ファイルオープン std::ofstream file(_slnFilename); if (!file.is_open()) { std::cerr << "Error opening file: " << _slnFilename << std::endl; return; } for (auto itr = _lines.begin(); itr != _lines.end(); ++itr) { file << (*itr)->getLine() << std::endl; } } };
/////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// void disp(SlnFileLines& sln);
void ChangeProjectTest(SlnFileLines& sln) { std::string BeforeProjName = "ConsoleApp1"; std::string AfterProjName = "MyNewProjectName"; // 編集 auto projects = sln.getProjects(); for (auto p : projects) { std::string name = p->getProjectName(); if (name == BeforeProjName) { p->setProjectName(AfterProjName); std::string pathstr = p->getProjectPath(); std::regex pattern( std::string("^") + BeforeProjName + "\\\\"); std::smatch match; std::regex_search(pathstr, match, pattern); pathstr = std::regex_replace(pathstr, pattern, AfterProjName + "\\"); p->setProjectPath(pathstr); } } }
int main() { SlnFileLines sln; sln.Load(R"(C:\test\ConsoleApp1.sln)"); // 編集前 disp(sln); // 編集 std::cout << "@@@@@@@@@@@@@@@@@@@@@" << std::endl; ChangeProjectTest(sln); std::cout << "@@@@@@@@@@@@@@@@@@@@@" << std::endl; // 編集後 disp(sln); sln.Save(); }
void disp(SlnFileLines& sln) { std::cout << "FormatVersion: " << sln.header->getFormatVersion().lock()->getVersion() << std::endl; std::cout << "VisualStudioVersion: " << sln.header->getVisualStudioVersion().lock()->getVersion() << std::endl; std::cout << "MinimumVisualStudioVersion: " << sln.header->getMinimumVisualStudioVersion().lock()->getVersion() << std::endl; auto projects = sln.getProjects(); for (auto p : projects) { std::cout << p->getSectionType() << std::endl; std::cout << p->getProjectGUID() << std::endl; std::cout << p->getProjectName() << std::endl; std::cout << p->getProjectPath() << std::endl; std::cout << p->getProjectTypeGUID() << std::endl; std::cout << "-------------------" << std::endl; } auto sections = sln.global->getGlobalSections(); for (auto ss : sections) { std::shared_ptr<GlobalSection> gs = std::static_pointer_cast<GlobalSection>(ss); ; std::cout << "-------------------" << std::endl; std::cout << "1 " << gs->getSectionType() << std::endl; std::cout << "2 " << gs->getSectionName() << std::endl; std::cout << "3 " << gs->getSectionTiming() << std::endl; std::cout << "4 " << (*gs->getSectionBegin())->getLine() << std::endl; std::cout << "5 " << (*gs->getSectionTail())->getLine() << std::endl; std::cout << "=================" << std::endl; } }
MSBuild APIを使おうとした。まず.NET系のAPIなのでC#でプロジェクトを作る。.NET Framework 4.8.1が必要。
プロジェクトを作ったら右クリック→NuGet パッケージの管理へ行き、
・Microsoft.Build
・Microsoft.Build.Locator
をインストール。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Build.Locator; using Microsoft.Build.Evaluation; namespace Project4 {
internal class Class1 { static void Main(string[] args) { // Microsoft.Build.dllのロードを行うので、最初に実行しておく必要がある。 MSBuildLocator.RegisterDefaults(); // 何を置いても先にMicrosoft.Build.dllをロードしておかなければいけないが、 // Class1の中でnew Project()をすると、Class1のJITが実行されたタイミング(プログラム実行前)に // Microsoft.Build.dllがロードされる。 // すると // 1. JITによるMicrosoft.Build.dllのロード // 2. MSBuildLocator.RegisterDefaults()によるMicrosoft.Build.dllのロード // の順番になり、ランタイムエラーとなる。 // それを避けるため、Projectクラスを使うのはMyUseProjectクラスに移動している。 // // これによりMyUseProjectが使われるこの時点で // new Project()を含むコードがJITされるため、順序の問題が解消される。 MyUseProject.Run(); // 待機 Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } }
internal class MyUseProject { public static void Run() { // プロジェクトファイルを読み込み var project = new Project("ConsoleApp1-MSBuildAPI.csproj"); // プロジェクトのプロパティを取得 var ret = project.GetPropertyValue("TargetFramework"); // プロパティの値を表示 Console.WriteLine($"TargetFramework: {ret}"); } }
}
AssocQueryStringWを使ってファイルの拡張子または存在するファイルから関連付けられたアプリケーションを特定できる。
#include <iostream> #include <windows.h> #include <shlwapi.h> #include <string> #include <vector> #pragma comment(lib, "shlwapi.lib")
std::wstring CallAssocQueryString(const std::wstring& filepath, ASSOCSTR str) { DWORD len = 0; // まずは、必要なバッファサイズを取得 // lenにはnull終端を含む文字列の長さが返される HRESULT hr = AssocQueryStringW( ASSOCF_NONE, str, filepath.c_str(), // 拡張子".txt"等、 または存在するファイルパス nullptr, nullptr,// 結果を格納するバッファを指定するが、先にサイズを取得するためnullptrを指定 &len // 結果を格納するのに必要なバッファサイズを取得 ); if (hr != S_OK && hr != S_FALSE) { std::wcerr << L"AssocQueryStringW failed with error: " << hr << std::endl; return L""; } // バッファサイズがわかったので、結果を格納するバッファを確保 std::vector<wchar_t> appPath(len , L'\0'); hr = AssocQueryStringW( ASSOCF_NONE, str, filepath.c_str(), nullptr, &appPath[0], &len ); return std::wstring(appPath.data()); }
int main() { std::wstring target = L".sln"; // 拡張子から関連付けられたアプリケーションのパスを取得 std::wstring appPath = CallAssocQueryString(target, ASSOCSTR_EXECUTABLE); std::wcout << appPath << std::endl; // 関連付けられたアプリケーションの表示名を取得。 // ただし登録されていない場合も多いので、必ずしも取得できるとは限らない。 std::wstring appName = CallAssocQueryString(target, ASSOCSTR_FRIENDLYAPPNAME); std::wcout << appName << std::endl; // 関連付けられたアプリケーションの実行コマンドを取得 std::wstring appCommand = CallAssocQueryString(target, ASSOCSTR_COMMAND); std::wcout << appCommand << std::endl; return 0; }
windowsではアプリケーションの第一引数にファイルを与えるのが一般的らしいのでASSOCSTR_EXECUTABLEで取得した アプリケーションのパス + ファイルパスでもいいが、より確実にはASSOCSTR_COMMANDで取得した実行コマンドの %1 の部分をファイルパスに置換した方が確実。
#include <iostream> #include <windows.h> #include <shlwapi.h> #include <string> #include <vector> #include <regex> #pragma comment(lib, "shlwapi.lib")
std::wstring CallAssocQueryString(const std::wstring& filepath, ASSOCSTR str) { DWORD len = 0; // まずは、必要なバッファサイズを取得 // lenにはnull終端を含む文字列の長さが返される HRESULT hr = AssocQueryStringW( ASSOCF_NONE, str, filepath.c_str(), // 拡張子".txt"等、 または存在するファイルパス nullptr, nullptr,// 結果を格納するバッファを指定するが、先にサイズを取得するためnullptrを指定 &len // 結果を格納するのに必要なバッファサイズを取得 ); if (hr != S_OK && hr != S_FALSE) { std::wcerr << L"AssocQueryStringW failed with error: " << hr << std::endl; return L""; } // バッファサイズがわかったので、結果を格納するバッファを確保 std::vector<wchar_t> appPath(len , L'\0'); hr = AssocQueryStringW( ASSOCF_NONE, str, filepath.c_str(), nullptr, &appPath[0], &len ); return std::wstring(appPath.data()); }
int main() { std::wstring target = LR"(C:\test\test.txt)"; // 関連付けられたアプリケーションの実行コマンドを取得 std::wstring appCommand = CallAssocQueryString(target, ASSOCSTR_COMMAND); std::wcout << appCommand << std::endl; // appCommandは、"C:\Path\To\App.exe" "%1" のような形式 // "%1"をファイルパスに置き換えるとApp.exeでファイルを開ける std::wregex percent1_pattern(LR"((\"?)%1(\"?))"); // %1 または "%1" にマッチ std::wstring quoted_path = L"\"" + target + L"\""; std::wstring command = std::regex_replace(appCommand, percent1_pattern, quoted_path); std::wcout << "Command: " << command << std::endl; STARTUPINFOW si = { sizeof(si) }; PROCESS_INFORMATION pi; BOOL ret = CreateProcessW( nullptr, &command[0], // 開きたいファイルを含むコマンド nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT, nullptr, nullptr, &si, &pi ); return 0; }
CreateProcessで外部アプリケーションを起動するとき、アプリケーションによっては現在の環境変数が有効でないと起動できない場合がある。lpEnvironmentが非nullptrの場合、現在の環境変数が反映されないらしく、この場合は自分で取得して、それに今回自分が必要な値を加えて渡してやるという形をとる。
GetEnvironmentStringsを使用すれば、環境変数設定をまるごと取得できる。
#include <iostream> #include <windows.h> //#include <shlwapi.h> #include <string> #include <vector> // 現在の環境変数一覧を取得 void GetCurrentEnvironemts() { // \0区切りの環境変数を一括取得する。 // 例: // SystemRoot=C:\WINDOWS\0SystemDrive=C:\0\0 LPWCH envStrings = GetEnvironmentStringsW(); LPWCH p = envStrings; while(*p != 0) { // 現在のpから\0までの文字列を取得 std::wstring envVar(p); std::wcout << envVar << std::endl; // 次の環境変数へ移動 // p+envVar.size が \0 を表すので、+1すると次の環境変数の先頭 p += envVar.size() + 1; } FreeEnvironmentStringsW(envStrings); } int main() { GetCurrentEnvironemts(); }
NAME=VALUE\0の形で取得できるので、必要であれば加工して渡してもいい。
今回はただCreateProcessの呼び出しに継承したいだけなので、環境変数の終端\0\0から\0を一つとり、その後に自分が設定したい値を加える。
#include <iostream> #include <windows.h> #include <string> #include <vector>
// 現在の環境変数一覧を取得 std::wstring GetCurrentEnvironemts() { // \0区切りの環境変数を一括取得する。 // 例: // SystemRoot=C:\WINDOWS\0SystemDrive=C:\0\0 LPWCH envStrings = GetEnvironmentStringsW(); std::wstring allenv; LPWCH p = envStrings; while(*p != 0) { // 現在のpから\0までの文字列を取得 std::wstring envVar(p); allenv += envVar; allenv.push_back(L'\0'); // 環境変数の区切り // 次の環境変数へ移動 // p+envVar.size が \0 を表すので、+1すると次の環境変数の先頭 p += envVar.size() + 1; } FreeEnvironmentStringsW(envStrings); return allenv; // 末尾は\0なので、このまま与えたいときは\0をもう一つ追加 }
int main() { ///////////////////////////////////////////////// // 現在の環境変数を取得 std::wstring all_environments = GetCurrentEnvironemts(); ///////////////////////////////////////////////// // 自分が追加したい環境変数 std::wstring envVar1 = L"MY_ENV_STR=HelloWorld"; std::wstring envVar2 = L"MY_ENV_INT=12345"; // 追加可能な形に整形 // 環境変数の終端はNULL文字 std::wstring envVars; envVars += envVar1; envVars.push_back(L'\0'); // 環境変数の区切り envVars += envVar2; envVars.push_back(L'\0'); // 環境変数の区切り // ここまでで、 // envVars=="MY_ENV_STR=HelloWorld\0MY_ENV_INT=12345\0" // all_environmentsの末尾にenvVarsを追加 std::wstring new_environments = all_environments + envVars; // 最後に\0を追加して\0\0にする new_environments.push_back(L'\0'); ///////////////////////////////////////////////// // アプリケーション起動 STARTUPINFOW si = { sizeof(si) }; PROCESS_INFORMATION pi; std::wstring apppath = L"notepad.exe"; BOOL ret = CreateProcessW( nullptr, &apppath[0], // 破壊的パースがされるため非constで渡す nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT, (void*)new_environments.c_str(), nullptr, &si, &pi ); }