Amazon Web Services 한국 블로그

윈도 Visual Studio 환경에서 AWS C++ SDK 활용하기

게임제작에 있어서 클라이언트 개발은 물론 서버 개발의 경우도 윈도 환경 하에서 C++을 사용하여 게임을 만드는 경우가 많을 것입니다. 온라인 게임뿐만 아니라 모바일 게임의 경우도 Cocos2d-x엔진을 쓰거나 Unreal Engine을 사용한다면 이와 같은 환경에서 개발하는 경우가 대부분일 것입니다.

AWS는 지난해 9월 AWS C++ SDK를 공개하였고, 이를 게임 엔진이나 게임서버에서 사용하게 되면 AWS의 다양한 서비스들을 게임에서 직접 활용하고 제어할 수 있습니다.

AWS C++ SDK의 경우, 타 언어 AWS SDK와는 다르게 설치 프로그램(installer)나 패키지 관리자(Nuget 등)를 통해서 설치하는 방법을 제공하지 않기 때문에 직접 소스 코드로부터 빌드하여 사용하여야 합니다. 게임 개발자들이 많이 쓰는 환경인 Windows 및 Visual Studio에서 AWS C++ SDK를 바로 빌드하여 사용하기에는 주의해야 할 점들이 존재합니다. 이 글에서는여러분이 AWS C++SDK을 Windows 환경의 Visual Studio 사용 단계 및 알아두시면 좋은 점 위주로 설명 드리고자 합니다.

1. 사전 준비 작업
AWS C++ SDK를 Windows 환경에서 빌드하기 위해서는 Visual Studio뿐만 아니라 플랫폼 독립적인 빌드 환경 생성 도구인 CMAKE가 필요합니다. 또한, AWS C++ SDK를 받기 위해서는 GIT 클라이언트도 미리 준비해두시기 바랍니다. 마지막으로, AWS상의 자원(S3 등)를 제어하기 위해서는 그에 맞는 권한이 있어야 합니다. 해당 권한을 갖는 IAM 사용자를 만드신 후, Access Key 및 Secret Key를 미리 설정해놓으시기 바랍니다. AWS C++ SDK가 자동으로 해당 Access Key를 검색하는 과정은 다음과 같습니다.

  1. 환경 변수 검색
    1. AWS_ACCESS_KEY_ID = < Access Key 값 >
    2. AWS_SECRET_ACCESS_KEY = < Secret Key 값 >
    3. AWS_DEFAULT_REGION= < 기본적으로 사용할 AWS리전, (예) ap-northeast-2 >
  2. 사용자 HOME디렉토리 내의 .aws 폴더 아래의 credentials 파일 검색
    1. 이 파일은 AWS CLI의 “aws configure”명령을 통해 생성이 가능합니다.
  3. EC2 인스턴스 위에서 동작하는 경우 기본적으로 EC2MetadataInstanceProfile을 검색
    1. 해당 권한을 갖고 있는 IAM Role이 있는지 찾음

본 글의 예시에서는 Visual Studio 2015 버전 및 CMAKE 3.x 버전 사용을 가정하고 예를 들겠습니다.  사전 준비를 마쳤다면, Github에서 AWS C++ SDK를 다음 명령을 통해 내려 받습니다.

PS > git clone https://github.com/aws/aws-sdk-cpp <sdk-root-folder>

명령 수행이 완료되면, 지정한 <sdk-root-folder>내에 AWS C++ SDK 소스 코드가 다운됩니다.

2. AWS C++ SDK를 정적 라이브러리 형태로 빌드하기
이제 본격적으로 AWS C++ SDK를 빌드해 보도록 하겠습니다. 우선 빌드 결과물(라이브러리 파일 등)이 생성될 폴더를 하나 만듭니다. 본 예제에서는 <sdk-root-folder>하위에 sdk-build-64 폴더를 만들고 이곳에서 빌드를 해보도록 하겠습니다. 아래 cmake명령은 <sdk-root-folder>에 있는 AWS C++ SDK의 CMakeLists.txt 파일을 참조하여Visual Studio 2015 버전 형태의 솔루션(.sln)파일 생성하여 줍니다. 정적 라이브러리 형태로 빌드하기 위해STATIC_LINKING 옵션을 주었습니다.

PS > md sdk-build-64
PS > cd sdk-build-64
PS > cmake .. -G "Visual Studio 14 2015 Win64" -DSTATIC_LINKING=1

참고로, -G옵션은 Visual Studio 솔루션 형태의 프로젝트 생성뿐만 아니라 다양한 형태의 프로젝트 파일을 생성(generation)할 수 있도록 해줍니다. 구체적으로 지원하는 형태는 cmake –G를 실행해보시면 확인하실 수 있습니다. STATIC_LINKING 옵션을 주지 않으면 기본적으로 동적 라이브러리(DLL)형태로 빌드가 되도록 구성 됩니다. (AWS C++ SDK를 동적 라이브러리 형태로 사용할 경우는 몇 가지 주의할 사항이 있습니다. 이 부분에 대해서는 아래에 따로 설명하겠습니다.)

위의 명령이 완료되면 sdk-build-64 폴더 내에 AWS C++ SDK 빌드를 위한 솔루션 파일이(aws-sdk-cpp-all.sln) 생성되어 있을 겁니다. 이 파일을 Visual Studio에서 열고 빌드를 하시면 됩니다.  빌드가 완료되면 sdk-build-64의 하위 디렉토리에 AWS C++ SDK의 라이브러리(.lib)파일들이 생성됩니다.

만일, 소스 코드 파일의 인코딩 타입 문제로 빌드가 되지 않는다면, 해당 코드 파일을 열어서 UTF-8 형태로 다시 저장하시기 바랍니다. Visual Studio에서 에러가 발생한 파일을 열고 File 메뉴에서Advanced Save Options항목을 선택한 다음에 “Unicode (UTF-8 with signature)”항목을 선택하고 저장하시면 됩니다. UTF-8형태로 바꿔야 하는 파일이 많다면 아래의 Powershell 스크립트를 활용하면 소스코드를 일괄적으로 UTF-8형태로 바꾸어 저장할 수 있습니다.

Function  SaveAsUtf8([string] $path)
{
  [String[]] $files = Get-ChildItem $path -Recurse -Include *.h, *.cpp;
  foreach ($file  in $files)
    {
      "Saving As $file...";
       [String]$s = [IO.File]::ReadAllText($file);
       [IO.File]::WriteAllText($file, $s, [Text.Encoding]::UTF8);
     }
}

Windows에 기본적으로 포함된 Powershell을 열고 <sdk-root-folder>로 이동한 후 에서 위의 파일(save_as_utf8.ps1)에 정의된 함수를 다음과 같은 방법으로 실행하게 되면 <sdk-root-folder>하위의 모든 소스 코드를 UTF-8의 형태로 재 저장하게 됩니다.

PS > . .\save_as_utf8.ps1 
PS > SaveAsUtf8 .

3. 샘플 프로그램을 통해 AWS C++ SDK 사용해보기
앞서 빌드한 AWS C++ SDK를 직접 사용하는 간단한 C++ 프로그램(S3에 특정 문자열이 포함된 파일을 업로드)을 제작해보도록 하겠습니다.  우선 편의를 위하여 <sdk-root-folder>하위에 app-test라는 폴더를 만들고 이곳에 해당 프로그램을 위한 프로젝트 파일들을 cmake를 통하여 생성해보도록 합니다. 우선, 프로그램 본체에 해당하는 main.cpp를 작성하고, cmake를 통하여 Visual Studio 솔루션 파일 생성을 위한 CMakeLists.txt를 작성하면 다음과 같습니다.

#include <aws/core/Aws.h>
#include <aws/s3/S3Client.h>
#include <aws/s3/model/PutObjectRequest.h>
#include <aws/s3/model/GetObjectRequest.h>
#include <aws/core/utils/memory/stl/AwsStringStream.h> 

using namespace Aws::S3;
using namespace Aws::S3::Model;

static const char* KEY = "s3_my_sample_key3";
static const char* BUCKET = "s3-demo-korea";

int main()
{
    // SDK를 사용을 위한 내부 자원 초기화 및 옵션 설정 (지금은 default)
    Aws::SDKOptions options;
    Aws::InitAPI(options);

    S3Client client;
    
    // s3에 put 요청 생성 
    PutObjectRequest putObjectRequest;
    putObjectRequest.WithKey(KEY).WithBucket(BUCKET);

    // 파일에 기록할 스트림 생성
    auto requestStream = Aws::MakeShared("s3-sample");
    *requestStream << "Hello World!";
    
    // 스트림을 put요청에 연결
    putObjectRequest.SetBody(requestStream);

    auto putObjectOutcome = client.PutObject(putObjectRequest);

    if (putObjectOutcome.IsSuccess())
    {
        std::cout << "Put object succeeded" << std::endl;
    }
    else
    {
        std::cout << "Error while putting Object " <<
               putObjectOutcome.GetError().GetExceptionName() << 
               " " << putObjectOutcome.GetError().GetMessage() << std::endl;
    }

    // S3에 기록된 파일 내용을 다시 GET해보는 요청 생성 
    GetObjectRequest getObjectRequest;
    getObjectRequest.WithBucket(BUCKET).WithKey(KEY);

    auto getObjectOutcome = client.GetObject(getObjectRequest);

    if(getObjectOutcome.IsSuccess())
    {
        std::cout << "Successfully retrieved object from s3 with value: " << std::endl;
        std::cout << getObjectOutcome.GetResult().GetBody().rdbuf() << std::endl 
        << std::endl;
    }
    else
    {
        std::cout << "Error while getting object " <<
             getObjectOutcome.GetError().GetExceptionName() <<
             " " << getObjectOutcome.GetError().GetMessage() << std::endl;
    }

    // SDK에서 사용된 내부 자원 해제
    Aws::ShutdownAPI(options);
    return 0;  
}

AWS C++ SDK를 사용하는 코드 작성시, main함수의 첫 부분에서 반드시”Aws::InitAPI(options);”를 호출하여SDK 초기화를 해주어야 합니다. 이 과정에서 SDK사용에 필요한 각종 옵션들(로깅 여부, 커스텀 메모리 관리자 설정, HTTP 클라이언트 설정 등)을 직접 지정할 수 있습니다. 사용할 수 있는 옵션에 관한 자세한 내용은 AWS C++ SDK 블로그를 참조하시기 바랍니다.

AWS C++ SDK를 사용하는 애플리케이션 코드를 작성하였다면, 이를 포함하는 Visual Studio 솔루션 파일을 생성하는 cmake용 프로젝트 파일(CMakeLIsts.txt)을 아래와 같이 생성하여야 합니다. 이 파일에서 라이브러리 의존성 관계를 설정할 수 있습니다.

cmake_minimum_required(VERSION 2.8)
# 생성될 프로젝트 이름 설정
project(s3-sample)

# 이 프로젝트에서 사용할 AWS C++ SDK 찾아서 의존성 설정
find_package(aws-sdk-cpp)

# 이 프로젝트에 실행파일로 생성될 대상 코드 지정
add_executable(s3-sample main.cpp)

#링크가 필요한 AWS C++ SDK 라이브러리 지정 (이 예에서는 S3관련 기능만 사용함)
target_link_libraries(s3-sample aws-cpp-sdk-s3)

CMakeList.txt를 작성하였다면 아래의 명령을 통하여 샘플 프로그램에 해당하는 Visual Studio용 솔루션 파일을 생성할 수 있습니다.

PS > md app-test
PS > cd app-test
PS > cmake -Daws-sdk-cpp_DIR=..\sdk-build-64 .  –G “Visual Studio 14 2015 Win64”

app-test 폴더 내에s3-sample.sln이 생성되어 있음을 확인하실 수 있습니다. Visual Studio로 s3-sample 프로젝트를 열어서 빌드를 하고 실행을 해보시기 바랍니다. 위의 main.cpp에서 작성한 코드가 제대로 실행되었는지 확인하기 위해AWS 콘솔에 접속하여 위의 코드에서 지정한 S3버킷 내에 파일이 생성되었는지 직접 확인해 보시기 바랍니다.

4. AWS C++ SDK 사용시 주의할 점
지금까지 AWS C++ SDK를 정적 라이브러리(static library) 형태로 빌드 하고 이를 사용하는 방법에 대해서 알아보았습니다. cmake를 통하여 AWS C++ SDK를 구성하는 과정에서 STATIC_LINKING 옵션을 주지 않으면 기본적으로 동적 라이브러리 형태로 AWS C++ SDK가 빌드가 되고, 추가적으로 DLL파일이 생성됩니다.

이 파일을 애플리케이션의 실행 파일과 함께 두거나 Windows시스템이 기본적으로 DLL파일을 찾는 폴더(Windows\System32 등)에 두어야 해당 애플리케이션이 실행이 됩니다.  또한, AWS C++ SDK를 사용하는 프로젝트의 속성 페이지에서 “USE_IMPORT_EXPORT” 전처리 선언을 다음과 같이 추가 해주어야 합니다.

또한, 동적 라이브러리의 형태로 AWS C++ SDK를 빌드하는 경우 자동으로 커스텀 메모리 관리자를 사용하도록 설정이 됩니다.  그 이유는 메모리 경계(boundary) 문제가 있기 때문입니다. 즉, AWS C++ SDK DLL내에서 할당한(new)메모리의 경우는 반드시 DLL내에서 해제(delete)되어야 하고, 애플리케이션(.EXE) 내에서 할당한 경우는 반드시 해당 애플리케이션 내에서 해제되어야 하기 때문입니다. 그런데, C++의 경우 STL컨테이너를 많이 활용하게 됩니다.

STL컨테이너 특성상 내부에서 자동으로 메모리가 할당되고 해제되는 경우가 많기 때문에, 애플리케이션과 라이브러리간 STL객체를 주고 받는다면, STL내부에서 일어나는 메모리 할당/해제를 명백히 제어하기 어렵습니다. 즉, 애플리케이션 내에서 자동 할당된 메모리가 DLL내부에서 해제되거나, 그 반대의 경우가 있을 수 있습니다. 그래서, 커스텀 메모리 관리자를 통하여 DLL 및 애플리케이션의 메모리 할당/해제가 한 곳에서 일어나게끔 일원화 할 수 있도록 한 것입니다.

커스텀 메모리 관리자는 다음과 같은 형식으로 “Aws::InitAPI()”시에 지정할 수 있습니다만, 동적 라이브러리 형태로 사용하는 경우 따로 커스텀 메모리 관리자를 명시적으로 구현하여 지정하지 않더라도 AWS C++ SDK에서 제공하는 기본(default) 커스텀 메모리 관리자가 자동으로 설정이 됩니다. 기본 커스텀 메모리 관리자는 메모리 할당/해제 시에 C++ 런타임 시스템의 기본 할당자(default new/delete)를 사용하게 되지만 위에서 설명한 DLL 경계 문제를 해결해 준다는 장점이 있습니다.

// 커스텀 메모리 관리자를 직접 구현하여 사용하는 경우,
// MemorySystemInterface를 상속받아 AllocateMemory및 FreeMemory 구현

class MyMemoryManager : public Aws::Utils::Memory::MemorySystemInterface
{
  public:
    // ...
    virtual void* AllocateMemory(std::size_t blockSize, std::size_t alignment,
                                 const char *allocationTag = nullptr) override;
    virtual void FreeMemory(void* memoryPtr) override;
};

// main함수의 첫 부분에서 다음과 같은 방식으로 사용
MyMemoryManager sdkMemoryManager;
Aws::SDKOptions options;
options.memoryManagementOptions.memoryManager = &sdkMemoryManager;
Aws::InitAPI(options);

사용자가 자체적으로 커스텀 메모리 관리자를 어떻게 구현하여 사용할 수 있는지에 관한 내용은 다음에 구체적으로 다루도록 하겠습니다.

추가적으로, 동적 라이브러리 형태로 사용하는 경우에 있어서, AWS C++ SDK (.DLL)를 빌드할 때 사용한 컴파일러 종류 및 버전과, 애플리케이션(.EXE)을 빌드할 때 사용한 컴파일러가 반드시 같아야 합니다. (이 예제의 경우 Visual Studio 2015, MSVC 14) 그 이유는 Application Binary Interface (ABI) 호환 문제가 발생할 수 있기 때문입니다.

동적 라이브러리 형태로 사용할 경우 이와 같이 고려해야 할 면들이 많습니다. 그래서, 특별한 경우(AWS C++ SDK DLL을 여러 프로세스에서 공유해서 사용하는 경우 또는 자체 구현한 커스텀 메모리 관리자 사용 등)가 아니면 정적 라이브러리의 형태로 빌드해서 사용하는 것을 추천합니다. (cmake를 통하여 정적 라이브러리로 구성해서 사용하는 경우는 자동으로 전처리 선언이 꺼지기 때문에 커스텀 메모리 매니저는 사용하지 않게 됩니다.)

본 글은 아마존웹서비스 코리아의 솔루션즈 아키텍트가 국내 고객을 위해 전해 드리는 AWS 활용 기술 팁을 보내드리는 코너로서, 이번 글은 구승모 솔루션즈 아키텍트께서 작성해주셨습니다.