728x90
반응형

경로를 입력받아 폴더인지 파일인지 확인하기

 C++ 17로 윈도우에서 프로그램을 만들면서 폴더와 파일을 구분해야하는 경우가 생겨 아래 코드를 찾게되었다.

찾은 코드는 테스트 시 작동에는 문제가 없었지만 문자열을 LPCWSTR (WCHAR *)로 받아야 한다는 사소한 단점이 있었다. 혹시 String 클래스를 사용해 입력받은 경로가 폴더인지 파일인지 구분할 수 있는 방법이 있는지도 찾아봤지만, 검색을 해봐도 찾지 못했다.. 

int isFolder(LPCWSTR path) {
    /*
       args:
           LPCWSTR fileName : 파일의 full path
       return:
            int code : 폴더는 1, 파일은 0, 에러는 -1
        summary:
            폴더인지 파일인지 구분
   */
    WIN32_FIND_DATA wInfo;
    HANDLE hInfo = ::FindFirstFile(path, &wInfo);
    ::FindClose(hInfo);

    if (hInfo != INVALID_HANDLE_VALUE)
    {
        if (wInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        {
            return 1;
        }
        return 0;
    }
    return -1;
}

출처 : https://jangjy.tistory.com/388

 

int main() {
    cout << isFolder(L"C:\\Users\\user\\Downloads") << "\n";

    return 0;
}


 

String to LPCWSTR

 위 내용에서 함수를 찾긴했지만 입력받는 방식이 달라 사용이 어려웠다.

작성중인 프로그램에서는 문자열을 String 클래스를 사용하여 처리하기 때문에 변환이 필요했는데, 구글링을 통해 아래 코드를 알게 되었다.

std::wstring s2ws(const std::string& s)
{
    int len;
    int slength = (int)s.length() + 1;
    len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0);
    wchar_t* buf = new wchar_t[len];
    MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len);
    std::wstring r(buf);
    delete[] buf;
    return r;
}

 

출처 : https://wiserloner.tistory.com/316 

 

 

위의 코드를 아래처럼 사용하면 변환된 결과를 얻을 수 있다.

int main() {
    std::wstring stemp = s2ws("C:\\Users\\user\\Downloads");
    LPCWSTR result = stemp.c_str();
    
    wprintf(result);
    
    return 0;
}


 

String으로 폴더 여부 확인하기

 이제 isFolder() 함수가 String을 사용해 경로를 확인할 수 있도록 수정해보자.

#include <windows.h>
#include <iostream>
#include <string>
#include <fstream>
#include <filesystem>
#include <vector>
#include <map>

using namespace std;


wstring s2ws(const string& s) {
    int slength = (int)s.length() + 1;
    int len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0);
    wchar_t* buf = new wchar_t[len];

    MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len);
    wstring r(buf);
    delete[] buf;

    return r;
}

int isFolder(string path) {
    /*
       args:
           LPCWSTR fileName : 파일의 full path
       return:
            int code : 폴더는 1, 파일은 0, 에러는 -1
        summary:
            폴더인지 파일인지 구분
   */
    WIN32_FIND_DATA wInfo;
    HANDLE hInfo = ::FindFirstFile(s2ws(path).c_str(), &wInfo);
    ::FindClose(hInfo);

    if (hInfo != INVALID_HANDLE_VALUE){
        if (wInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){
            return 1;
        }
        return 0;
    }
    return -1;
}

int main() {
    cout << isFolder("C:\\Users\\user\\Downloads") << "\n";

    return 0;
}

728x90
반응형
728x90
반응형

프로그램 제작 이유

 만약 PC와 모니터가 2대씩 있을 때 각 PC를 둘 다 듀얼 모니터로 사용하고 싶다면 어떻게 하는게 좋을까?
가장 쉽게 문제를 해결하려면 매번 사용하는 PC의 HDMI 케이블을 바꿔서 연결하면 된다!


만약 그것도 귀찮다면 KVM 스위치를 구매해 연결해두고 필요한 PC의 버튼이나 단축키를 눌러서 사용하면 된다.
(망 분리 환경이라면 보통 Aten과 같은 회사의 보안 인증이 된 제품을 지급해준다. 하지만 개인이 구매하기 어렵고 비싸다..!)
하지만 PC를 사용하다보면 양쪽 PC를 동시에 활용해야하는 경우가 꽤 많았고 개인적으로 KVM에서 듀얼 모니터 설정이 더 어려웠던 것 같다.

차라리 아래처럼 케이블을 전부 연결해두고 모니터 버튼으로 필요할 때마다 입력소스를 바꿔주는게 더 편하다.


많이 편해졌지만 여전히 몸을 움직여 버튼을 눌러야 하고 만약 PC 1을 듀얼로 사용하던 중에 PC 2를 듀얼로 사용하려면 버튼을 두번 눌러야 한다.

여러 대의 PC를 하나의 인터페이스로 다루는 KVM과 사용자의 편의를 위한 듀얼 모니터를 최소의 움직임으로 사용할 순 없는 걸까?

프로그램을 만들어보기로 했다.


 

MCCS, DDC/CI, VCP Code

 MCCS (Monitor Control Command Set)는 모니터 제어 명령 세트로 VESA (Video Electronics Standards Association)에서 개발한 컴퓨터 디스플레이 표준이다.
보통 PC나 셋탑박스 같은 장치에서 모니터를 제어하기 위한 바이너리 프로토콜로 사용되지만, 해당 글에서는 프로그램 제작을 위해 사용하려고 한다.

MCCS를 활용하면 DDC/CI (Display Data Channel/ Command Interface)와 VCP (Virtual Control Panel) 코드를 통해 모니터에 명령을 보낼 수 있다.

VCP 코드는 가상 제어 패널의 약어로 아래 이미지와 같은 표준 명령어 타입을 따른다.

VCP 코드


정해진 값을 약속된 프로토콜로 모니터에 명령을 전달하면 모니터가 해당 동작을 수행한다.
예를 들어 VCP 코드로 0xD6을 넣어주면 모니터의 전원이 On/Off되고, 0x60을 전달하면 모니터의 입력 소스가 변경된다.

현재 만들어지는 모니터는 대부분(거의 전부) 해당 표준을 따르기 때문에 코드만 잘 전달하면 정말 편할 것 같다..!
근데 모니터에 코드를 어떻게 전달하라는 걸까?

여러 방식이 있겠지만 난 보통 윈도우를 많이 사용하기 때문에 exe 프로그램을 제작하기로 했다.
Windows API를 통해 모니터로 VCP 코드를 보내보자.


 

모니터 제어 API

 일반적으로 모니터와 관련된 API를 검색하면 모니터의 핸들을 얻어오거나 정보를 얻어오기 위해 MONITORINFOEXA와 같은 구조체나 GetMonitorInfo()와 같은 함수를 사용한다. 하지만 제어를 위해서는 DDC/CI 프로토콜에 맞춰서 모니터로 데이터를 보내주어야 한다.

다행히 MSDN에서 DDC/CI를 사용하는 API들을 찾을 수 있었다. (만약 없었다면 따로 라이브러리(dll)가 존재하긴 한다.)
https://docs.microsoft.com/ko-kr/windows/win32/monitor/monitor-configuration

 

구성 모니터링(구성 모니터링) - Win32 apps

모니터 구성

docs.microsoft.com


MCCS 표준을 따르면 모니터 버튼으로 할 수 있는 동작 대부분을 프로그램으로 수행할 수 있지만 지금은 모니터 입력 소스만 정확하게 변경하면 된다.

위에서 VCP 코드를 DDC/CI 프로토콜에 따라서 모니터로 보내주기만 하면 기능이 수행된다고 했었다.
그런데 MSDN을 보면 API들은 내부적으로 DDC/CI를 사용하여 모니터에 명령을 보내준다고 되어 있기 때문에, VCP 코드만 제대로 맞춰서 전달해주면 될 것 같다.

이제 VCP 코드를 내가 원하는 모니터로 전달해주는 방법을 찾아야된다.


 

High-Level/ Low-Level Monitor 구성 함수

 위 MSDN 링크에 들어가보면 High-Level과 Low-Level로 나뉘어진 함수들을 볼 수 있는데, 이 중 나에게 필요한 내용은 VCP 코드를 다룰 수 있는 Low-Level 함수들이다.

MSDN의 Low-level Monitor 함수 사용법


대충 윈도우에서 감지한 모니터들을 알려주는 Enum 함수와 VCP 관련된 함수들을 알려주는데, 8번의 SetVCPFeature 함수가 눈에 띄였다.

https://docs.microsoft.com/en-us/windows/win32/api/lowlevelmonitorconfigurationapi/nf-lowlevelmonitorconfigurationapi-setvcpfeature

 

SetVCPFeature function (lowlevelmonitorconfigurationapi.h) - Win32 apps

Sets the value of a Virtual Control Panel (VCP) code for a monitor.

docs.microsoft.com

_BOOL SetVCPFeature(
  [in] HANDLE hMonitor,
  [in] BYTE   bVCPCode,
  [in] DWORD  dwNewValue
);


SetVCPFeature 함수는 위와 같은 형태로 되어 있다.
Enum 함수 등으로 얻어온 모니터의 핸들을 구해서 넣고 원하는 VCP Code 값을 넣으면 될거 같다.
이제 변경을 원하는 모니터와 원하는 VCP 기능을 전달하는 방법은 알았다.

그런데 우리의 상황처럼 1개의 모니터에 연결된 케이블이 여러 개라면 원하는 케이블은 어떻게 설정해야 되는 걸까?

이 방법을 알기 위해 엄청난 검색을 했는데..
방법은 의외로 간단했다.
3번째 인자인 dwNewValue에 해당 케이블의 값을 넣어주면 된다!
그렇다면 그 값은 어떻게 알 수 있을까??

다시 엄청난 삽질이 시작됐다.


 

GetVCPFeatureAndVCPFeatureReply() 함수

 온갖 키워드와 방법으로 검색해 알게된 방법은 GetVCPFeatureAndVCPFeatureReply() 함수를 사용하는 것 이었다.
그런데 위의 MSDN 이미지의 7번을 자세히 보면 이미 설명이 잘 되어 있다.
MS에선 처음부터 전부 다 알려주었지만 내가 이해를 못했을 뿐이다. (멍청하면 손발이 고생한다..)

https://docs.microsoft.com/en-us/previous-versions/ms775219(v=vs.85)

 

GetVCPFeatureAndVCPFeatureReply Function

Table of contents Monitor GetVCPFeatureAndVCPFeatureReply Function  Article 11/02/2006 2 minutes to read In this article --> Monitor Configuration API GetVCPFeatureAndVCPFeatureReply Function Retrieves the current value, maximum value, and code type of a

docs.microsoft.com

  BOOL GetVCPFeatureAndVCPFeatureReply(
    HANDLE
    hMonitor,

    BYTE
    bVCPCode,

    LPMC_VCP_CODE_TYPE
    pvct,

    LPDWORD
    pdwCurrentValue,

    LPDWORD
    pdwMaximumValue

  );


함수를 살펴보면 먼저 SetVCPFeature 함수와 비슷하게 모니터의 핸들과 VCP 코드를 요구한다.
그리고 3, 4번째 인자를 통해 Current Value 값과 Maximum Value 값을 얻어 낼 수 있었다.
(cf. 일반적으로 return을 통해 함수의 결과를 받지만 return으로는 하나의 값만 반환받을 수 있기 때문에 포인터 형태로 비어있는 변수를 전달해주면 알아서 채워준다.)

위의 함수를 잘 사용하면 모니터에 연결되어 있는 케이블들의 Value 값을 구할 수 있다.
(HDMI, DP, VGA등에 따라 값이 다르다.)

이제 SetVCPFeature() 함수를 사용하는데 필요한 모든 데이터를 구했다.

하지만 아직 마지막 한 가지 문제가 남아있다.
모니터의 제조사나 모델마다 해당 Value의 값이 다르다는 것이다ㅋㅋ
(예를 들면 A사의 A1 모니터는 HDMI의 Value가 10이고, B사의 B1모니터는 101이다. 심지어 같은 제조사의 다른 제품일 경우도 값이 다른 경우가 있었다.)

위의 API들을 잘 조합하면 자동으로 구할 수 있을 것 같았지만, 더 삽질하면 주말동안 끝내지 못할 것 같아 모니터들의 값을 알아낸 뒤 그냥 하드코딩했다.


 

코드

 먼저 프로그램의 기본 골격은 https://hwan001.tistory.com/1?category=1041434 글의 코드를 사용했다.
아무것도 없는 윈도우를 띄워주는 코드이다.

개발 환경은 Visual Studio Community 2022이고 프로젝트는 아래 이미지처럼 구성했다.

jsoncpp.cpp는 c++에서 json 파싱을 위해 나중에 추가한 라이브러리 코드이고 test.cpp는 테스트 함수를 작성해둔 파일이라 생략했다.
그리고 사실 EnumDisplayMonitors 함수는 MonitorEnumProc라는 콜백 함수를 내부에서 호출하기 때문에 SetVCPFeature 함수를 사용하기 위해서 콜백 함수들을 작성해야 하는데, 해당 내용들은 나중에 시간되면 추가로 작성해보겠다.

코드는 돌아가게만 만들어 두었기 때문에 정리도 잘 안되어 있지만 이것도 나중에 정리하기로 하고 일단 올려보겠다.


function.h 코드

#pragma once

#pragma warning(disable: 28251)

// api 헤더
#include <windows.h>
#include <winuser.h>
#include <Shlwapi.h>

#define VC_EXTRALEAN 
#define _WIN32_WINDOWS 0x0500

// library
#pragma comment(lib, "dxva2")
#pragma comment(lib, "user32")
#pragma comment(lib, "Shlwapi.lib")

// 모니터 vcp 관련 헤더
#include <lowlevelmonitorconfigurationapi.h>
#include <PhysicalMonitorEnumerationAPI.h>
#include <HighLevelMonitorConfigurationAPI.h>

// c++ 헤더
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include "json/json.h"

using namespace std;


// 구조체
typedef struct _MONITORPARAM
{
    LPCWSTR szPhysicalMonitorDescription;
    BYTE VCPcode;
    int source;
    BOOL bPowerOn;
    int curVal;
} MONITOR_PARAM, * PMONITOR_PARAM;

typedef struct _GETMONITORINFO
{
    LPCWSTR szPrimaryMonitorDescription;
    LPCWSTR szSecondMonitorDescription;
    int curVal;
    int curVal_second;
} GET_MONITOR_INFO, * GET_PMONITOR_INFO;


// 얘를 모니터 개수만큼 포인터 배열로 만들어서 _getCurrentValue에 전달하면, 모니터 돌면서 정보를 채워줌.
typedef struct _MONITOR{
    int num;
    char * monName;
    BYTE vcpValue;
} MONITOR, * PMONITOR;


// test 함수
VOID test_setInputSource(HWND, DWORD);
VOID test_getPrimaryMonitor(HWND);
VOID test_setInputSource(HWND, DWORD);
VOID test_SetMonitorPower(LPCWSTR, BOOL);

// interface 함수
VOID ChangeMonitorInput_hwan(PMONITOR mon, int monNum);
VOID GetMonitorInfo_hwan(PMONITOR mon, int monNum);

// callback 함수
BOOL CALLBACK MonitorEnumProc(HMONITOR, HDC, LPRECT, LPARAM);
BOOL CALLBACK _getCurrentValue(HMONITOR, HDC, LPRECT, LPARAM); // 얘가 모니터 정보 얻어와줌
BOOL CALLBACK _setMonitorInput(HMONITOR, HDC, LPRECT, LPARAM); // 얘는 특정 모니터에 value값 넣어줌.

// json 함수
void load_json(LPCWSTR file_path, PMONITOR mon, int monNum);
void save_json(LPCWSTR file_path, int monNum, Json::Value *monitor);


main.cpp 코드

#include "function.h"

#define BTN_SET 111
#define BTN_SAVE 112
#define EDIT_INFO 11
#define EDIT_INPUTNAME 21
#define EDIT_INPUTNUM 31
#define BTN_CLEAR 0

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

HINSTANCE g_Inst;
LPCWSTR lpszClass = TEXT("Change Monitor");

static BOOL mode; 
static TCHAR str_filename[1024];

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszClassParam, int nCmdShow)
{
    HWND hWnd;
    MSG Message;
    WNDCLASS WndClass;
    g_Inst = hInstance;
    TCHAR str_currentPath[1024];

    GetCurrentDirectory(1024, str_currentPath);
    wsprintf(str_filename, L"%s\\%s", str_currentPath, L"vcp.json");

    if (PathFileExists(str_filename)) {
        mode = TRUE;
    }
    else {
        mode = FALSE;
    }

    WndClass.cbClsExtra = 0;
    WndClass.cbWndExtra = 0;
    WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    WndClass.hInstance = hInstance;
    WndClass.lpfnWndProc = WndProc;
    WndClass.lpszClassName = lpszClass;
    WndClass.lpszMenuName = NULL;
    WndClass.style = CS_HREDRAW | CS_VREDRAW;

    RegisterClass(&WndClass);

    hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW
        , CW_USEDEFAULT, CW_USEDEFAULT, 500, 300
        , NULL, (HMENU)NULL, hInstance, NULL);
    
    if(!mode) ShowWindow(hWnd, nCmdShow);

    while (GetMessage(&Message, NULL, 0, 0))
    {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return (int)Message.wParam;
}


LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    static HWND hEdit_name, hEdit_num, btn_set, btn_save;
    static HWND hEdit_monitorNum;
    static HWND *hEdit_Monitor, *hEdit_inputName, *hEdit_inputNum;
    static int monNum;
    static PMONITOR mon;
    
    char str_monNum[1024], str_name[1024], str_num[1024];
    
    switch (iMessage)
    {
    case WM_CREATE:
        monNum = GetSystemMetrics(SM_CMONITORS);

        mon = (PMONITOR) malloc(sizeof(MONITOR) * monNum);
        for (int i = 0; i < monNum; i++) {
            mon[i].num = 0;
            mon[i].monName = new char[1024];
            mon[i].vcpValue = 0;
        }

        if (mode) {
            load_json(str_filename, mon, monNum);
            ChangeMonitorInput_hwan(mon, monNum);

            DestroyWindow(hWnd);
        }
        else {
            hEdit_Monitor = (HWND *)malloc(sizeof(HWND) * monNum);
            hEdit_inputName = (HWND *)malloc(sizeof(HWND) * monNum);
            hEdit_inputNum = (HWND *)malloc(sizeof(HWND) * monNum);

            for (int i = 0; i < monNum; i++) {
                hEdit_Monitor[i] = CreateWindow(L"edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_READONLY, 20, 10 + (i * 30), 300, 25, hWnd, (HMENU)(EDIT_INFO + i), g_Inst, NULL);
                hEdit_inputName[i] = CreateWindow(L"edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL, 20, (10 + (monNum * 30)) + (i * 30), 145, 25, hWnd, (HMENU)(EDIT_INPUTNAME + i), g_Inst, NULL);
                hEdit_inputNum[i] = CreateWindow(L"edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL, 175, (10 + (monNum * 30)) + (i * 30), 145, 25, hWnd, (HMENU)(EDIT_INPUTNUM + i), g_Inst, NULL);
            }
            
            btn_set = CreateWindow(L"button", L"Set", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 330, 10, 60, 25, hWnd, (HMENU)BTN_SET, g_Inst, NULL);
            btn_save = CreateWindow(L"button", L"Save", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 400, 10, 60, 25, hWnd, (HMENU)BTN_SAVE, g_Inst, NULL);

            // get monitor info
            GetMonitorInfo_hwan(mon, monNum);

            // set monitor info
            for (int i = 0; i < monNum; i++) {
                SetWindowTextA(hEdit_Monitor[i], mon[i].monName);
            }
        }
        break;

    case WM_COMMAND:
        switch (LOWORD(wParam)) {
        case BTN_SET:
            for (int i = 0; i < monNum; i++) {
                GetWindowTextA(hEdit_inputName[i], str_name, 1024);
                GetWindowTextA(hEdit_inputNum[i], str_num, 1024);

                MONITOR tmp_mon;
                tmp_mon.monName = new char[1024];
                wsprintfA(tmp_mon.monName, "%s", str_name);
                tmp_mon.vcpValue = atoi(str_num);

                ChangeMonitorInput_hwan(&tmp_mon, 1);

                delete tmp_mon.monName;
            }
            break;

        case BTN_SAVE:
            Json::Value valueMon[3];
            
            for (int i = 0; i < monNum; i++) {
                GetWindowTextA(hEdit_inputName[i], str_name, 1024);
                GetWindowTextA(hEdit_inputNum[i], str_num, 1024);

                valueMon[i]["name"] = str_name;
                valueMon[i]["value"] = atoi(str_num);
            }

            save_json(str_filename, monNum, valueMon);
            break;
        }
        break;

    case WM_PAINT:
        break;

    case WM_DESTROY:
        for (int i = 0; i < monNum; i++) {
            delete mon[i].monName;
        }

        free(mon);
        
        free(hEdit_Monitor);
        free(hEdit_inputName);
        free(hEdit_inputNum);

        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hWnd, iMessage, wParam, lParam);
}


function.cpp 코드

#include "function.h"


VOID ChangeMonitorInput_hwan(PMONITOR mon, int monNum)
{
    MONITOR tmp_mon;

    for (int i = 0; i < monNum; i++) {
        tmp_mon.monName = mon[i].monName;
        tmp_mon.vcpValue = mon[i].vcpValue;

        EnumDisplayMonitors(NULL, NULL, _setMonitorInput, (LPARAM)&tmp_mon);
    }
}


VOID GetMonitorInfo_hwan(PMONITOR mon, int monNum)
{
    MONITOR tmp_mon;

    for (int i = 0; i < monNum; i++) {
        tmp_mon.num = i;
        tmp_mon.monName = new char[1024];
        tmp_mon.vcpValue = -1;

        EnumDisplayMonitors(NULL, NULL, _getCurrentValue, (LPARAM)&tmp_mon);

        wsprintfA(mon[i].monName, "%s", tmp_mon.monName);
        mon[i].vcpValue = tmp_mon.vcpValue;

        delete tmp_mon.monName;
    }
}

BOOL CALLBACK _getCurrentValue(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwDate) {
    DWORD dwMonitorCapabilities = 0;
    DWORD dwSupportedColorTemperatures = 0;
    DWORD dwMonitorCount;
    DWORD curVal = -1;

    int len;
    char strMultibyte[1024];

    if (GetNumberOfPhysicalMonitorsFromHMONITOR(hMonitor, &dwMonitorCount))
    {
        PHYSICAL_MONITOR* pMons;
        if (pMons = (PHYSICAL_MONITOR*)HeapAlloc(GetProcessHeap(), 0, sizeof(PHYSICAL_MONITOR) * dwMonitorCount))
        {
            if (GetPhysicalMonitorsFromHMONITOR(hMonitor, dwMonitorCount, pMons))
            {
                for (int i = 0; i < dwMonitorCount; i++)
                {
                    if (((PMONITOR)dwDate)->num == i) {
                        GetVCPFeatureAndVCPFeatureReply(pMons[i].hPhysicalMonitor, 0x60, NULL, &curVal, NULL);

                        len = WideCharToMultiByte(CP_ACP, 0, pMons[i].szPhysicalMonitorDescription, -1, NULL, 0, NULL, NULL);
                        WideCharToMultiByte(CP_ACP, 0, pMons[i].szPhysicalMonitorDescription, -1, strMultibyte, len, NULL, NULL);

                        wsprintfA(((PMONITOR)dwDate)->monName, "%s", strMultibyte);
                        ((PMONITOR)dwDate)->vcpValue = curVal;

                        DestroyPhysicalMonitor(pMons[i].hPhysicalMonitor);

                        break;
                    }
                }
            }

            HeapFree(GetProcessHeap(), 0, pMons);
        }
    }

    return TRUE;
}


BOOL CALLBACK _setMonitorInput(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwDate) {
    DWORD dwMonitorCapabilities = 0;
    DWORD dwSupportedColorTemperatures = 0;
    DWORD dwMonitorCount;
    DWORD curVal = -1;

    if (GetNumberOfPhysicalMonitorsFromHMONITOR(hMonitor, &dwMonitorCount))
    {
        PHYSICAL_MONITOR* pMons;
        if (pMons = (PHYSICAL_MONITOR*)HeapAlloc(GetProcessHeap(), 0, sizeof(PHYSICAL_MONITOR) * dwMonitorCount))
        {
            if (GetPhysicalMonitorsFromHMONITOR(hMonitor, dwMonitorCount, pMons))
            {
                int len;
                char str_tmp[1024];
                char strMultibyte[1024];

                for (int i = 0; i < dwMonitorCount; i++) 
                {
                    len = WideCharToMultiByte(CP_ACP, 0, pMons[i].szPhysicalMonitorDescription, -1, NULL, 0, NULL, NULL);
                    WideCharToMultiByte(CP_ACP, 0, pMons[i].szPhysicalMonitorDescription, -1, strMultibyte, len, NULL, NULL);

                    if (strcmp(((PMONITOR)dwDate)->monName, strMultibyte) == 0) {
                        GetVCPFeatureAndVCPFeatureReply(pMons[i].hPhysicalMonitor, 0x60, NULL, &curVal, NULL);

                        if (curVal != -1)
                            SetVCPFeature(pMons[i].hPhysicalMonitor, 0x60, ((PMONITOR)dwDate)->vcpValue);
                    }

                    DestroyPhysicalMonitor(pMons[i].hPhysicalMonitor);
                }
            }

            HeapFree(GetProcessHeap(), 0, pMons);
        }
    }

    return TRUE;
}


void load_json(LPCWSTR file_path, PMONITOR mon, int monNum) {
    bool parsingSuccessful;

    Json::CharReaderBuilder builder;
    Json::CharReader* reader = builder.newCharReader();
    Json::Value root;
    string errors, text = "", line;

    ifstream jsonFile(file_path);
    if (jsonFile.is_open()) {
        
        while (getline(jsonFile, line)) {
            text += line;
        }

        jsonFile.close();
    }

    parsingSuccessful = reader->parse(text.c_str(), text.c_str() + text.size(), &root, &errors);
    delete reader;

    size_t rv = 0;
    for (Json::Value::const_iterator outer = root.begin(); outer != root.end(); outer++)
    {
        for (Json::Value::const_iterator inner = (*outer).begin(); inner != (*outer).end(); inner++)
        {
            for (int i = 0; i < monNum; i++) {
                if (outer.key().asString() == "monitor_" + to_string(i)) {
                    if (inner.key().asString() == "name") wsprintfA(mon[i].monName, "%s", inner->asCString());
                    if (inner.key().asString() == "value") mon[i].vcpValue = stoi(inner->asString());
                }
            }

        }
    }
}


void save_json(LPCWSTR file_path, int monNum, Json::Value *monitor) {
    Json::Value root;

    Json::Value MON;
    string key = "";

    for (int j = 0; j < monNum; j++) {
        key = "monitor_" + to_string(j);
        MON[key] = monitor[j];
    }

    root = MON;

    Json::StreamWriterBuilder writer;

    string str = Json::writeString(writer, root);

    ofstream write_JsonFile;
    write_JsonFile.open(file_path);
    if (write_JsonFile.is_open()) {
        write_JsonFile.write(str.c_str(), str.length());
    }

    write_JsonFile.close();
}

 

프로그램 설명

 프로그램을 시작할 때 동일 경로 상의 vcp.json 파일 존재를 기준으로 mode를 정하는데, 해당 mode가 활성화 되어 있을 경우 UI는 나타나지 않고 vcp.json에 정의된 대상으로 변경만 수행한 뒤 프로그램을 종료한다.
(최초에 한번 값을 넣어두면 다음부턴 실행했을 경우 모니터가 원하는 대상으로 변경된다. 개인적으로는 아래 작업 표시줄에 바로가기를 등록해두고 필요할때 눌러서 사용했다.)

여기에 등록해서 누르면 모니터가 바뀜


아래는 최초 실행 시 나오는 프로그램의 UI이다.

최초 실행 시 UI


현재는 연결된 모니터가 없어 노트북의 내장 모니터인 Generic PnP Monitor만 나오지만 다른 모니터가 연결되어 있을 경우, Dell H00000 (DP) 와 같은 식으로 모니터의 이름이 나온다.
(제대로 나오지 않을 경우 장치 관리자의 모니터 탭에서 확인할 수 있다.)

참고로 이 부분이 모니터 개수만큼 아래로 늘어난다.


프로그램에서는 해당 문자열을 1번에 입력하여 여러개의 모니터들 중 변경을 원하는 대상을 구분할 수 있고 2번에는 연결된 케이블의 Value을 입력하여 원하는 케이블을 선택할 수 있다.

입력 후 Set 버튼을 누르면 모니터가 변경되고, Save를 누르면 동일 경로에 입력된 값으로 vcp.json 파일이 생성된다
이후 프로그램을 종료하면 실행 시 마다 vcp.json 내의 값을 기준으로 모니터가 변경된다.
만약 값 변경을 원하면 vcp.json을 직접 수정하거나 파일을 삭제하고 프로그램을 재실행해서 입력해주면 된다.

추가로 Value를 구하는 방법이 궁금하면 위의 GetVCPFeatureAndVCPFeatureReply() 함수를 잘 활용해서 구해보기 바란다..
(만약 귀찮으면 1부터 Max까지 값을 직접 넣어보면서 바뀌는 값을 찾는 방법도 있다.)


 

결과 및 느낀점

 회사에 다니면서 주말 프로젝트로 간단하게 생각하고 진행했었는데, 자료를 찾다보니 점점 내용이 많아졌다.

어쨋든 기능적으로 나름 잘 돌아가는 프로그램을 만들었고 회사에서도 잘 사용을 하고 있기 때문에 마무리는 했지만, 중간에 작성했던 유용한 테스트 함수들이 계속 수정되면서 사라져 추가하지 못한 기능들이 많아 아쉬운 점이 많다. (Value 찾아주는 함수 등)

나중에 좀 더 기능을 추가해서 깔끔하게 업그레이드된 프로그램을 만들면 좋을 것 같다.

추가로 ddcutil? 라이브러리를 사용하면 dll을 통해 더 정리가 잘된 함수들을 사용할 수 있고 커맨드 형식으로도 제공이 되는 것 같다. (API로 삽질하지 말고 라이브러리 사용하면 편하다..)
https://www.ddcutil.com/

 

ddcutil Documentation

ddcutil and ddcui Announcements 05 August 2022 ddcui release 0.3.0 contains the following changes of interest to general users; CTL-Q terminates ddcui (does not apply within dialog boxes) Errors opening /dev/i2c and /dev/usb/hiddev devices are reported usi

www.ddcutil.com



그리고 전부는 아니지만 모니터 제조사와 모델별 특성과 값을 적어둔 페이지도 찾았다.
https://www.ddcutil.com/monitor_notes/

 

Notes on Specific Monitors - ddcutil Documentation

The following list describes some monitors that have been tested or reported by users. It highlights the variability in DDC implementation among monitors. VCP Version: 2.2 Controller Manufacturer: Mstar Controller Model: mh=0xff, ml=0x16, sh=0x00 Firmware

www.ddcutil.com

 

실행 파일은 깃허브에 올려두었다.

https://github.com/hwan001/Change_monitor_input

 

GitHub - hwan001/Change_monitor_input: 모니터 화면 전환 프로그램

모니터 화면 전환 프로그램. Contribute to hwan001/Change_monitor_input development by creating an account on GitHub.

github.com


728x90
반응형
728x90
반응형

 

MSDN

https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getcurrentdirectory

 

GetCurrentDirectory function (winbase.h) - Win32 apps

Retrieves the current directory for the current process.

docs.microsoft.com

 


 

Header

#include <windows.h>

 


 

Syntax

DWORD GetCurrentDirectory(
  [in]  DWORD  nBufferLength,
  [out] LPTSTR lpBuffer
);
  • Parameters
    - [in] DWORD nBufferLength : NULL을 포함한 문자열 변수의 길이
    - [out] LPTSTR lpBuffer : 현재 디렉토리 문자열을 받을 변수 ( ex) TCHAR )

  • Return Value
    DWORD : NULL을 제외하고 버퍼에 기록되는 문자 수, 실패시 0 반환 ( 자세한 에러는 GetLastError )

 


 

Example

#include <windows.h>

int main()
{
	TCHAR str_currentPath[1024];
    
	GetCurrentDirectory(1024, str_currentPath);
}

 

728x90
반응형
728x90
반응형

 

MSDN

https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-pathfileexistsa

 

PathFileExistsA function (shlwapi.h) - Win32 apps

Determines whether a path to a file system object such as a file or folder is valid.

docs.microsoft.com


 

Header

#include <Shlwapi.h>

#pragma comment(lib, "Shlwapi.lib")

 

Syntax

BOOL PathFileExistsA(
  [in] LPCSTR pszPath
);
  • Parameters
    - 파일의 절대 경로를 LPCTSTR 타입으로 넣어줌. ( ex) L"C:\Users\hwan\Desktop\test.exe" )

 

  • Return value
    - 파일이 존재할 경우 True, 존재하지 않은 경우 False

728x90
반응형
728x90
반응형

CallBack 함수

 함수는 일반적으로 프로그래머에 의해 작성되고 호출된다. 

CallBack 함수는 일반적인 함수와 비슷하지만, 호출되는 시점이 시스템(이벤트)에 의해 결정된다는 차이가 있다.

 

윈도우 API를 공부하면 가장 처음 배우는 Window를 띄우는 코드(링크)에서도 콜백 함수인 윈도우 프로시져를 볼 수 있는데, 해당 콜백함수(프로시져)는 WinMain에서 WndClass.lpfnWndProc=WndProc; 로 WNDCLASS 구조체에 등록된 뒤에 RegisterClass(&WndClass); 되어진 이후 따로 호출하지 않는다.

 

하지만 구조체의 정보가 등록된 이후는 WinMain 내부의 메시지 루프인

while(GetMessage(&Message, NULL, 0, 0))
{
        TranslateMessage(&Message);
        DispatchMessage(&Message);
}

부분을 통해 윈도우의 메시지 큐에서 GetMessage로 가져오고, TranslateMessage를 사용해 가상 키 메시지를 문자 메시지로 변환한 뒤 DispatchMessage를 통해서 연결된 콜백 함수(프로시져)에 전달한다.

 

728x90
반응형
728x90
반응형

1. UI/ XML

<Window x:Class="youtube_viewer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:youtube_viewer"
        mc:Ignorable="d"
        Title="YouTube Player" Height="500" Width="800"
        Background="#FFE5E5E5"
        >

    <Grid Name="GridViewer">
        <Grid.RowDefinitions>
            <RowDefinition Height="215*"/>
            <RowDefinition Height="27*"/>
        </Grid.RowDefinitions>

        <Grid Margin="0,3,0,0" Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="677*"/>
                <ColumnDefinition Width="123*"/>
            </Grid.ColumnDefinitions>

            <Button Name ="btn_play" Margin="5,10,10,10" Content="Play" Click="btn_play_Click" Grid.Column="1"/>
            <TextBox Name="txtbox_url" Margin="10,10,5,10" Grid.Column="0" TextWrapping="Wrap" Text="Input youtube url" Grid.ColumnSpan="1"/>
        </Grid>
    </Grid>
</Window>

2. C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace youtube_viewer
{
    /// <summary>
    /// MainWindow.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class MainWindow : Window
    {
        CefSharp.Wpf.ChromiumWebBrowser _browser;
        string str;

        public MainWindow()
        {
            InitializeComponent();

            _browser = new CefSharp.Wpf.ChromiumWebBrowser();
            GridViewer.Children.Add(_browser);
        }

        private void btn_play_Click(object sender, RoutedEventArgs e)
        {
            str = txtbox_url.Text.Split('=')[1].Split('&')[0];

            if (this._browser != null)
            {
                _browser.Address = $"https://www.youtube-nocookie.com/embed/{str}";
            }
        }
    }
}

3. Nuget

cef.redist.x64

728x90
반응형
728x90
반응형

1. 목적

  • WPF에서 OpenCV를 활용하여 기본 WPF 컨트롤인 Image에 노트북 (또는 USB) 카메라에서 가져온 영상을 Bitmap 형식으로 그려줌
  • 환경 구축 및 빌드는 https://hwan001.tistory.com/144?category=836235 링크 참조

 

2. 코드 (Xaml)

<Window x:Class="WPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF"
        Loaded="windows_loaded"
        mc:Ignorable="d"
        Title="MainWindow" Height="400" Width="600">
    <Grid>
        <Image Name="Cam_1" Margin="20,20,20,100"/>
    </Grid>
</Window>

 

3. 코드 (C#)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

// OpenCV 사용을 위한 using
using OpenCvSharp;
using OpenCvSharp.WpfExtensions;

// Timer 사용을 위한 using
using System.Windows.Threading;

namespace WPF
{
    // OpenCvSharp 설치 시 Window를 명시적으로 사용해 주어야 함 (window -> System.Windows.Window)
    public partial class MainWindow : System.Windows.Window
    {
    	// 필요한 변수 선언
        VideoCapture cam;
        Mat frame;
        DispatcherTimer timer;
        bool is_initCam, is_initTimer;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void windows_loaded(object sender, RoutedEventArgs e)
        {
            // 카메라, 타이머(0.01ms 간격) 초기화
            is_initCam = init_camera();
            is_initTimer = init_Timer(0.01); 

            // 초기화 완료면 타이머 실행
            if(is_initTimer && is_initCam) timer.Start();
        }

        private bool init_Timer(double interval_ms)
        {
            try
            {
                timer = new DispatcherTimer();

                timer.Interval = TimeSpan.FromMilliseconds(interval_ms);
                timer.Tick += new EventHandler(timer_tick);

                return true;
            }
            catch
            {
                return false;
            }
        }

        private bool init_camera() {
            try {
                // 0번 카메라로 VideoCapture 생성 (카메라가 없으면 안됨)
                cam = new VideoCapture(0);
                cam.FrameHeight = (int)Cam_1.Height;
                cam.FrameWidth = (int)Cam_1.Width;

                // 카메라 영상을 담을 Mat 변수 생성
                frame = new Mat();

                return true;
            }catch{
                return false;
            }
        }

        private void timer_tick(object sender, EventArgs e)
        {
            // 0번 장비로 생성된 VideoCapture 객체에서 frame을 읽어옴
            cam.Read(frame);
            // 읽어온 Mat 데이터를 Bitmap 데이터로 변경 후 컨트롤에 그려줌
            Cam_1.Source = OpenCvSharp.WpfExtensions.WriteableBitmapConverter.ToWriteableBitmap(frame);
        }
    }
}

 

 

4. 결과

결과

728x90
반응형
728x90
반응형

1. 설치 목적

  • WPF에서 OpenCV 최신 버전(2022년 2월 2일 기준) 기능 사용

 

2. 주의점

  • OpenCVSharp4는 여러개의 Nuget으로 나워져 있음
  • 한 개라도 없으면 함수 또는 자료형의 일부를 찾을 수 없음

 

3. 설치 (Nuget Package Manager)


- WPF 환경에서 사용하기 위해 초록색 박스 부분 (OpenCvSharp4, OpenCvSharp4.runtime.win, OpenCvSharp4.WpfExtentions) 설치

- 윈도우 환경이라면 빨간색 박스 부분 설치가 더 간편함 (OpenVcSharp4.Windows)

 

 

4. OpenVcSharp4.Windows (빨간 박스) 설치 완료

참조

 

728x90
반응형
728x90
반응형

1. Visual Studio 2022 Community 설치

.NET 데스크톱 개발 선택 후 설치

 

2. 새 프로젝트 만들기 > WPF 앱 (.NET Framework) 선택

프로젝트 생성


프로젝트 생성 2

 

3. XAML/ 디자인 편집기와 C# 편집기

xaml/디자인 편집기


C# 편집기

 

4. 빌드 및 실행

빌드 및 실행 성공

728x90
반응형
728x90
반응형

[기본 지식]

 - 로그인 배경화면 변경 방법

 - 관리자 권한 요구

 - 파일열기 대화상자

 - 레지스트리 변경 방법

 

[Example Source]

#include <windows.h>
#include <stdio.h>
#include "ShReg.h" // Windows API정복 책 참조

#define KEY L"SOFTWARE\\Microsoft\\windows\\CurrentVersion\\Authentication\\LogonUI\\Background" 
#define PATH L"C:\\windows\\system32\\oobe\\info\\backgrounds\\backgroundDefault.jpg"
#define DIRPATH L"C:\\windows\\system32\\oobe\\info\\backgrounds"

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_Inst;
LPCWSTR lpszClass = TEXT("Window");

// http://blog.naver.com/PostView.nhn?blogId=dolicom&logNo=10096040743 참조
void CreateDir(WCHAR* Path)
{
    WCHAR DirName[256];  //생성할 디렉초리 이름
    WCHAR* p = Path;     //인자로 받은 디렉토리
    WCHAR* q = DirName;  

    while(*p)
    {
        if (('\\' == *p) || ('/' == *p))   //루트디렉토리 혹은 Sub디렉토리
        {
            if (':' != *(p-1))
            {
                CreateDirectory(DirName, NULL);
            }
        }
        *q++ = *p++;
        *q = '\0';
    }
    CreateDirectory(DirName, NULL);  
}

// WinMain 생략

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    static HANDLE hFile;
    HANDLE hFile_new;
    DWORD numWritten;
    static DWORD nFileSize;
    OPENFILENAME OFN;
    TCHAR lpstrFile[MAX_PATH] = L"";

    static HDC hdc;
    PAINTSTRUCT ps;
    static BOOL file_state;
    static WCHAR str[100];

    static char *buf;
    DWORD readn; 

    switch(iMessage)
    {
    case WM_CREATE:
        CreateWindow(L"button", L"Image Select", WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON, 10, 10, 150, 25, hWnd, (HMENU)1, g_Inst, NULL);
        CreateWindow(L"button", L"convert Background", WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON, 10, 40, 150, 25, hWnd, (HMENU)2, g_Inst, NULL);
        file_state = FALSE;
        CreateDir(DIRPATH); // DIRPATH 경로에 디렉토리가 없다면 생성 (부모 디렉토리가 없어도 알아서 생성)
        break;

    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
        case 1:
            memset(&OFN, 0, sizeof(OPENFILENAME));
            OFN.lStructSize = sizeof(OPENFILENAME);
            OFN.hwndOwner=hWnd;
            OFN.lpstrFilter=TEXT("jpg\0*.jpg\0");
            OFN.lpstrFile=lpstrFile;
            OFN.nMaxFile=MAX_PATH;

            if(GetOpenFileName(&OFN) != 0)
            {
                if( (hFile = CreateFile(OFN.lpstrFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL)) != INVALID_HANDLE_VALUE)
                {
                    
                    if((nFileSize = GetFileSize(hFile, NULL)) >= 256000) // 파일 사이즈 검사
                    {
                        MessageBox(hWnd, L"File size is too big \"Below 256kb\"", L"Error", MB_OK);
                        DestroyWindow(hWnd);
                    }

                    buf = (char*) malloc (nFileSize); // 파일 사이즈 만큼 할당
                    ReadFile(hFile, buf, nFileSize, &readn, NULL); // 파일 사이즈 만큼 읽어옴
                    
                    file_state = TRUE;
                    
                    swprintf_s(str, 100, L"%s", OFN.lpstrFile);
                    InvalidateRect(hWnd, NULL, TRUE);
                }
            }
            break;
        case 2:
            if(file_state != FALSE) // 파일이 열려있는지 확인
            {
                // 파일을 쓰기위해 핸들을 얻어옴 (PATH에 해당하는 파일이 없다면 생성)
                if( (hFile_new = CreateFile(PATH, GENERIC_ALL, 0, NULL, CREATE_ALWAYS, 0, NULL)) != INVALID_HANDLE_VALUE)
                {
                    // PATH에 아까 열었던 이미지파일을 써줌
                    if( WriteFile(hFile_new, buf, nFileSize, &numWritten, NULL) != 0)
                        MessageBox(hWnd, L"File Write Success", 0, MB_OK);
                    else
                        MessageBox(hWnd, L"File Write Failed", 0, MB_OK);
                
                    free(buf);
                    DestroyWindow(hWnd);
                }
                SHRegWriteInt(SHLM, KEY, L"OEMBackground", 1); 
 // 레지스트리 경로 내부의 OEMBackground를 1로 변경 (PATH를 배경으로 사용하겠다는 의미)
            }
            else
            {
                MessageBox(0, L"File open Failed", 0, 0);
            }
        } 
        break;

    case WM_PAINT:
        hdc = BeginPaint(hWnd , &ps);
        
        TextOut(hdc, 170, 10, str, WideCharToMultiByte( CP_ACP, 0, str, -1, NULL, 0, NULL, NULL )-1); 
 // 유니코드 문자열의 사이즈를 구하기 위해 WideCharToMultiByte() 사용

        EndPaint(hWnd, NULL);
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hWnd, iMessage, wParam,lParam);
}

 

 

728x90
반응형
728x90
반응형

1. 개요

 ExpandEnvironmentStrings(LPCTSTR lpSrc, LPTSTR lpDst, DWORD nSize)

 - 환경 변수에 저장된 데이터를 불러올 수 있음.

 

 Parameter

 - lpSrc : 아래 표의 왼쪽 값을 문자열로 넣어줌.

 - lpDst : 아래 표 오른쪽 결과를 얻어옴.

 - nSize : lpDst 의 크기

출처 : http://securityfactory.tistory.com/315

2. 코드

#include<windows.h>
#include<stdio.h>

#define SYSTEMDRIVE  "%SystemDrive%"
#define PUBLIC       "%PUBLIC%"
#define USERDOMAIN   "%USERDOMAIN%"
#define USERNAME     "%USERNAME%"
#define OS           "%OS%"
#define COMPUTERNAME "%COMPUTERNAME%"

int main()
{
    LPTSTR lpDst[200];

    ExpandEnvironmentStrings(SYSTEMDRIVE, lpDst, sizeof(lpDst));
    printf("Result : %s\n", lpDst);
    
    return 0;
}

 

728x90
반응형
728x90
반응형

1. 개요

 - hosts 파일 수정으로 특정 URL을 웹 브라우저에 입력 시 원하는 서버로 연결 가능.

ex) www.daum.net 입력 -> 결과는 www.google.com 로 이동

 - hosts 파일 경로 : C:\Windows\System32\drivers\etc\hosts

 - 리소스 드랍 활용

 - 관리자 권한 필요

 - hosts 파일 변경은 백신에서 탐지됨

 

2. 설정

 

3. 코드

#include<windows.h>
#include "resource.h" // 수정된 hosts 파일을 리소스로 등록 : IDR_FILE1

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_Inst;
LPCWSTR lpszClass = TEXT("WINDOW");

// WinMain 생략

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    HMODULE hDll;
    HRSRC hResource;
    HGLOBAL hData;
    DWORD filesize, numWritten;
    LPVOID pResource;
    HANDLE hFile;

    switch(iMessage)
    {
    case WM_CREATE:
        // 리소스 가져옴
        hResource = FindResource(g_Inst, MAKEINTRESOURCE(IDR_FILE1), L"file");
        hData = LoadResource(g_Inst, hResource);
        pResource = LockResource(hData);
		filesize = SizeofResource(hDll, hResource);

        // 가져온 리소스를 hosts 경로에 써줌
        hFile = CreateFile(L"C:\\Windows\\System32\\drivers\\etc\\hosts", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
        if(hFile != INVALID_HANDLE_VALUE)
        {
            if(WriteFile(hFile, pResource, filesize, &numWritten, NULL))
            {
                MessageBox(0, L"write success", 0, 0);
            }
        }
	CloseHandle(hFile);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hWnd, iMessage, wParam,lParam);
}
 
728x90
반응형
728x90
반응형

1. 코드

//리소스 변수 선언
HMODULE hDll;
HRSRC hResource;
HGLOBAL hData;
DWORD filesize, numWritten;
LPVOID pResource;
HANDLE hFile;
TCHAR Adr[] = L"C:\\File.exe"; // 파일 경로

// 아래는 코드 적용 부분, 필요한 부분에 적절히 사용
// 리소스 찾기, 에러 시 NULL
hResource = FindResource(g_Inst, MAKEINTRESOURCE(IDR_EXE1), L"exe");
// 리소스 로드, 에러 시 NULL
hData = LoadResource(g_Inst, hResource);
// 락, 에러 시 NULL
pResource = LockResource(hData);

// 리소스 사이즈 구하기, 에러 시 NULL
filesize = SizeofResource(hDll, hResource);

// 리소스를 파일로 작성
hFile = CreateFile(Adr, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
if(hFile != INVALID_HANDLE_VALUE)
{
	WriteFile(hFile, pResource, filesize, &numWritten, NULL);
}
CloseHandle(hFile);
 

 

728x90
반응형
728x90
반응형

1. 개요

- OSVERSIONINFOEX 구조체로 윈도우의 버전을 알수있음.

 

2. 코드

OSVERSIONINFOEX osvi;

ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); 

// OSVERSIONINFOEX 구조체를 이용하여 버전 구분
switch(osvi.dwPlatformId){    
    // NT 기반 운영체제    
    case VER_PLATFORM_WIN32_NT:        
    /*                   
    dwMajorVersion, dwMinorVersion 멤버로 운영체제 구분        
    ex)             
        if(osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0) version = "vista";            
        if(osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1) version = "window 7";                    
    ...        
    */        
    break;
}

- 윈도우 NT 4.0 SP6 보다 낮은 버전 os에서는 OSVERSIONINFO 구체를 사용해야함.

 
OSVERSIONINFOEX osvi;

ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

// 윈도우 NT 4.0 SP6 이하 윈도우라면 FALSE == 윈도우 버전이 낮음
if(!GetVersionEx((OSVERSIONINFO *)&osvi))
{
    // OSVERSIONINFO로 재설정
    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
}

switch(osvi.dwPlatformId){
    // 윈도우즈 9x 기반의 운영체제인 경우
    case VER_PLATFORM_WIN32_WINDOWS:
        /*
        if(osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 0) version = "window 95";
        else if(osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 10) version = "window 98";
        else if(osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 90) version = "window ME";
        */
        break;
}
 

 

[Major/Minor Chart]

 

[표 출처]

https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms724833(v=vs.85).aspx

728x90
반응형
728x90
반응형

1. 코드

// 현재 프로세스 비트 구분
#if defined(_WIN64)
    return TRUE;
#else
    return FALSE;
#endif

// 운영체제 = 64bit, 프로세스 = 32bit 일 경우 TRUE
BOOL IsCurrentProcessWow64()
{
    BOOL bIsWow64 = FALSE;
    typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS)(HANDLE, PBOOL);
    LPFN_ISWOW64PROCESS fnIsWow64Process;

    fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
    if (!fnIsWow64Process)
        return FALSE;

    return fnIsWow64Process(GetCurrentProcess(), &bIsWow64) && bIsWow64;
}

// 프로세스의 bit에 상관없이 운영체제의 비트 확인 가능
// 프로세스 = 64 -> os = 64
// 프로세스 = 32 -> WOW64 조사 -> TRUE = 64, FALSE = 32
BOOL Is64BitWindows()
{
    if (IsCurrentProcess64bit())
        return TRUE;

    return IsCurrentProcessWow64();
}

 

2. 참고

 - WOW64 (Windows On Windows 64-bit) : 64비트 운영체제에서 32비트 프로세스가 동작하게 만들어주는 서브시스템

 

3. 출처

 - http://www.devpia.com/Maeul/Contents/Detail.aspx?BoardID=51&MAEULNO=20&no=8704&page=3

728x90
반응형
728x90
반응형

1. 설명

- 현재 프로세스 정보

DWORD pid;
HANDLE hdl;

pid = GetCurrentProcessId();
hdl = OpenProcess(MAXIMUM_ALLOWED, FALSE, pid);

 

- 다른 프로세스 정보

HWND hWnd;
DWORD pid;
HANDLE hdl;

// 윈도우 이름으로 윈도우 핸들을 얻음.
hWnd = FindWindow(NULL, L"Window Name");
// hWnd의 pid값 구하기
GetWindowThreadProcessId(hWnd, &pid);
// 얻은 pid값으로 대상 프로세스의 핸들을 얻음.
hdl = OpenProcess(MAXIMUM_ALLOWED, FALSE, pid);
 

- 여러 프로세스 정보

#include "tlhelp32.h"

HANDLE hSnapShot = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe;

// 스냅샷
pe.dwSize = sizeof( PROCESSENTRY32 );
hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL );

// 프로세스 목록의 끝까지 반복
Process32First(hSnapShot, &pe);
do
{
    // PROCESSENTRY32 구조체를 이용하여 프로세스 정보 획득 가능
}
while(Process32Next(hSnapShot, &pe));

CloseHandle(hSnapShot);

 

2. PROCESSENTRY32 구조체
typedef struct tagPROCESSENTRY32 {
  DWORD     dwSize;
  DWORD     cntUsage;
  DWORD     th32ProcessID; // PID
  ULONG_PTR th32DefaultHeapID;
  DWORD     th32ModuleID;
  DWORD     cntThreads; // 프로세스 내부의 쓰레드 개수
  DWORD     th32ParentProcessID; // 부모 프로세스 ID
  LONG      pcPriClassBase;
  DWORD     dwFlags;
  TCHAR     szExeFile[MAX_PATH]; // 프로세스 실행 경로
} PROCESSENTRY32, *PPROCESSENTRY32;
 
728x90
반응형
728x90
반응형

1. 코드

HANDLE hFile;
OPENFILENAME OFN;
TCHAR lpstrFile[MAX_PATH] = L"";

// 필요한 부분에 아래 내용 사용
memset(&OFN, 0, sizeof(OPENFILENAME));

OFN.lStructSize = sizeof(OPENFILENAME);
OFN.hwndOwner=hWnd;
OFN.lpstrFilter=TEXT("ALL FILE(*.*)\0*.*\0");
OFN.lpstrFile=lpstrFile;
OFN.nMaxFile=MAX_PATH;

if(GetOpenFileName(&OFN) != 0) 
{
    hFile = CreateFile(OFN.lpstrFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hFile == INVALID_HANDLE_VALUE)
    {
        MessageBox(hWnd, L"파일 안열림", L"Error", MB_OK);
    }
    else
    {
        MessageBox(hWnd, L"파일 열림", L"Open", MB_OK);
        CloseHandle(hFile);
    }
}

 

728x90
반응형
728x90
반응형

Property -> Linker -> Manifest File -> UAC Execution Level

728x90
반응형
728x90
반응형

ATL90.dll

[Project] Property -> General -> Use of ATL -> Static Link to ATL

 

 

MSVCR90.dll

[Project] Property -> C/C++ -> Code Generation -> Runtime Library -> /MT

단, Debug로 컴파일 할 경우에는 /MTd

 

728x90
반응형
728x90
반응형

1. 코드

#include<windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_Inst;
LPCWSTR lpszClass = TEXT("Window");

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszClassParam, int nCmdShow)
{
    HWND hWnd;
    MSG Message;
    WNDCLASS WndClass;
    g_Inst = hInstance;

    WndClass.cbClsExtra=0;
    WndClass.cbWndExtra=0;
    WndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
    WndClass.hCursor=LoadCursor(NULL, IDC_ARROW);
    WndClass.hIcon=LoadIcon(NULL, IDI_APPLICATION);
    WndClass.hInstance=hInstance;
    WndClass.lpfnWndProc=WndProc;
    WndClass.lpszClassName=lpszClass;
    WndClass.lpszMenuName=NULL;
    WndClass.style=CS_HREDRAW|CS_VREDRAW;

    RegisterClass(&WndClass);

    hWnd=CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW
    	, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT
        , NULL, (HMENU)NULL, hInstance, NULL);
    ShowWindow(hWnd,nCmdShow);

    while(GetMessage(&Message, NULL, 0, 0))
    {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return (int)Message.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{

    switch(iMessage)
    {
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hWnd, iMessage, wParam,lParam);
}

 

728x90
반응형

+ Recent posts