블로그 이미지
devtang
Instagram : @taebr0 devtaehyeong@gmail.com

calendar

1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

Notice

2024. 2. 7. 19:54 Mac OS Application

회사 제품이 원격 관련 제품이라 macOS에서 앱의 [화면 기록] 권한이 필요하다.

 

업무를 하다가 발생한 이슈인데, 좀 얼탱이 없어서 정리겸 작성한다.

14.3에서도 정상적으로 문제없이 돌아가던 권한 체크 방식이 macOS 14.4 beta 2에서 갑자기 말썽이라는 것이다.

 

Google Chrome Remote Desktop 으로 제어시에도 마찬가지로 Host단에서 허용을 안해주면 제어가 불가능하다

 

위의 사진과 같이 앱의 화면 기록 권한 (macOS 14부터는 화면 및 시스템 오디오 녹음으로 변경)이 있음에도 불구하고

시스템 OS에서 발생하는 팝업에서 허용을 눌러주지 않으면 제어가 불가능하다는 것이다.

 

우선 기존 화면 권한을 확인하는 로직은 대부분의 macOS개발자들이 아마 아래와 같이 사용했을것이다.

#import <AVFoundation/AVFoundation.h>

BOOL isScreenRecordingPermissionEnabled() {
    NSDictionary *options = @{(__bridge NSString *)kCGDisplayStreamShowCursor: @NO};
    CGDisplayStreamRef displayStream = CGDisplayStreamCreate(CGMainDisplayID(), 1, 1, kCVPixelFormatType_32BGRA, (__bridge CFDictionaryRef)options);
    
    CGDisplayStreamStart(displayStream);
    CGDisplayStreamStop(displayStream);
    
    BOOL isPermissionEnabled = (CGDisplayStreamIsActive(displayStream) == true);
    
    CGDisplayStreamRelease(displayStream);
    
    return isPermissionEnabled;
}

// 사용 예시
BOOL isPermissionEnabled = [self isScreenRecordingPermissionEnabled];
if (isPermissionEnabled) {
    NSLog(@"화면 기록 권한이 활성화되어 있습니다.");
} else {
    NSLog(@"화면 기록 권한이 비활성화되어 있습니다.");
}

(뤼튼에게 물어봐)

 

CGDisplayStream 계열의 API 를 사용하고 있다.

14.0 부터 deprecated 된대서 1차 싸함을 느꼈다.

 

 

후 .. 14.4 beta2 Release Note에 해당 api 관련해서 뭐 바뀐것도 없다고 생각했었는데 14.0 release note 보니까 

Screen Sharing 관련하여 CGDisplayStream APIs 사용시 추가적인 알림을 표시한다고 Release Note에 명시되어있더라

그럼 14부터 안되야지 왜 14.3까지는 되고 14.4 beta2 에서만 안되는지 이해가 안갔다.

 

https://developer.apple.com/documentation/macos-release-notes/macos-14-release-notes#ScreenSharing

 

macOS Sonoma 14 Release Notes | Apple Developer Documentation

Update your apps to use new features, and test your apps against API changes.

developer.apple.com

 

 

해당 이슈로 골치아픈게 나뿐만 아니라 분명 다른 나라에도 있을거라고 생각하고 해당 이슈 관련해서 좀 찾아봤는데

진짜로 있다요

https://support.jumpdesktop.com/hc/en-us/community/posts/23796182773133

https://forums.macrumors.com/threads/macos-14-4-beta-1-bug-fixes-changes-and-more.2417788/

 

macOS 14.4 Beta 1 - Bug fixes, changes, and more

Release Date ― January 29, 2024 Build Number ― 23E5180j Apple Silicon System Firmware Version ― 10151.100.738.0.1 T2 BridgeOS System Firmware Version ― Pending (BridgeOS: Pending) New Features & Changes New Emojis 🐦‍🔥🙂‍↕️🙂‍↔

forums.macrumors.com

 

닉네임 unlockman212 로 바꿔야할듯

 

일단 나부터 급하니 화면 권한 얻는 방법에대해서 엄청나게 찾아봤다.

 

https://developer.apple.com/videos/play/wwdc2019/701/?time=1007 

 

Advances in macOS Security - WWDC19 - Videos - Apple Developer

We are on a journey to continuously improve macOS security, with a particular focus on preventing malware and protecting user data. Join...

developer.apple.com

 

WWDC19 에서도 나왔던 얘긴데, 간략히 설명하자면 사용자가 화면 녹화용 앱을 사전 승인하지 않는 한 window name과 공유 상태를 알수 없다.

 

따라서 아래와 같이 권한을 확인하는 방법도 있다는걸 적어놓도록 하겠슴다

 

- (BOOL)ScreeningRecordPermissionCheck {
    if (@available(macOS 10.15, *)) {
    // CGWindowListCopyWindowInfo 함수를 사용하여 현재 화면에 표시되는 창에 대한 정보를 가져와서
	// kCGWindowListOptionOnScreenOnly 옵션을 사용하여 화면에만 표시되는 창에 대한 정보만 가져오도록 설정하고, kCGNullWindowID를 사용하여 모든 창을 대상
        CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
        NSUInteger numberOfWindows = CFArrayGetCount(windowList);
        NSUInteger numberOfWindowsWithInfoGet = 0;
        for (int idx = 0; idx < numberOfWindows; idx++) {

            NSDictionary *windowInfo = (NSDictionary *)CFArrayGetValueAtIndex(windowList, idx);
            NSString *windowName = windowInfo[(id)kCGWindowName];
            NSNumber* sharingType = windowInfo[(id)kCGWindowSharingState];

		// 현재 창의 이름(windowName)이 존재하거나 공유 상태(sharingType)가 kCGWindowSharingNone이 아닌 경우, numberOfWindowsWithInfoGet 변수를 증가
            if (windowName || kCGWindowSharingNone != sharingType.intValue) {
                numberOfWindowsWithInfoGet++;
            } else {
                NSNumber* pid = windowInfo[(id)kCGWindowOwnerPID];
                NSString* appName = windowInfo[(id)kCGWindowOwnerName];
            }
        }
        CFRelease(windowList);
        // 즉 모든 창 정보를 성공적으로 받은 경우에는 YES를 반환
        if (numberOfWindows == numberOfWindowsWithInfoGet) {
            return YES;
        } else {
            return NO;
        }
    }
    return YES;
}

 

 

추가적으로 CGRequestScreenCaptureAccess() 함수도 필요할 수 있으니 알아서 잘 조합해서 사용하면 될 거 같다.

 

 

*화면 권한 사용하는 프로그램 몇개 다운받아서 테스트해봤는데

Google Chrome Remote Desktop은 호스트 단에서 허용 안해주면 화면 안나오고 TeamViewer는 잘되네요. (2024/02/07)

posted by devtang
2021. 3. 26. 15:11 개인 프로젝트

youtu.be/llFMVTj1pzg

실행영상

안녕하세요.

이번 프로젝트에서는 활성화 되어있는 카카오톡 채팅방에 원하는 메세지를 보낼 수 있는 프로그램을 제작하였습니다.

대화상자 기반 프로젝트를 전에도 몇번 진행해보면서 완벽하고 잘하진 않지만 사용할만한 프로그램 제작을 생각하다

카카오톡 전송 프로그램을 생각해보았습니다.

 

보통 대학교나 회사에서 단체로 공지를 보내야할 때 일일히 대화방에 복사+붙여넣기가 귀찮은 경우를 많이 보았기 때문에 만들어보게 되었습니다.

 

카카오톡에 메세지나 파일을 보내기 위해서는

카카오톡의 프로세스에 접근해서 키보드를 후킹하는 방식으로 진행하였습니다.

 

Visual Studio 에서는 Spy++라는 프로그램을 통해 현재 개인 컴퓨터내에서 작동하는 프로세스, 스레드 등을 확인 할 수 있습니다.

 

Spy++ 프로그램을 통해 카카오톡의 프로세스는 다음과 같습니다.

프로세스의 클래스ID, 캡션을 확인하여 카카오톡이 실행중인지 확인하는 함수에서 이용할 수 있습니다.

다음은 함수 코드중 일부입니다. FindWindow , FindWindowEx 함수를 통해서 카카오톡의 정상 구동을 확인하였습니다.

 

문자를 보내기 위해서는 카카오톡 대화상자의 프로세스를 확인해야합니다.

위의 사진과 같이 빨간색 테두리가 대화방의 부모클래스이며

노란색(RICHEDIT50W) 와 초록색Edit가 자식 클래스입니다.

 

카카오톡 메세지를 보내기 위해서는 프로세스에서 활성화 되어있는 대화방을 모두 찾고

각 대화방의 Edit Control 클래스에 보낼 메세지를 입력하는 방식으로 진행하여야 합니다.

 

카카오톡 대화방에 메세지를 전송하는 함수입니다.

대화방의 클래스 이름은 #32770이며 찾아낸 카톡방에 SendMessage , PostMessage를 이용하여

원하는 메세지를 입력시킨후 VK_RETURN이라는 가상 키보드 입력 이벤트를 작동시켜 전송하게 되는것입니다.

사용자가 메세지를 입력하고 엔터키를 입력하는 과정을 컴퓨터 프로세스를 통해 자동으로 동작하게 만들었습니다.

파일첨부기능역시 이를 약간 응용하면 됩니다.

카카오톡 대화방에서 Ctrl + T를 입력하면 파일 탐색기가 실행이되며 파일을 선택하여 첨부할 수 있습니다.

이때 위의 탐색기 창에서 파일 이름란에 파일의 경로를 입력하고 엔터를 누르면 첨부가 되는것을 확인하였습니다.

 

따라서 결론적으로 파일 전송을 위해서는

1. 대화방 프로세스에 접근한다.

2. Ctrl + T 키를 가상 키보드 이벤트 처리하여 입력한다.

3. 파일 탐색기 창의 Edit Control에 정해진 파일의 경로를 입력한다.

4. 가상 키보드 이벤트 처리를 통해 엔터키를 입력한다.

 

아래는 파일 첨부했을 때 전송 하는 함수입니다.

처음에는 메세지 전송함수와 비슷하게 카톡 대화방의 프로세스를 찾아냅니다.

이후에 Ctrl + T를 입력하는 가상 키보드 이벤트를 진행한 후에

열린 탐색기 창의 프로세스를 FindWindow 를 통해 탐색기의 Edit Control을 찾아낸 후 경로를 입력해줍니다.

이때 Sleep을 사용하지 않으면 탐색기가 마저 열리기도 전에 아래 코드가 실행되기 때문에 오작동되는 경우가

발생하여 사용하였습니다.

 

추가적으로 INFO 버튼을 클릭하면 저의 인스타그램과 티스토리에 접속할 수 있도록 하이퍼링크를 연결해보았습니다.

만들면서 귀찮고 어렵다고 생각하기 보단 모르던 기능들을 하나하나씩 찾아서 꾸며내는 재미로 만들었던 것 같습니다.

 

이번 프로젝트에서는 아쉽지만 카톡 대화방이 열려있어야하며, 특정 상대에게만 보내는 등의 기능을 구현하지 않았습니다.

 

다음에도 기회가 된다면 여기에 조금더 추가해서 다양한 기능을 만들어보았으면 좋겠습니다.

 

posted by devtang
2021. 3. 8. 17:35 MFC Programming

안녕하세요. 이번시간에는 지난 시간에 작성한 예제를 기반으로 Check Box 와 Radio Button 을 추가한 예제를 만들어 보도록 하겠습니다.

ultrakid.tistory.com/44

 

[MFC] Dialog 기반 프로젝트 생성 및 Button 과 Edit Control 사용

안녕하세요. 본 게시물에서는 대화상자 기반 MFC 프로젝트를 생성하고 위의 사진과 같이 간단한 예제를 만들 것입니다. 본 설명은 Visual Studio 2015 기준으로 진행하고 있습니다. 우선 MFC 프로젝트

ultrakid.tistory.com

 

지난번 진행했던 코드에 이어서 진행하기 때문에 추가적으로 변동된 사항만 넣도록 하겠습니다.

 

리소스 편집기를 통해서 도구 상자에서 Radio Button , Check Box, Group Box를 추가적으로 선택하여

위의 사진처럼 배치를 진행하면 됩니다.

추가적으로 변수 추가 할 때 필요한 속성으로는 아래 테이블에 맞게 진행하면 됩니다.

(굵은 글자체가 전 게시물과 비교해서 추가된 컨트롤 변수들입니다.)

컨트롤 ID 기타 속성
Edit Control IDC_EDIT_NAME  
Edit Control IDC_EDIT_INFO Multiline : True, Read Only : True,
Horizontal Scroll : True
Vertical Scroll : True
Edit Control IDC_EDIT_INTEREST  
Group Box IDC_STATIC Caption : 이름을 입력하세요
Group Box IDC_STATIC Caption : 성별을 선택하세요
Group Box IDC_STATIC Caption : 관심분야를 선택하세요
(예 : 앱 개발 , 데이터 베이스)
Radio Button IDC_RADIO_FEMALE Caption : 여성, Group : True
Radio Button IDC_RADIO_MALE Caption : 남성, Group : False
Button IDC_BUTTON_ENLIST Caption : 가입

Radio Button 을 만들 때 같은 그룹에 속하는 Radio Button 중 가장 먼저 만들어진 라디오 버튼의 Group 속성을 True 로 설정해야 합니다. 이렇게 하게 되면 그 이후에 만들어진 라디오 버튼이 모두 자동으로 같은 그룹에 속하게 됩니다.

 

ID 자료형 변수 이름 컨트롤 변수
IDC_EDIT_NAME CString m_strName 체크 해제
IDC_EDIT_INFO CString m_strInfo 체크 해제
IDC_EDIT_INTEREST CString m_interest 체크 해제
IDC_RADIO_FEMALE int m_Sex 체크 해제

같은 그룹에 속하는 Radio Button 을 위한 변수를 만들 때는 정수 변수 하나면 됩니다.

Radio Button 은 같은 그룹에서 하나만 선택이 가능하기 때문에 선택되면 변수에는 선택된 버튼의 인덱스 번호가 저장됩니다.

Radio Button 가운데 Group 속성이 True 인 컨트롤에 대해서만 변수를 만들면 되는것입니다.

 

이제 TestDlg.cpp 파일로 넘어와서 DoDataExchange()함수에 Radio Button 과 Edit Control 변수를 추가해줍니다.

위의 IDC_EDIT_NAME 과 IDC_EDIT_INFO 는 전 게시물에서 추가하였고, 이번에 만든 두 컨트롤에 대해 새로운 함수를 추가하였습니다.

 

void CTestDlg::OnBnClickedButtonEnlist()
{
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
	LPCTSTR gender[2] = { _T("여성"),_T("남성") };// TCHAR 문자열에 대한 포인터 상수 자료형
	UpdateData();									
	if (m_strName.IsEmpty())					
		return;
	m_strInfo.Append(m_strName);		
	m_strInfo.AppendChar(_T(':'));		// 성별과 관심 분야 정보를 추가할 때 구분하기 위해서 : 문자를 추가함
	m_strInfo.Append(gender[m_Sex]);	// m_Sex에 저장된 값을 이용하여 성별 정보 문자열을 m_strInfo 에 추가한다.
	m_strInfo.AppendChar(_T(':'));		// 성별과 관심 분야 정보를 추가할 때 구분하기 위해서 : 문자를 추가함
	m_strInfo.Append(m_interest);		// 관심 분야 정보를 m_interest 에서 가져와서 추가함
	m_strInfo.Append(_T("\r\n"));	
	m_Sex = 0;				// 가입 버튼을 완료한후 선택 Radio Button 을 초기화함
	m_strName.Empty();
	m_interest.Empty();
	UpdateData(false);						
}

OnBnClickedButtonEnlist() 함수에도 마찬가지로 주석이 달린 부분을 추가해 줍니다.

여기서 LPCTSTR 자료형은 다음과 같습니다.

 

LPCTSTR = long pointer constant string = const char * 로써

const 를 붙히는 이유는 안정성 때문이라고 합니다.

 

이제 빌드를 진행해보면 전 게시물의 예제와는 다르게 성별을 Radio Button 을 통해 선택할 수 있고

관심분야도 추가할 수 있습니다.

 

관심 분야를 입력할 수 있지만, 프로그램은 특정 분야에 사용되는 경우가 많기 때문에 

이번 프로그램에서는 컴퓨터 분야에 한정하여 관심분야를 선택할 수 있도록 Check Box를 이용하여 진행해보도록 하겠습니다.

 

다음과 같이 Check Box 컨트롤을 배치합니다. 각 컨트롤의 속성과 컨트롤에 대한 변수는 아래의 테이블과 같습니다.

컨트롤 ID 기타 속성
Edit Control IDC_EDIT_NAME  
Edit Control IDC_EDIT_INFO Multiline : True, Read Only : True,
Horizontal Scroll : True
Vertical Scroll : True
Edit Control IDC_EDIT_INTEREST  
Group Box IDC_STATIC Caption : 이름을 입력하세요
Group Box IDC_STATIC Caption : 성별을 선택하세요
Group Box IDC_STATIC Caption : 관심분야를 선택하세요
(예 : 앱 개발 , 데이터 베이스)
Radio Button IDC_RADIO_FEMALE Caption : 여성, Group : True
Radio Button IDC_RADIO_MALE Caption : 남성, Group : False
Button IDC_BUTTON_ENLIST Caption : 가입
Check Box IDC_CHECK_APP Caption : 앱 개발
Check Box IDC_CHECK_NET Caption : 네트워크 보안
Check Box IDC_CHECK_DB Caption : 데이터베이스
Check Box IDC_CHECK_EMB Caption : 임베디드
ID 자료형 변수 이름 컨트롤 변수
IDC_EDIT_NAME CString m_strName 체크 해제
IDC_EDIT_INFO CString m_strInfo 체크 해제
IDC_EDIT_INTEREST CString m_interest 체크 해제
IDC_RADIO_FEMALE int m_Sex 체크 해제
IDC_CHECK_APP int * m_flag 체크 해제

각 체크박스는 선택될 수도 있고 선택되지 않을 수 있기 때문에 체크박스마다 변수가 필요합니다.

여기에서는 배열 변수를 만들어 각 체크박스에 연결할 것이므로 포인터 변수 하나만 만들어 줍니다.

여기서 중요한 점이 아까 관심 분야에 대한 코딩을 진행할 때 DDX_Text함수를 추가한 상태에서

리소스 편집기에서 Edit Control 를 제거하여도 코딩했던 부분은 자동적으로 지워지지 않기 때문에 

TestDlg.h 와 TestDlg.cpp 파일에서 생성된 변수 및 함수를 모두 수동으로 지워주셔야 합니다.

지우지 않으면 빌드 에러가 나기 때문에 잊으셔도 에러 문구를 따라가서 차근차근 지울 수 있습니다.

 

이제 m_flag 변수를 생성하였으니 TestDlg.cpp 파일에서 동적 메모리를 할당하여 변수 네개를 생성합니다.

마찬가지로 데이터 교환을 위해 DoDataExchange()함수에서도 CheckBox 에 대한 DDX 함수를 작성해주어야 합니다.

for문을 이용하여 IDC_CHECK_APP 이라는 ID 주소에 1씩 증가시켜 정의하는 이유는 아래에 Resource.h 에 해답이 있습니다.

Resource.h 파일에는 각 ID 에 해당하는 주소값이 모두 define 되어 있습니다.

리소스 편집기에서 컨트롤들을 생성하고 저장하면 각 컨트롤들의 ID 에 해당하는 주소값들을 자동으로 비주얼 스튜디오에서 배정해 주는 겁니다.

Check Box를 네개 연속으로 생성하였기때문에 자동적으로 주소 + 1 씩 증가 되어 있기 때문에 for문을 이용하여

DDX_Check()함수를 생성할 수 있는겁니다.

만약 ID의 주소값이 위와 일치하지 않다면 수동으로 주소를 바꿀 필요가 없고(위험합니다) 일일히 지정해 주셔도 됩니다.

 

마지막으로 버튼을 클릭하는 이벤트 처리 함수에 아래와 같이 추가해 줍니다.

void CTestDlg::OnBnClickedButtonEnlist()
{
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
	int i;
	bool comma = false;
	LPCTSTR gender[2] = { _T("여성"),_T("남성") };		
	LPCTSTR kind[4][2] = { {_T(""), _T("앱 개발")},{ _T(""), _T("네트워크 보안") } ,
				{ _T(""), _T("데이터베이스") } ,{ _T(""), _T("임베디드") } };
	UpdateData();									
	if (m_strName.IsEmpty())					
		return;
	m_strInfo.Append(m_strName);		
	m_strInfo.AppendChar(_T(':'));		
	m_strInfo.Append(gender[m_Sex]);		
	m_strInfo.AppendChar(_T(':'));	
	for (i = 0; i < 4; i++)
	{
		if (comma && m_flag[i])
			m_strInfo.Append(_T(","));
		m_strInfo.Append(kind[i][m_flag[i]]);
		comma = comma || m_flag[i];
		m_flag[i] = 0;
	}
	m_strInfo.Append(_T("\r\n"));	
	m_Sex = 0;		
	m_strName.Empty();
	UpdateData(false);						
}

위와 같이 OnBnClickedButtonEnlist() 함수를 작성하고 빌드를 진행하면 아래와 같이 정상적으로 프로그램이 구동됩니다.

감사합니다.

posted by devtang
2021. 3. 8. 12:08 MFC Programming

안녕하세요. 본 게시물에서는 대화상자 기반 MFC 프로젝트를 생성하고 위의 사진과 같이 간단한 예제를 만들 것입니다.

 

본 설명은 Visual Studio 2015 기준으로 진행하고 있습니다.

우선 MFC 프로젝트를 위해서는 Visual Studio 에 MFC 템플릿을 설치해야 합니다.

비주얼 스튜디오 설치 당시에 C++ 체크를 진행하고 설치를 해야 이용할 수 있습니다.

 

Visual C++ -> MFC -> MFC 응용 프로그램을 체크합니다.

저는 대화 상자 기반으로 예제를 진행하기 때문에 위와 같이 체크를 한 후 다음을 눌러줍니다.

SDL 검사는 체크를 꼭 해제 해 줍니다. C언어나 C++ 을 진행해본 사람이라면 프로젝트 생성시 한번 씩 눌렀던 경험이 있을겁니다. 자세히는 모르지만 SDL 검사가 2010 버전부터 생겨서 체크를 진행하고 프로젝트를 실행하면 scanf 대신 scanf_s 를 써주거나 pragma를 이용하여 오류검사에서 배제시키는 귀찮은 일이 많이 생기기 때문에 해제한다고 알고 있습니다.

사용자 인터페이스 기능 창에서는 정보 상자 박스체크를 해제 합니다.

본 기능은 게시물에서 사용하지 않는 번거로운 코드가 생기므로 추후에 필요할 때 추가할 수 있으니 체크 해제를 하고 진행하도록 합니다.

고급 기능 역시 마찬가지로 모두 체크를 해제하고 다음을 눌러줍니다.

저도 여기에서 사용해본 기능은 MFC에서 소켓을 이용한 프로그램 공부시에 Windows 소켓 기능 빼고 진행해본 적이 없습니다..

 

이제 마침버튼을 누르면 컴퓨터 마다 상이할 수 있으나 로딩이 조금 걸립니다.

위의 사진과 같이 파일 구문 분석을 진행하면서 아래 하단 바에 로딩을 하고 있습니다.

 

이제 프로젝트명을 각자 다르게 적었기 때문에 파일 이름이 조금 다를 수 있지만 일단 프로젝트명을 Test라고 가정하고

Test.cpp , TestDlg.cpp , Test.h 파일이 있습니다. 여기서 Test.cpp 에 들어갑니다.

 

BOOL CTestApp::InitInstance()
{
	CWinApp::InitInstance();


	// 대화 상자에 셸 트리 뷰 또는
	// 셸 목록 뷰 컨트롤이 포함되어 있는 경우 셸 관리자를 만듭니다.
	CShellManager *pShellManager = new CShellManager;

	// MFC 컨트롤의 테마를 사용하기 위해 "Windows 원형" 비주얼 관리자 활성화
	CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));

	// 표준 초기화
	// 이들 기능을 사용하지 않고 최종 실행 파일의 크기를 줄이려면
	// 아래에서 필요 없는 특정 초기화
	// 루틴을 제거해야 합니다.
	// 해당 설정이 저장된 레지스트리 키를 변경하십시오.
	// TODO: 이 문자열을 회사 또는 조직의 이름과 같은
	// 적절한 내용으로 수정해야 합니다.
	SetRegistryKey(_T("로컬 응용 프로그램 마법사에서 생성된 응용 프로그램"));

	CTestDlg dlg;
	m_pMainWnd = &dlg;
	INT_PTR nResponse = dlg.DoModal();
	if (nResponse == IDOK)
	{
		// TODO: 여기에 [확인]을 클릭하여 대화 상자가 없어질 때 처리할
		//  코드를 배치합니다.
	}
	else if (nResponse == IDCANCEL)
	{
		// TODO: 여기에 [취소]를 클릭하여 대화 상자가 없어질 때 처리할
		//  코드를 배치합니다.
	}
	else if (nResponse == -1)
	{
		TRACE(traceAppMsg, 0, "경고: 대화 상자를 만들지 못했으므로 응용 프로그램이 예기치 않게 종료됩니다.\n");
		TRACE(traceAppMsg, 0, "경고: 대화 상자에서 MFC 컨트롤을 사용하는 경우 #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS를 수행할 수 없습니다.\n");
	}

	// 위에서 만든 셸 관리자를 삭제합니다.
	if (pShellManager != NULL)
	{
		delete pShellManager;
	}

#ifndef _AFXDLL
	ControlBarCleanUp();
#endif

	// 대화 상자가 닫혔으므로 응용 프로그램의 메시지 펌프를 시작하지 않고  응용 프로그램을 끝낼 수 있도록 FALSE를
	// 반환합니다.
	return FALSE;
}

보이는 코드와 같이 InitInstance 함수에 들어가서 실질적으로 필요한 코드만 남겨놓아야 합니다.

레지스트리에 키를 등록하는 작업을 사용하지 않기 때문에 지우는게 좋다고 합니다.

BOOL CTestApp::InitInstance()
{
	CWinApp::InitInstance();

	CTestDlg dlg;
	m_pMainWnd = &dlg;
	dlg.DoModal();
	return FALSE;
}

따라서 위와 같이 간단하게 필요한 것만 남기고 정리 하도록 합니다.

 

이제 빌드를 진행하면 위와 같이 윈도우창이 생성됩니다. 아직 아무것도 추가하지 않아서 디폴트로 설정된 창이 나옵니다. 아직 아무것도 할 수 없고 확인 취소 버튼을 누르면 윈도우 창이 종료되는 것 밖에 없습니다.

솔루션 탐색기에서 rc 파일을 더블클릭하거나 리소스 뷰를 클릭하여 Dialog 폴더 내에 DIALOG 파일을 더블클릭합니다.

그러면 이제 눈으로 저희가 아까 빌드시에 실행되었던 창을 확인할 수 있습니다.

오른쪽의 도구 상자는 제가 사용시의 편의를 위해 세팅해놓은 것이므로 없으신 분도 있기 때문에

없으신 분들은 Ctrl + Q 버튼을 눌러 도구 상자를 입력해서 띄우면 됩니다.

 

본 예제에서 사용하는 것은 Button 과 Edit Control 입니다.

 

Button은 말그대로 클릭 할 수 있는 버튼입니다. 마우스로 클릭시에 이벤트를 추가할 수 있습니다.

Edit Control은 키보드로 입력된 문자열을 받아들이거나 텍스트 형태의 정보를 출력 할 수 있는 도구입니다.

대화 상자 기반 응용 프로그램에서 입력 , 출력을 받고 사용하는 도구를 컨트롤이라고 합니다.

프로그램 생성시에 기본적으로 생성이 되있는 컨트롤을 클릭하면 가장자리에 네모가 생성이됩니다.

마우스로 드래그하여 원하는 곳에 옮길수 있으며, Del 버튼을 통해 삭제도 가능하고 복사 붙혀넣기도 가능합니다.

우선 예제를 위해 지워줍니다. 확인 취소 버튼도 모두 지워줍니다.

Edit Control 을 추가하기 위해 더블 클릭하거나 쭉 끌고 윈도우 창으로 가져올 수도 있습니다.

 

생성을 하게되면 기본적으로 속성창에서 ID가 IDC_EDIT1 로 설정이 되어있습니다.

본 예제에서는 Edit Control 이 두개밖에 사용되지 않아 쉽게 인지할 수 있지만 프로그램이 커지면서 다양한 컨트롤들을 사용하게 되고 제어해야 될 상황이 많아지기 때문에 꼭 ID 작명을 하는 습관을 길들이는게 좋습니다.

우선 대화상자를 클릭하여 폰트를 굴림 , 9 포인트로 바꿔 줍니다. 기본적으로 MS에서 제공하는 폰트로 설정이 되어 있기 때문에 한글 폰트가 약간 깨져보일 수 있습니다. 따라서 굴림으로 바꿔주면 정상적으로 한글이 표시됩니다.

 

이제 도구 상자에서 아래의 테이블과 같은 컨트롤들을 추가하여 좀더 Program같이 만들어줍니다!

ID와 기타 속성은 속성창에서 변경할 수 있으며, 다른 것들은 건들지 않고 테이블에 명시된 속성만 바꿔주도록 합니다.

컨트롤 ID 기타 속성
Edit Control IDC_EDIT_NAME  
Edit Control IDC_EDIT_INFO Multiline : True
Read Only : True
Horizontal scroll : True
Vertical scroll : True
Button IDC_BUTTON_ENLIST Caption : 가입
Group Box IDC_EDIT_GROUP Caption : 이름을 입력하세요

 

Multiline 이 True 면 출력하는 문자열 내에 개행 문자( 콘솔에서는 "\n" 이지만 MFC에서는 "\r\n" 입니다.) 가 있을 때 새로운 행이 추가됩니다.

Read Only 가 True 면 입력을 받지 못하고 출력만 가능하게 되어 Edit Control 색상이 회색으로 변하게 됩니다.

Horizontal scroll 과 Vertical scroll 은 수평 수직 라인쪽에 이동을 할 수 있는 화살표가 생성이됩니다.

자 위와 같이 뭔가 작동할 것 같은 GUI 가 만들어졌습니다.

참고로 리소스 뷰에서 수정을 하게되면 위의 별표가 생깁니다. 이때 Ctrl + S 버튼을 누르면 메세지 창이뜹니다.

예를 눌러주어야 변경된 내용이 코드에 저장이됩니다.

이제 뭔가 작동할것 같지만 빌드를 해서 클릭을 하고 텍스트를 써봐도 아무일도 일어나지 않습니다.

컨트롤에서 입력받은 데이터를 얻어와 프로그램에서 이용하려면 변수를 추가하여 데이터를 받아야 합니다.

본 예제는 이름을 입력하고 가입 버튼을 누르면 아래 Edit Control에 작성한 이름이 생성이 되는 예제이므로

총 두개의 컨트롤 변수가 필요합니다.

오른쪽 버튼을 눌러 변수 추가 버튼을 눌러줍니다.

컨트롤 변수 체크 박스를 해제한 후 변수 형식을 CString 으로 바꿔 줍니다.

이때 추가하려는 변수의 컨트롤 ID가 본인이 추가하려는 컨트롤의 ID가 맞는지 확인 하셔야 합니다.

마찬가지로 아래의 Edit Control 역시 위와 같이 변수를 추가해 줍니다.

각 컨트롤들의 속성은 아래의 테이블과 같이 설정합니다.

ID 자료형 변수 이름 컨트롤 변수
IDC_EDIT_NAME CString m_strName 체크 해제
IDC_EDIT_INFO CString m_strInfo 체크 해제

추가를 하게 되면 TestDlg.h 파일에 CString 변수 형식으로 자동으로 마법사가 생성해줍니다.

이제 버튼을 클릭했을때 이벤트 발생 처리를 위해서 이벤트 처리 함수를 추가해주어야 합니다.

위와 같이 이벤트 처리기 추가 버튼 클릭후 BN_CLICKED 를 선택하여 생성할 수도 있고

버튼 GUI를 더블 클릭하여 생성할 수도 있습니다.

생성하게 되면 TestDlg.cpp 파일과 TestDlg.h 파일에 자동으로 함수 및 정의가 생성되는 것을 볼 수 있습니다.

이제 Edit Control에 추가된 변수에 데이터 교환을 위해 DDX 계열 함수 호출을 추가하여야합니다.

DDX 란?

위에서도 말했다 시피 다이얼로그의 컨트롤을, 변수로 사용하려고 하고싶다면

변수와, 컨트롤 사이에 값을 주고받는 일이 일어나야 합니다.

간단하게 설명하자면 컨트롤들과 변수 사이에 데이터 교환이 이루어 지려면

DoDataExchange()함수를 거쳐야 합니다.

void CTestDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Text(pDX, IDC_EDIT_NAME, m_strName);
	DDX_Text(pDX, IDC_EDIT_INFO, m_strInfo);
}

 TestDlg.cpp 파일로 들어가서 DoDataExchange()함수를 위와같이 수정해줍니다.

pDX : CDataExchange 객체에 대한 포인터로 데이터 교환 방향과 대화 상자에 대한 포인터 등이 저장되어 있습니다.

위의 DDX_Text 함수는 IDC_EDIT_NAME과 m_strName 에 대한 데이터 교환을 위한 함수입니다.

 

이제 데이터 교환을 위한 준비는 모두 끝났으니,

버튼을 클릭했을 때 작성한 이름이 아래 컨트롤에 생성이되는 이벤트를 추가하여야 합니다.

 

따라서 아까 만들어놨던 이벤트 함수인 OnBnClickedButtonEnlist() 함수로 이동하여 코드를 수정해 줍니다.

 

void CTestDlg::OnBnClickedButtonEnlist()
{
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
	UpdateData();			// 컨트롤에 입력된 데이터를 변수로 얻어온다.
	if (m_strName.IsEmpty())	// 이름을 입력하지 않은 채 버튼을 클릭한 상황을 걸러내기 위함이다.
		return;
	m_strInfo.Append(m_strName);	// m_strName 에 저장된 이름을 Append()를 이용하여 m_strInfo 에 추가한다.
	m_strInfo.Append(_T("\r\n"));	// 개행 문자를 추가하여 다음 이용자의 이름을 다음 행에 출력하기 위함이다.
	m_strName.Empty();		// 이름이 추가되었으므로 원래 작성했던 이름을 삭제해야한다.
	UpdateData(false);		// false를 통해 m_strName 의 데이터를 컨트롤에 전달한다.
}

UpdateData() or UpdateData(true) 의 경우는 컨트롤 -> 변수로 값을 받아오며

UpdateData(false) 의 경우는 변수 -> 컨트롤 로 값을 전달합니다.

 

이제 모든 작업이 끝났으니 빌드를 하면 됩니다.

이름을 Edit Control에 작성하고 가입버튼을 누르면 아래의 Edit Control에 정상적으로 기입이 됩니다.

 

이번시간에는 MFC 대화상자 기반 프로젝트 기본 생성 방법과 Edit Control , Button 의 사용법에 대해 정리해 보았습니다.

 

감사합니다.

'MFC Programming' 카테고리의 다른 글

[MFC] Dialog 기반 Check Box 와 Radio Button 사용 예제  (1) 2021.03.08
posted by devtang
2021. 1. 11. 16:21 C++

시스템 메모리 구조는 위와 같이 나뉜다.

Code는 우리가 작성한 코드가 들어가있는 영역이다.

Data영역은 두가지로 나뉘는데 초기화가 되어있는 전역변수나 static 변수,

BSS는 0으로 초기화 되어있는 전역 변수나 static 변수

 

스택은 (LIFO : Last in First out) 구조로 위의 사진에서는 second()부터 순차적으로 할당해제가 된다고 보면 될 것 같다.

 

스택의 메모리가 감당할 수 없는 크기의 메모리를 할당하면 넘치게 되서 오류가 발생한다.

이를 방지하기 위해서는 heap영역에 접근할 수 있는 동적할당이 필요함

 

여기서 중요할점은 delete[] 한 후에 더사용을 해야한다면 반드시 포인터 변수를 다시 초기화하여 엉뚱한 값을 가리키지 않도록 해야한다.

 

delete를 하지 않으면 힙메모리영역에 사용하지도 않는 메모리를 계속 생성해놓을 수 있어서 메모리 누수(memory leak)가 발생할 수 있기때문에 동적할당 시에 반드시 해줘야한다.

'C++' 카테고리의 다른 글

[C++] 이중포인터와 동적 이차원 배열 생성 예제  (0) 2021.01.10
[C++] 포인터와 const  (0) 2021.01.10
[C++] 포인터의 기본  (0) 2021.01.10
posted by devtang
2021. 1. 10. 23:53 C++
#include <iostream>

using namespace std;


int main()
{
	const int row = 3;
	const int col = 5;

	int s2da[row][col] = {
		{1,2,3,4,5},
		{6,7,8,9,10},
		{11,12,13,14,15}
	};

	//동적 2차원 배열 생성
	int **matrix = new int*[row]; 

	for (int r = 0; r < row; r++)
		for (int c = 0; c < col; c++)
			matrix[r] = new int[col];
		
	
	//s2da의 값 복사
	for (int r = 0; r < row; r++)
		for (int c = 0; c < col; c++)
			matrix[r][c] = s2da[r][c];

	//matrix 출력	
	for (int r = 0; r < row; r++)
	{
		for (int c = 0; c < col; c++)
		{
			cout << matrix[r][c] << " ";

		}
		cout << endl;
	}

	// 동적할당 메모리 해제
	for (int i = 0; i < row;i++)
		delete[] matrix[i];

	delete[] matrix;

}

'C++' 카테고리의 다른 글

[C++] 스택과 힙  (0) 2021.01.11
[C++] 포인터와 const  (0) 2021.01.10
[C++] 포인터의 기본  (0) 2021.01.10
posted by devtang
2021. 1. 10. 22:02 C++

//본 게시물은 따배씨++ 강의를 듣고 정리한 학습 노트입니다.

 

 

값을 바꿀수 없는 const int value를 선언하고 마찬가지로 const int *ptr변수에 value의 주소값을 저장한다.

포인터를 통해서도 const int value 변수의 값을 수정할 수가 없다.

 

 

마찬가지로 int value가선언이되었어도 const int *ptr은 저장된 주소의 변수의 값을 바꾸지 않겠다고 생각하면 될거같다.

 

위의 코드를 보면 const int *ptr은 다른 주소값을 받을수 있다.

 

ptr이라는 포인터 변수가 가리키는 주소에 있는 값을 바꾸지 않겠다 라는 것이다. 

 

하지만 포인터 변수가 가리키는 주소 자체를 바꿔버리면 새로운 값을 가질 수 있게된다.

 

const로인해 포인터 변수가 가진 주소를 직접 참조해 값을 바꿀 수는 없어도

저장하는 주소자체를 새로 바꿀 수가 있다.

 

위와 같이 int *const ptr을 선언하게되면 저장되있는 주소의 내부 값을 변경시킬수 있다.

 

주소값 자체를 바꿔버릴 수 있어도 주소가 가리키는 곳에 저장된 값을 바꿀 수 없었는데,

이번에는 역참조로 값을 바꿀 수 있지만, 주소값 자체를 새로 바꾸는 것이 안되는 상황이다.

이것은 주소값을 저장하는 역할에 const 가 적용이 된 것이다.

 

만약 const int *const ptr의 경우라면 저장된 주소값의 변경도 불가능하고, 주소를 참조하여 값을 바꾸는 것도 불가능하다.

 

위의 방식들을 사용할 때는 함수 파라미터로 array를 집어넣을 때, 값과 주소값을 바꾸지 않을 때 필요하다.

 

'C++' 카테고리의 다른 글

[C++] 스택과 힙  (0) 2021.01.11
[C++] 이중포인터와 동적 이차원 배열 생성 예제  (0) 2021.01.10
[C++] 포인터의 기본  (0) 2021.01.10
posted by devtang
2021. 1. 10. 16:10 C++

//본 게시물은 따배씨++ 강의를 듣고 정리한 학습 노트입니다.

 

포인터란?

간단하게 설명하면 메모리의 주소값을 담고 있는 변수라고 볼 수 있다.

 

위의 사진과 같이 x의 변수에 40을 담고

& 연산자를 이용하여 x가 위치한 메모리 주소를 가져올수 있다.

포인터 역시 x의 메모리 주소값을 담고 있으므로 동일한 출력 결과를 가져올 것이다.

 

그러나 C++ 에서 &와 *는 엄연히 다르다.

 

& : reference operator

 

* : de-reference operator

 

 de-reference 에서의 reference 의 의미는

포인터가 "저쪽 주소에 가면 이 데이터가 있어요"라고 간접적으로 가리키기만 하는 것에 대해서, "그럼 거기에 진짜 뭐가 있는지 내가 들여다 볼께" 라며 직접적으로 접근하겠다는 의미라 볼 수 있다.

 

포인터는 x86 에서는 항상 4바이트, x64에서는 8바이트로 할당된다.

'C++' 카테고리의 다른 글

[C++] 스택과 힙  (0) 2021.01.11
[C++] 이중포인터와 동적 이차원 배열 생성 예제  (0) 2021.01.10
[C++] 포인터와 const  (0) 2021.01.10
posted by devtang
prev 1 2 3 4 next