CWinApp::m_pszRegistryKey
VC で MFC プロジェクトを作成すると,CXxxApp::InitInstance が次の様になる.
SetRegistryKey(_T(“アプリケーション ウィザードで生成されたローカル アプリケーション”));
ドキュメントにある様に,会社名などを指定する.
The registry key is usually the name of a company.
あまりやってはいけないことだろうと思うが,ドキュメントを見ていると,その設定した値 CWinApp::m_pszRegistryKey を直接変更できる.
そこのサンプルコードが… 何年も前から変わっていない.
CWinApp::SetRegistryKey の動作は,新しいキーに変更して m_pszPforileName も変更している.
MFC を使用するコンソール AP での SetRegistryKey の使用は次の所.
https://dev.mish.work/wordpress/2021/07/21/console-ap-setregistrykey/
[MyDNS.JP] IPアドレスの通知が …
OpenMP Fatal User Error 1002
OpenMP が有効な時でも動作する様にテストしていると…
Fatal User Error 1002: A ‘#pragma omp critical’ is illegally nested in one of the same name
元々あった次のコードに #pragma omp critical としたことによるもの.
tstring Get_Self_Original (void)
{
static bool Yet = true ;
static tstring Self_original ;
if (Yet) {
tstring self_name = ::Get_module_name() ;
Self_original = Get_OriginalFilename(self_name.c_str()) ;
if (Self_original.empty()) {
Self_original = ::Path_GetName(self_name) ;
}
Yet = false ;
}
return Self_original ;
}
次の様に並列処理可能にすることで動作する様にはなるが …
tstring Get_Self_Original (void)
{
#ifdef _OPENMP
// #pragma omp critical (_Get_Self_Original_)
#endif
{
#ifdef _OPENMP
bool Yet = true ;
tstring Self_original ;
#else
static bool Yet = true ;
static tstring Self_original ;
#endif
if (Yet) {
tstring self_name = ::Get_module_name() ;
Self_original = Get_OriginalFilename(self_name.c_str()) ;
if (Self_original.empty()) {
Self_original = ::Path_GetName(self_name) ;
}
Yet = false ;
}
return Self_original ;
}
}
以前にも同じようなことをやっていた.
https://dev.mish.work/wordpress/2010/03/31/openmp-error-1002/
2023/04/27
この関数は exe の起動直後などに一度呼ばれることを意図しているので,critical をもっと局所的に.
tstring Get_Self_Original (void)
{
static bool Yet = true ;
static tstring Self_original ;
if (Yet) {
#ifdef _OPENMP
#pragma omp critical (_Get_Self_Original_)
#endif
{
tstring self_name = ::Get_module_name() ;
Self_original = Get_OriginalFilename(self_name.c_str()) ;
if (Self_original.empty()) {
Self_original = ::Path_GetName(self_name) ;
}
Yet = false ;
}
}
return Self_original ;
}
C++ メンバ関数テンプレート
.ini に対してのアクセスは関数として用意した が,今度はレジストリ.
クラスとして実装して,基本的な動作は何とかできた.
さらに .ini と同様に,文字列としてアクセスする部分を呼出す関数をテンプレートに…
と思って書き始めたが,今まで使ってなかったのか書き方がわからない.
検索すると,次の所があり参考にさせてもらった.
メンバ関数テンプレート | Programming Place Plus C++編【言語解説】 第33章
メンバー関数テンプレート
特に通常の関数テンプレートと書き方は変わらない.
template <typename T> T get ( LPCTSTR ent,const T& def) {
tstring dst = ::To_tstring(def) ;
tstring str = this->get(ent,dst.c_str()) ;
T val ;
::string_to(str.c_str(),&val) ;
return val ;
}
template <typename T> bool set ( LPCTSTR ent,const T& val) {
tstring str = ::To_tstring(val) ;
return this->set(ent,str.c_str()) ;
}
C++ 戻り値の異なる関数 template
先日からやっている .ini やレジストリにアクセスする関数.
MFC の CWinApp::GetProfileString , CWinApp::WriteProfileString にあたる部分は目途がついた.
それで,それらを呼出す部分.前に作成したものもそうだったが,型ごとにクラスの関数を定義していた.
これらをもう少し簡単にできないかと…
set の方は,特に難しい所はない(::To_tstring は,文字列に変換する関数として用意している).
template <class T> bool INI_set (LPCTSTR sec,LPCTSTR ent,const T& val)
{
tstring ini = ::INI_get_module_ini() ;
tstring str = ::To_tstring(val) ;
return ::INI_set(ini.c_str(),sec,ent,str.c_str()) ;
}
get の場合,文字列から変数に変換する方法をどうするか?
例えば,atoi や atof ,他にも 4 つの整数の文字列を RECT に変換するなど.
検索すると template で可能みたい だが…
よくわからなかったので,簡単な方法にした.
inline RECT To_RECT (LPCTSTR str)
{
RECT rect = { 0 } ;
{
v_tstring str_ary = ::String_SplitSpace(str,_T(" ,\t\r\n")) ;
if (0 < str_ary.size()) { rect.left = ::ttoi4(str_ary[0]) ; }
if (1 < str_ary.size()) { rect.top = ::ttoi4(str_ary[1]) ; }
if (2 < str_ary.size()) { rect.right = ::ttoi4(str_ary[2]) ; }
if (3 < str_ary.size()) { rect.bottom = ::ttoi4(str_ary[3]) ; }
}
return rect ;
}
inline bool string_to (LPCTSTR str,RECT* rect_) { *rect_ = ::To_RECT (str) ; return true ; }
inline bool string_to (LPCTSTR str,POINT* point) { *point = ::To_POINT(str) ; return true ; }
inline bool string_to (LPCTSTR str,SIZE* size_) { *size_ = ::To_SIZE (str) ; return true ; }
それぞれの型に合わせた関数を呼べるようになったので template に.
template <class T> T INI_get (LPCTSTR sec,LPCTSTR ent,const T& def)
{
tstring ini = ::INI_get_module_ini() ;
tstring dst = ::To_tstring(def) ;
tstring str = ::INI_get(ini.c_str(),sec,ent,dst.c_str()) ;
T val ;
::string_to(str.c_str(),&val) ;
return val ;
}
これで,次の様な使い方ができる様になる.
{
POINT point = ::POINT_set( 10, 20) ;
SIZE size_ = :: SIZE_set( 1111, 525) ;
RECT rect_ = :: RECT_set(point,size_) ;
{
::INI_set(_T("test"),_T("rect_"),rect_) ;
::INI_set(_T("test"),_T("point"),point) ;
::INI_set(_T("test"),_T("size_"),size_) ;
std::tout << ::To_tstring(::INI_get(_T("test"),_T("rect_"),rect_)) << std::endl ;
std::tout << ::To_tstring(::INI_get(_T("test"),_T("point"),point)) << std::endl ;
std::tout << ::To_tstring(::INI_get(_T("test"),_T("size_"),size_)) << std::endl ;
}
}
C++ NonCopyable
MFC を使用しないコードに書き直していて,代入できない構造体が欲しくなった.
オリジナルのコードは 20 年以上前のもので,CRegKey が簡単には使えなかった?頃.
「C++ クラス 代入できなくする」で検索.
コピー禁止を徹底させるNoncopyableクラス
More C++ Idioms/コピー禁止ミックスイン(Non-copyable Mixin)
明示的に既定された関数および削除された関数
MFC の CObject も同様と思いソースを見ると,やはり private になっている.
C++11 以降では =delete も使える.
関数のdefault/delete宣言
wiki C++11
これらを調べていて,次の所を見つけた.
旧時代のC言語を使うのはそろそろやめよう。
2016年、C言語はどう書くべきか (前編)
2016年、C言語はどう書くべきか (後編)
幾つかは既に意識しているが,Windows に依存する部分はなかなかできてない.
RegOpenKeyEx REGSAM
レジストリアクセスのコードを書き直していて,::RegOpenKeyEx の samDesired を調べてみた.
KEY_READ (0x20019) 0010 0000 0000 0001 1001
KEY_WRITE (0x20006) 0010 0000 0000 0000 0110
KEY_EXECUTE (0x20019) 0010 0000 0000 0001 1001
KEY_QUERY_VALUE (0x0001) 0000 0000 0000 0001
KEY_SET_VALUE (0x0002) 0000 0000 0000 0010
KEY_CREATE_SUB_KEY (0x0004) 0000 0000 0000 0100
KEY_ENUMERATE_SUB_KEYS (0x0008) 0000 0000 0000 1000
KEY_NOTIFY (0x0010) 0000 0000 0001 0000
KEY_CREATE_LINK (0x0020) 0000 0000 0010 0000
KEY_WOW64_64KEY (0x0100) 0000 0001 0000 0000
KEY_WOW64_32KEY (0x0200) 0000 0010 0000 0000
KEY_ALL_ACCESS (0xF003F) 1111 0000 0000 0011 1111
読み込み時,KEY_READ の方が速いなどはあるのか?それともファイルアクセスなどと同じ?
GetMonitorInfo
2002/08 に,マルチディスプレイ対応のコードを書いている.
今回,高 DPI 対応やディスプレイ位置が変わった時などのためもう一度…
ChatGPT で.
そのままでは VC 6 ではうまくビルドできなかったので,VC 8 で.
printf を使用しているので #include <cstdio> が必要.
そのまま実行すると 1920×1080 となる.
高 DPI スケール設定を「アプリケーション」とすると,3840×2160 .
2023/04/14
以前のコードを見ると,プライマリのみの情報は ::SystemParametersInfo などを使用している.
これで求められる SPI_GETWORKAREA は,::GetMonitorInfo で戻されるものと同じ?
システム 0 61 1920 1080
アプリケーション 0 122 3840 2160
::GetSystemMetrics(SM_?VIRTUALSCREEN) は,-1920 0 .
::GetSystemMetrics(SM_C?VIRTUALSCREEN) は,上をずらしているので少し異なる.
システム 3840 1210
アプリケーション 5760 2160
https://learn.microsoft.com/ja-jp/windows/win32/gdi/the-virtual-screen
MFC タイトルバーの変更
ダイアログベースであれば C…Dlg::OnInitDialog() に次の様なコードを追加.
{
tstring str_title = ::GetWindowText(this->GetSafeHwnd()) ;
str_title+= _T(" ") + ::Get_ModuleVersion() + ::Get_BuildStrMSC() ;
SetWindowText(str_title.c_str()) ;
}
SDI や MDI の場合は,
MainFrm.h に OnUpdateFrameTitle を追加.
virtual void OnUpdateFrameTitle (BOOL bAddToTitle);
MainFrm.cpp に次の様な OnUpdateFrameTitle を追加.
// SDI
void CMainFrame::OnUpdateFrameTitle(BOOL bAddToTitle)
{
CFrameWnd::OnUpdateFrameTitle(bAddToTitle) ;
{
tstring str_title = ::GetWindowText(this->GetSafeHwnd()) ;
str_title+= _T(" ") + ::Get_ModuleVersion() + ::Get_BuildStrMSC() ;
SetWindowText(str_title.c_str()) ;
}
}
// MDI
void CMainFrame::OnUpdateFrameTitle(BOOL bAddToTitle)
{
CMDIFrameWnd::OnUpdateFrameTitle(bAddToTitle) ;
{
tstring str_title = ::GetWindowText(this->GetSafeHwnd()) ;
str_title = ::MDI_Add_VerBuildStr(str_title.c_str()) ;
SetWindowText(str_title.c_str()) ;
}
}
高 DPI CToolBar のリサイズ
CToolBar を使用した 自前のコード .
CDialogBar や CStatusBar はスケーリングしてくれるのに,CToolBar は対応しない?
以前は設定などで変更可能なコードにしていたが,DPI を求める方法に.
if (newExtendSize == CSize(0,0)) {
double dpi_s = ::GetDPI_scale(toolBar->GetSafeHwnd()) ; // ::GetDpiForWindow()/96.
if (dpi_s > 1) {
CToolBarCtrl& tbCtrl = toolBar->GetToolBarCtrl() ;
CSize orgSize = tbCtrl.GetButtonSize() ;
CSize newSize = orgSize ;
newSize.cx = int(orgSize.cx*dpi_s) ;
newSize.cy = int(orgSize.cy*dpi_s) ;
newExtendSize.cx = newSize.cx - orgSize.cx ;
newExtendSize.cy = newSize.cy - orgSize.cy ;
resizeType = 'R' ;
}
}
あとは既存コードの,ビットマップのリサイズと CToolBar::SetSizes を呼出せば良さそう.
CMFCToolBar は DPI を正しく処理しているみたいで,その部分のコード.
…\VC\…\atlmfc\src\mfc\afxtoolbar.cpp の CMFCToolBar::LoadToolBarEx .
まだ幾つか問題はあるが,今回の対応部分としてはこれで良さそう.
更にツールバー上のコンボボックスのスケーリングは次の様にした.
{
int width = 50 ;
{
double dpi_s = 1. ;
dpi_s = ::GetDPI_scale(AfxGetMainWnd()->GetSafeHwnd()) ;
dpi_s = ::GetDPI_scale( m_wndToolBar.GetSafeHwnd()) ;
dpi_s = ::GetDPI_scale( this->GetSafeHwnd()) ;
if (dpi_s > 1.) {
width = int(width*dpi_s) ;
}
}
m_wndToolBar.SetButtonInfo (19,ID_COMBO_ANGLE,TBBS_SEPARATOR,width) ;
CRect rectCombo ;
m_wndToolBar.GetItemRect (19,&rectCombo) ;
rectCombo.top = 1 ;
rectCombo.bottom = rectCombo.top + 300 ;
if (!m_ComboAngle.Create(CBS_DROPDOWN | WS_VSCROLL | WS_VISIBLE,rectCombo,&m_wndToolBar,ID_COMBO_ANGLE)) {
TRACE0("Failed to ComboBox\n");
return -1;
}
m_ComboAngle.SetFont(m_wndToolBar.GetFont()) ;
m_ComboAngle.AddString(_T(" 0.")) ;
m_ComboAngle.AddString(_T("10.")) ;
// ...
m_ComboAngle.AddString(_T("90.")) ;
}
高 DPI テスト exe
先日の exe をテストしていると…
高 DPI をテストするために,次の様なコードの exe を作成.
void CT_aesDlg::OnDropFiles(HDROP hDropInfo)
{
v_tstring drop_files = ::DropFilesTo(hDropInfo) ;
for (size_t index=0 ; index<drop_files.size() ; index++) {
tstring drop_file = drop_files[index] ;
tstring ext = ::Path_GetExtLow(drop_file) ;
if (ext != _T("exe")) { continue ; }
{
S_Exec se ;
se.SetFile(drop_file.c_str()) ;
se.Execute() ;
}
}
CDialog::OnDropFiles(hDropInfo);
}
この exe に,他の exe をドロップして起動すると,「高 DPI スケール設定」が引き継がれる.
これらの設定は,次の所に持っている?
HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers
A ~ HIGHDPIAWARE
S ~ DPIUNAWARE
E ~ GDIDPISCALING DPIUNAWARE
それぞれを実行すると表示が異なることはわかるが…
左から,「システム」,「システム(拡張)」,「アプリケーション」.
「システム」と「システム(拡張)」を区別する方法がわからない.
また「システム(拡張)」.exe で,何かの情報の取得が違っていて 表示が正しくない ものと思う.
::GetDeviceCaps の情報の表示は dc.DrawText(str,rect,DT_LEFT) としている.
この時,表示するフォントを指定していないため,「アプリケーション」では小さくなってしまう.
CFont::CreatePointFont などを呼ぶことで対応可能.
tcsncpy_s Buffer is too small
先日更新した ツール をテストしていると,フォントによりアプリケーションエラー?となってしまう.
デバッガで追いかけると,
—————————
Microsoft Visual C++ Runtime Library
—————————
Debug Assertion Failed!
Program: c:\Temp\i_Tools\TToPA\Debug.120\TToPA.exe
File: f:\dd\vctools\crt\crtw32\h\tcsncpy_s.inl
Line: 62
Expression: (L"Buffer is too small" && 0)
For information on how your program can cause an assertion
failure, see the Visual C++ documentation on asserts.
(Press Retry to debug the application)
—————————
中止(A) 再試行(R) 無視(I)
—————————
CRichEditCtrl を使用した CHARFORMATW の szFaceName の指定がうまくなかった.
::TcsNCpy(cf.szFaceName,LF_FACESIZE-1,faceName,LF_FACESIZE-1) ;
::TcsNCpy は,幾つかの環境でビルドできる様にしたもので,今回の場合は ::tcsncpy_s と同様もの.
faceName に与えたものが L"Bahnschrift Light SemiCondensed" で,コピー先のバッファが足りないため.
正しくは,
::TcsNCpy(cf.szFaceName,LF_FACESIZE-0,faceName,LF_FACESIZE-1) ;
これらの動作は,LOGFONT の時にもよく利用する.
既存のコードを検索してみると,LF_FACESIZE を正しく指定できていた.
::TcsCpy(lf.lfFaceName,LF_FACESIZE,tstring(faceName).substr(0,LF_FACESIZE-1).c_str()) ;
与えるデータは LF_FACESIZE-1 としている.