본문 바로가기
컴퓨터 활용(한글, 오피스 등)/기타

Rust에서 Microsoft의 winapi를 사용해서 기본적인 윈도우 생성과, 응용예제

by 3604 2024. 5. 5.
728x90

 출처: https://mawile.tistory.com/160

 

 

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

우선 아래 명령어를 통해서, 기본적인 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~~~ 끝...!!!!

출처: https://mawile.tistory.com/160 [전생했더니 C딱인생이였던건에 대하여∑:티스토리]

728x90
반응형