🧲Rust/심화및 응용

윈도우 생성 Rust Winapi [ Rust GUI Programming ]

Mawile 2021. 6. 3.
728x90

 

개발환경 >> Visual Studio Code

언어 >> Rust 1.52.1

운영체제 >> Windows10

 


 

안녕하세요! 이번에는 Rust에서 Microsoft의 winapi를 사용해서 기본적인 윈도우 생성과,

응용예제에 관해서 설명하겠습니다.

그럼 바로 시작 하겠습니다!


 

우선은 기본적인 환경설정이 되어있어야합니다!

우선 아래 명령어를 통해서, 기본적인 rust개발에 필요한 환경을 맞추어줍시다!

cargo new [프로젝트명]
그 후, 그 프로젝트폴더경로로 명령프롬프트의 cd명령을 통해 이동을 합니다.

 

 

그러면은 Cargo.toml이라는 파일이 만들어졌을겁니다.

다음과같이 "[dependencies]" 항목에 다음과 같이 적어줍니다.

이 부분은 외부로부터 라이브러리를 불러오는 부분입니다.

아래의 라이브러리내용을 알고싶거나, 최신버전을 알고싶다면 밑에 링크로 들어가주세요!!

[dependencies]
winapi = { version = "0.3.8", features = ["winuser", "libloaderapi"] }
user32-sys = "0.2.0"
kernel32-sys = "0.2.2"

https://docs.rs

 

Docs.rs

strong-xml-derive-0.6.3 Derive marco of strong-xml. an hour ago

docs.rs

 

 

이제 본격적으로 작업할 main.rs 파일로 들어가서 맨 위부분부터 다음과 같은 소스코드를 적어줍니다!
use std::io::Error;
use std::mem::{size_of, zeroed};

#[cfg(windows)]
use winapi::shared::minwindef::{UINT, WPARAM, LPARAM, LRESULT, LPVOID, HINSTANCE, LOWORD};
use winapi::shared::windef::{HWND, HMENU, HBRUSH};
use winapi::um::winnt::{LPCSTR, LPCWSTR};
use winapi::um::winuser::{WNDCLASSEXW, LoadCursorW, LoadIconW, GetMessageW, DispatchMessageW, RegisterClassExW, CreateWindowExW, ShowWindow, MessageBoxA, TranslateMessage, DefWindowProcW, PostQuitMessage}; // functions
use winapi::um::winuser::{IDI_APPLICATION, IDC_ARROW, CS_HREDRAW, CS_VREDRAW, WS_EX_TOPMOST, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, WM_DESTROY, SW_SHOWDEFAULT, WM_CREATE, WS_VISIBLE, WS_CHILD, WM_COMMAND}; // const variable
use winapi::um::libloaderapi::GetModuleHandleA;

 

이 부분을 설명하자면, use는 해당 라이브러리의 클래스, 함수, 열거형을 사용하겠다는 의미입니다.

참고로 위의 winapi::um이라든지, winapi::shared 라든지, 이 라이브러리에 뭐가들었는지 모르겠으신분들은, 위에 링크에서 검색해서 찾아보시면 아주 자세하게 나와있습니다.

 

그다음 함수들을 적어줍니다.
fn hide_console_window() //콘솔창을 숨기는 함수
{
    let window = unsafe { //메모리보호를 강요하지않음 (unsafe)
        kernel32::GetConsoleWindow() //콘솔창 핸들값 얻어오기
    };

    if window != std::ptr::null_mut() { //만약 nullptr이 아닐경우
        unsafe {
            user32::ShowWindow (window, 0 as winapi::ctypes::c_int) //창 숨기기
        };
    }
}

#[cfg(windows)] //windows사용명시
fn to_wstring(str: &str) -> Vec<u16> { //str를 winapi의 wide-string으로 변환하는 함수
    use std::ffi::OsStr; //문자열 인코딩관련 라이브러리
    use std::os::windows::ffi::OsStrExt; //같음
    use std::iter::once; //반복자관련 라이브러리
    
    let v: Vec<u16> = OsStr::new(str).encode_wide().chain(once(0).into_iter()).collect(); //인코딩
    return v; //반환
}

 

 

그다음은 windows 메세지 콜백함수 입니다.
#[cfg(windows)]
unsafe extern "system"
fn wnd_proc(hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM) -> LRESULT {  //__stdcall
    match msg { //msg로 패턴매칭
        WM_CREATE => { //창생성 메세지
        //윈도우옵션에서 WS_EX_TOPMOST지우기 (메세지박스가 잘안보여서요. 이 옵션은 지웠습니다.)
            winapi::um::winuser::SetWindowLongW(hwnd, winapi::um::winuser::GWL_EXSTYLE, winapi::um::winuser::GetWindowLongW(hwnd, winapi::um::winuser::GWL_EXSTYLE) & !WS_EX_TOPMOST as i32);
        	//버튼생성
            CreateWindowExW(0, to_wstring("button").as_ptr(), to_wstring("btn1").as_ptr(), WS_VISIBLE | WS_CHILD,
            0 as winapi::ctypes::c_int, 0 as winapi::ctypes::c_int,
            100 as winapi::ctypes::c_int, 50 as winapi::ctypes::c_int,
            hwnd,  0x01 as HMENU, 0 as HINSTANCE, 0 as LPVOID); 0
        },
        WM_COMMAND => { //메세지전송 메세지
            let cmdMsg = LOWORD(wparam as u32); //wparam에 저장된 하위비트의 내용을 cmdMsg에 저장
            if cmdMsg == 0x01 { //1일시
            //메세지박스출력
                winapi::um::winuser::MessageBoxW(hwnd, to_wstring("btn1").as_ptr(), to_wstring("btn1").as_ptr(), winapi::um::winuser::MB_OK);
            }
            //switch문으로 따지면 break;
            0
        },
        WM_DESTROY => {
        //윈도우파괴 메세지받을시 메세지종료
            PostQuitMessage(0); 0
        },
        _ => {
        //다 아니라면, 해당함수의 반환값을 반환
            return DefWindowProcW(hwnd, msg, wparam, lparam);
        }
    }
}

fn main() {
    let msg: &str = "test"; //윈도우 제목
    let wide: Vec<u16> = to_wstring(msg); //2바이트 와이드문자열 생성

    unsafe {
        let h_instance = GetModuleHandleA(0 as LPCSTR); //현재 모듈값구하고

        let wndclass = WNDCLASSEXW { //WNDCLASSEXW구조체 내용입력.
            cbSize: size_of::<WNDCLASSEXW>() as u32,
            cbClsExtra: 0, //사용x
            cbWndExtra: 0, //사용x
            hbrBackground: 16 as HBRUSH, //하얀색창
            hCursor: LoadCursorW(0 as HINSTANCE, IDC_ARROW), // 커서불러오기
            hIcon: LoadIconW(0 as HINSTANCE, IDI_APPLICATION), //아이콘불러오기
            hIconSm: LoadIconW(0 as HINSTANCE, IDI_APPLICATION), //작은아이콘
            hInstance: h_instance, //모듈값
            lpfnWndProc: Some(wnd_proc), //메세지 콜백함수
            lpszClassName: wide.as_ptr(), //윈도우의 클래스이름
            lpszMenuName: 0 as LPCWSTR, //사용x
            style: CS_HREDRAW | CS_VREDRAW, //창 스타일
        };
        
        //부모 윈도우생성
        CreateWindowExW(
            0,
            wide.as_ptr(),
            wide.as_ptr(),
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
            0 as HWND, 0 as HMENU,
            h_instance,
            0 as *mut winapi::ctypes::c_void
        );
        
        //구조체내용입력
        match RegisterClassExW(&wndclass) {
            0 => { //만약 FALSE일시
                MessageBoxA(
                    0 as HWND,
                    b"Failed to call an RegisterClassEx\0".as_ptr() as *const i8,
                    b"\0".as_ptr() as *const i8,
                    0 as UINT
                );
            },
            _atom => { //아니라면 그 내용을 _atom에 입력
                let window = CreateWindowExW(
                    0,
                    wide.as_ptr(),
                    wide.as_ptr(),
                    WS_OVERLAPPEDWINDOW,
                    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                    0 as HWND, 0 as HMENU,
                    h_instance,
                    0 as LPVOID
                );
                if window.is_null() { //만약 nullptr일경우
                    MessageBoxA(
                        0 as HWND,
                        b"failed to load an windows\0".as_ptr() as *const i8,
                        b"\0".as_ptr() as *const i8,
                        0 as UINT
                    );
                } else {
                //그것도아니라면 창생성
                    ShowWindow(window, SW_SHOWDEFAULT);
                    hide_console_window();
                    let mut msg = zeroed();
                    while GetMessageW(&mut msg, 0 as HWND, 0, 0) != 0 {
                        TranslateMessage(&msg);
                        DispatchMessageW(&msg);
                    }
                }
            }
        }
    };
}

 

 

후.... 코드가 100줄이 넘어가니까 주석쓰기가 점점 귀찮아지네요..ㅠㅠ

모르는 부분있으면, 댓글로 질문주세요!

 

 

어쨌든, 위 코드를 모두 작성후, 실행하기위해서는 해당명령어를 입력하세요.

cargo run

 

 

그러면................????????

 

 

 

WOW~~~ 끝...!!!!

 

 

728x90

'🧲Rust > 심화및 응용' 카테고리의 다른 글

[Rust] Raw-Pointer 직접 관리하는 방법  (0) 2021.11.24

댓글