Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)

.NET Core/5+에서 동적 컴파일한 C# 코드를 (Breakpoint도 활용하며) 디버깅하는 방법 - #line 지시자

지난 글에서,

.NET Core/5+에서 C# 코드를 동적으로 컴파일/사용하는 방법
; https://www.sysnet.pe.kr/2/0/12809

C# 소스 코드를 동적으로 컴파일 및 사용하는 방법을 다뤘는데요, 사실 이에 대한 디버깅도 가능합니다. 어떻게 할 수 있는지 한번 다뤄볼까요? ^^

지난 글의 소스 코드를 활용해 바꿔볼 텐데요, 우선 C# 코드를 별도의 파일로 빼내야 합니다. 테스트를 위해 "dynamic_code.txt"라는 파일로 동적 코드를 빼내고,

string codeToCompile = File.ReadAllText("dynamic_code.txt");

다음과 같이, 해당 파일은 "Build Action: None", "Copy to Output Directory: Copy if newer" 옵션을 설정해 줍니다. (위의 경우에는 "Build Action" 설정은 필요 없지만, 원래 C# 코드를 담은 파일의 확장자를 .cs로 주는 것이 일반적이기 때문에 그런 때를 위해 명시하는 것이 좋습니다.)

cs_dynamic_compile_debug_1.png

그리고, 디버깅이 가능하려면 debug 모드로 빌드해야 하므로 옵션을 조정하고,

CSharpCompilationOptions options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary
                , optimizationLevel: OptimizationLevel.Debug);

동적 빌드된 어셈블리의 바이너리뿐만 아니라 PDB 정보까지도 출력하도록 바꾸고, 사용할 때도 Symbol 정보를 로드하도록 바꿉니다.

using (var symbolsStream = new MemoryStream())
using (var ms = new MemoryStream())
{
    EmitResult result = compilation.Emit(ms, pdbStream: symbolsStream);

    if (result.Success)
    {
        ms.Seek(0, SeekOrigin.Begin);
        symbolsStream?.Seek(0, SeekOrigin.Begin);

        Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(ms, symbolsStream);
        var type = assembly.GetType("MyType");
        var instance = assembly.CreateInstance("MyType");
        var meth = type.GetMember("Print").First() as MethodInfo;
        meth.Invoke(instance, new[] { "World" });
    }
}

혹은 새로운 옵션인 embedded를 사용해,

닷넷 응용 프로그램을 위한 PDB 옵션 - full, pdbonly, portable, embedded
; https://www.sysnet.pe.kr/2/0/12554

다음과 같이 코드를 좀 더 간략하게 만들 수 있습니다.

using (var ms = new MemoryStream())
{
    var emitOptions = new EmitOptions(debugInformationFormat: DebugInformationFormat.Embedded);

    EmitResult result = compilation.Emit(ms,
        options: emitOptions);

    if (result.Success)
    {
        ms.Seek(0, SeekOrigin.Begin);

        Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(ms);
        var type = assembly.GetType("MyType");
        var instance = assembly.CreateInstance("MyType");
        var meth = type.GetMember("Print").First() as MethodInfo;
        meth.Invoke(instance, new[] { "World" });
    }
}

기본적인 준비는 여기까지가 끝입니다.




자, 이제 마지막 남은 것이 하나 있는데요, 바로 비주얼 스튜디오에게 동적으로 컴파일한 MyType.Print 메서드가 불렸을 때 화면에 어떤 소스 코드 파일을 로드해 보여줄 것인지를 결정하는 것입니다.

그리고 바로 그런 역할을 하는 것이 #line 지시자입니다. 따라서 동적 소스 코드를 포함한 dynamic_code.txt 파일에 다음의 정보를 포함하고,

 1 
 2 #line 3 "dynamic_code.txt"
 3 
 4 using System;
 5 using System.Text;
 6 
 7 public class MyType
 8 {
 9     public void Print(object obj)
10     {
11         StringBuilder sb = new StringBuilder();
12         sb.Append(DateTime.Now);
13 
14         Console.WriteLine("Hello: " + obj + " : " + sb.ToString());
15     }
16 }

빌드하면, 이제 C# 컴파일러는 해당 소스 코드가 "dynamic_code.txt" 파일에 있으며 기준점으로 3번째 라인에 매핑시켜야 한다는 것을 알 수 있습니다.

따라서, 이제 위의 소스 코드에 Breakpoint를 걸 수도 있고 실제로 디버그 모드로 실행하면 다음과 같이 BP도 잡히고 심지어 Watch 창에서 변숫값도 확인할 수 있습니다.

cs_dynamic_compile_debug_2.png

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




한 가지 가정을 해볼까요? 만약 제가 Razor 템플릿 개발자이고 다음과 같은 코드 양식을 사용했을 때,

@page
@{
    var name = string.Empty;
    if (Request.HasFormContentType)
    {
        name = Request.Form["name"];
    }
}
<div style="margin-top:30px;">
    <form method="post">
        <div>
            Name: <input name="name" />
        </div>
        <div>
            <input type="submit" />
        </div>
    </form>
</div>
<div>
    @if (!string.IsNullOrEmpty(name))
    {
    <p>Hello @name!</p>
    }
</div>

어떻게 저 razor 파일 내에 있는 C# 코드에 BP를 걸게 만들 수 있었을까요? ^^ 그렇습니다. 자동 생성된 코드에서 저 파일을 지정하도록 하면 되는 것입니다.

// sample.cshtml.g.cs

#line 3 "sample.cshtml"  // 위의 파일 이름이 sample.cshtml라고 가정
using System;
using System.Text;

// razor 파일을 C#으로 다루는 코드




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 8/27/2021]

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

비밀번호

댓글 작성자
 




... 76  77  78  79  80  81  82  83  [84]  85  86  87  88  89  90  ...
NoWriterDateCnt.TitleFile(s)
11879정성태4/24/201929887.NET Framework: 825. (번역글) .NET Internals Cookbook Part 10 - Threads, Tasks, asynchronous code and others파일 다운로드2
11878정성태4/22/201924721.NET Framework: 824. (번역글) .NET Internals Cookbook Part 9 - Finalizers, queues, card tables and other GC stuff파일 다운로드1
11877정성태4/22/201924993.NET Framework: 823. (번역글) .NET Internals Cookbook Part 8 - C# gotchas파일 다운로드1
11876정성태4/21/201922833.NET Framework: 822. (번역글) .NET Internals Cookbook Part 7 - Word tearing, locking and others파일 다운로드1
11875정성태4/21/201924978오류 유형: 530. Visual Studo에서 .NET Core 프로젝트를 열 때 "One or more errors occurred." 오류 발생
11874정성태4/20/201925043.NET Framework: 821. (번역글) .NET Internals Cookbook Part 6 - Object internals파일 다운로드1
11873정성태4/19/201923769.NET Framework: 820. (번역글) .NET Internals Cookbook Part 5 - Methods, parameters, modifiers파일 다운로드1
11872정성태4/17/201924640.NET Framework: 819. (번역글) .NET Internals Cookbook Part 4 - Type members파일 다운로드1
11871정성태4/16/201922072.NET Framework: 818. (번역글) .NET Internals Cookbook Part 3 - Initialization tricks [3]파일 다운로드1
11870정성태4/16/201921467.NET Framework: 817. Process.Start로 실행한 콘솔 프로그램의 출력 결과를 얻는 방법파일 다운로드1
11869정성태4/15/201928203.NET Framework: 816. (번역글) .NET Internals Cookbook Part 2 - GC-related things [2]파일 다운로드2
11868정성태4/15/201923266.NET Framework: 815. CER(Constrained Execution Region)이란?파일 다운로드1
11867정성태4/15/201922267.NET Framework: 814. Critical Finalizer와 SafeHandle의 사용 의미파일 다운로드1
11866정성태4/9/201924821Windows: 159. 네트워크 공유 폴더(net use)에 대한 인증 정보는 언제까지 유효할까요?
11865정성태4/9/201921255오류 유형: 529. 제어판 - C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Administrative Tools is not accessible.
11864정성태4/9/201919780오류 유형: 528. '...' could be '0': this does not adhere to the specification for the function '...'
11863정성태4/9/201919665디버깅 기술: 127. windbg - .NET x64 EXE의 EntryPoint
11862정성태4/7/201921770개발 환경 구성: 437. .NET EXE의 ASLR 기능을 끄는 방법
11861정성태4/6/201920985디버깅 기술: 126. windbg - .NET x86 CLR2/CLR4 EXE의 EntryPoint
11860정성태4/5/201925801오류 유형: 527. Visual C++ 컴파일 오류 - error C2220: warning treated as error - no 'object' file generated
11859정성태4/4/201923042디버깅 기술: 125. WinDbg로 EXE의 EntryPoint에서 BP 거는 방법
11858정성태3/27/201924071VC++: 129. EXE를 LoadLibrary로 로딩해 PE 헤더에 있는 EntryPoint를 직접 호출하는 방법파일 다운로드1
11857정성태3/26/201921012VC++: 128. strncpy 사용 시 주의 사항(Linux / Windows)
11856정성태3/25/201921703VS.NET IDE: 134. 마이크로소프트의 CoreCLR 프로파일러 리눅스 예제를 Visual Studio F5 원격 디버깅하는 방법 [1]파일 다운로드1
11855정성태3/25/201924093개발 환경 구성: 436. 페이스북 HTTPS 인증을 localhost에서 테스트하는 방법
11854정성태3/25/201919627VS.NET IDE: 133. IIS Express로 호스팅하는 사이트를 https로 접근하는 방법
... 76  77  78  79  80  81  82  83  [84]  85  86  87  88  89  90  ...