C++ 17로 윈도우에서 프로그램을 만들면서 폴더와 파일을 구분해야하는 경우가 생겨 아래 코드를 찾게되었다.
찾은 코드는 테스트 시 작동에는 문제가 없었지만 문자열을 LPCWSTR (WCHAR *)로 받아야 한다는 사소한 단점이 있었다. 혹시 String 클래스를 사용해 입력받은 경로가 폴더인지 파일인지 구분할 수 있는 방법이 있는지도 찾아봤지만, 검색을 해봐도 찾지 못했다..
intisFolder(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)
{
return1;
}
return0;
}
return-1;
}
여러 개 서버의 경우로 우측 check 버튼을 통해 체크할 경우 다양한 현재 상태가 출력된다.
여러 개 서버를 각자 체크하기
주기적인 상태 체크
위에서 체크 기능이 잘 동작하는걸 봤지만 프로그램이 내가 원하는 역할을 수행하게 하려면 입력한 정보를 일정 주기로 반복하여 체크할 수 있어야 한다. Repeat Check는 Timer를 활용하여 일정 주기(interval)로 ssh 연결을 시도한다.
후기
간단한 기능을 제공하는 서버긴하지만 확장할 수 있는 내용들이 많다.
프론트에서는 html, css, js 파일을 분리하여 관리/ 확장성을 높이거나 React.js를 활용하여 더 깔끔하고 모던한 웹 사이트로 개선할 수 있고, 백엔드에서도 보안(openssl, oauth)과 디자인 패턴(MVC)을 적용하고 NoSQL DB와 연동해 볼 수 있을 듯 하다. 또한 DevOps 관점에서는 컨테이너화 시켜 쿠버네티스에 배포하고 GitOps를 적용해볼 수도 있다.
퇴근 후와 주말에만 가끔식 하는 프로젝트라 진행은 더디지만 하나씩 공부해서 Pluto에도 적용해보겠다.
FastAPI는 Python 3.6이상의 API를 빌드하기 위한 빠르고 현대적인 웹 프레임워크이다. 웹 부분을 위한 Starlette와 데이터 부분을 위한 Pydantic 덕분에 NodeJS와 Go에 대등할 정도로 높은 성능을 가지고 있으며, 빠른 코드 작성이 가능하다. 직관적이고 쉬운 코드로 인해 휴먼 에러가 약 40%(내부 개발팀 테스트 기준) 감소했다고 한다. 또한 API에 완전히 호환되는 개방형 표준을 기반으로 하여 Swagger(OpenAPI)와 json Schema를 지원한다.
개인적으로는 Flask와 문법이 비슷하지만 기본적으로 추가된 기능이 많고 Django보다는 자동화(?)는 덜 되었지만 훨씬 가볍고 빠르다는 느낌을 받았다.
공식 문서에서는 위 명령어로 방법을 설명하지만, pip로 "uvicorn[standard]"을 install 하면 에러가 발생하고 명령어로 쳐봐도 당연히 실행되지 않는다. 그래서 아래처럼 python 코드 상에서 실행하는 방법을 찾았다. 실행에 문제가 없고 디버깅 측면에서도 더 좋다고 생각한다.
import os
workspace = "C:\\Users\\Hwan\\Desktop\\TestDir"
keywords = ["pass"]
list_result = []
for file in os.walk(workspace):
ifany([Trueif keyword in file[0] elseFalsefor keyword in keywords]): continuefor x in file[2]:
list_result.append(file[0] + "\\" + x)
print(list_result)
코드를 실행하면 입력받은 경로 밑의 (경로나 파일 명에 pass가 포함되지 않은) 모든 파일을 리스트에 담아준다.
이제 입력한 경로 내의 모든 동일한 파일을 찾도록 아래의 코드를 추가했다.
dict_hash = {}
for file in list_result:
try:
dict_hash[my_hash(file)].append(file)
except:
dict_hash[my_hash(file)]= [file]
딕셔너리의 키로 파일 내용을 해싱한 값을 줬고, 값으로는 해당 해쉬값을 갖는 파일명 리스트를 담았다.
아래는 전체 코드이다.
import os
import hashlib
defmy_hash(file):withopen(file, 'rb') as f:
read_data = f.read()
hash = hashlib.md5()
hash.update(read_data)
hexSHA512 = hash.hexdigest()
hashValue = hexSHA512.upper()
return hashValue
workspace = "C:\\Users\\hwan\\Desktop"
keywords = ["pass"]
list_result = []
for file in os.walk(workspace):
ifany([Trueif keyword in file[0] elseFalsefor keyword in keywords]): continuefor x in file[2]:
list_result.append(file[0] + "\\" + x)
dict_hash = {}
for file in list_result:
try:
dict_hash[my_hash(file)].append(file)
except:
dict_hash[my_hash(file)]= [file]
for x in dict_hash:
iflen(dict_hash[x]) > 1:
print(x, dict_hash[x])
파이썬은 자료형이 명시되지 않기 때문에(언어의 동적 특성) 파이썬에서의 문서화는 매우 중요하다.
문서화를 위해서 파이썬은 docstring이라는 문서화 문자열 기능을 제공하고 """ 문서화 문자열입니다. """ 처럼 표현할 수 있다. docstring은 모듈, 클래스, 함수에 붙일 수 있고 각 객체의 __doc__ 속성에 접근하여 문자열을 가져올 수 있다. 어떤 대상에 docstring을 작성할 지에 따라 적어야하는 내용들도 달라진다.
모든 하위 디렉토리 내부 파일까지 포함된 전체 파일의 목록을 가져오기 위해 파이썬으로 코드를 작성하려고 한다.
그런데 디렉토리(또는 폴더) 내부에 하위 디렉토리가 있다면?
그리고 그 디렉토리 내부에 하위 디렉토리가 있는지 없는지 모른다면?
코드를 어떻게 작성해야 될까?
먼저 파이썬에서 경로를 다루기위해 os 패키지를 import 했다.
그리고 아래와 같은 디렉토리 구조(주석)를 만들어 둔 뒤 하위 디렉토리 어딘가에 FindMe.txt 파일을 숨겨두었다!
#Find_TMP#┣━1234#┗━TEST# ┗━TEST1import os
os 모듈 내부의 메소드들을 사용해서 FindMe.txt 파일이 어디에 있는지 찾아보자.
os.listdir() 메소드를 사용하면 입력한 폴더 내부에 존재하는 파일과 디렉토리를 모두 가져올 수 있다.
deffindFiles(root):
files = os.listdir(root)
for file in files:
path = os.path.join(root, file)
if os.path.isdir(path):
print("Directory :", path)
else:
print("File :", path)
findFiles("C:/Users/Hwan/Desktop/Find_TMP")
Find_dir의 하위 디렉토리인 1234와 TEST는 잘 찾았지만 우리가 원하는 FindMe.txt 파일은 찾지 못했다.
1234와 TEST 내부까지 확인하려면 그리고 내부에 우리가 알지 못하는 디렉토리가 있다면 어떻게 해야될까?
재귀를 이용한 디렉토리 탐색
디렉토리 내부에 디렉토리, 그리고 그 내부에 디렉토리 그리고 또 하위 디렉토리.. 만들어둔 구조를 보고 있으면 뭔가 떠오르는 것 같다..!
재귀를 사용해서 모든 경로를 확인해보자.
deffindFiles_recursion(path):for x in os.listdir(path):
subdirectory_path = f"{path}/{x}"print(subdirectory_path)
if os.path.isdir(subdirectory_path):
findFiles_recursion(subdirectory_path)
findFiles_recursion("C:/Users/Hwan/Desktop/Find_TMP")
우리가 원하던 FindMe.txt 파일을 드디어 찾았다!
위 구조에선 숨겨져있던 Secret 디렉토리 내부까지 확인이 되었다.
하지만 만약 폴더의 구조가 매우 깊다면 재귀를 사용한 함수는 Runtime Error를 뱉는다.
import sys
sys.setrecursionlimit(10000)
물론 이런 코드를 추가하면 좀 더 깊게 탐색할 수는 있지만 스택을 사용하면 더 좋을 것 같다.
코드로 작성해보자.
deffindFiles_stack(path):
list_stack = [path]
while list_stack.__len__():
cur_path = list_stack.pop()
for x in os.listdir(cur_path):
_path = os.path.join(cur_path, x)
if os.path.isdir(_path):
list_stack.append(_path)
print(_path)
findFiles_stack("C:/Users/Hwan/Desktop/Find_TMP")
탐색 순서는 조금 다르지만 이제 깊이에 상관없이 FindMe.txt를 찾을 수 있게 되었다!
os.walk
그런데 사실 파이썬의 os 모듈에서는 위에서 고민했던 내용들을 한번에 해결해줄수 있는 메소드를 제공한다.
for x in os.walk("C:/Users/Hwan/Desktop/Find_TMP"):
print(x)
JWT는 웹 표준(RFC 7519) 으로 두 개체 사이에서 JSON을 사용하여 정보를 안전성 있게 전달해준다.
웹에서 로그인을 하거나 인증을 받게되면 보통 세션을 사용하여 인증 정보를 기록하는데, JWT는 토큰 내부의 Signature에 해당 정보를 기록한다. JWT를 사용할 경우 인증을 위해 웹 브라우저의 세션 공간을 사용하지 않고 인증 여부를 알 수 있기 때문에 확장성이 좋다. 또한 생성 시 권한을 지정할 수 있어 각 토큰별로 기능을 제어할 수 있고 플랫폼에 종속적이지 않다.
JWT의 구조는 Header.Payload.Signature로 나뉘어져 있다. Header는 토큰의 typ(해당 토큰의 타입)과 alg(해싱 알고리즘)을 Payload는 토큰에 담을 정보를 그리고 마지막 Signature는 Header와 Paylaod의 Base64 인코딩 값을 시크릿 키와 함께 다시 한번 해싱한 후 인코딩한 값을 가진다.
파이썬의 PyJWT 패키지를 사용해서 직접 토큰을 만들고 검증해보자.
구현
파이썬에서의 jwt 사용은 아래처럼 PyJWT를 설치하거나 requirements.txt에 작성해두고 사용할 수 있다.
Java, C#, PHP, RUST, GO, JavaScript, Python, Ruby, C++ 등 프로그래밍 언어
Version Control Systems
코드의 형상 관리와 버전 관리를 위해 Git 또는 Subversion(SVN) 을 사용한다.
각 도구의 차이점과 특징, 사용법 등을 알아두면 좋을 것 같다.
Basic Usage of Git
Repo hosting services : GitHub, GitLab, Bitbucket
Databases
SQL 쿼리를 사용하는 RDB(관계형 데이터베이스)와 NoSQL DB의 차이를 알고 각 디비를 프로그램에서 연동해보면 좋을 듯 하다. 테이블 설계와 최적화, 샤딩 등을 추가로 해보면 좋다.
Relational Databases : PostgreSQL, MySQL, MariaDB, MS SQL, Oracle
NoSQL : MongoDB, RethinkDB, CouchDB, DynamoDB
More about Databases : ORMs, ACID, Transactions, N+1 Problem, Database Normalization, Indexes and how they work Data Replication, Sharding Strategies, CAP Theorem
Learn about APIs
REST와 GraphQL 차이와 사용법을 다른 글에서 작성 중이다.
Swagger와 JSON, JWT는 기본적으로 알아두어야된다고 생각한다.
HATEOAS
Open API Spec and Swagger
Authentication : twilio, opt, e-mail, JWT
REST
Graph QL : Apollo, Relay Modern
JSON APIs
SOAP
Caching
CDN과 Redis에 대해서 알아볼 예정이다.
Redis는 In-Memory DB로 알고있는데 Caching으로 분류된게 의문이지만 일단 넣어두었다.
CDN
Server Side : Redis, Memcached
Client Side
Web Security Knowledge
해싱, SSL/TLS, CA인증서 등에 대해서 알아보자. 좀 더 확장하면 RSA, ECC 등의 암호화 알고리즘을 공부해봐도 좋을 것 같다.(SSL 안에서 사용된다.)
Hashing Algorithms : MD5 and why not to use it, SHA Family, scrypt, bcrypt
# requirements.txt : requests_html, yahoo_fin 추가import sys
import subprocess
try:
from yahoo_fin.stock_info import *
except:
subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--upgrade', 'pip'])
subprocess.check_call([sys.executable, '-m', 'pip', 'install', '-r', 'requirements.txt'])
from yahoo_fin.stock_info import *
if __name__ == '__main__':
ticker = "JEPI"if ticker in tickers_dow():
print("dow")
if ticker in tickers_other():
print("other")
if ticker in tickers_sp500():
print("sp500")
if ticker in tickers_ftse250():
print("ftse250")
if ticker in tickers_ftse100():
print("ftse100")
if ticker in tickers_ibovespa():
print("ibovespa")
if ticker in tickers_nasdaq():
print("nasdaq")
print(get_data(ticker))
후기
yfinance, FinanceDataReader 등을 사용봤지만 전체 티커의 목록을 얻어오거나 실시간 데이터를 얻기엔 불편한 점이 있었다. yahoo_fin에선 여러 소스에서 스크랩과 api를 활용하여 데이터를 받아오기 때문에 실시간 데이터와 전체 목록을 얻어오는 기능이 필요하다면 유용하게 사용할 수 있다. 하지만 내부 코드를 보면 위키피디아 등에서도 데이터를 스크랩해오는데.. 누구나 수정할 수 있는 위키피디아의 특성 상 항상 신뢰하긴 어려울 수도 있을 것 같다.
function tar_xvzf() {
local File=$1.tar.gz
local Dir=`echo $File | sed 's/.tar.gz//g'`
if [[ ! -d $Dir ]]; then
#echo$Dir" - not exist"
if [[ ! -f $File ]]; then
#echo$File" - exist"
echo "<Start Release - $File >"
tar -xvzf $File
echo "<End Release - $File >"
fi
fi
#echo""
}
tar_xvzf "test*"
만약 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 프로토콜에 맞춰서 모니터로 데이터를 보내주어야 한다.
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 함수가 눈에 띄였다.
SetVCPFeature 함수는 위와 같은 형태로 되어 있다. Enum 함수 등으로 얻어온 모니터의 핸들을 구해서 넣고 원하는 VCP Code 값을 넣으면 될거 같다. 이제 변경을 원하는 모니터와 원하는 VCP 기능을 전달하는 방법은 알았다.
그런데 우리의 상황처럼 1개의 모니터에 연결된 케이블이 여러 개라면 원하는 케이블은 어떻게 설정해야 되는 걸까?
이 방법을 알기 위해 엄청난 검색을 했는데.. 방법은 의외로 간단했다. 3번째 인자인 dwNewValue에 해당 케이블의 값을 넣어주면 된다! 그렇다면 그 값은 어떻게 알 수 있을까??
다시 엄청난 삽질이 시작됐다.
GetVCPFeatureAndVCPFeatureReply() 함수
온갖 키워드와 방법으로 검색해 알게된 방법은 GetVCPFeatureAndVCPFeatureReply() 함수를 사용하는 것 이었다. 그런데 위의 MSDN 이미지의 7번을 자세히 보면 이미 설명이 잘 되어 있다. MS에선 처음부터 전부 다 알려주었지만 내가 이해를 못했을 뿐이다. (멍청하면 손발이 고생한다..)
함수를 살펴보면 먼저 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들을 잘 조합하면 자동으로 구할 수 있을 것 같았지만, 더 삽질하면 주말동안 끝내지 못할 것 같아 모니터들의 값을 알아낸 뒤 그냥 하드코딩했다.
개발 환경은 Visual Studio Community 2022이고 프로젝트는 아래 이미지처럼 구성했다.
jsoncpp.cpp는 c++에서 json 파싱을 위해 나중에 추가한 라이브러리 코드이고 test.cpp는 테스트 함수를 작성해둔 파일이라 생략했다. 그리고 사실 EnumDisplayMonitors 함수는 MonitorEnumProc라는 콜백 함수를 내부에서 호출하기 때문에 SetVCPFeature 함수를 사용하기 위해서 콜백 함수들을 작성해야 하는데, 해당 내용들은 나중에 시간되면 추가로 작성해보겠다.
코드는 돌아가게만 만들어 두었기 때문에 정리도 잘 안되어 있지만 이것도 나중에 정리하기로 하고 일단 올려보겠다.
function.h 코드
#pragma once#pragmawarning(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"usingnamespace std;
// 구조체typedefstruct _MONITORPARAM
{
LPCWSTR szPhysicalMonitorDescription;
BYTE VCPcode;
int source;
BOOL bPowerOn;
int curVal;
} MONITOR_PARAM, * PMONITOR_PARAM;
typedefstruct _GETMONITORINFO
{
LPCWSTR szPrimaryMonitorDescription;
LPCWSTR szSecondMonitorDescription;
int curVal;
int curVal_second;
} GET_MONITOR_INFO, * GET_PMONITOR_INFO;
// 얘를 모니터 개수만큼 포인터 배열로 만들어서 _getCurrentValue에 전달하면, 모니터 돌면서 정보를 채워줌.typedefstruct _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 함수voidload_json(LPCWSTR file_path, PMONITOR mon, int monNum);
voidsave_json(LPCWSTR file_path, int monNum, Json::Value *monitor);
프로그램을 시작할 때 동일 경로 상의 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/
CallBack 함수는 일반적인 함수와 비슷하지만, 호출되는 시점이 시스템(이벤트)에 의해 결정된다는 차이가 있다.
윈도우 API를 공부하면 가장 처음 배우는 Window를 띄우는 코드(링크)에서도 콜백 함수인 윈도우 프로시져를 볼 수 있는데, 해당 콜백함수(프로시져)는 WinMain에서 WndClass.lpfnWndProc=WndProc; 로 WNDCLASS 구조체에 등록된 뒤에 RegisterClass(&WndClass); 되어진 이후 따로 호출하지 않는다.