윈도우 스토어 앱 개발하긔~

@codemaru · July 31, 2014 · 19 min read

iOS가 판도라의 상자를 연 이후로 요즘만큼 개발자들이 피곤한 시대가 또 있었을까 싶다. 초창기 윈도우 프로그래머로 일하던 시절에는 윈도우 9x만 없다면 뭐든지 다할수 있을 것 같다는 말들이 유행했었다. 그렇게 영원이 악몽같이 존재할 것 같던 윈도우 9x도 윈도우 비스타와 윈도우 7의 출현으로 역사의 뒤안길로 사라졌다. 그렇게 잠시나마 태평성대가 오나 싶더니만 iOS와 함께 온갖 종류의 플랫폼이 우후죽순 등장해 버렸다. 이제는 차라리 비슷했던 9x를 지원하는게 좋았다는 생각마저 들 정도다. 갈라파고스도 진짜 이보다는 상태가 나았지 싶다. iOS, 안드로이드, 윈폰, 블랙베리 같은 온갖 종류의 디바이스가 세상 밖으로 출몰해 버렸고 똑똑한 프로그래머라는 소리를 들으려면 그 온갖 종류의 디바이스에서 동작하는 프로그램을 다 만들 줄 알아야 하는 시대가 도래한 것이다. API 세트도 다르고, 언어도 다르며, 심지어는 OS, 더 나아가서는 CPU까지 다른 환경에 익숙해져야 하는 골치아픈 세상이 돼 버렸다.

이쯤되니 요즘엔 윈도우가 PC 시장에서 그랬던 것처럼 누군가가 그냥 독점해 버렸으면 좋겠다는 마음마저 든다. 사용자에게는 선택권이 제한되고 개발 생태계에도 좋지 않은 일이란 걸 알지만 이런 온갖 종류의 디바이스에 들어가는 프로그램을 만들어야 하는 프로그래머의 고충도 보통 일은 아니기 때문이다. 하나 하기에도 바쁜데 하나 배우면 또 하나가 튀어나오는 식이다. 그.래.서. 요즘 개발자들은 정말 피곤하다.

그런 피곤한 개발자들에게 윈도우 스토어 앱이라는 신세계가 또 열리고 있다. 지금은 시장 지배력이 낮지만 영원히 낮으라는 법은 없고, 아직까진 경쟁자가 별로 없는 블루오션 시장이라는 매력도 있는 그 윈도우 스토어 말이다. 남들보다 조금 일찍 배에 올라타려는 프로그래머를 위해서 스토어 개발에 관한 이야기를 몇 자 끄적여 보려고 한다. 모든 플랫폼이 그렇겠지만 기본적인 환경을 구축하고 돌아가는 메커니즘을 이해하고 나면 나머지는 그저 소소하게 변경된 API와 언어 뿐이라 생각보단 금방 접근할 수 있다. 그럼 이제 윈도우 스토어 프로그래밍에 관해서 한 번 알아보도록 하자.

#0

백문이 불여일견이랬다. 일단 한 번 만들어보자. Visual Studio 2013을 사용하길 추천한다. 그리고 영문판으로 설치하길 권장한다. 이번에 컴퓨터를 새로 사면서 한글 윈도우를 설치했는데 내가 한 일 중에 가장 병신같은 짓이었던 것 같다. 프로그래머라면 영문판을 쓰자. 그게 검색하기도 편하고 문제 해결하기도 수월하다. Visual Studio 2013을 설치하고 Project, New를 선택하면 아래와 같은 깜찍한 다이알로그가 출력된다.

                md 0
윈도우 스토어 앱 프로젝트

Visual C++, Store Apps 아래에 우리가 선택할 수 있는 프로젝트 형태가 있다. 유니버셜 앱이라고 표기된 것은 PC 윈도우, 윈도우 폰 환경에서 모두 동작하는 앱을 개발하는 것을 의미한다. Windows Apps는 PC 윈도우에서 동작하는 앱을, Windows Phone Apps는 윈폰에서 돌아가는 앱을 개발하는 것을 의미한다. 유니버셜 앱이라고 특별한게 있는 건 아니고 공통 코드가 Shared로 빠져 있고, 각각 디바이스에 맞는 프로젝트 두 개가 생성되는 것을 볼 수 있다.

프로젝트 종류도 여러가지가 있는데 Blank App은 그냥 비어있는 앱을, DLL은 네이티브 DLL 코드를, Static Library는 정적 라이브러리 코드를, Windows Runtime Component는 윈도우 런타임을 사용하는 DLL을 만드는 것을 의미한다.

유니버셜 앱의 Blank App을 선택하고 프로젝트명을 halo로 만들어 보자. 만들고 나면 솔루션 탐색기가 아래와 같이 구성된 것을 볼 수 있다. CTRL + F5를 눌러서 실행해 보자. 윈도우8 환경이라면 그냥 실행될 것이다. 개발자 환경 등록이 되지 않았으면 뭐 자질구레하게 묻는데 MS 계정으로 로그인해서 인증을 해주도록 하자.

                md 1
스토어 앱 솔루션 탐색기

#1

CTRL + F5를 눌러서 앱을 실행하면 어디서 실행되는 것일까? 센스 있는 프로그래머라면 알아차렸겠지만 CTRL + F5를 눌렀을 때 시스템에 설치되거나 해서 실행되지는 않는다. 아래와 같이 디버그 빌드된 폴더에서 실행된다는 것을 알 수 있다.

                md 2
Debug 빌드 폴더

그렇다면 여기에서 의문이 생긴다. 스토어에는 무슨 파일을 올리는 거지? 릴리즈로 빌드하면 뭔가 패키지 파일이 나올까? 하지만 그렇게 해봐도 패키지 파일은 나오지 않는다. 패키지 파일을 만들기 위해서는 Visual Studio 2013의 프로젝트 메뉴 아래 있는 스토어 메뉴를 활용해야 한다. 메뉴를 선택하면 아래와 같이 나오는데 여기서 Create App Packages 메뉴를 선택하면 된다.

                md 3
스토어 메뉴

종종 스토어 메뉴 자체가 활성화가 되어 있지 않은 경우도 있다. 그런 경우에는 솔루션 탐색기의 Package.appxmanifest 파일을 선택해서 연다. 그럼 아래와 같은 화면이 출력될 것이다. 거기서 Packaging 항목에 있는 버전 정보를 수정해주면 활성화 된다. Packaging 항목에 보면 Publisher 항목이 있는 그 부분이 디지털 서명을 선택하는 항목이다. 시스템에 설치된 인증서가 있다면 거기서 선택해주면 해당 인증서로 서명이 된다.

                md 4
Package.appxmanifest

Create App Package 메뉴가 활성화되면 선택해서 패키지 파일을 만들 수 있다. 처음 물어보는 항목이 MS 계정과 연동해서 업로드 하겠냐는 질문인데 연동 작업을 할게 아니라면 NO를 선택해 주면된다. 그렇게 앱 패키지를 생성하면 마지막에 어떤 폴더에 생성이 되었는지 알려주는 화면이 출력된다. 클릭해서 해당 폴더로 이동해보면 아래와 같이 패키지 파일이 생성된 것을 볼 수 있다. halo.Windows_1.0.0.1_Win32_Debug.appxupload 파일을 스토어에 업로드하면 된다. halo.Windows_1.0.0.1_Win32_Debug_Test 폴더에 들어가보면 halo.Windows_1.0.0.1_Win32_Debug.appx 파일이 존재한다. 그 파일을 통해서 시스템에 직접 설치해 볼 수도 있다.

                md 5
생성된 앱 폴더

#2

스토어에 올리지않고 appx 파일을 시스템에 설치하기 위해서는 윈도우 그룹 정책을 변경해 주어야 한다. 실행 프롬프트에서 gpedit.msc를 실행하자. 그럼 아래와 같이 그룹 정책 편집기 화면이 출력된다.

                md 6
gpedit.msc 실행

여기서 로컬 컴퓨터 정책 => 컴퓨터 구성 => 관리 템플릿 => Windows 구성 요소 => 앱 패키지 배포 항목으로 이동해보자. 그럼 “Allow all trusted apps to install (모든 신뢰할 수 있는 앱을 설치할 수 있음)” 함목이 있는 것을 볼 수 있을 것이다. 기본적으로 비활성화 돼 있다. 그걸 선택해서 활성화시킨 다음 재부팅을 하면 appx 패키지를 설치할 수 있다. 정책을 바꾼 다음 재부팅을 해보자.

재부팅을 했으면 앱을 명령 프롬프트에서 설치하기 위해서는 파워쉘의 add-appxpackages를, 제거하기 위해서는 remove-appxpackages를 사용하면 된다. “add-appxpackages appx파일명”을 입력하면 해당 앱이 설치되는 것을 볼 수 있다. 아래는 그렇게 앱을 설치하는 화면을 보여준다. 디버그로 실행해서 이전에 설치된 인스턴스가 있기에 제거후에 다시 설치시키는 장면이다.

                md 7
파워쉘을 통해 앱 설치 및 제거

파워쉘을 통해서 시스템에 직접 설치하면 아래 화면에 나오는 것처럼 Program Files 폴더의 WindowsApps 폴더에 빌드한 앱이 설치된 것을 볼 수 있다. WindowsApps 폴더는 기본적으로 관리자 권한을 가져도 들어가 볼 수 없다록 권한 설정이 되어 있다. 내부를 살펴보기 위해서는 해당 폴더의 소유자를 관리자로 변경해 주어야 한다.

                md 8
WindowsApps 폴더

이렇게 개발한 appx 패키지를 친구한테 주고, gpedit 설정을 변경하는 것 까지 알려주어도 친구는 앱이 실행되지 않는다는 문제를 호소할 수 있다. 이벤트 로그를 살펴보면 0x80073CFC 오류가 발생하는 것을 볼 수 있다. 개발자 라이선스 갱신이 되지 않아서 발생하는 문제다. 관리자 권한으로 파워쉘을 실행한 다음 Show-WindowsDeveloperLicenseRegistration 명령어를 입력해서 개발자 라이선스를 갱신해 주면 문제가 해결된다.

#3

지금까지 앱 설치 및 배포에 관한 기본적인 방법들을 살펴보았다. 이제 개발적인 측면을 살펴보도록 하자. Windows 8이 출시되면서 아래 그림과 같이 앱을 위한 WinRT API가 추가되었다. 앱 개발은 그 API를 사용해서 쪼물닥 거리는 것이라 생각하면 된다.

                md 9
윈도우 8 개발 아키텍처

마이크로소프트 공식 페이지를 살펴보면 C++ 개발자를 위해서는 두 가지 종류의 API가 제공되고 있는 것을 볼 수 있다. 하나가 그림에 있는 Windows Runtime이고 다른 하나가 Win32 and COM API다. Win32 and COM API가 지원된다늬? 그럼 그냥 다 쓸 수 있는건가? 라고 생각하면 천만의 말씀이다. 앱을 위해서 지원되는 API 목록을 보면 알겠지만 굉장히 제한적으로 지원되는 구조다. GetProcAddress는 있지만, GetModuleHandle은 없고, LoadLibrary는 없지만 LoadPackagedLibrary는 있는 것과 같은 식이다. CreateProcess는 당연히 없을 수도 있겠다고 생각이 들지만 CreateThread 마저도 없다. 결국 스레드를 쓰려면 Windows Runtime에 있는 Windows.System.Threading에서 지원해 주는 것을 사용할 수 밖에 없다. 이말은 기존 코드를 100% 재활용할 수는 없다는 것을 의미한다. 어떠한 형태로든 일정 수준 이상의 포팅 작업이 필요하다는 것을 의미한다. 물론 그렇다고 모두 다 새로 만들 필요는 없다. 이미 우리를 대신해 CraeteThread를 포팅해 주신 감사한 분들이 있기에 우리는 선검색 후포팅 하면 되겠다.

오픈 소스를 활용해 개발을 진행하고 있다면 기존의 코드가 문제가 될 수 있다. 대다수 오픈 소스들이 _WIN32 전처리기가 있으면 윈도우 환경으로 인식하고 기존의 윈도우 API를 사용해서 코딩한 부분이 많이 존재하기 때문이다. 이런 경우는 범용적으로 컴파일되게 만들기 위해서는 좀 더 세분화해서 조건을 판단할 필요가 있다. 예를들어 아래와 같은 오픈 소스 코드가 문제가 될 수 있다는 것을 의미한다.

#ifdef _WIN32
#include <windows.h>

void MyFunc()
{
    VirtualAlloc(... param ...);
}
#endif

이 코드의 경우 데스크톱 프로그램으로 컴파일 할 때는 VirtualAlloc이 존재하기 때문에 문제가 되지 않지만, 스토어 앱으로 개발할 때에는 VirtualAlloc이 없기에 문제가 된다. 당연히 윈도우 헤더부터 시작해서 MS에서도 이런 문제들을 겪었을 것이다. 그래서 이 상황을 판단할 수 있는 전처리기 매크로가 추가됐다. 아래 전처리기들이 그것이다. 위에서부터 PC앱, 폰앱, PC앱+폰앱, 데스크톱 애플리케이션 개발을 구분할 수 있는 전처리기다.

#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PC_APP)
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP)
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP)
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)

전처리기를 통해서 앞선 코드를 고쳐보면 다음과 같이 고칠 수 있다. 앱 개발 환경이면 malloc을 사용해서 컴파일하도록 만들어주는 것이다.

#ifdef _WIN32
#include <windows.h>

void MyFunc()
{
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP)
    malloc(... param ...);
#else
    VirtualAlloc(... param ...);
#endif
}
#endif

당.연.한. 소리겠지만 그 많은 소스 코드에 API 호출을 다 찾아서 이런 쌩쑈를 하고 있을 수는 없다. 좀 더 편리하게 문제를 해결하기 위해서는 API 자체를 포팅하는 것이 좋다. 다음과 같이 헤더를 만들어서 앱 개발 환경일 때에도 그 API가 존재하는 것처럼 만들어 주는 것이다.

//windows_app.h
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP)

static
inline
LPVOID
WINAPI
VirtualAlloc(...)
{
    return malloc(...);
}

#endif

위와같이 존재하지 않는 API를 포팅한 헤더를 만든 다음에는 그 헤더를 프로젝트에 포함시켜 주면 된다. 전체 참조 되는 헤더에 같이 추가해주면 데스크톱과 앱 개발 환경에서 모두 빌드 문제가 없도록 손쉽게 만들 수 있다.

@codemaru
돌아보니 좋은 날도 있었고, 나쁜 날도 있었다. 그런 나의 모든 소소한 일상과 배움을 기록한다. 여기에 기록된 모든 내용은 한 개인의 관점이고 의견이다. 내가 속한 조직과는 1도 상관이 없다.
(C) 2001 YoungJin Shin, 0일째 운영 중