Microsoft MVP성태의 닷넷 이야기
개발 환경 구성: 104. Visual C++ Custom Build Tool 사용예 [링크 복사], [링크+제목 복사],
조회: 33877
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

Visual C++ Custom Build Tool 사용예

아래의 글을 쓸 때만 해도,
XmlCodeGenerator를 C/C++ 코드 생성에 적용
; https://www.sysnet.pe.kr/2/0/775

Visual Studio에 이런 기능이 있는 줄 몰랐었습니다.

Specifying Custom Build Tools
; https://docs.microsoft.com/en-us/previous-versions/hefydhhy(v=vs.140)

도대체... 언제 이런 기능들이 소리 소문 없이 추가되는지... ^^;

어쨌든 위의 글을 보면서 예전에 읽고 지나쳤던 다음의 글이 생각났습니다.

어려운 문제, 컴파일 타임 문자열 난독화
; http://jiniya.net/wp/archives/1371

아래와 같은 표현을 쓰고 싶다는 것인데요.

Obfuscator<0x10221337,0xA30624DD>(CompileTimeEncode("Hello", 0x10221337)+0xA30624DD).GetData()

언어적으로 우아하게 해결하는 것이 최선이겠지만 일단 VC++에서의 지원은 모호하니 다른 방법을 모색해봐야 할 것 같습니다. 본문에서도 나오지만, "프리프로세서를 만들어서 데이터 파일에서 코드를 생성"하는 것이 있겠는데, 바로 그 방법이 Visual Studio의 Custom Build Tool과 결합되면 마치 DSL(Domain-Specific Language)를 사용하는 것과 같은 분위기를 낼 수 있습니다.

이번 문제를 위해서, 우리만의 XML로 된 문자열 인코딩 언어(?)를 만들어 볼까요?




예전에 쓴 글이 하나 있는데요. 전체적인 동작 방식은 그 글에서 이미 다 설명되어 있습니다.

XmlCodeGenerator를 C/C++ 코드 생성에 적용
; https://www.sysnet.pe.kr/2/0/775

대신 한 가지 바뀐 것이 있다면, C#에서는 코드 생성 도구가 Visual Studio와 확장 DLL 수준에서 연결이 되는 반면, Visual C++의 Custom Build Tool을 위해서는 EXE 파일로 연결이 됩니다. 이를 위해 XmlCodeGenerator를 오랜만에 업그레이드를 했는데, 1,0,0,7 이상의 버전을 설치하면 XmlCodeBuilder.exe라는 실행 파일이 함께 설치됩니다. XmlCodeGenerator 설치 관련해서는 다음의 문서를 참조하세요.

XmlCodeGenerator 1.0.0.4 업데이트
; https://www.sysnet.pe.kr/2/0/760

물론, 여러분들만의 XmlCodeBuilder.exe를 만들어도 되고 아니면 전혀 별개의 소스 코드 생성기를 하나 정의하셔도 됩니다.

준비가 끝났으면, 이제 다음과 같이 encoded.xml 파일을 Visual C++ 프로젝트에 추가합니다.

<?xml version="1.0" encoding="utf-8"?>

<encoded>

    <item id="HELLO" value="Hello" />
    <item id="GOOD_MORNING" value="Good morning" />
    <item id="GOOD_MORNING_KR" value="좋은 아침!" />

</encoded>

위의 XML 파일로부터 CPP 코드를 생성해 줄 encoded.xslt 파일을 추가하고 다음과 같이 채워줍니다.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" 
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:xcgs="https://www.sysnet.pe.kr/CppProfilerCodeGenerator"
>
    <xsl:output method="text" encoding="utf-8" indent="yes"></xsl:output>

    <xsl:template match="encoded">

#if defined(ENCODED_HEADER)
   
        <xsl:for-each select="item">
#define MSG_<xsl:value-of select="@id"/><xsl:text disable-output-escaping="yes"> g_encodes[</xsl:text><xsl:value-of select="position() - 1"/>]            
        </xsl:for-each>
        
extern wchar_t *g_encodes[];

#endif       

#if defined(ENCODED_IMPL)
        <xsl:for-each select="item">
wchar_t *g_encodedText_<xsl:value-of select="@id"/> = L"<xsl:value-of select="xcgs:EncodeText(string(@value))"/>";
        </xsl:for-each>

wchar_t *g_encodes[] =
{
        <xsl:for-each select="item">
            g_encodedText_<xsl:value-of select="@id"/>,  
        </xsl:for-each>
};

#endif
        
    </xsl:template>
    
<msxsl:script language="JScript" implements-prefix="xcgs">

<![CDATA[
    
  function EncodeText(text)
  {
    var textLength = text.length;
    var pos = 0;
    var encResult = "";
    
    for (pos = 0; pos < textLength; pos ++)
    {
        var ch = text.charCodeAt(pos);
        encResult += ch.toString() + ",";
    }
    
    return encResult.toString();
  }    

]]>

</msxsl:script>

</xsl:stylesheet>

이렇게 준비된 XML과 XSLT를 결합시켜줄 작업을 해야 하는데요. 바로 이 과정이 Visual Studio 2008 이후부터 제공되는 "Custom Build Tool"입니다. 방법은 간단합니다. 솔루션 탐색기 뷰에서 encoded.xml 항목을 오른쪽 마우스 버튼을 눌러 속성창을 띄우고, 처음에는 다음과 같이 "Does not participate in build" 항목으로 선택되어 있을 텐데, 이를 "Custom Build Toold"로 바꿔주고 "Apply(적용)" 버튼을 눌러줍니다.

visual_studio_cpp_custom_build_1.png

그럼, 다음과 같이 왼쪽 트리 구성이 바뀌면서 "Custom Build Tool" 동작을 위한 옵션이 새롭게 나타납니다.

visual_studio_cpp_custom_build_2.png

옵션 입력을 아래와 같이 해줍니다.

Command Line: D:\Tools\XmlCodeGenerator\XmlCodeBuilder.exe %(FullPath) .\%(Filename).cpp
Outputs: .\%(Filename).cpp

자... 이제 끝났군요. xml + xslt가 되도록 Custom Build가 호출되는 경우는 다음과 같습니다.

  • encoded.xml 파일을 Visual Studio에서 열고 편집 윈도우 상에서 "Ctrl + F7" 키를 누르는 경우
  • 솔루션 탐색기에서 encoded.xml 항목을 선택하고 마우스 오른쪽 버튼을 눌러 "compile" 메뉴를 선택하는 경우
  • encoded.xml 파일이 변경된 후 "Build"를 하게 되는 경우
  • 프로젝트를 "Rebuild"하게 되는 경우

간단히 정리하면, encoded.xml 파일에 문자열 정의를 자유롭게 한 후 빌드만 해주면 되는데, 이럴 때 아래와 같이 Output 창에서 소스 코드 생성 과정이 이뤄지는 것을 확인할 수 있습니다.

1>------ Rebuild All started: Project: encode_preproc, Configuration: Debug Win32 ------
1>  Performing Custom Build Tools
1>  stdafx.cpp
1>  Obfuscator.cpp
1>  encode_preproc.cpp
1>  Generating Code...
1>  encode_preproc.vcxproj -> D:\...[생략]...\Debug\encode_preproc.exe
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========

아래는 이렇게 해서 생성된 encoded.cpp 소스 파일입니다.

#if defined(ENCODED_HEADER)
           
#define MSG_HELLO g_encodes[0]            
        
#define MSG_GOOD_MORNING g_encodes[1]            
        
#define MSG_GOOD_MORNING_KR g_encodes[2]            
        
extern wchar_t *g_encodes[];

#endif       

#if defined(ENCODED_IMPL)
        
wchar_t *g_encodedText_HELLO = L"72,101,108,108,111,";
        
wchar_t *g_encodedText_GOOD_MORNING = L"71,111,111,100,32,109,111,114,110,105,110,103,";
        
wchar_t *g_encodedText_GOOD_MORNING_KR = L"51339,51008,32,50500,52840,33,";
        

wchar_t *g_encodes[] =
{
        
            g_encodedText_HELLO,  
        
            g_encodedText_GOOD_MORNING,  
        
            g_encodedText_GOOD_MORNING_KR,  
        
};
#endif

보는 것처럼 Hello라는 문자열이 인코딩되어서 L"72,101,108,108,111,"로 바뀌었습니다. 물론, 간단하게 10진수로 바꾼 것이지만 xslt 내의 자바스크립트 확장 함수에서 ActiveX를 생성할 수도 있으니 여러분들의 상상력에 따라 얼마든지 자유롭게 인코딩을 할 수 있습니다.

인코딩을 런타임 시에 해제해 주는 코드인 Obfuscator.h/cpp 파일은 여러분들이 XSLT에서 사용한 인코딩 방식에 준해서 역으로 만들어주면 됩니다. 그래서, 코드에서는 다음과 같이 사용해 줄 수 있는 것이고.

wstring text1 = Obfuscator.GetData(MSG_HELLO);
wstring text2 = Obfuscator.GetData(MSG_GOOD_MORNING);
wstring text3 = Obfuscator.GetData(MSG_GOOD_MORNING_KR);

첨부된 예제 프로젝트를 빌드해서 실행하면 다음과 같은 결과를 얻습니다.

visual_studio_cpp_custom_build_3.png

처음에는 XSLT를 만들어주는 부담이 있지만, 본격적인 생산성은 프로젝트가 진행되면서 유감없이 발휘됩니다. 문자열이 변경/추가/삭제가 필요하면 언제든지 XML 파일을 자유롭게 변경하고 다시 빌드만 해주면 끝이기 때문입니다. 게다가, 결과물 코드가 변경되어야 하는 상황이 되면 일일이 코드상에서 변경해 주는 것이 아니라 XSLT 파일에서만 변경해주고 빌드만 해주면 모두 적용되기 때문에 그 편리함은 이루 말할 수 없지요. ^^ 언어에서의 한계가 Visual Studio라는 도구를 이용해서 멋지게 해결되는 좋은 사례입니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 7/17/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2018-09-19 01시46분
정성태

... 106  107  108  109  110  [111]  112  113  114  115  116  117  118  119  120  ...
NoWriterDateCnt.TitleFile(s)
11149정성태2/21/201722890오류 유형: 378. A 64-bit test cannot run in a 32-bit process. Specify platform as X64 to force test run in X64 mode on X64 machine.
11148정성태2/20/201721895.NET Framework: 644. AppDomain에 대한 단위 테스트 시 알아야 할 사항
11147정성태2/19/201721121오류 유형: 377. Windows 10에서 Fake 어셈블리를 생성하는 경우 빌드 시 The type or namespace name '...' does not exist in the namespace 컴파일 오류 발생
11146정성태2/19/201719733오류 유형: 376. Error VSP1033: The file '...' does not contain a recognized executable image. [2]
11145정성태2/16/201721235.NET Framework: 643. 작업자 프로세스(w3wp.exe)가 재시작되는 시점을 알 수 있는 방법 - 두 번째 이야기 [4]파일 다운로드1
11144정성태2/6/201724583.NET Framework: 642. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (부록 1) - CallingConvention.StdCall, CallingConvention.Cdecl에 상관없이 왜 호출이 잘 될까요?파일 다운로드1
11143정성태2/5/201722018.NET Framework: 641. [Out] 형식의 int * 인자를 가진 함수에 대한 P/Invoke 호출 방법파일 다운로드1
11142정성태2/5/201730006.NET Framework: 640. 닷넷 - 배열 크기의 한계 [2]파일 다운로드1
11141정성태1/31/201724276.NET Framework: 639. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (4) - CLR JIT 컴파일러의 P/Invoke 호출 규약 [1]파일 다운로드1
11140정성태1/27/201720053.NET Framework: 638. RSAParameters와 RSA파일 다운로드1
11139정성태1/22/201722729.NET Framework: 637. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (3) - x64 환경의 __fastcall과 Name mangling [1]파일 다운로드1
11138정성태1/20/201721005VS.NET IDE: 113. 프로젝트 생성 시부터 "Enable the Visual Studio hosting process" 옵션을 끄는 방법 - 두 번째 이야기 [3]
11137정성태1/20/201719723Windows: 135. AD에 참여한 컴퓨터로 RDP 연결 시 배경 화면을 못 바꾸는 정책
11136정성태1/20/201718883오류 유형: 375. Hyper-V 내에 구성한 Active Directory 환경의 시간 구성 방법 - 두 번째 이야기
11135정성태1/20/201719921Windows: 134. Windows Server 2016의 작업 표시줄에 있는 시계가 사라졌다면? [1]
11134정성태1/20/201727349.NET Framework: 636. System.Threading.Timer를 이용해 타이머 작업을 할 때 유의할 점 [5]파일 다운로드1
11133정성태1/20/201723494.NET Framework: 635. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (2) - x86 환경의 __fastcall [1]파일 다운로드1
11132정성태1/19/201734970.NET Framework: 634. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (1) - x86 환경에서의 __cdecl, __stdcall에 대한 Name mangling [1]파일 다운로드1
11131정성태1/13/201723870.NET Framework: 633. C# - IL 코드 분석을 위한 팁 [2]
11130정성태1/11/201724405.NET Framework: 632. x86 실행 환경에서 SECURITY_ATTRIBUTES 구조체를 CreateEvent에 전달할 때 예외 발생파일 다운로드1
11129정성태1/11/201728764.NET Framework: 631. async/await에 대한 "There Is No Thread" 글의 부가 설명 [9]파일 다운로드1
11128정성태1/9/201723188.NET Framework: 630. C# - Interlocked.CompareExchange 사용 예제 [3]파일 다운로드1
11127정성태1/8/201722722기타: 63. (개발자를 위한) Visual Studio의 "with MSDN" 라이선스 설명
11126정성태1/7/201727484기타: 62. Edge 웹 브라우저의 즐겨찾기(Favorites)를 편집/백업/복원하는 방법 [1]파일 다운로드1
11125정성태1/7/201724300개발 환경 구성: 310. IIS - appcmd.exe를 이용해 특정 페이지에 클라이언트 측 인증서를 제출하도록 설정하는 방법
11124정성태1/4/201727754개발 환경 구성: 309. 3년짜리 유효 기간을 제공하는 StartSSL [2]
... 106  107  108  109  110  [111]  112  113  114  115  116  117  118  119  120  ...