Microsoft MVP성태의 닷넷 이야기
.NET Framework: 257. Roslyn 맛보기 - Roslyn Symbol / Binding API [링크 복사], [링크+제목 복사],
조회: 25415
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 6개 있습니다.)

Roslyn 맛보기 - Roslyn Symbol / Binding API


Roslyn 맛보기 (1) - C# 소스 코드를 스크립트 처럼 다루는 방법
Roslyn 맛보기 (2) - C# Interactive (1)
Roslyn 맛보기 (3) - C# Interactive (2)
Roslyn 맛보기 (4) - Roslyn Services APIs를 이용한 Code Issue 및 Code Action 기능 소개
Roslyn 맛보기 (5) - Syntax Analysis (Roslyn Syntax API)
Roslyn 맛보기 (6) - Roslyn Symbol / Binding API
Roslyn 맛보기 (7) - SyntaxTree 조작

(이번 글은, "%PROGRAMFILES% (x86)\Microsoft Codename Roslyn CTP\Documentation\Getting Started - Semantic Analysis (CSharp).docx" 문서를 요약한 것입니다.)

지난번에는 "Syntax API"를 살펴봤었지요.

Roslyn 맛보기 - Syntax Analysis (Roslyn Syntax API)
; https://www.sysnet.pe.kr/2/0/1157

즉, C# Parser에 대해서 알아본 것인데, 이것만으로는 C# 소스 코드로부터 '의미있는 정보'를 얻어내는 데에는 아직 부족한 면이 있습니다. 왜냐하면, 아직 Binding 작업이 이뤄지지 않았기 때문입니다.

이쯤에서, 이야기를 더 진행하기에 앞서 Symbol과 Binding에 대해서 설명을 하는 것이 좋겠군요. ^^

지난번의 예제 소스 코드를 다시 한번 들여다 볼까요?

static void Main(string[] args)
{

    SyntaxTree tree = SyntaxTree.ParseCompilationUnit(
        @"using System;
        using System.Collections.Generic;
        using System.Linq;

        namespace HelloWorld
        {
            class Program
            {
                static void Main(string[] args)
                {
                    Console.WriteLine(""Hello, World!"");
                }
            }
        }");

    var root = (CompilationUnitSyntax)tree.Root;
}

위의 소스 코드를 가지고 하나의 질문을 해보겠습니다. 과연, ParseCompilationUnit 메서드를 이용하여 SyntaxTree를 생성해 낸 경우 해당 tree로부터 "Console"에 정의된 다른 메서드를 열람할 수 있을까요?

이 문제에 대한 답은 SyntaxTree.ParseCompilationUnit에 대한 '인자'를 살펴보면 알 수 있습니다. 보는 바와 같이, ParseCompilationUnit 메서드로는 Console을 정의하고 있는 어셈블리 정보가 전달되지 않았습니다. 즉, 이런 과정을 통해서 생성된 SyntaxTree는 말 그대로 "C# 문법"에 적합한 구문 분석 결과를 내놓는 역할만 합니다.

예제를 좀 더 확장해 볼까요?

static void Main(string[] args)
{
    SyntaxTree tree1 = SyntaxTree.ParseCompilationUnit(
        @"using System;
        using System.Collections;
        using System.Linq;
 
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine(""Hello, World!"");
                new MyClass().DoMethod();
            }
        }");

    SyntaxTree tree2 = SyntaxTree.ParseCompilationUnit(
        @"using System;
        using System.Collections;
        using System.Linq;
 
        public class MyClass
        {
            public void DoMethod()
            {
                Console.WriteLine(""DoMethod!"");
            }
        }");
}

여기서도 질문을 해보겠습니다. tree1 인스턴스에서 사용된 "MyClass"가 과연 struct인지, class인지 tree1 자체에서 알 수 있는 방법이 있을까요? 역시 이번에도 SyntaxTree 내에 저장된 정보로는 알 수 없습니다.

자, 이제 감이 오시죠? ^^ 바로 이렇게 외부에 참조된 어셈블리의 메타데이터 또는 다른 소스 코드에 선언된 클래스들과 현재의 SyntaxTree를 '연결'하는 작업이 "Binding"입니다. 컴파일러 입장에서는 tree2 인스턴스로부터 "MyClass"가 class임을 인식하고 별도의 "Symbol Table"에 이 값을 저장해 둔 후, 다른 소스 코드에서 "MyClass"를 사용했을 때 "Symbol Table"로부터 그에 대한 정보를 연결시키는 작업이라고 볼 수 있습니다.

당연하겠지만, Roslyn에서는 이런 바인딩 작업을 위해 Compilation.Create 메서드를 제공해 주고 있습니다. 아래는 위의 2가지 SyntaxTree에 대해 사용한 예제 코드입니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
 
namespace SemanticsCS
{
    class Program
    {
        static void Main(string[] args)
        {
            SyntaxTree tree1 = SyntaxTree.ParseCompilationUnit(...);
            SyntaxTree tree2 = SyntaxTree.ParseCompilationUnit(...);
 
            var systemCore = new AssemblyFileReference(typeof(System.Linq.Enumerable).Assembly.Location);
            var mscorlib = new AssemblyFileReference(typeof(object).Assembly.Location);
            var compilation = Compilation.Create("HelloWorld")
                                     .AddReferences(systemCore)
                                     .AddReferences(mscorlib)
                                     .AddSyntaxTrees(tree1)
                                     .AddSyntaxTrees(tree2);
         }
    }
}

예상한 데로, 소스 코드에서 사용되고 있는 Symbol들에 대한 메타데이터 정보를 얻을 수 있는 어셈블리 2개를 참조시켰고, tree1과 tree2에 대한 SyntaxTree를 모두 전달함으로써 MyClass에 대한 이름 풀이도 가능하게 했습니다.

이제부터는, Roslyn에서 제공되는 "Semantic" 관련 API를 통해서 SyntaxTree에 정의된 특정 이름에 대해 Binding 된 심벌 정보를 얻어낼 수 있습니다. 아래는 그 예입니다.

var model = compilation.GetSemanticModel(tree1);
var nameInfo = model.GetSemanticInfo(root.Usings[0].Name);

foreach (var ns in (nameInfo.Symbol as NamespaceSymbol).GetNamespaceMembers())
{
    Console.WriteLine(ns.Name);
}

// 출력 결과:
Security
Reflection
Diagnostics
Configuration
Linq
Globalization
StubHelpers
IO
Text
Dynamic
Deployment
Collections
Runtime
Management
Threading
Resources

첫 번째 "using System;"에서 "System"에 대한 정보가 Binding 되었으므로, 이제는 System이 "mscorlib.dll"에 정의되었음을 알 수 있고 "System" 네임스페이스 하위에 정의된 서브 네임스페이스 정보까지 열람할 수 있게 된 것입니다.




정리해 보면, 여러분이 C# 소스 코드로부터 원하는 정보가 어떤 것이냐에 따라서 Syntax API만 사용해서 끝내거나, 아니면 Binding까지 시켜서 Binding API를 사용해야만 할 수도 있습니다. 이 차이를 알고 있어야 적절한 API를 선택할 수 있겠지요. ^^

마무리 하기 전에, Compilation.Create 작업까지 했으면 사실상 어셈블리 생성까지 이미 가능한 수준이 되었기 때문에 마저 '컴파일' 과정을 완료시켜 보면 다음과 같습니다.

var compilation = Compilation.Create("HelloWorld")
                                     .AddReferences(systemCore)
                                     .AddReferences(mscorlib)
                                     .AddSyntaxTrees(tree1)
                                     .AddSyntaxTrees(tree2);

using (var stream = new MemoryStream())
{
    EmitResult compileResult = compilation.Emit(stream);

    if (compileResult.Success == true)
    {
        Assembly compiledAssembly = Assembly.Load(stream.GetBuffer());

        Type test = compiledAssembly.GetType("Program");
    }
    else
    {
        foreach (var diag in compileResult.Diagnostics)
        {
            Console.WriteLine(diag.Info);
        }
    }
}

역시 그렇게 어려운 작업은 아니죠? ^^ (이런 경우, 컴파일 결과만 놓고봐서는 차라리 스크립트 엔진을 사용하는 것이 더 간단할 수 있습니다.)

(아직 CTP 버전이라서 그런지 모르겠지만) 다소 이상한 점이 있다면 EmitResult.Success == false인 경우, 위와 같이 코드에서는 EmitResult.Diagnostics를 이용하여 오류 원인을 알아낼 수 있었던 반면, Visual Studio Debug 창에서는 알 수 없었습니다.

굳이 Debug 창을 통해서 알아내려면 ... ^^; 다음과 같은 다소 복잡한 하위 탐색을 이용해서만 가능했습니다.

[그림: System.Linq를 참조하지 않았을 때의 오류 메시지 열람]
roslyn_binding_1.png

첨부된 파일은 위의 코드를 포함한 예제 프로젝트입니다.




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

[연관 글]






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

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

비밀번호

댓글 작성자
 




... 121  122  123  124  125  [126]  127  128  129  130  131  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
2904정성태4/27/201527061DDK: 6. ZwTerminateProcess로 프로세스를 종료하는 Device Driver 프로그램 [2]파일 다운로드1
2903정성태4/20/201520544Windows: 110. (무료) 마이크로소프트 온라인 강좌 소개 - Azure에서 제공하는 계정 관리 서비스
2902정성태4/16/201526503Windows: 109. (무료) 마이크로소프트 온라인 강좌 소개 - Active Directory 이해
2901정성태4/15/201523106Windows: 108. (무료) 마이크로소프트 온라인 강좌 소개 - Windows Server 2012 R2 주요 기술 (Hyper-V 관점)
2900정성태3/24/201522147오류 유형: 279. robocopy 오류 - The file cannot be accessed by the system [4]
2899정성태3/24/201530245개발 환경 구성: 264. Visual Studio 2013 솔루션을 2015로 마이그레이션
2898정성태3/24/201520833개발 환경 구성: 263. SharePoint 2013을 Windows Server 2012 R2에 설치
2897정성태3/18/201519501오류 유형: 278. LoadLibrary("...") failed - Invalid access to memory location.
2896정성태3/18/201519655VC++: 90. Visual Studio 2013에서 Visual Basic 6용 ATL Control 제작
2895정성태3/18/201522708VC++: 89. Visual Studio 2015 - auto 반환 타입 및 thread_local 예약어 지원(C++ 11 표준) [2]
2894정성태3/18/201520966.NET Framework: 509. ELEMENT_TYPE_MODIFIER의 조합
2893정성태3/18/201521054오류 유형: 277. command line error MIDL1004: cannot execute C preprocessor cl.exe
2892정성태3/17/201525927오류 유형: 276. robocopy - Logon failure: unknown user name or bad password.
2891정성태3/17/201542065개발 환경 구성: 262. Visual Basic 6 (Enterprise Edition)을 Windows 7 x86에 설치하는 방법 [1]
2890정성태3/17/201524422오류 유형: 275. Internet Explorer - This page can't be displayed
2889정성태3/17/201525068Windows: 107. (2015-03-12) 업데이트 이후 작업 표시줄 또는 탐색기의 반응이 느려지는 문제 [1]
2888정성태3/17/201523116.NET Framework: 508. Visual Studio 빌드 - fatal error C1033: cannot open program database ''
2887정성태3/13/201520335.NET Framework: 507. CoreFx 빌드하는 방법
2886정성태3/13/201522107오류 유형: 274. CoreFx, CoreCLR 빌드 시 "error CS0518: Predefined type 'System.Object' is not defined or imported" 오류 해결 방법
2885정성태3/13/201533431VS.NET IDE: 99. Visual Studio는 2019는 32비트, 2022부터 64비트 버전입니다. [2]
2884정성태3/12/201526725.NET Framework: 506. .NETCore = CoreFX + CoreCLR [5]
2883정성태3/10/201523855.NET Framework: 505. OpenCover 소스 코드 분석을 Visual Studio 2013에서 하는 방법 [1]
2882정성태3/10/201522750.NET Framework: 504. OpenCover 코드 커버리지 도구의 동작 방식을 통해 살펴보는 Calli IL 코드 사용법
2881정성태3/9/201523275개발 환경 구성: 261. OpenCover 오픈 소스를 이용한 .NET 코드 커버리지(Code coverage)
2880정성태3/7/201521527개발 환경 구성: 260. C# Code Coverage 도구 - Semantic Designs 소개
2879정성태3/3/201526532개발 환경 구성: 259. Visual Studio 없이 Visual C++ 컴파일하는 방법
... 121  122  123  124  125  [126]  127  128  129  130  131  132  133  134  135  ...