UIを簡易的に定義したいので、自作xmlとそれに基づいて領域を計算するコードを書く。
まずXMLを定義。
nameは識別子だが一意である必要はない。arrangementは子要素の配置方法。ratioは親要素に対するその要素の割合。
<?xml version="1.0" encoding="utf8" ?> <box name="main" ratio="1.0" arrangement="horizonal"> <box name="red" ratio="0.3" arrangement="vertical"> <box name="area1"></box> <box name="area2"></box> <box name="area3" ratio="0.5"></box> </box> <box name="blue" ratio="0.6" arrangement="vertical" > <box name="area1" ratio="0.2"></box> <box name="area2" ratio="0.8"></box> </box> <box name="green" ratio="0.1" arrangement="vertical" > <box name="area1" ratio="0.3"></box> <box name="area2" ratio="0.3"></box> <box name="area3" ratio="0.3"></box> <box name="area4" ratio="0.1" arrangement="horizonal"> <box name="target1" ratio="0.3"></box> <box name="target2" ></box> </box> </box> </box>
#include <windows.h> #include <iostream> #include <string> #include <vector> #include <pugixml.hpp> #include <functional> // 子要素の配置方法 enum class Arrangement { Horizonal, Vertical, }; // 各情報を元に計算される実際のサイズ。sx,sy, ex,eyは矩形の最小、最大を表すピクセル座標 struct Rect { int sx, sy; int ex, ey; }; struct BOX { Arrangement type; // 子要素の配置方法 Rect rect; // この領域の矩形 std::string name; // この領域の名前 double ratio; // この領域が親要素のどれだけの割合を占めるか std::vector<BOX> children; // 子要素 };
// xmlファイルの内容を解析してノード作成 void parseNode(const pugi::xml_node& xmlNode, BOX& node) { // BOXの名前を取得 node.name = xmlNode.attribute("name").as_string(""); // BOXの親要素に対する割合を取得。指定がない場合はNaNにしておいて、自動計算する node.ratio = xmlNode.attribute("ratio").as_double( std::numeric_limits<double>::quiet_NaN() ); // 配置方法を取得。デフォルトをverticalとしておく。 std::string arrangement = xmlNode.attribute("arrangement").as_string("vertical"); node.type = (arrangement == "horizonal") ? Arrangement::Horizonal : Arrangement::Vertical; // 子要素を再帰的に解析 for (pugi::xml_node child = xmlNode.first_child(); child; child = child.next_sibling()) { BOX childNode; parseNode(child, childNode); node.children.push_back(childNode); } }
// xmlファイルからレイアウト情報を読み込む BOX LoadLayout(const char* xmlfilename) { pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file(xmlfilename); // ファイルが読み込めなかったら例外を投げる if (!result) { throw std::runtime_error("xmlファイルが読み込めませんでした"); } BOX rootNode; pugi::xml_node root = doc.child("box"); parseNode(root, rootNode); return rootNode; }
// 読み込んだxmlファイルから領域を計算 void UpdateLayout(BOX& node, const Rect& parent) { node.rect = parent; int totalWidth = parent.ex - parent.sx; int totalHeight = parent.ey - parent.sy; ///////////////////////////////// // 親要素を占める割合の計算 // ratioを省略可能としている // ratioが省略されたときは、その領域内の残りのratioを省略されたbox数で割る double remainingRatio = 1.0; // 1.0 = 100% parent全体の割合を1.0とする int undefinedRatioCount = 0; // ratioが定義されていないboxの個数 for (auto& child : node.children) { if (std::isnan(child.ratio) == true) { // ratioが定義されていないノードの個数を知りたいのでここで確認する undefinedRatioCount++; } else { // ratioが定義されているノードの割合を引き、 // ratio未定義のboxが使用可能な割合を計算する remainingRatio -= child.ratio; } } // ratioが定義されていないノードがある場合は、使用可能な割合を等分する if (undefinedRatioCount > 0) { // ratioが定義されていないノードは、使用可能な量を等分する double defaultRatio = remainingRatio / undefinedRatioCount; // ratio未定義のノードにratioを与える for (auto& child : node.children) { if (std::isnan(child.ratio) == true) { child.ratio = defaultRatio; } } } // 子要素の矩形を計算する double accumRatio = 0.0; for (auto& child : node.children) { if (node.type == Arrangement::Horizonal) { child.rect.sx = parent.sx + totalWidth * accumRatio; child.rect.ex = child.rect.sx + totalWidth * child.ratio; child.rect.sy = parent.sy; child.rect.ey = parent.ey; } else { child.rect.sy = parent.sy + totalHeight * accumRatio; child.rect.ey = child.rect.sy + totalHeight * child.ratio; child.rect.sx = parent.sx; child.rect.ex = parent.ex; } accumRatio += child.ratio; } // 子要素の子要素の矩形を計算する for (auto& child : node.children) { UpdateLayout(child, child.rect); } }
// 領域を描画 void drawRects(HDC hdc, const BOX& node) { RECT rect = { node.rect.sx, node.rect.sy, node.rect.ex, node.rect.ey }; // ランダムな色を作成 int r = rand() % 256; int g = rand() % 256; int b = rand() % 256; HPEN hPen = CreatePen(PS_SOLID, 1, RGB(r, g, b)); // 1ピクセル幅のペンを作成 HGDIOBJ oldPen = SelectObject(hdc, hPen); // ペンを選択 // 矩形を描画 Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom); SelectObject(hdc, oldPen); // 元のペンに戻す DeleteObject(hPen); // ペンを削除 for (const BOX& child : node.children) { drawRects(hdc, child); } } BOX rootArea; LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { HDC hdc; PAINTSTRUCT ps; RECT rect; switch (msg) { case WM_DESTROY: PostQuitMessage(0); return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); GetClientRect(hwnd, &rect); FillRect(hdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH)); drawRects(hdc, rootArea);// テスト用 矩形描画 EndPaint(hwnd, &ps); return 0; } return DefWindowProc(hwnd, msg, wp, lp); } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) { HWND hwnd; WNDCLASS winc; MSG msg; 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-WINDOW"); if (!RegisterClass(&winc)) return 0; Rect rootRect{ 0, 0, 800, 300 }; rootArea = LoadLayout(R"(layout2.xml)");// レイアウト読み込み UpdateLayout(rootArea, rootRect);// 矩形を計算 hwnd = CreateWindow( TEXT("SZL-WINDOW"), TEXT("test window"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL ); if (hwnd == NULL) return 0; while (GetMessage(&msg, NULL, 0, 0)) DispatchMessage(&msg); return msg.wParam; }
Rustでスレッドを作成するにはspawnを使用する。よくあるように、一度作成したスレッドはハンドルを取得し、join()で待機する。スレッドとして走らせた関数の戻り値はjoin()の戻り値に入っているが、これはエラーなども扱うResultとして入っているのでunwrapして戻り値を取り出す。
spawnには原則として引数なしの関数しか渡せない。
// スレッドとして実行する関数 // 引数をとることはできないが、任意の型の戻り値を返すことはできる fn my_sum()->i32 { let mut sum = 0; for i in 0..50 { println!("{}", i); sum += i; } return sum; }
fn main(){ // 関数が引数を一つも取らない場合、spawnに関数名を渡すだけでスレッドが作成される let thread_handle1 = std::thread::spawn(my_sum); let thread_handle2 = std::thread::spawn(my_sum); // my_sumは引数をとることはできないが、任意の型の戻り値を返すことはできる let result1:i32 = thread_handle1.join().unwrap(); let result2:i32 = thread_handle2.join().unwrap(); println!("results are {} , {} ", result1,result2); }
引数を与える場合、直接spawnに与えることができないので、クロージャ(ラムダ関数のようなもの)を与える。Rustのクロージャは頭に || をつける。
以下の例では、「myprintに'a' 'b' 50 というマジックナンバーを与えて呼び出す」という処理を行う引数なしの関数をspawnに渡している。
// 値を表示する関数 fn myprint(mark : char,count:i32) { for i in 0..count{ println!("{} -> {}",mark,i); } }
fn main() { // spawnにはクロージャ(≒ラムダ関数)を渡す。 || はクロージャを表す。 // 即ち、「myprintを引数付きで渡す」という処理を行う、引数なしの関数を渡している。 let thread_handle1 = std::thread::spawn(|| myprint( 'a',50)); let thread_handle2 = std::thread::spawn(|| myprint( 'b',50)); // 終了まで待機 thread_handle1.join().unwrap(); thread_handle2.join().unwrap(); }
メインスレッドのデータをスレッドの中で処理したい場合、メインスレッド側でデータが消えたりするとまずいので、そのまま参照を渡すことができない。
そこでデータを参照カウンタ付きのスマートポインタArcで渡すことで、メインスレッドを含め各スレッドが処理している最中にデータが消える危険性を排除する。
// vecの不変参照を受け取り、表示する関数 fn myprint(input: &Vec<i32>) { for i in input.iter(){ println!("i=={}",i); } }
// テスト用のVecのデータを作成する関数 fn create_data()->Vec<i32>{ let mut data = vec![]; for k in 0..100{ data.push(k); } return data; }
fn main() { // スレッドに渡すためにはArcでラップする必要がある let data1 = std::sync::Arc::new(create_data()); let data2 = std::sync::Arc::new(create_data()); // spawnのmoveキーワードによりスレッドに変数の所有権が移ってしまうので // clone()で参照のコピーを作成する。Arcのcloneは参照カウンタが増えるだけなので負荷はほぼない let d1_clone = data1.clone(); let d2_clone = data2.clone(); let thread_handle1 = std::thread::spawn(move || myprint( &d1_clone)); // d1_cloneの所有権が移動 let thread_handle2 = std::thread::spawn(move || myprint( &d2_clone)); // d2_cloneの所有権が移動 // 終了まで待機 thread_handle1.join().unwrap(); thread_handle2.join().unwrap(); // データの所有権は移行していないので、ここで表示できる println!("data: {:?}", data1); println!("data: {:?}", data2); }
スレッド内で変数を編集したい場合、さらにMutexでラップする。
// 配列を受け取り、二倍にする関数 fn double(input: &std::sync::Mutex<Vec<i32>>) { let mut vecdata = input.lock().unwrap(); for i in vecdata.iter_mut() { *i *= 2; } }
fn main() { let data1 = std::sync::Arc::new(std::sync::Mutex::new(vec![1,2,3,4,5])); let data2 = std::sync::Arc::new(std::sync::Mutex::new(vec![10,20,30,40,50])); let mut d1_clone = data1.clone(); let mut d2_clone = data2.clone(); let thread_handle1 = std::thread::spawn(move || double(&d1_clone)); let thread_handle2 = std::thread::spawn(move || double(&d2_clone)); thread_handle1.join().unwrap(); thread_handle2.join().unwrap(); println!("data1: {:?}", &data1); println!("data2: {:?}", &data2); }
C言語のタイマーと違い、精度の高いタイマーが標準で使用できる。ただし精度は環境依存なので、比較的新しい環境だとマイクロ秒,ナノ秒単位で取得できるが、そうでない場合も考えられる。
fn main() { // 時間計測開始 let mytimer = std::time::Instant::now(); use_rand(); // 時間計測終了 let elapsed = mytimer.elapsed(); // 経過時間を表示 println!("{:?}",elapsed); }
fn use_rand(){ use rand::Rng; // 乱数を使うためにrand::Rngを使用 let mut rng = rand::thread_rng(); let min:i32 = 0; let max:i32 = 20; (0..5000).for_each(|_|{ let randint:i32 = rng.gen_range(min..max); // 最大・最小を指定して乱数を生成 println!("{}",randint); }); }
CのclockなどはCPU時間なので、Sleep時間がタイマーに反映されないが、Rustのstd::timeはちゃんとSleep時間も含んだ結果が得られる。
fn main() { // 時間計測開始 let mytimer = std::time::Instant::now(); // 5秒間スリープ std::thread::sleep(std::time::Duration::from_secs(5)); // 時間計測終了 let elapsed = mytimer.elapsed(); // 経過時間を表示 println!("{:?}",elapsed); }
結果
表示だけならDuration型に対して{:?}すればよいが、値を取り出す場合は以下のようにする。
// 経過時間を表示 println!("second i32 {}",elapsed.as_secs()); println!("second f32 {}",elapsed.as_secs_f32()); println!("micro second {}",elapsed.as_micros()); println!("nano second {}",elapsed.as_nanos());
結果
second i32 5 second f32 5.000559 micro second 5000558 nano second 5000558900
Rustのスライスを使ってみたくて書いた。
クイックソートを書いたのはたぶん15年ぶりくらい。正直てこずった。
fn my_qsort_slice(data: &mut [i32]){ if data.len() <= 1{ return; } // ピボットを求める let pivot =data[data.len()/2]; let idmin:i32 = 0; let idmax:i32 = (data.len()-1) as i32; ////////////////////////////////////////////////// // ピボットより小さいものを左に、大きいものを右に分ける let mut l =idmin; // 左側の探索位置。pivotより大きい値を探す let mut r =idmax; // 右側の探索位置。pivot未満の値を探す loop{ // 左側の交換すべき値を探す while data[l as usize] < pivot { l += 1; } // 右側の交換すべき値を探す while data[r as usize] > pivot { r -= 1; } if l >= r{ break; } // 交換 let tmp = data[l as usize]; data[l as usize] = data[r as usize]; data[r as usize] = tmp;
l += 1;
r -= 1; } { // 左側のスライスを作成 let mut left = &mut data[0..l as usize]; // 左側のスライスを再帰的にソート my_qsort_slice(&mut left); } { // 右側のスライスを作成 let mut right = &mut data[ (r+1) as usize ..]; // 右側のスライスを再帰的にソート my_qsort_slice(&mut right); } }
fn my_qsort(data : &mut Vec<i32>){ my_qsort_slice(&mut data[..]); }
fn main() { let mut data:Vec<i32> = Vec::new(); data = [8,1,9,2,4,6].to_vec(); my_qsort(&mut data); for i in 0..data.len(){ println!("{}",data[i]); } println!("Hello, world!"); }
Rustの乱数の使い方。
乱数を使用するためにはrandクレートを使用する。
fn main() { use rand::Rng; // 乱数を使うためにrand::Rngを使用 let mut rng = rand::thread_rng(); let min:i32 = 0; let max:i32 = 20; // 50回ループ (0..50).for_each(|_|{ let randint:i32 = rng.gen_range(min..max); // 最大・最小を指定して乱数を生成 println!("{}",randint); }); }
rand_distrクレートを使用すると分布を変えられる。rand_distr::Normalは正規分布。
[dependencies]
# 乱数を使用 https://crates.io/crates/rand
rand = "0.8.5"
# 乱数の分布を指定 https://crates.io/crates/rand_distr
rand_distr = "0.4.3"
fn main() { use rand::prelude::{Distribution, thread_rng}; use rand_distr::Normal; let mut rng = rand::thread_rng(); let mean:f32 = 10.0; let stddev:f32 = 8.0; // 平均値と標準偏差を用いた乱数生成器を作成 let normal = Normal::new( mean, // 平均 stddev // 標準偏差 ).unwrap(); // 50回ループ (0..100).for_each(|_|{ let randint:i32 = normal.sample(&mut rng) as i32; // 乱数を生成 println!("{}",randint); }); }
これだけではさすがにつまらないので、win32apiを使ってSetPixelで点を散布してみる。
以前作ったメモリデバイスコンテキストのコードで乱数のプロットを表示する。
#[path="./win32dib.rs"] mod win32dib; use win32dib::Win32DIBCanvas; use windows::{ core::*, Win32::Foundation::*, Win32::Graphics::Gdi::*, Win32::UI::WindowsAndMessaging::*, }; use once_cell::sync::Lazy; use std::sync::Mutex; // ■ Mutex ... std::sync::Mutex // ■ Lazy ... once_cell::sync::Lazy static mydib:Lazy<Mutex<Win32DIBCanvas>> = Lazy::new(|| Mutex::new( Win32DIBCanvas::new() ) ); /////////////////////////////////////////////// /// MemDCの背景をクリア ////////////////////// fn clear_mydib_background(rmdib : &mut Win32DIBCanvas){ // super::super::Foundation::COLORREF // let refmydib = &mut mydib.lock().unwrap(); let width:i32 = rmdib.bitmapInfo.bmiHeader.biWidth; let height:i32 = -rmdib.bitmapInfo.bmiHeader.biHeight; let rect:RECT = RECT{ left:0, top:0, right:width, bottom:height, }; unsafe { let whitebrush = CreateSolidBrush(COLORREF(0x00FFFFFF)); FillRect(rmdib.hMemDC,&rect,whitebrush); DeleteObject(whitebrush); } }
/////////////////////////////////////////////// /// count個の乱数を生成 fn get_random(count:i32, min:i32,max:i32) -> Vec<i32>{ use rand::Rng; // 乱数を使うためにrand::Rngを使用 let mut rng = rand::thread_rng(); let mut rlist:Vec<i32> = Vec::new(); for i in 0..count{ let randint:i32 = rng.gen_range(min..max); rlist.push(randint); } return rlist; }
/////////////////////////////////////////////// // ■ HWND ... windows::Win32::Foundation::HWND // ■ WPARAM ... windows::Win32::Foundation::WPARAM // ■ LPARAM ... windows::Win32::Foundation::LPARAM // ■ LRESULT ... windows::Win32::Foundation::LRESULT extern "system" fn wndproc(window: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { unsafe { // ■ RECT ... windows::Win32::Foundation::RECT let rectnullptr: Option<*const RECT> = None; match message as u32 { WM_CREATE => { // mydib.lock().unwrap() への可変な参照をrefmydibに束縛 let refmydib = &mut mydib.lock().unwrap(); refmydib.create(640,480); clear_mydib_background(refmydib);
/////////////////////////////////////// /// 乱数で座標を作成 ////////////////// let count = 50000; let xlist:Vec<i32> = get_random(count,0,640); let ylist:Vec<i32> = get_random(count,0,480); // 点をプロット for pindex in 0..count as usize{ SetPixel( refmydib.hMemDC, xlist[pindex], ylist[pindex], COLORREF(0x00000000)); } ///////////////////////////////////////
// ■ LRESULT ... windows::Win32::Foundation::LRESULT LRESULT(0) } WM_PAINT => { // ■ PAINTSTRUCT ... windows::Win32::Graphics::Gdi::PAINTSTRUCT // ■ BeginPaint ... windows::Win32::Graphics::Gdi::BeginPaint let mut ps = PAINTSTRUCT::default(); let hdc = BeginPaint(window, &mut ps); let refmydib = &mut mydib.lock().unwrap(); ////////////////////////////////////////////////////////// // 背景をクリア /////////////////////////////////////////// let mut clientrect:RECT = RECT::default(); GetClientRect(window, &mut clientrect); let whitebrush = CreateSolidBrush(COLORREF(0x00FFFFFF)); FillRect(hdc,&clientrect,whitebrush); DeleteObject(whitebrush); ////////////////////////////////////////////////////////// // ■ BitBlt ... windows::Win32::Graphics::Gdi // ■ SRCCOPY ... windows::Win32::Graphics::Gdi::SRCCOPY BitBlt( hdc, 0, 0, 640, 480, refmydib.hMemDC, 0, 0, SRCCOPY ); // ■ EndPaint ... windows::Win32::Graphics::Gdi::EndPaint EndPaint(window, &ps); // ■ LRESULT ... windows::Win32::Foundation::LRESULT LRESULT(0) } WM_DESTROY => { println!("WM_DESTROY"); // ■ windows::Win32::UI::WindowsAndMessaging::PostQuitMessage PostQuitMessage(0); // ■ LRESULT ... windows::Win32::Foundation::LRESULT LRESULT(0) } // ■ windows::Win32::UI::WindowsAndMessaging::DefWindowProcA _ => DefWindowProcA(window, message, wparam, lparam), } } } // ■ HINSTANCE ... windows::Win32::Foundation::HINSTANCE /// 自作 WinMain pub fn WinMain( hInstance : HINSTANCE, hPrevInstance : HINSTANCE, lpCmdLine:Vec<String>, nCmdShow:i32 )->i32{ unsafe{ // ■ PCSTR ... windows::core::PCSTR // ■ windows::Win32::UI::WindowsAndMessaging::LoadCursorW // ■ windows::Win32::UI::WindowsAndMessaging::IDC_ARROW // ■ windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW // ■ windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW let _hCursor = LoadCursorW(None,IDC_ARROW).unwrap(); let _hInstance = hInstance; let _lpszClassName = PCSTR::from_raw("MY_NEW_WINDOW\0".as_ptr()); let _style = CS_HREDRAW | CS_VREDRAW; // https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/UI/WindowsAndMessaging/struct.WNDCLASSA.html // ■ WNDCLASSA ... windows::Win32::UI::WindowsAndMessaging::WNDCLASSA let wc=WNDCLASSA{ hCursor :_hCursor, hInstance: _hInstance, lpszClassName: _lpszClassName, style: _style, lpfnWndProc: Some(wndproc), ..Default::default() }; // ■ windows::Win32::UI::WindowsAndMessaging::RegisterClassA let atom = RegisterClassA(&wc); debug_assert!(atom != 0); let nullptr: Option<*const ::core::ffi::c_void> = None; // ■ windows::Win32::UI::WindowsAndMessaging::CreateWindowExA // ■ PCSTR ... windows::core::PCSTR // ■ windows::Win32::UI::WindowsAndMessaging::WS_OVERLAPPEDWINDOW // ■ windows::Win32::UI::WindowsAndMessaging::WS_VISIBLE // ■ windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT CreateWindowExA( Default::default(), _lpszClassName, PCSTR::from_raw("This is a sample window\0".as_ptr()), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 700, 600, None, None, _hInstance, nullptr, ); // ■ windows::Win32::UI::WindowsAndMessaging::MSG let mut message = MSG::default(); // ■ windows::Win32::UI::WindowsAndMessaging::GetMessageA // ■ windows::Win32::UI::WindowsAndMessaging::DispatchMessageA // ■ HWND ... windows::Win32::Foundation::HWND while GetMessageA(&mut message, HWND(0), 0, 0).into() { DispatchMessageA(&mut message); } } return 0; }
先のプログラムのget_randomを以下のように変える(minは使わないことにした)と、乱数の分布が変わったのがわかる。
/////////////////////////////////////////////// /// 正規分布の乱数を生成する /////////////////// fn get_random(count:i32, max:i32) -> Vec<i32>{ use rand::prelude::{Distribution, thread_rng}; use rand_distr::Normal; let mut rng = rand::thread_rng(); // 平均値と標準偏差を用いた乱数生成器を作成 let normal = Normal::new( (max as f32) / 2.0, // 平均 (max as f32) / 8.0 // 標準偏差 ).unwrap(); let mut rlist:Vec<i32> = Vec::new(); for i in 0..count{ let randint:i32 = normal.sample(&mut rng) as i32;// 平均値と標準偏差で乱数生成 rlist.push(randint); } return rlist; }
シードを指定する場合、以下のように[u8;32]の配列をシードとして与える。シードだけで32byteも与えなければならない。なお[0;32]の配列指定で要素の型がu8に定まるのは、文脈的にseedがfrom_seedで与えられていることからデータ型を推論しているかららしい。
fn main() { use rand::Rng; // 乱数を使うためにrand::Rngを使用 use rand::SeedableRng; // シードの指定に必要 use rand::rngs::StdRng; // シードの指定に必要 let seed = [0; 32];// 全要素0、要素数32,u8型の配列を作成 let mut rng:StdRng = SeedableRng::from_seed(seed); // シードを固定した乱数生成器を初期化 //let mut rng = rand::thread_rng(); // シード未指定の場合 let min:i32 = 0; let max:i32 = 20; // 50回ループ (0..50).for_each(|_|{ let randint:i32 = rng.gen_range(min..max);// 最大・最小を指定して乱数を生成 println!("{}",randint); }); }
しばらく使っていなかったらGeometry nodeでオブジェクト周辺に点群を生成する機能の使い方が随分と変わっていたので更新しておきたい。初期のころはPoint Distributeとか呼ばれていたもののこと。
なお各ノードの追加はすべて[Shift+A]から検索している。
過去記事:

変数形式で入力しなければいけなかったところがノード接続になって好感が持てる。
検索して手間取ったのが生成するインスタンスを指定する方法が、Point InstanceからObject Info->Geometry to Instance とより抽象化されたところ。
(誤解を恐れずに言うなら)conceptはテンプレートで渡す際のclass TのTに条件を付けるための機能。
以下のように、
という形で定義する。
型Tで与えられた変数valの使い方を定義すると考えると読みやすくなる。
#include <iostream>
// T型の変数valは、print()と++を持たなければならない
template <class T> concept CallableFunction = requires (T val) { val.print(); // print()関数があることを要求 val.operator++(); // ++演算子があることを要求 };
// CallableFunctionの制約がついている所で使用できるクラス class MyValidClass { int val; public: MyValidClass() { val = 5; } void print() {// print()関数を定義 std::cout << "val == " << val << std::endl; } MyValidClass& operator++() {// ++演算子を要求 ++val; return *this; } };
// CallableFunctionの制約がある場合は指定できない。 // operator++() がない。 class MyInvalid { int val; public: MyInvalid() { val = 5; } void print() { std::cout << "val == " << val << std::endl; } };
// print()関数と++演算子を持つクラスを引数に取る関数 template <CallableFunction Type> void func(Type val) { val.print(); }
int main() { MyValidClass my; func(my); }
{ } 内に式を書くと、その式を書くことができるような型だけを要求することができる。
#include <iostream> #include <algorithm> #include <vector>
template <class T> concept Sortable = requires (T& val) { // { } 内の式が有効であることを要求する書き方ができる。 // 例えば、以下は T 型の val はソート可能であることを要求する { std::sort(val.begin(), val.end()) }; };
// print()関数と++演算子を持つクラスを引数に取る関数 template <Sortable Type> void func(Type& val) { std::sort(val.begin(), val.end()); }
int main() { std::vector<int> my{ 5,3,8 }; func(my); for (auto& i : my) { std::cout << i << std::endl; } }
{ 式 } で、式が有効であることを要求できるが、式の結果のデータ型を要求するには
とする。
この仕組みを応用すれば、関数の戻り値を要求できる。
#include <iostream> #include <algorithm> #include <vector>
template <class T> concept Sizable = requires (T & val) { { val.size() // size()関数を持つ }->std::same_as<size_t>; // size()関数の戻り値はsize_t型 // std::same_asはconcept。式の戻り値の型を指定する。 };
class MyArray { std::vector<int>vec; public: void push_back(int i) { vec.push_back(i); } int size() { return vec.size(); } };
// この関数は、戻り値がsize_t型のsize()関数を持つ型しか受け付けない template <Sizable Type> void func(Type& val) { std::cout<<val.size()<<std::endl; }
int main() { std::vector<int> my; // MyArray my; my.push_back(3); my.push_back(1); my.push_back(8); func(my); }
wxWidgetsを使用した場合の問題の一つが、配布時に同梱するDLLの数が増えることで、これを解決するためにwxWidgetsをStatic Link Libraryとしてビルドしてexeとくっつけてしまうことで、DLLの同梱を不要にする。
まずソースコードをダウンロードするが、zipでは依存関係が多くて面倒なので、git clone --recursiveで依存関係も含め一括でダウンロードする。
CMakeでは、wxBUILD_SHAREDを検索し、このチェックを外す
ALL_BUILDをビルドし、INSTALLする。
基本的な使い方はShared Libraryの時と同じだが、何点か注意点がある。
VC++でwxWidgetsを試す(1)
・WXUSINGDLL を指定しない
プリプロセッサにWXUSINGDLLを指定しない。指定してしまうとLNK2001が大量発生する
・windowsのlibファイルのリンクを追加
comctl32.lib , rpcrt4.lib をリンクする。
// プリプロセッサに以下を追加 // __WXMSW__ // static link library の場合、以下は指定しない // WXUSINGDLL // サブシステムをWindowsに設定(WinMainで呼び出すので) // Windows (/SUBSYSTEM:WINDOWS) #ifndef WX_PRECOMP #include <wx/wx.h> #endif #include <wx/gdicmn.h> // wxPointに必要 #include <wx/frame.h> // wxFrameに必要 // Win32 API #pragma comment(lib, "comctl32.lib") #pragma comment(lib, "rpcrt4.lib") // wxWidgets #pragma comment(lib,"wxmsw33u_core.lib") #pragma comment(lib,"wxbase33u.lib") #pragma comment(lib,"wxbase33u_net.lib") #pragma comment(lib,"wxbase33u_xml.lib") #pragma comment(lib,"wxexpat.lib") #pragma comment(lib,"wxjpeg.lib") #pragma comment(lib,"wxlexilla.lib") #pragma comment(lib,"wxmsw33u_adv.lib") #pragma comment(lib,"wxmsw33u_aui.lib") #pragma comment(lib,"wxmsw33u_gl.lib") #pragma comment(lib,"wxmsw33u_html.lib") #pragma comment(lib,"wxmsw33u_media.lib") #pragma comment(lib,"wxmsw33u_propgrid.lib") #pragma comment(lib,"wxmsw33u_qa.lib") #pragma comment(lib,"wxmsw33u_ribbon.lib") #pragma comment(lib,"wxmsw33u_richtext.lib") #pragma comment(lib,"wxmsw33u_stc.lib") #pragma comment(lib,"wxmsw33u_webview.lib") #pragma comment(lib,"wxmsw33u_xrc.lib") #pragma comment(lib,"wxpng.lib") #pragma comment(lib,"wxregexu.lib") #pragma comment(lib,"wxscintilla.lib") #pragma comment(lib,"wxtiff.lib") #pragma comment(lib,"wxzlib.lib") ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // ウィンドウ作成 class MyFrame : public wxFrame { public: MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame(NULL, wxID_ANY, title, pos, size) { } 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);
なんでもタスクトレイという言い方は正しくなく、通知領域、Notification Areaが正しいらしい。知ったことではない。
タスクトレイにアイコンを表示する方法を以前調べた。
タスクトレイにアイコンを表示する
クリックしたときにメニューを表示する場合、GetCursorPosでマウスカーソルの位置を取得して、TrackPopupMenuでメニューを表示する。
#pragma warning(disable:4996) #include <windows.h> #include <shellapi.h> static const int TasktrayIconID = 100; static const int WM_MY_NOTIFYICON = WM_USER + TasktrayIconID; const int ID_MENU_MSG = 100; const int ID_MENU_EXIT = 101; HMENU myCreateMenu(); void intoTasktray(HWND hwnd, const int IconID); void fromTasktray(HWND hwnd, const int IconID); void menuPopup(HWND hwnd, HMENU menu); ///////////////////////////////// ///////////////////////////////// ///////////////////////////////// /////////////////////////////////
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { static HMENU hMenu = nullptr; switch (msg) { case WM_CREATE: hMenu = myCreateMenu();//メニュー作成 break; case WM_LBUTTONDOWN://ウィンドウをクリックしたらアイコン表示開始 { intoTasktray(hwnd, TasktrayIconID); break; } case WM_MY_NOTIFYICON: if (lp == WM_LBUTTONDOWN && wp == TasktrayIconID) { // アイコンを左クリックしたらウィンドウを表示 fromTasktray(hwnd, TasktrayIconID); } else if (lp == WM_RBUTTONUP) { // アイコンを右クリックしたらポップアップメニューを表示 menuPopup(hwnd, hMenu); } break; case WM_COMMAND: switch (wp) { case ID_MENU_MSG: // "Open"がクリックされた場合 MessageBoxA(hwnd, "MessageBox", "Info", MB_OK); break; case ID_MENU_EXIT: // "Exit"がクリックされた場合 DestroyWindow(hwnd); break; } break; case WM_DESTROY: DestroyWindow(hwnd); PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, msg, wp, lp); }
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) { 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-WINDOW"); if (!RegisterClass(&winc)) return -1; hwnd = CreateWindow( TEXT("SZL-WINDOW"), TEXT("test"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 300, 300, NULL, NULL, hInstance, NULL ); if (hwnd == NULL) return -1; while (GetMessage(&msg, NULL, 0, 0)) DispatchMessage(&msg); return msg.wParam; }
// メニューの作成 HMENU myCreateMenu() { HMENU menu = CreatePopupMenu(); AppendMenuA(menu, MF_STRING, ID_MENU_MSG, "Open"); AppendMenuA(menu, MF_STRING, ID_MENU_EXIT, "Exit"); return menu; }
// ウィンドウを消し、アイコンをタスクトレイへ格納 void intoTasktray(HWND hwnd, const int IconID) { NOTIFYICONDATA nid; nid.cbSize = sizeof(nid); nid.hWnd = hwnd; nid.uID = IconID;//複数のアイコンを表示したときの識別ID。なんでもいい。 nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; nid.uCallbackMessage = WM_MY_NOTIFYICON; //WM_USER以降の定数。 nid.hIcon = (HICON)LoadIcon(NULL, IDI_APPLICATION); wcscpy(nid.szTip, L"タスクトレイで表示する文字列"); int ret = (int)Shell_NotifyIcon(NIM_ADD, &nid); ShowWindow(hwnd, SW_HIDE); }
// タスクトレイからアイコンを削除し、ウィンドウを表示 void fromTasktray(HWND hwnd, const int IconID) { ShowWindow(hwnd, SW_SHOW); //タスクトレイアイコンを削除 NOTIFYICONDATA nid; nid.cbSize = sizeof(nid); nid.hWnd = hwnd; nid.uID = IconID; nid.uFlags = 0; Shell_NotifyIcon(NIM_DELETE, &nid); }
// ポップアップメニューを表示 void menuPopup(HWND hwnd, HMENU menu) { POINT pt; GetCursorPos(&pt); SetForegroundWindow(hwnd); TrackPopupMenu(menu, TPM_RIGHTALIGN, pt.x, pt.y, 0, hwnd, NULL); }
CreateDIBSectionを使ってDIBを作り、CreateCompatibleDCを使って描画してみる。
各関数・データ型・定数がwindows-rsのどこにあるかさえわかれば、あとはそこまで難しくはない。
一番厄介なのはwinmain.rs内でmydibのインスタンスであるグローバル変数で、これはstatic + Lazy + Mutexで実現しないといけない。
use windows::Win32::Graphics::Gdi::*; use windows::core::*; use windows::Win32::Foundation::*; // ■ BITMAPINFO ... windows::Win32::Graphics::Gdi::BITMAPINFO // ■ BITMAPINFOHEADER ... windows::Win32::Graphics::Gdi::BITMAPINFOHEADER // ■ BI_RGB ... windows::Win32::Graphics::Gdi::BI_RGB // ■ CreatedHDC ... windows::Win32::Graphics::Gdi::CreatedHDC // ■ CreateDCA ... windows::Win32::Graphics::Gdi::CreateDCA // ■ CreateCompatibleDC ... windows::Win32::Graphics::Gdi::CreateCompatibleDC // ■ CreateDIBSection ... windows::Win32::Graphics::Gdi::CreateDIBSection // ■ DIB_RGB_COLORS ... windows::Win32::Graphics::Gdi::DIB_RGB_COLORS // ■ DeleteDC ... windows::Win32::Graphics::Gdi::DeleteDC // ■ HBITMAP ... windows::Win32::Graphics::Gdi::HBITMAP // ■ HBITMAP ... windows::Win32::Graphics::Gdi::HBITMAP // ■ SelectObject ... windows::Win32::Graphics::Gdi::SelectObject // ■ HANDLE ... windows::Win32::Foundation::HANDLE // ■ PCSTR ... windows::core::PCSTR pub struct Win32DIBCanvas{ pub bitmap :HBITMAP, pub bitmapInfo :BITMAPINFO, pub ppixel :*mut std::ffi::c_void, pub hMemDC : CreatedHDC, } // ppixel:*mut std::ffi::c_void, があるので、Send,Syncを実装しなければmyDIBをLazy<Mutex<myDIB>>に入れられない unsafe impl Send for Win32DIBCanvas {} unsafe impl Sync for Win32DIBCanvas {} impl Win32DIBCanvas{ pub fn new()->Win32DIBCanvas{ Win32DIBCanvas{ bitmap :HBITMAP(0), bitmapInfo:Default::default(), ppixel :std::ptr::null_mut(), hMemDC :CreatedHDC(0), } } pub fn create(&mut self,width:i32,height:i32){ unsafe{ let mut hdca = CreateDCA( PCSTR::from_raw("DISPLAY\0".as_ptr()), None, None, None ); self.bitmapInfo.bmiHeader.biSize = std::mem::size_of::<BITMAPINFOHEADER>() as u32; self.bitmapInfo.bmiHeader.biWidth = width; self.bitmapInfo.bmiHeader.biHeight = -height; self.bitmapInfo.bmiHeader.biPlanes = 1; self.bitmapInfo.bmiHeader.biBitCount = 3*8; self.bitmapInfo.bmiHeader.biCompression = BI_RGB; let hBitmap = CreateDIBSection( hdca, &self.bitmapInfo, DIB_RGB_COLORS, &mut self.ppixel, HANDLE(0), 0 ); self.hMemDC = CreateCompatibleDC(hdca); if let Ok(hbmp) = hBitmap{ SelectObject(self.hMemDC,hbmp); } DeleteDC(hdca); } } }
main関数。CではWinMain開始だが、Rustではwindowsプログラムもmainから始める。
気分的にWinMainが欲しいので、main関数からWinMainを呼ぶようにしている。
mod winmain; use winmain::WinMain; use windows::Win32::Foundation::*; use windows::Win32::System::LibraryLoader::*; use windows::Win32::System::Threading::*; // ■ HINSTANCE ... windows::Win32::Foundation::HINSTANCE // ■ GetModuleHandleA ... windows::Win32::System::LibraryLoader::GetModuleHandleA // ■ STARTUPINFOW ... windows::Win32::System::Threading::STARTUPINFOW // ■ GetStartupInfoW ... windows::Win32::System::Threading::GetStartupInfoW // Rustのwindows-rsからウィンドウズアプリケーションをビルドする時には、 // WinWain ではなくmainをエントリポイントとする。 fn main()-> windows::core::Result<()> { let hInstance:HINSTANCE; unsafe{ // https://github.com/microsoft/windows-samples-rs/blob/master/create_window/src/main.rs hInstance = GetModuleHandleA(None).unwrap(); } // https://doc.rust-jp.rs/book-ja/ch12-01-accepting-command-line-arguments.html let args: Vec<String> = std::env::args().collect(); // https://stackoverflow.com/questions/68322072/how-to-get-args-from-winmain-or-wwinmain-in-rust let mut si = STARTUPINFOW { cb: std::mem::size_of::<STARTUPINFOW>() as u32, ..Default::default() }; unsafe { GetStartupInfoW(&mut si) }; let cmd_show = si.wShowWindow as i32; //////////////////////////////////////////////////////////////////////////////////// // 自作WinMainを呼び出す WinMain( hInstance, HINSTANCE(0), args, cmd_show ); Ok(()) }
#[path="./win32dib.rs"] mod win32dib; use win32dib::Win32DIBCanvas; use windows::{ core::*, Win32::Foundation::*, Win32::Graphics::Gdi::*, Win32::UI::WindowsAndMessaging::*, }; use once_cell::sync::Lazy; use std::sync::Mutex; // ■ Mutex ... std::sync::Mutex // ■ Lazy ... once_cell::sync::Lazy // DIBを管理する構造体のグローバル変数 static mydib:Lazy<Mutex<Win32DIBCanvas>> = Lazy::new(|| Mutex::new( Win32DIBCanvas::new() ) ); // ■ COLORREF ... windows::Win32::Foundation::COLORREF // ■ HINSTANCE ... windows::Win32::Foundation::HINSTANCE // ■ HWND ... windows::Win32::Foundation::HWND // ■ LPARAM ... windows::Win32::Foundation::LPARAM // ■ LRESULT ... windows::Win32::Foundation::LRESULT // ■ POINT ... windows::Win32::Foundation::POINT // ■ RECT ... windows::Win32::Foundation::RECT // ■ WPARAM ... windows::Win32::Foundation::WPARAM // ■ BeginPaint ... windows::Win32::Graphics::Gdi::BeginPaint // ■ BitBlt ... windows::Win32::Graphics::Gdi::BitBlt // ■ CreatePen ... windows::Win32::Graphics::Gdi::CreatePen // ■ EndPaint ... windows::Win32::Graphics::Gdi::EndPaint // ■ LineTo ... windows::Win32::Graphics::Gdi::LineTo // ■ MoveToEx ... windows::Win32::Graphics::Gdi::MoveToEx // ■ PS_SOLID ... windows::Win32::Graphics::Gdi::PS_SOLID // ■ PAINTSTRUCT ... windows::Win32::Graphics::Gdi::PAINTSTRUCT // ■ SelectObject ... windows::Win32::Graphics::Gdi::SelectObject // ■ SRCCOPY ... windows::Win32::Graphics::Gdi::SRCCOPY // ■ CreateWindowExA ... windows::Win32::UI::WindowsAndMessaging::CreateWindowExA // ■ CS_HREDRAW ... windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW // ■ CS_VREDRAW ... windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW // ■ CW_USEDEFAULT ... windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT // ■ DefWindowProcA ... windows::Win32::UI::WindowsAndMessaging::DefWindowProcA // ■ DispatchMessageA ... windows::Win32::UI::WindowsAndMessaging::DispatchMessageA // ■ GetMessageA ... windows::Win32::UI::WindowsAndMessaging::GetMessageA // ■ LoadCursorW ... windows::Win32::UI::WindowsAndMessaging::LoadCursorW // ■ IDC_ARROW ... windows::Win32::UI::WindowsAndMessaging::IDC_ARROW // ■ MSG ... windows::Win32::UI::WindowsAndMessaging::MSG // ■ PostQuitMessage ... windows::Win32::UI::WindowsAndMessaging::PostQuitMessage // ■ RegisterClassA ... windows::Win32::UI::WindowsAndMessaging::RegisterClassA // ■ WNDCLASSA ... windows::Win32::UI::WindowsAndMessaging::WNDCLASSA // ■ WS_OVERLAPPEDWINDOW ... windows::Win32::UI::WindowsAndMessaging::WS_OVERLAPPEDWINDOW // ■ WS_VISIBLE ... windows::Win32::UI::WindowsAndMessaging::WS_VISIBLE // ■ PCSTR ... windows::core::PCSTR extern "system" fn wndproc(window: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { unsafe { let rectnullptr: Option<*const RECT> = None; match message as u32 { WM_CREATE => { let refmydib = &mut mydib.lock().unwrap(); refmydib.create(640,480); let hPen = CreatePen( PS_SOLID, 1, COLORREF(0x000000FF) ); // 赤いペンを作成 if hPen.is_invalid() == false{ // ペンが作成できていたら線を引く let oldPen = SelectObject( refmydib.hMemDC, hPen ); // refmydib.hMemDC の 50,50 ~ 100,100 に線を引く MoveToEx( refmydib.hMemDC, 50, 50, None as Option<*mut POINT> ); LineTo( refmydib.hMemDC, 100, 100 ); SelectObject( refmydib.hMemDC, oldPen ); // ペンを削除 DeleteObject(hPen); } LRESULT(0) } WM_PAINT => { let mut ps = PAINTSTRUCT::default(); let hdc = BeginPaint(window, &mut ps); let refmydib = &mut mydib.lock().unwrap(); BitBlt( hdc, 0, 0, 640, 480, refmydib.hMemDC, 0, 0, SRCCOPY ); EndPaint(window, &ps); LRESULT(0) } WM_DESTROY => { PostQuitMessage(0); LRESULT(0) } _ => DefWindowProcA(window, message, wparam, lparam), } } } /// 自作 WinMain pub fn WinMain( hInstance : HINSTANCE, hPrevInstance : HINSTANCE, lpCmdLine:Vec<String>, nCmdShow:i32 )->i32{ unsafe{ let _hCursor = LoadCursorW(None,IDC_ARROW).unwrap(); let _hInstance = hInstance; let _lpszClassName = PCSTR::from_raw("MY_NEW_WINDOW\0".as_ptr()); let _style = CS_HREDRAW | CS_VREDRAW; // https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/UI/WindowsAndMessaging/struct.WNDCLASSA.html let wc=WNDCLASSA{ hCursor :_hCursor, hInstance: _hInstance, lpszClassName: _lpszClassName, style: _style, lpfnWndProc: Some(wndproc), ..Default::default() }; let atom = RegisterClassA(&wc); debug_assert!(atom != 0); let nullptr: Option<*const ::core::ffi::c_void> = None; CreateWindowExA( Default::default(), _lpszClassName, PCSTR::from_raw("This is a sample window\0".as_ptr()), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, None, None, _hInstance, nullptr, ); let mut message = MSG::default(); while GetMessageA(&mut message, HWND(0), 0, 0).into() { DispatchMessageA(&mut message); } } return 0; }