wxImage::LoadFileを使用したら、Failed to load image from fileというエラーダイアログが出た。エラーは自分で処理したいので、このダイアログが出ないようにしたい。
void PostCreate() { wxImage myimage; // エラーの時、望まないメッセージボックスが出る bool ret = myimage.LoadFile(R"(D:\test.jpeg)", wxBITMAP_TYPE_PNG); this->Layout(); // レイアウトの更新 }
wxLogNull のインスタンスを作成しておくと、そのインスタンスが破棄されるまではログの出力を行わない。
void PostCreate() { { // ログを表示しない // logNo がスコープを抜けるまでエラー時のログがメッセージボックスで表示されない wxLogNull logNo; wxImage myimage; // エラーの時、望まないメッセージボックスが出る bool ret = myimage.LoadFile(R"(D:\test.jpeg)", wxBITMAP_TYPE_PNG); // エラーハンドリングを自分で行う if (ret == false) { wxMessageBox("画像の読み込みに失敗しました"); return; } } this->Layout(); // レイアウトの更新 }
.xrcファイルはxml形式なので、独自のタグを入れられる。ただしそれを解析する機能がデフォルトではないので、ハンドラを用意する必要がある。
.xrcファイルにはコントロールwx_my_Buttonという識別子でボタンを配置する。このボタンはC++では標準のボタン(wxButton)として生成する。wxButtonのハンドラを書き換えられないので、wx_my_Buttonのハンドラを作成し、中でwxButtonを作成する。
<?xml version="1.0" ?> <resource version="2.3.0.1"> <!-- トップレベルはコントロールである必要がある。パネルを作成。 --> <object class="wxPanel" name="myContainerPanel"> <bg>#00FF00</bg> <!-- 名前 myButton を追加 --> <object class="wx_my_Button" name="myButton"> <myoption attr="5">ユーザー定義オプション</myoption> <label>ボタン</label> </object> </object> </resource>
// wxXmlNode を使うために必要 #include <wx/xml/xml.h>
SetClientDataで値のインスタンス(ここではmyoption_dataの変数)へのポインタをvoid*で指定する。
struct myoption_data { int attr; wxString str; };
GetParamNodeの使用にはwx/xml/xml.hが必要。
// .xrcから独自タグを読み込むには、自作ウィジェットをwxXmlResourceが読み込むための // ハンドラを作成して登録する必要がある class MyButtonXmlHandler : public wxXmlResourceHandler { public: MyButtonXmlHandler() : wxXmlResourceHandler() { AddWindowStyles(); } virtual wxObject* DoCreateResource() override { XRC_MAKE_INSTANCE(mybutton, wxButton); // 普通のボタンを作成 // プロパティの設定 mybutton->Create( m_parentAsWindow, GetID(), GetText("label"), GetPosition(), GetSize(), GetStyle(), wxDefaultValidator, GetName() );
// タグ名 wxString tagname = "myoption"; // データを格納する構造体のインスタンスを作成 // あとで解放しなければいけない myoption_data *pdata = new myoption_data; // 値を取得 // GetTextでも取得できるが、GetTextは\nや$を勝手にエスケープしたり置換したりするので // 内容をそのまま取得できるGetParamValueを使う wxString myopt = GetParamValue(tagname); if (!myopt.IsEmpty()) { pdata->str = myopt; } // 属性を取得 wxXmlNode* node = GetParamNode(tagname); if (node) { wxString attr = node->GetAttribute("attr"/*属性名*/, "0"/*初期値*/); pdata->attr = wxAtoi(attr); } mybutton->SetClientData(pdata);// データをコントロールに指定
SetupWindow(mybutton); return mybutton; } // このハンドラが、xrc内に定義された"MyControl"というウィジェットを作成できることを示すために必要 virtual bool CanHandle(wxXmlNode* node) override { // 生成するのはただのボタンだが、wxButtonのハンドラを置換はできないので // 別のクラス名を識別子にする。 return IsOfClass(node, "wx_my_Button"); } // wxCreateObject() ,GetClassInfo() ,ms_classInfo,wxCreateObject()が定義される wxDECLARE_DYNAMIC_CLASS(MyButtonXmlHandler); }; // 自作ウィジェットのハンドラを登録 IMPLEMENT_DYNAMIC_CLASS(MyButtonXmlHandler, wxXmlResourceHandler)
生成されるのは普通のwxButtonなので、生成時にAddHandlerしておくこと以外には特別なことはしない。ただしハンドラ内でnewしたmyoption_dataのポインタは自分で削除しなければいけないので、wxEVT_DESTROYに後始末用関数をバインドする。
// ウィンドウ作成 class MyFrame : public wxFrame { public: void PostCreate() { wxXmlResource::Get()->InitAllHandlers();// 初期化 // カスタムコントロールのハンドラを登録 wxXmlResource::Get()->AddHandler(new MyButtonXmlHandler); wxXmlResource::Get()->Load(R"(C:\test\myctrl.xrc)"); // ファイル読み込み wxXmlResource::Get()->LoadPanel(this, "myContainerPanel"); // GUIの生成 this->Layout(); // レイアウトの更新 wxButton* btn = (wxButton*)FindWindowByName("myButton"); btn->Bind(wxEVT_BUTTON, &MyFrame::OnClick, this); // ボタンのクリックイベント btn->Bind(wxEVT_DESTROY, &MyFrame::OnBtnDestroy, this); // ユーザー定義のデータを破棄するための処理を追加 } MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame(NULL, wxID_ANY, title, pos, size) { // CallAfter : 現在処理中のイベントが終わったらPostCreateを実行 // コンストラクタはウィンドウ生成イベント扱い CallAfter(&MyFrame::PostCreate); }
// ボタンのクリックイベント void OnClick(wxCommandEvent& event) { wxButton* btn = (wxButton*)event.GetEventObject(); myoption_data* data = (myoption_data*)btn->GetClientData(); wxString text = wxString::Format("%s %d", data->str, data->attr); wxMessageBox(text); }
// ユーザー定義のデータを持つボタンの破棄イベント void OnBtnDestroy(wxWindowDestroyEvent& event) { wxButton* btn = (wxButton*)event.GetEventObject(); myoption_data* data = (myoption_data*)btn->GetClientData(); delete data; }
};
wxWidgetsでは、wxWindowなどを継承してカスタムコントロールを作れる。それを.xrcファイルに定義した場合、.xrcを読み込む wxXmlResource がカスタムコントロールを知らないため、専用のハンドラを定義しなければいけない。
以下のようなカスタムコントロール CMyControl を定義する
// ボタンとテキストを持つコントロールをパネルで作成 class CMyControl : public wxWindow { public: // 今回はMyControlXmlHandler側でXRC_MAKE_INSTANCEでインスタンス生成をする // MyControlXmlHandlerはデフォルトコンストラクタを呼び出すので定義する CMyControl() :wxWindow() { } // デフォルトコンストラクタでは最小限の初期化しか行わないので // 初期化用のCreate関数を定義する必要が出てくる bool Create(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name) { if (!wxWindow::Create(parent, id, pos, size, style, name)) { return false; } my_create(); return true; } // parentを指定するコンストラクタ CMyControl(wxWindow* parent) :wxWindow(parent, wxID_ANY) { my_create(); } // ウィジェットの初期化関数 void my_create() { // ボタンの作成 wxButton* myButton = new wxButton(this, wxID_ANY, "ボタン", wxPoint(10, 10), wxSize(150, 50)); // テキストの作成 wxTextCtrl* myText = new wxTextCtrl(this, wxID_ANY, "テキスト", wxPoint(10, 70), wxSize(150, 50)); this->SetBackgroundColour(wxColour(255, 0, 0)); } };
以下のように.xrcファイルを定義する
<?xml version="1.0" ?> <resource version="2.3.0.1"> <!-- トップレベルはコントロールである必要がある。パネルを作成。 --> <object class="wxPanel" name="myContainerPanel"> <bg>#00FF00</bg> <!-- レイアウト用のサイザーを作成 --> <object class="wxBoxSizer" name="myBoxSizer"> <orient>wxVERTICAL</orient> <!-- レイアウト方法の指定 --> <!-- コントロールは sizeritem に入れる --> <object class="sizeritem"> <!-- 周囲に15pxの余白を入れる --> <flag>wxALL | wxEXPAND</flag> <border>15</border> <!-- このコントロールはサイザーのサイズ変更で大きさが変わる --> <option>1</option> <!-- カスタムコントロールを追加 --> <object class="MyControl" name="nameMyControl" /> </object> </object> </object> </resource>
// .xrcから読み込むには、自作ウィジェットをwxXmlResourceが読み込むための // ハンドラを作成して登録する必要がある class MyControlXmlHandler : public wxXmlResourceHandler { public: MyControlXmlHandler() : wxXmlResourceHandler() { XRC_ADD_STYLE(wxTAB_TRAVERSAL); XRC_ADD_STYLE(wxNO_BORDER); XRC_ADD_STYLE(wxDOUBLE_BORDER); AddWindowStyles(); }
// これが必要 // wxXmlResource::LoadObject() が呼び出されたときに呼び出される関数 virtual wxObject* DoCreateResource() override { // 概ねやっていることは以下と同じ: // MyControl* control = new MyControl; // 注意として、このマクロはMyControlのデフォルトコンストラクタを呼び出すので // 引数なしのコンストラクタが必要 // 別にXRC_MAKE_INSTANCEを使わなくても、普通にnewしてもいい。 XRC_MAKE_INSTANCE(control, CMyControl); // デフォルトコンストラクタでは最小限の初期化しかされないので // Create()を呼び出して、実際の初期化を行う // 従ってMyControl::Create()を定義しておく control->Create( m_parentAsWindow, // 親ウィンドウ GetID(), GetPosition(), GetSize(), GetStyle(), GetName() ); SetupWindow(control); return control; }
// このハンドラが、xrc内に定義された"MyControl"というウィジェットを作成できることを示すために必要 virtual bool CanHandle(wxXmlNode* node) override { return IsOfClass(node, "MyControl"); }
// wxCreateObject() ,GetClassInfo() ,ms_classInfo,wxCreateObject()が定義される wxDECLARE_DYNAMIC_CLASS(MyControlXmlHandler);
}; // 自作ウィジェットのハンドラを登録 IMPLEMENT_DYNAMIC_CLASS(MyControlXmlHandler, wxXmlResourceHandler)
読み込むときは、AddHandlerで、作成したハンドラのインスタンスをwxXmlResourceに渡す。
// ウィンドウ作成 class MyFrame : public wxFrame { public: void PostCreate() { wxXmlResource::Get()->InitAllHandlers();// 初期化 // カスタムコントロールのハンドラを登録 wxXmlResource::Get()->AddHandler(new MyControlXmlHandler); wxXmlResource::Get()->Load(R"(C:\test\myctrl.xrc)"); // ファイル読み込み wxXmlResource::Get()->LoadPanel(this, "myContainerPanel"); // GUIの生成 this->Layout(); // レイアウトの更新 } MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame(NULL, wxID_ANY, title, pos, size) { // CallAfter : 現在処理中のイベントが終わったらPostCreateを実行 // コンストラクタはウィンドウ生成イベント扱い CallAfter(&MyFrame::PostCreate); } };
<?xml version="1.0" ?> <resource version="2.3.0.1"> <!-- トップレベルはコントロールである必要がある。パネルを作成。 --> <object class="wxPanel" name="myMainPanel"> <!-- レイアウト用のサイザーを作成 --> <object class="wxBoxSizer" name="myBoxSizer"> <orient>wxVERTICAL</orient> <!-- レイアウト方法の指定 --> <!-- コントロールは sizeritem に入れる --> <object class="sizeritem"> <!-- 左側と上側に15pxの余白を入れる --> <flag>wxLEFT|wxTOP</flag> <border>15</border> <!-- このコントロールはサイザーのサイズ変更で大きさが変わらない --> <option>0</option> <!-- 名前 myButton を追加 --> <object class="wxButton" name="myButton"> <label>ボタン</label> <size>100,30</size> </object> </object> <!-- コントロールは sizeritem に入れる --> <object class="sizeritem"> <!-- 上下左右の余白は15px --> <flag>wxALL|wxEXPAND</flag> <border>15</border> <!-- このコントロールはサイザーのサイズ変更で大きさが変わる --> <option>1</option> <object class="wxTextCtrl" name="myText"> <value>テキスト</value> </object> </object> </object> </object> </resource>
// https://docs.wxwidgets.org/3.0/overview_helloworld.html // プリプロセッサに以下二つを追加 // __WXMSW__ // WXUSINGDLL // サブシステムをWindowsに設定(WinMainで呼び出すので) // Windows (/SUBSYSTEM:WINDOWS) #ifndef WX_PRECOMP #include <wx/wx.h> #endif #include <wx/gdicmn.h> // wxPointに必要 #include <wx/frame.h> // wxFrameに必要 #pragma comment(lib,"wxbase32u.lib") #pragma comment(lib,"wxbase32u_net.lib") #pragma comment(lib,"wxbase32u_xml.lib") #pragma comment(lib,"wxmsw32u_adv.lib") #pragma comment(lib,"wxmsw32u_aui.lib") #pragma comment(lib,"wxmsw32u_core.lib") #pragma comment(lib,"wxmsw32u_gl.lib") #pragma comment(lib,"wxmsw32u_html.lib") #pragma comment(lib,"wxmsw32u_media.lib") #pragma comment(lib,"wxmsw32u_propgrid.lib") #pragma comment(lib,"wxmsw32u_qa.lib") #pragma comment(lib,"wxmsw32u_ribbon.lib") #pragma comment(lib,"wxmsw32u_richtext.lib") #pragma comment(lib,"wxmsw32u_stc.lib") #pragma comment(lib,"wxmsw32u_webview.lib") #pragma comment(lib,"wxmsw32u_xrc.lib") ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// #include <wx/xrc/xmlres.h> #include <string> // ウィンドウ作成 class MyFrame : public wxFrame { public:
void PostCreate() { wxXmlResource::Get()->InitAllHandlers();// 初期化 wxXmlResource::Get()->Load(R"(C:\test\layout.xrc)"); // ファイル読み込み wxXmlResource::Get()->LoadPanel(this, "myMainPanel"); // GUIの生成 this->Layout(); // レイアウトの更新 // ボタンの取得 wxButton* myButton = XRCCTRL(*this, "myButton", wxButton); // ボタンのクリックイベントに対するイベントハンドラを接続 myButton->Bind(wxEVT_BUTTON, &MyFrame::OnMyButtonClick, this); }
MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame(NULL, wxID_ANY, title, pos, size) { // CallAfter : 現在処理中のイベントが終わったらPostCreateを実行 // コンストラクタはウィンドウ生成イベント扱い CallAfter(&MyFrame::PostCreate); }
// ボタンがクリックされたときのイベントハンドラ void OnMyButtonClick(wxCommandEvent& event) { // テキストボックスの取得 wxTextCtrl* textctrl = XRCCTRL(*this, "myText", wxTextCtrl); // テキストボックスの内容を取得 wxString str = textctrl->GetValue(); // ボタンがクリックされたときの処理 wxMessageBox(str); }
private: }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // wxWidgetsのアプリケーション作成 class MyApp : public wxApp { public: virtual bool OnInit() { MyFrame* frame = new MyFrame("Hello World", wxPoint(50, 50), wxSize(450, 340)); frame->Show(true); return true; } }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // WinMainをマクロで定義 wxIMPLEMENT_APP(MyApp);
XRCCTRLの実体はFindWindowをdynamic_castしたもので、実際はもっと複雑だがざっくりいうと以下のようなことをやっている。
wxButton* myButton = dynamic_cast<wxButton*>(this->FindWindow("myButton"));
wxWidgetsで画面デザインを定義する.xrcファイルを読み込んでGUIを作成する。
<?xml version="1.0" ?> <resource version="2.3.0.1"> <!-- トップレベルはコントロールである必要がある。パネルを作成。 --> <object class="wxPanel" name="myMainPanel">
<!-- レイアウト用のサイザーを作成 --> <object class="wxBoxSizer" name="myBoxSizer"> <orient>wxVERTICAL</orient> <!-- レイアウト方法の指定 -->
<!-- コントロールは sizeritem に入れる --> <object class="sizeritem"> <!-- 左側と上側に15pxの余白を入れる --> <flag>wxLEFT|wxTOP</flag> <border>15</border> <!-- このコントロールはサイザーのサイズ変更で大きさが変わらない --> <option>0</option>
<!-- 名前 myButton を追加 --> <object class="wxButton" name="myButton"> <label>ボタン</label> <size>100,30</size> </object>
</object>
<!-- コントロールは sizeritem に入れる --> <object class="sizeritem"> <!-- 上下左右の余白は15px --> <flag>wxALL|wxEXPAND</flag> <border>15</border> <!-- このコントロールはサイザーのサイズ変更で大きさが変わる --> <option>1</option>
<object class="wxTextCtrl" name="mytext1"> <label>ボタン</label> <size>100,30</size> <!-- サイザーが有効なのでサイズは無視される --> </object>
</object>
</object>
</object> </resource>
// https://docs.wxwidgets.org/3.0/overview_helloworld.html // プリプロセッサに以下二つを追加 // __WXMSW__ // WXUSINGDLL // サブシステムをWindowsに設定(WinMainで呼び出すので) // Windows (/SUBSYSTEM:WINDOWS) #ifndef WX_PRECOMP #include <wx/wx.h> #endif #include <wx/gdicmn.h> // wxPointに必要 #include <wx/frame.h> // wxFrameに必要 #pragma comment(lib,"wxbase32u.lib") #pragma comment(lib,"wxbase32u_net.lib") #pragma comment(lib,"wxbase32u_xml.lib") #pragma comment(lib,"wxmsw32u_adv.lib") #pragma comment(lib,"wxmsw32u_aui.lib") #pragma comment(lib,"wxmsw32u_core.lib") #pragma comment(lib,"wxmsw32u_gl.lib") #pragma comment(lib,"wxmsw32u_html.lib") #pragma comment(lib,"wxmsw32u_media.lib") #pragma comment(lib,"wxmsw32u_propgrid.lib") #pragma comment(lib,"wxmsw32u_qa.lib") #pragma comment(lib,"wxmsw32u_ribbon.lib") #pragma comment(lib,"wxmsw32u_richtext.lib") #pragma comment(lib,"wxmsw32u_stc.lib") #pragma comment(lib,"wxmsw32u_webview.lib") #pragma comment(lib,"wxmsw32u_xrc.lib") ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// #include <wx/xrc/xmlres.h> // ウィンドウ作成 class MyFrame : public wxFrame { public:
void PostCreate() { wxXmlResource::Get()->InitAllHandlers();// 初期化 wxXmlResource::Get()->Load(R"(C:\test\layout.xrc)"); // ファイル読み込み wxXmlResource::Get()->LoadPanel(this, "myMainPanel"); // GUIの作成 this->Layout(); // レイアウトの更新 }
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() { MyFrame* frame = new MyFrame("Hello World", wxPoint(50, 50), wxSize(450, 340)); frame->Show(true); return true; } }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // WinMainをマクロで定義 wxIMPLEMENT_APP(MyApp);
パネルにはLoadPanelがあるが、すべてのコントロールにLoad***が用意されているわけではない。LoadObject関数を使って、部品名とその部品のクラスを指定して読み込む。
<?xml version="1.0" ?> <resource version="2.3.0.1"> <!-- 名前 myButton を追加 --> <object class="wxButton" name="myButton"> <label>ボタン</label> <size>300,30</size> </object> </resource>
void PostCreate() { wxXmlResource::Get()->InitAllHandlers();// 初期化 wxXmlResource::Get()->Load(R"(C:\test\button.xrc)"); // ファイル読み込み wxWindow* btn = (wxWindow*)wxXmlResource::Get()->LoadObject(this, "myButton"/*識別子*/, "wxButton"/*クラス*/); // boxsizerの追加 wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); this->SetSizer(sizer); sizer->Add(btn, 0, wxALL, 10); this->Layout(); // レイアウトの更新 }
原則として、メモリ上のテキストを読み込む方法はない。しかし、wxWidgetsにはメモリ上に仮想的なファイルを作成する機能があるので、これを使って仮想的な.xrcファイルを作れば実現できる。
仮想ファイルを作るために、fs_mem.h が必要。
#include <wx/fs_mem.h>
void PostCreate() { wxXmlResource::Get()->InitAllHandlers(); wxString layout_xrc =
R"(<?xml version="1.0" ?> <resource version="2.3.0.1"> <object class="wxPanel" name="myPanel"> <object class="wxBoxSizer"> <orient>wxVERTICAL</orient> <object class="sizeritem"> <object class="wxTextCtrl" name="text"/> <option>0</option> <size>100,30</size> <flag>wxALL</flag> <border>10</border> </object> </object> </object> </resource> )"
; wxFileSystem::AddHandler(new wxMemoryFSHandler); wxMemoryFSHandler::AddFile("myxrc", layout_xrc.GetData().AsWChar(), layout_xrc.size()*2); wxXmlResource::Get()->Load("memory:myxrc"); wxWindow* mysizer = wxXmlResource::Get()->LoadPanel(this, "myPanel"); this->Layout(); // レイアウトの更新 }
諸事情により簡易的でよいので.exeファイルの解析をしたくなった。.exeファイルをバイナリエディタで見てみて、全く同じプロジェクトを二回以上ビルドした結果が完全一致しない。理由の一つがタイムスタンプなので、これをいじってみる。
VC++でコンソールアプリケーションを作成する。この時、余計なものが埋め込まれるとそれだけビルド時の変化が大きくなる可能性があるので、わかる範囲で不要なオプションを取り除く。
・.pdbファイルを出力しない
プロジェクトのプロパティ → リンカー → デバッグ → デバッグ情報の生成 → いいえ
・マニフェストを埋め込まない
プロジェクトのプロパティ → リンカー → マニフェスト ファイル → マニフェストの生成 → いいえ
・ASLRをしない(Address Space Layout Randomization)
ASLRはexe内のデータの位置をランダムにするクラッキング対策のオプションで、多分悪影響を与えるので切っておく。/DYNAMICBASE:NOを設定。
プロジェクトのプロパティ → リンカー → 詳細設定 → ランダム化されたベースアドレス → いいえ
まずビルドし、上書きされないように.exeファイルの名前を変える。それからもう一度ビルドする。
右クリック→プロパティ→詳細 で更新日時を確認。
実際にはここの表示を変えるわけではないのだが、バイナリ中の値を解析してこの日付とおおむね同じものが出てくれば正解だとわかるので参考にする。
WinMergeで二つの.exeをバイナリ比較する。手元のバージョンでは、CompareボタンのプルダウンにBinary比較がある。
一部違う場所が出てくるので、その右4バイトに着目する。時間を置かずにビルドすると、数秒しか違わないはずなので、違いはせいぜい下1バイトになる可能性が高い。intelのCPUはリトルエンディアンなので、バイトの並び順が逆になっている。従って変化している場所を含めて右に4バイトがタイムスタンプとなる。
上記で得られた値 0x65abe167 が実際にビルド時刻であることを確認するため、以下のプログラムを書いて確認する。
#include <ctime> #include <string> #include <iostream> #pragma warning(disable:4996) // タイムスタンプから時刻表示を作成 std::string TimestampToDate(time_t timestamp) { struct tm* tm = localtime(×tamp);// 整数値 timestamp を tm 構造体に変換 char str[50]; strftime(str, sizeof(str), "%Y/%m/%d %H:%M:%S", tm);// tmを文字列化 return std::string(str); } int main() { // タイムスタンプ(16進数指定) // バイナリに埋め込まれている値はリトルエンディアンなので // バイナリエディタ上の表示とバイト単位で逆順にして入力する time_t timestamp = 0x65abe167; std::cout << TimestampToDate(timestamp) << std::endl; return 0; }
結果
確かにファイルのプロパティ上の表示と一致する内容を得られたので、この数値がタイムスタンプであることは理解できた。今度はこれを書き換えてみる。
まず、適当な日付をタイムスタンプに変換するため、以下のプログラムを書いて走らせる。
#include <ctime> #include <string> #include <iostream> #pragma warning(disable:4996) // 時刻からタイムスタンプを作成 time_t DateToTimestamp(const std::string date) { struct tm tm = {}; int year, month, day; int hour, minute, second; sscanf(date.c_str(), "%d/%d/%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second ); tm.tm_year = year - 1900; tm.tm_mon = month - 1; tm.tm_mday = day; tm.tm_hour = hour; tm.tm_min = minute; tm.tm_sec = second; return mktime(&tm); } int main() { std::string date_str = "2022/01/01 5:25:30"; // 適当な日付 unsigned int timestamp = DateToTimestamp(date_str); printf("%x \n", timestamp); }
結果
こうして得られたタイムスタンプを、バイナリエディタで書き込む。書き込みにはStirlingを使っている。入力時、リトルエンディアンなのでバイト単位で逆順に指定する。
タイムスタンプはビルド時の色々なステップで入れられるらしいので、同じ値を見つけたらすべて書き換える。
先ほどはファイルのプロパティから確認したが、この日付はファイルのプロパティには実は表示されないので、PEヘッダを読むツールで確認する。探したところPEviewが適している。
以下からPEview version 0.9.9 ( .zip 31KB )をダウンロードする。
http://wjradburn.com/software/
wxWebView上でTinyMCEを動かし、その編集内容をC++側に取得する。
これを応用すればHTMLエディタ的なものが作れる。
<!DOCTYPE html> <html> <head> <script src="js/tinymce/tinymce.min.js"></script> <script> tinymce.init({ selector: 'textarea' }); </script> <script>
// TinyMCEから入力内容を取得する関数 function MyGetContent(){ var myContent = tinymce.get("myTinyMCEarea").getContent(); return myContent; }
</script> </head> <body> <!-- TinyMCE本体 --> <textarea id="myTinyMCEarea">my text</textarea> </body> </html>
wxWebView::RunScriptを使用すれば、読み込んでいるページのJavaScriptを実行し、結果を取得できる。これを応用し、my_test.html側にMyGetContent()関数を作成してから、RunScriptをして、取得した結果をメッセージボックスで表示する。
// https://docs.wxwidgets.org/3.0/overview_helloworld.html // プリプロセッサに以下二つを追加 // __WXMSW__ // WXUSINGDLL // サブシステムをWindowsに設定(WinMainで呼び出すので) // Windows (/SUBSYSTEM:WINDOWS) #ifndef WX_PRECOMP #include <wx/wx.h> #endif #include <wx/gdicmn.h> // wxPointに必要 #include <wx/frame.h> // wxFrameに必要 #pragma comment(lib,"wxbase32u.lib") #pragma comment(lib,"wxbase32u_net.lib") #pragma comment(lib,"wxbase32u_xml.lib") #pragma comment(lib,"wxmsw32u_adv.lib") #pragma comment(lib,"wxmsw32u_aui.lib") #pragma comment(lib,"wxmsw32u_core.lib") #pragma comment(lib,"wxmsw32u_gl.lib") #pragma comment(lib,"wxmsw32u_html.lib") #pragma comment(lib,"wxmsw32u_media.lib") #pragma comment(lib,"wxmsw32u_propgrid.lib") #pragma comment(lib,"wxmsw32u_qa.lib") #pragma comment(lib,"wxmsw32u_ribbon.lib") #pragma comment(lib,"wxmsw32u_richtext.lib") #pragma comment(lib,"wxmsw32u_stc.lib") #pragma comment(lib,"wxmsw32u_webview.lib") #pragma comment(lib,"wxmsw32u_xrc.lib") // wxWebViewを使うために必要 #include <wx/webview.h> ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // ウィンドウ作成 class MyFrame : public wxFrame { wxWebView* webView; public: MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame(NULL, wxID_ANY, title, pos, size) { wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); // wxWebView インスタンスの作成 webView = wxWebView::New( this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxWebViewBackendEdge, 0, "" ); webView->LoadURL(R"(C:\test\data\tinymce\my_test.html)");// TinyMCEを使うHTML読み込み // ボタンを作成し、sizerに追加 wxButton* button = new wxButton(this, wxID_ANY, wxT("Run Script")); sizer->Add(webView, 1, wxEXPAND, 0); sizer->Add(button, 0, wxALIGN_CENTER | wxALL, 10); SetSizer(sizer); // ボタンのイベントをバインド button->Bind(wxEVT_BUTTON, &MyFrame::OnButtonClicked, this); } // ボタンクリックイベントのハンドラ void OnButtonClicked(wxCommandEvent& event) { if (webView) {
wxString jsCode = wxT(R"(MyGetContent();)"); wxString ret; webView->RunScript(jsCode, &ret);// my_test.html に定義したJavaScriptのfunction呼び出し wxMessageBox(ret);
} } private: }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // wxWidgetsのアプリケーション作成 class MyApp : public wxApp { public: virtual bool OnInit() { MyFrame* frame = new MyFrame("Hello World", wxPoint(50, 50), wxSize(450, 340)); frame->Show(true); return true; } }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // WinMainをマクロで定義 wxIMPLEMENT_APP(MyApp);
TinyMCEはブラウザ上で使えるHTMLのWYSIWYGエディタ。
公式ページ:
サイトの最下部付近にある GET TinyMCE FREE から、「TinyMCE Open Source Community」 内の 「Download TinyMCE Community」をクリックしてダウンロード開始。
<!DOCTYPE html> <html> <head> <script src="js/tinymce/tinymce.min.js"></script> <script> tinymce.init({ selector: 'textarea' }); </script> </head> <body> <!-- TinyMCE入力エリア --> <textarea>first text</textarea> </body> </html>
C:\test\data │ └─tinymce │ CHANGELOG.md │ my_test.html │ └─js └─tinymce ├─icons ├─langs ├─models ├─plugins ├─skins └─themes
my_test.htmlをFirefoxなどのブラウザで開くと、エディタを使用できる。
JavaScriptで、入力したテキストを取得する。
<!DOCTYPE html> <html> <head> <script src="js/tinymce/tinymce.min.js"></script> <script> tinymce.init({ selector: 'textarea' }); </script>
<script> // TinyMCEから入力内容を取得する関数 function MyGetContent(){ var myContent = tinymce.get("myTinyMCEarea").getContent(); return myContent; } // ボタンが押された時に実行する関数。 // TinyMCEからテキストを取得し、MyCheckAreaに表示する。 function MyButtonClick(){ document.getElementById("MyCheckArea").innerHTML = MyGetContent(); } </script>
</head> <body> <!-- TinyMCE本体 --> <textarea id="myTinyMCEarea">first text</textarea> <!-- 内容取得確認用ボタン --> <button id="showContent" onclick="MyButtonClick()" >Show Content</button> <!-- 取得した内容の表示領域 --> <div id="MyCheckArea"></div> </body> </html>
公式ドキュメントよりも詳しいTinyMCEの使い方(基本編)
公式ドキュメントよりも詳しいTinyMCEの使い方(基本編)
wxWebViewを使うとWebサイトを簡単に表示できる。問題はレンダリングエンジンの指定に注意が必要。
// https://docs.wxwidgets.org/3.0/overview_helloworld.html // プリプロセッサに以下二つを追加 // __WXMSW__ // WXUSINGDLL // サブシステムをWindowsに設定(WinMainで呼び出すので) // Windows (/SUBSYSTEM:WINDOWS) #ifndef WX_PRECOMP #include <wx/wx.h> #endif #include <wx/gdicmn.h> // wxPointに必要 #include <wx/frame.h> // wxFrameに必要 #pragma comment(lib,"wxbase32u.lib") #pragma comment(lib,"wxbase32u_net.lib") #pragma comment(lib,"wxbase32u_xml.lib") #pragma comment(lib,"wxmsw32u_adv.lib") #pragma comment(lib,"wxmsw32u_aui.lib") #pragma comment(lib,"wxmsw32u_core.lib") #pragma comment(lib,"wxmsw32u_gl.lib") #pragma comment(lib,"wxmsw32u_html.lib") #pragma comment(lib,"wxmsw32u_media.lib") #pragma comment(lib,"wxmsw32u_propgrid.lib") #pragma comment(lib,"wxmsw32u_qa.lib") #pragma comment(lib,"wxmsw32u_ribbon.lib") #pragma comment(lib,"wxmsw32u_richtext.lib") #pragma comment(lib,"wxmsw32u_stc.lib") #pragma comment(lib,"wxmsw32u_webview.lib") #pragma comment(lib,"wxmsw32u_xrc.lib") // wxWebViewを使うために必要 #include <wx/webview.h> ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // ウィンドウ作成 class MyFrame : public wxFrame { public: MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame(NULL, wxID_ANY, title, pos, size) { // wxWebView インスタンスの作成 wxWebView* webView = wxWebView::New( this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, // レンダリングエンジンの指定 // wxWebViewBackendWebKit, // wxWebViewBackendEdge, // wxWebViewBackendIE, wxWebViewBackendDefault, 0, "" ); webView->LoadURL("https://www.suzulang.com/"); } private: // イベント処理しないときはこれを入れない // wxDECLARE_EVENT_TABLE(); }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // wxWidgetsのアプリケーション作成 class MyApp : public wxApp { public: virtual bool OnInit() { MyFrame* frame = new MyFrame("Hello World", wxPoint(50, 50), wxSize(450, 340)); frame->Show(true); return true; } }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // WinMainをマクロで定義 wxIMPLEMENT_APP(MyApp);
少なくともWindowsでレイアウトが崩れる理由は、wxWebViewBackendDefaultの指定だとIEになってしまうからで(’明示的に指定する場合はwxWebViewBackendIE)、これをEdgeにしなければならない。
wxWebViewBackendEdgeを指定する。
ただし、これを指定してエラーになったり、何も表示されない場合は、wxWidgetsをCMakeする際にwxUSE_WEBVIEW_EDGE,wxUSE_WEBVIEW_EDGE_STATICを含めて再ビルドする必要がある。
なお、どうもWindowsではwxWebViewBackendWebKitは動かないらしい。とはいえEdgeは中身がChromiumなのでwxWebViewBackendEdgeで大抵のページは表示される(に違いない)。
MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame(NULL, wxID_ANY, title, pos, size) { // wxWebView インスタンスの作成 wxWebView* webView = wxWebView::New( this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, // レンダリングエンジンの指定 // wxWebViewBackendWebKit, wxWebViewBackendEdge, // wxWebViewBackendIE, //wxWebViewBackendDefault, 0, "" ); webView->LoadURL("https://www.suzulang.com/"); }
ファイルパスを指定して、ローカルにあるhtmlを読み込める。
webView->LoadURL("C:/test/local.html");
LoadURLの代わりにSetPageを使用すれば、メモリ上のHTMLを表示できる。
//webView->LoadURL("C:/test/local.html"); webView->SetPage( R"( <html> <head></head> <body><p style="font-size:5em;">テスト</p></body> </html> )" , "");
ANTLR4を使うと自分のプロジェクトに様々な言語のパーサーを実装できる。
ANTLR4の実行形式(javaの実行形式)を実行して、「パースしたい言語」のための、「実装したい言語」用のソースコードを生成する。
今回は、HTMLをパースするためのC++のソースコードを作成する。
こうして出力したC++のパース用コードは、ANTLR4のライブラリとリンクすることでコンパイルできるようになる。
https://www.antlr.org/download.html
URLから、Complete ANTLR 4.13.1 java binaries jarから、antlr-4.13.1-complete.jar をダウンロードする。
-Dlanguage=Cppを指定してC++のコードを出力することを指定。トークン定義ファイルHTMLLexer.g4と、HTMLParser.g4構文解析用のファイル。
実行はHTMLLexer.g4 → HTMLParser.g4 の順で行う。
以下が生成される:
・HTMLLexer.cpp
・HTMLLexer.h
・HTMLLexer.interp
・HTMLLexer.tokens
以下が生成される:
・HTMLParser.cpp
・HTMLParser.h
・HTMLParser.interp
・HTMLParser.tokens
・HTMLParserBaseListener.cpp
・HTMLParserBaseListener.h
・HTMLParserListener.cpp
・HTMLParserListener.h
以下のようなエラーが出る可能性がある(出た)。Java系のエラーなので新しいJavaをインストールして解決する。
このエラーは、「実行しようとしているツールはclass file version 55 (=Java 11)で作られているが、システムに入っているclass file version 52 (=Java 8)だ」という意味。
解決するために、今回は以下からJava 11 をダウンロードする。なおJava 11からJREがなくなったらしい。
https://www.oracle.com/jp/java/technologies/javase/jdk11-archive-downloads.html
なおダウンロードにはOracleプロファイルの作成が必要。勘弁してくれ。
生成したコードはそれだけでは動かないので、antlr4-devをCMakeする。
以下からantlr4のソースコードをダウンロードし、antlr4-dev をVC++用にCMakeにかける。
https://github.com/antlr/antlr4
antlr4-dev/runtime/Cpp/ にCMakeLists.txtがある。
CMAKE_INSTALL_PREFIXだけ好きな場所を指定し、後はそのままConfigure→Generate→Open Project。
・インクルードディレクトリに以下を追加
・追加のライブラリディレクトリに以下を追加
・C++言語標準を「ISO C++17標準」以上にする
・リンクするライブラリ
antlr4-runtime.lib
gmock.lib
gmock_main.lib
gtest.lib
gtest_main.lib
#include <iostream> #include "HTMLLexer.h" #include "HTMLParser.h" #include <antlr4-runtime.h> #pragma comment(lib,"antlr4-runtime.lib") #pragma comment(lib,"gmock.lib") #pragma comment(lib,"gmock_main.lib") #pragma comment(lib,"gtest.lib") #pragma comment(lib,"gtest_main.lib") int main(int, const char**) { // 入力 std::ifstream mysrc; mysrc.open("D:\\test.txt"); // ANTLR入力ストリームを作成 antlr4::ANTLRInputStream input(mysrc); // Lexer、トークンストリームを作成 HTMLLexer lexer(&input); antlr4::CommonTokenStream tokens(&lexer); // トークンストリームをパーサに渡す HTMLParser parser(&tokens); // HTML用のパーサなので最上位がhtmlDocument auto tree = parser.htmlDocument(); // ツリーを表示 std::cout << tree->toStringTree(&parser,true) << std::endl; return 0; }
<html> <head> <title>test</title> </head> <body> <p>test</p> </body> </html>
(htmlDocument (htmlElements (htmlElement < html > (htmlContent (htmlChardata \n) (htmlElement < head > (htmlContent (htmlElement < title > (htmlContent (htmlChardata test)) < / title >)) < / head >) (htmlChardata \n) (htmlElement < body > (htmlContent (htmlChardata \n) (htmlElement < p > (htmlContent (htmlChardata test)) < / p >) (htmlChardata \n)) < / body >) (htmlChardata \n)) < / html >)))