スキップしてメイン コンテンツに移動

Rust + 各種 Win32 API

共通(Common)

type GenericError = Box<dyn std::error::Error + Send + Sync>;
type Result<T> = std::result::Result<T, GenericError>;
Cargo.toml

[dependencies] scopeguard = "1" windows = { version = "0", features = [ "Win32_Security_Authorization", "Win32_Graphics_Gdi", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_DataExchange", "Win32_System_Environment", "Win32_System_IO", "Win32_System_Memory", "Win32_System_Ole", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_RemoteDesktop", "Win32_System_Services", "Win32_System_Shutdown", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging" ] }

Rust の文字列と C++/wchar_t, Win32/WSTR の相互変換(Interconversion between Rust strings and C++/wchar_t, Win32/WSTR)

wstr.rs

#![allow(dead_code)] use std::{char::REPLACEMENT_CHARACTER, marker::PhantomData, mem::zeroed}; use windows::core::{Param, ParamValue, PCWSTR, PWSTR}; pub struct CWSTR<'a> { s: PCWSTR, phantom: PhantomData<&'a ()>, } pub struct WSTR<'a> { s: PWSTR, phantom: PhantomData<&'a ()>, } impl Param<PCWSTR> for CWSTR<'_> { unsafe fn param(self) -> ParamValue<PCWSTR> { if self.s.is_null() { ParamValue::Borrowed(zeroed()) } else { ParamValue::Owned(self.s) } } } impl Param<PWSTR> for WSTR<'_> { unsafe fn param(self) -> ParamValue<PWSTR> { if self.s.is_null() { ParamValue::Borrowed(zeroed()) } else { ParamValue::Owned(self.s) } } } pub trait WStrExt<'a> { fn to_cw(&'a self) -> CWSTR<'a>; fn to_w(&'a mut self) -> WSTR<'a>; } impl<'a> WStrExt<'a> for Vec<u16> { fn to_cw(&'a self) -> CWSTR<'a> { CWSTR { s: PCWSTR::from_raw(self.as_ptr()), phantom: PhantomData, } } fn to_w(&'a mut self) -> WSTR<'a> { WSTR { s: PWSTR::from_raw(self.as_mut_ptr()), phantom: PhantomData, } } } impl<'a> From<&'a Vec<u16>> for CWSTR<'a> { fn from(v: &'a Vec<u16>) -> Self { v.to_cw() } } impl<'a> From<&'a mut Vec<u16>> for WSTR<'a> { fn from(v: &'a mut Vec<u16>) -> Self { v.to_w() } } impl From<CWSTR<'_>> for PCWSTR { fn from(s: CWSTR<'_>) -> Self { s.s } } impl From<WSTR<'_>> for PWSTR { fn from(s: WSTR<'_>) -> Self { s.s } } impl From<CWSTR<'_>> for Option<PCWSTR> { fn from(s: CWSTR<'_>) -> Self { if s.s.is_null() { None } else { Some(s.s) } } } impl From<WSTR<'_>> for Option<PWSTR> { fn from(s: WSTR<'_>) -> Self { if s.s.is_null() { None } else { Some(s.s) } } } pub fn encode_utf16(source: &str) -> Vec<u16> { source.encode_utf16().chain(Some(0)).collect() } pub fn decode_utf16(source: &[u16]) -> String { std::char::decode_utf16(source.iter().cloned()) .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER)) .collect() }

URL を開く(Open URL)

pub fn browse_url(hwnd: HWND, url: &str, process: Option<&mut HANDLE>) -> Result<()> {
    let param_u16 = wstr::encode_utf16(&format!("url.dll,FileProtocolHandler {}", url));
    let mut si = SHELLEXECUTEINFOW {
        cbSize: mem::size_of::<SHELLEXECUTEINFOW>() as u32,
        fMask: SEE_MASK_NOCLOSEPROCESS,
        hwnd,
        lpFile: w!("rundll32.exe"),
        lpParameters: param_u16.to_cw().into(),
        nShow: SW_SHOWNORMAL.0,
        ..Default::default()
    };

    unsafe { ShellExecuteExW(&mut si)?; }

    match process {
        None => {
            unsafe { let _ = CloseHandle(si.hProcess); }
        },
        Some(process) => {
            *process = si.hProcess;
        },
    }

    Ok(())
}

実行ファイルのパスを取得(Get the path to the executable file)

pub fn query_full_process_path(process_id: u32) -> Result<String> {
    unsafe {
        let process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, process_id)?;
        defer! {
            let _ = CloseHandle(process);
        }

        let mut len = 4096u32;
        let mut path = [0u16; 4096];

        QueryFullProcessImageNameW(process, PROCESS_NAME_WIN32, PWSTR::from_raw(path.as_mut_ptr()), &mut len)?;

        return Ok(wstr::decode_utf16(&path[0..len as usize]));
    }

    Err(Box::new(Error::other("Error message here")))
}

プロセスID, 名前列挙(Process ID, Name Enumeration)

pub fn enum_processes() -> Result<Vec<(u32, String)>> {
    let mut processes = vec![0; 1024];
    let mut num_processes: u32 = 0;

    if let Err(e) = unsafe { EnumProcesses(processes.as_mut_ptr(), (mem::size_of::<u32>() * processes.len()) as u32, &mut num_processes) } {
        eprintln!("{e}");
        return Err(Box::new(Error::other("Error message here")));
    }

    num_processes /= mem::size_of::<u32>() as u32;

    let mut result = Vec::with_capacity(num_processes as usize);

    for i in 0..num_processes {
        let pid = processes[i as usize];

        if let Ok(path) = query_full_process_path(pid) {
            result.push((pid, path));
        }
    }

    Ok(result)
}

カレントディレクトリ(Current directory, Working directory)

pub fn get_wd() -> Result<String> {
    let mut current_exe = env::current_exe()?;

    if current_exe.pop() {
        Ok(current_exe.display().to_string())
    } else {
        Err(Box::new(Error::other("Error message here")))
    }
}

Win32 API 不要。

環境変数(Environment variables)

pub fn expand_env(src: &str) -> Result<String> {
    let src_u16 = wstr::encode_utf16(src);
    let mut dst = [0u16; 8192];
    let dst_len = unsafe { ExpandEnvironmentStringsW(src_u16.to_cw(), Some(&mut dst)) };

    if dst_len == 0 {
        return Err(Box::new(Error::other("Error message here")));
    }

    Ok(wstr::decode_utf16(&dst[0..dst_len as usize]))
}

環境変数自体は std::env でも取れるので使い勝手の問題。

ShellExecute

pub fn shell_execute(file: &str, param: Option<&str>, show_cmd: SHOW_WINDOW_CMD) -> HINSTANCE {
    let file_u16 = wstr::encode_utf16(file);

    match param {
        Some(param) => {
            let param_u16 = wstr::encode_utf16(param);

            unsafe { ShellExecuteW(Some(HWND::default()), None, file_u16.to_cw(), param_u16.to_cw(), None, show_cmd) }
        },
        None => {
            unsafe { ShellExecuteW(Some(HWND::default()), None, file_u16.to_cw(), None, None, show_cmd) }
        },
    }
}

CreateProcess

pub fn create_process(app: &str, param: Option<&str>, cd: Option<&str>, pi: Option<&mut PROCESS_INFORMATION>) -> Result<()> {
    let mut arg_u16 = match param {
        Some(param) => {
            wstr::encode_utf16(&(app.to_string() + " " + param))
        },
        None => {
            wstr::encode_utf16(app)
        },
    };
    let cd_u16 = match cd {
        Some(cd) => {
            Some(wstr::encode_utf16(cd))
        },
        None => {
            None
        },
    };
    let cd_pcwstr: PCWSTR = match cd {
        Some(_) => {
            cd_u16.unwrap().to_cw().into()
        },
        None => {
            PCWSTR::null()
        },
    };
    let cd_param: Option<&PCWSTR> = match cd {
        Some(_) => {
            Some(&cd_pcwstr)
        },
        None => {
            None
        },
    };
    let si = STARTUPINFOW {
        cb: mem::size_of::<STARTUPINFOW>() as u32,
        ..Default::default()
    };

    match pi {
        Some(pi) => {
            unsafe { CreateProcessW(None, arg_u16.to_w().into(), None, None, false, PROCESS_CREATION_FLAGS(0), None, cd_param, &si, pi)?; }
        },
        None => {
            let mut pi = PROCESS_INFORMATION::default();

            unsafe {
                CreateProcessW(None, arg_u16.to_w().into(), None, None, false, PROCESS_CREATION_FLAGS(0), None, cd_param, &si, &mut pi)?;
                let _ = CloseHandle(pi.hThread);
                let _ = CloseHandle(pi.hProcess);
            }
        },
    }

    Ok(())
}

pub fn create_process_as_user(app: &str, param: Option<&str>, token: HANDLE, creation_flags: PROCESS_CREATION_FLAGS, environment: *const c_void, pi: Option<&mut PROCESS_INFORMATION>) -> Result<()> {
    let mut desktop_u16 = wstr::encode_utf16(r#"winsta0\default"#);
    let si = STARTUPINFOW {
        cb: mem::size_of::<STARTUPINFOW>() as u32,
        lpDesktop: desktop_u16.to_w().into(),
        ..Default::default()
    };
    let arg = match param {
        Some(param) => {
            app.to_string() + " " + param
        },
        None => {
            app.to_string()
        },
    };
    let mut arg_u16 = wstr::encode_utf16(&arg);

    match pi {
        None => {
            let mut pi = PROCESS_INFORMATION::default();

            unsafe {
                CreateProcessAsUserW(Some(token), None, arg_u16.to_w().into(), None, None, false, creation_flags, Some(environment), None, &si, &mut pi)?;
                let _ = CloseHandle(pi.hThread);
                let _ = CloseHandle(pi.hProcess);
            }
        },
        Some(pi) => {
            unsafe { CreateProcessAsUserW(Some(token), None, arg_u16.to_w().into(), None, None, false, creation_flags, Some(environment), None, &si, pi)?; }
        },
    }

    Ok(())
}

pub fn create_process_as_active_user(app: &str, param: Option<&str>, pi: Option<&mut PROCESS_INFORMATION>) -> Result<()> {
    let session_id = get_active_session_id()?;
    let mut user_token = HANDLE::default();

    unsafe { WTSQueryUserToken(session_id, &mut user_token)? };
    defer! {
        unsafe { let _ = CloseHandle(user_token); }
    }

    unsafe {
        let mut size = mem::size_of::<HANDLE>() as u32;
        let mut linked_token = HANDLE::default();

        GetTokenInformation(user_token, TokenLinkedToken, Some(&mut linked_token as *mut _ as *mut _), size, &mut size)?;
        defer! {
            let _ = CloseHandle(linked_token);
        }

        let mut duplicated_token = HANDLE::default();

        DuplicateTokenEx(linked_token, TOKEN_ACCESS_MASK(MAXIMUM_ALLOWED), None, SecurityImpersonation, TokenPrimary, &mut duplicated_token)?;
        defer! {
            let _ = CloseHandle(duplicated_token);
        }

        let mut environment = ptr::null_mut();
        let mut creation_flags = CREATE_NEW_CONSOLE | NORMAL_PRIORITY_CLASS;

        match CreateEnvironmentBlock(&mut environment, Some(duplicated_token), true) {
            Ok(_) => {
                creation_flags |= CREATE_UNICODE_ENVIRONMENT;
            },
            Err(_) => {
                environment = ptr::null_mut();
            },
        }
        defer! {
            let _ = DestroyEnvironmentBlock(environment);
        }

        create_process_as_user(app, param, duplicated_token, creation_flags, environment, pi)?;
    };

    Ok(())
}

pub fn get_active_session_id() -> Result<u32> {
    let mut si: *mut WTS_SESSION_INFOW = ptr::null_mut();
    let mut count = 0u32;

    unsafe { WTSEnumerateSessionsW(Some(WTS_CURRENT_SERVER_HANDLE), 0, 1, &mut si, &mut count)?; }
    defer! {
        unsafe { WTSFreeMemory(si as *mut _); }
    }

    let mut iter = si;

    unsafe {
        for _ in 0..count {
            if (*iter).State == WTSActive {
                return Ok((*iter).SessionId);
            }

            iter = iter.offset(1);
        }
    }

    Err(Box::new(Error::new(ErrorKind::NotFound, "Error message here")))
}

ホットキー(Hot Key)

#[derive(FromPrimitive)]
enum IdHotKey {
    ABC,
}

// 登録削除
RegisterHotKey(Some(hwnd), IdHotKey::ABC as i32, MOD_CONTROL | MOD_SHIFT, VK_SPACE.0 as u32);
RegisterHotKey(Some(hwnd), IdHotKey::ABC as i32, MOD_ALT | MOD_CONTROL, 'A' as u32);
UnregisterHotKey(Some(hwnd), IdHotKey::ABC as i32);

// イベント処理
WM_HOTKEY => {
    match FromPrimitive::from_usize(w_param.0) {
        Some(IdHotKey::ABC) => { ... }
        ...
    }
    // enum に FromPrimitive を使わない場合
    // match w_param.0 {
    //     x if x == IdHotKey::ABC as usize => { ... }
    //     ...
    // }
}

ウィンドウ作成/メッセージループ(Window creation/Message loop)

pub fn create_window() -> Result<HWND> {
    let class_name = w!("class name here");
    let winc = WNDCLASSW {
        lpfnWndProc: Some(wnd_proc),
        lpszClassName: class_name,
        ..Default::default()
    };

    if unsafe { RegisterClassW(&winc) } == 0 {
        return Err(Box::new(Error::other("Error message here")))
    }

    match unsafe { CreateWindowExW(WINDOW_EX_STYLE(0), class_name, w!("window name here"), WS_OVERLAPPEDWINDOW | WS_MINIMIZE, 0, 0, 0, 0, None, None, None, None) } {
        Ok(hwnd) => {
            Ok(hwnd)
        },
        Err(e) => {
            Err(Box::new(e))
        },
    }
}

unsafe extern "system" fn wnd_proc(hwnd: HWND, msg: u32, w_param: WPARAM, l_param: LPARAM) -> LRESULT {
    match msg {
        WM_CLOSE => {
            let _ = DestroyWindow(hwnd);
        },
        WM_DESTROY => {
            PostQuitMessage(0);
        }
        _ => {
            return DefWindowProcW(hwnd, msg, w_param, l_param);
        }
    }

    LRESULT(0)
}

fn main() -> Result<()> {
    unsafe {
        let hwnd = create_window()?;

        let _ = ShowWindow(hwnd, SW_SHOWNORMAL);
        let _ = UpdateWindow(hwnd);

        let mut msg = MSG::default();

        while GetMessageW(&mut msg, None, 0, 0).as_bool() {
            let _ = TranslateMessage(&msg);
            DispatchMessageW(&msg);
        }
    }

    Ok(())
}

フォント(Font)

let log_font = LOGFONTW {
    lfHeight: 20,
    lfWidth: 0,
    lfEscapement: 0,
    lfOrientation: 0,
    lfWeight: FW_NORMAL.0 as i32,
    lfItalic: 0,
    lfUnderline: 0,
    lfStrikeOut: 0,
    lfCharSet: DEFAULT_CHARSET,
    lfOutPrecision: OUT_TT_PRECIS,
    lfClipPrecision: CLIP_DEFAULT_PRECIS,
    lfQuality: ANTIALIASED_QUALITY,
    lfPitchAndFamily: DEFAULT_PITCH.0 | FF_DONTCARE.0,
    lfFaceName: {
        let mut face_name_u16 = wstr::encode_utf16("Meiryo");
        face_name_u16.resize(mem::size_of_val(&LOGFONTW::default().lfFaceName) / mem::size_of::<u16>(), 0);
        face_name_u16.try_into().unwrap()
    },
};
let font = CreateFontIndirectW(&log_font);

名前付きパイプ(Named Pipe)

pub fn create_security_named_pipe(name: &str) -> Result<HANDLE> {
    let mut desc = SECURITY_DESCRIPTOR::default();

    unsafe {
        InitializeSecurityDescriptor(PSECURITY_DESCRIPTOR(&mut desc as *mut _ as *mut _), SECURITY_DESCRIPTOR_REVISION)?;

        let mut accesses: [EXPLICIT_ACCESS_W; 2] = [EXPLICIT_ACCESS_W::default(); 2];

        // 設定例
        BuildExplicitAccessWithNameW(&mut accesses[0], w!("Everyone"), FILE_ALL_ACCESS.0, GRANT_ACCESS, ACE_FLAGS(0));
        BuildExplicitAccessWithNameW(&mut accesses[1], w!("ANONYMOUS LOGON"), FILE_ALL_ACCESS.0, GRANT_ACCESS, ACE_FLAGS(0));

        let mut dacl: *mut ACL = ptr::null_mut();

        SetEntriesInAclW(Some(&accesses), None, &mut dacl).ok()?;
        defer! {
            LocalFree(Some(HLOCAL(dacl as _)));
        }

        SetSecurityDescriptorDacl(PSECURITY_DESCRIPTOR(&mut desc as *mut _ as *mut _), true, Some(dacl), false)?;

        let mut attr = SECURITY_ATTRIBUTES::default();

        attr.lpSecurityDescriptor = &mut desc as *mut _ as *mut _;
        attr.bInheritHandle = BOOL(0);

        let name_u16 = wstr::encode_utf16(name);
        let pipe = CreateNamedPipeW(name_u16.to_cw(), PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_READMODE_BYTE, 1, 1024, 1024, 1000, Some(&attr));

        if pipe == INVALID_HANDLE_VALUE {
            return Err(Box::new(Error::other("Error message here")));
        }

        Ok(pipe)
    }
}

特権(Privilege)

pub fn adjust_token_privilege(token: HANDLE, privilege: PCWSTR) -> Result<()> {
    let mut tp = TOKEN_PRIVILEGES::default();

    tp.PrivilegeCount = 1;

    unsafe {
        LookupPrivilegeValueW(None, privilege, &mut tp.Privileges[0].Luid)?;
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
        AdjustTokenPrivileges(token, false, Some(&tp), 0, None, None)?;
    }

    Ok(())
}

pub fn adjust_process_privilege(process: HANDLE, privilege: PCWSTR) -> Result<()> {
    let mut token = HANDLE::default();

    unsafe {
        OpenProcessToken(process, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &mut token)?;
        defer! {
            let _ = CloseHandle(token);
        }

        adjust_token_privilege(token, privilege)
    }
}

fn main() -> Result<()> {
    adjust_process_privilege(unsafe { GetCurrentProcess() }, SE_DEBUG_NAME) // 使用例
}

ExitWindows

pub fn exit_windows(flags: EXIT_WINDOWS_FLAGS) -> Result<()> {
    if flags.0 & EWX_LOGOFF.0 == 0 {
        adjust_process_privilege(unsafe { GetCurrentProcess() }, SE_SHUTDOWN_NAME)?;
    }

    unsafe { ExitWindowsEx(flags, SHTDN_REASON_NONE)?; }

    Ok(())
}

クリップボード(Clipboard)

pub fn get_clipboard_text(hwnd: HWND) -> Result<String> {
    unsafe {
        OpenClipboard(Some(hwnd))?;
        defer! {
            let _ = CloseClipboard();
        }

        let data = HGLOBAL(GetClipboardData(CF_UNICODETEXT.0 as u32)?.0);
        let lock = GlobalLock(data);

        defer! {
            let _ = GlobalUnlock(data);
        }

        let bytes = GlobalSize(data); // バイト単位で返却され、かつ data(lock) の実態は wchar_t* なので必ず 2 の倍数になる
        let slice = slice::from_raw_parts(lock as *const u16, (bytes / 2).saturating_sub(1)); // null 終端文字なので、末尾の一文字分減らす

        Ok(String::from_utf16(slice)?)
    }
}

pub fn set_clipboard_text(hwnd: HWND, text: &str) -> Result<()> {
    let text_utf16 = wstr::encode_utf16(text);

    unsafe {
        let mem = GlobalAlloc(GMEM_MOVEABLE, text_utf16.len() * mem::size_of::<u16>())?;
        defer! {
            let _ = GlobalFree(Some(mem));
        }

        {
            let lock = GlobalLock(mem);
            defer! {
                let _ = GlobalUnlock(mem);
            }

            ptr::copy_nonoverlapping(text_utf16.as_ptr(), lock as _, text_utf16.len());
        }

        OpenClipboard(Some(hwnd))?;
        defer! {
            let _ = CloseClipboard();
        }

        SetClipboardData(CF_UNICODETEXT.0 as u32, Some(HANDLE(mem.0)))?;
    }

    Ok(())
}

レジストリ(Registry)

pub fn read_string_reg_key(key: HKEY, sub_key: &str, value_name: Option<String>) -> Result<String> {
    unsafe {
        let sub_key_u16 = wstr::encode_utf16(sub_key);
        let mut key_opened = HKEY::default();

        if RegOpenKeyExW(key, Into::<PCWSTR>::into(sub_key_u16.to_cw()), None, KEY_QUERY_VALUE, &mut key_opened) != ERROR_SUCCESS {
            return Err(Box::new(Error::other("Error message here")));
        }
        defer! {
            let _ = RegCloseKey(key_opened);
        }

        let mut len = 4096u32;
        let mut path = [0u8; 4096];

        match value_name {
            None => {
                if RegQueryValueExW(key_opened, None, None, None, Some(path.as_mut_ptr()), Some(&mut len)) != ERROR_SUCCESS {
                    return Err(Box::new(Error::other("Error message here")));
                }
            },
            Some(value_name) => {
                let value_name_u16 = wstr::encode_utf16(&value_name);

                if RegQueryValueExW(key_opened, value_name_u16.to_cw(), None, None, Some(path.as_mut_ptr()), Some(&mut len)) != ERROR_SUCCESS {
                    return Err(Box::new(Error::other("Error message here")));
                }
            },
        }

        let path = slice::from_raw_parts(path.as_ptr() as *const u16, (len as usize / 2).saturating_sub(1)); // wide char 版 API(RegQueryValueExW) を呼び出しているので、戻り値の実態は char(u8) ではなく wchar_t(u16)
        let path: String = char::decode_utf16(path.iter().cloned())
            .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))
            .collect();

        Ok(path)
    }
}

サービス(Service)

開始(Start)

let mut service_name_u16 = wstr::encode_utf16("Service name");
let services: &[SERVICE_TABLE_ENTRYW] = &[
    SERVICE_TABLE_ENTRYW {
        lpServiceName: service_name_u16.to_w().into(),
        lpServiceProc: Some(service_main), // service_main は下記参照
    },
    SERVICE_TABLE_ENTRYW {
        ..Default::default()
    },
];

unsafe { StartServiceCtrlDispatcherW(services.as_ptr())?; }

登録/削除(Register/Unregister)

pub fn register_service() -> Result<()> {
    let manager = unsafe { OpenSCManagerW(None, None, SC_MANAGER_CREATE_SERVICE | SC_MANAGER_LOCK)? };

    if manager.is_invalid() {
        return Err(Box::new(Error::other("Error message here")));
    }
    defer! {
        unsafe { let _ = CloseServiceHandle(manager); }
    }

    let current_exe = env::current_exe()?;
    let current_exe_path = current_exe.display().to_string();
    let current_exe_path_u16 = wstr::encode_utf16(&current_exe_path);
    let service = unsafe { CreateServiceW(manager, w!("Service name"), w!("Display name"), SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, current_exe_path_u16.to_cw(), None, None, None, None, None)? };

    if service.is_invalid() {
        return Err(Box::new(Error::other("Error message here")));
    }
    defer! {
        unsafe { let _ = CloseServiceHandle(service); }
    }

    let mut description_u16 = wstr::encode_utf16("Service description");
    let desc = SERVICE_DESCRIPTIONW {
        lpDescription: description_u16.to_w().into(),
    };

    unsafe {
        {
            let lock = LockServiceDatabase(manager);
            defer! {
                let _ = UnlockServiceDatabase(lock);
            }

            ChangeServiceConfig2W(service, SERVICE_CONFIG_DESCRIPTION, Some(&desc as *const _ as *mut _))?;
        }

        std::thread::sleep(std::time::Duration::from_millis(500));
        StartServiceW(service, None)?;
    }

    Ok(())
}

pub fn unregister_service() -> Result<()> {
    let manager = unsafe { OpenSCManagerW(None, None, SC_MANAGER_CREATE_SERVICE | SC_MANAGER_LOCK)? };

    if manager.is_invalid() {
        return Err(Box::new(Error::other("Error message here")));
    }
    defer! {
        unsafe { let _ = CloseServiceHandle(manager); }
    }

    let service = unsafe { OpenServiceW(manager, w!("Service name"), SERVICE_STOP | SERVICE_QUERY_STATUS | DELETE.0)? };

    if service.is_invalid() {
        return Err(Box::new(Error::other("Error message here")));
    }
    defer! {
        unsafe { let _ = CloseServiceHandle(service); }
    }

    let mut ss = SERVICE_STATUS::default();

    unsafe {
        QueryServiceStatus(service, &mut ss)?;
        if ss.dwCurrentState == SERVICE_RUNNING {
            ControlService(service, SERVICE_CONTROL_STOP, &mut ss)?;
        }

        DeleteService(service)?;
    }

    Ok(())
}

ハンドラ(Service handler)

#[derive(Debug, PartialEq, Default)]
struct ServiceData {
    stop_event: HANDLE,
    service_status: SERVICE_STATUS,
    service_handle: SERVICE_STATUS_HANDLE,
}

unsafe impl Send for ServiceData {}

static SERVICE_DATA: LazyLock<Mutex<ServiceData>> = LazyLock::new(|| {
    Mutex::new(ServiceData::default())
});

fn handler_ex_stub(control: u32, _event_type: u32, _event_data: *mut core::ffi::c_void, _context: *mut core::ffi::c_void) -> Result<u32> {
    match control {
        SERVICE_CONTROL_STOP => {
            let mut sd = SERVICE_DATA.lock().unwrap();

            sd.service_status.dwCurrentState = SERVICE_STOP_PENDING;
            sd.service_status.dwCheckPoint   = 0;
            sd.service_status.dwWaitHint     = 1000;

            unsafe {
                SetServiceStatus(sd.service_handle, &sd.service_status)?;
                SetEvent(sd.stop_event)?;
            }
        },
        SERVICE_CONTROL_INTERROGATE => {
            let sd = SERVICE_DATA.lock().unwrap();
            unsafe { SetServiceStatus(sd.service_handle, &sd.service_status)?; }
        },
        _ => {
        },
    }

    Ok(NO_ERROR.0)
}

unsafe extern "system" fn handler_ex(control: u32, event_type: u32, event_data: *mut core::ffi::c_void, context: *mut core::ffi::c_void) -> u32 {
    match handler_ex_stub(control, event_type, event_data, context) {
        Ok(n) => {
            n
        },
        Err(e) => {
            // Error handling here
            NO_ERROR.0
        },
    }
}

fn service_main_stub(_num_services_args: u32, _service_arg_vectors: *mut PWSTR) -> Result<()> {
    {
        let mut sd = SERVICE_DATA.lock().unwrap();
        sd.stop_event = unsafe { CreateEventW(None, false, false, None)? };

        sd.service_handle = unsafe { RegisterServiceCtrlHandlerExW(w!("Service name"), Some(handler_ex), None)? };
        sd.service_status.dwServiceType             = SERVICE_WIN32_OWN_PROCESS;
        sd.service_status.dwCurrentState            = SERVICE_RUNNING;
        sd.service_status.dwControlsAccepted        = SERVICE_ACCEPT_STOP;
        sd.service_status.dwWin32ExitCode           = NO_ERROR.0;
        sd.service_status.dwServiceSpecificExitCode = 0;
        sd.service_status.dwCheckPoint              = 0;
        sd.service_status.dwWaitHint                = 0;

        unsafe { SetServiceStatus(sd.service_handle, &sd.service_status)?; }
    }
    defer! {
        unsafe { let _ = CloseHandle(SERVICE_DATA.lock().unwrap().stop_event); }
    }

    {
        let stop_event = SERVICE_DATA.lock().unwrap().stop_event;

        unsafe { WaitForSingleObject(stop_event, INFINITE); }

        let mut sd = SERVICE_DATA.lock().unwrap();

        sd.service_status.dwCurrentState = SERVICE_STOPPED;
        sd.service_status.dwCheckPoint   = 0;
        sd.service_status.dwWaitHint     = 0;

        unsafe { SetServiceStatus(sd.service_handle, &sd.service_status)?; }
    }

    return Ok(());
}

pub unsafe extern "system" fn service_main(num_services_args: u32, service_arg_vectors: *mut PWSTR) {
    if let Err(e) = service_main_stub(num_services_args, service_arg_vectors) {
        // Error handling here
    }
}

コメント