성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] The Windows Registry Adventure #1: ...
[정성태] systemd for Developers I ; https:/...
[정성태] 엄밀히 object 타입의 인스턴스가 다른 타입으로 형변환 가능...
[정성태] 아래의 글에서 나오는 "Windows Application Pa...
[정성태] The history of calling conventions,...
[정성태] Secure and Deploy .NET Windows Form...
[정성태] Get Started with Milvus Vector DB i...
[정성태] cyberark/PipeViewer - A tool that...
[정성태] WinForms in a 64-Bit world – our st...
[정성태] 예제에서 SELECT_SQL도 내부적으로는 SqlCommand/...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>.NET Core/5+에서 동적 컴파일한 C# 코드를 (Breakpoint도 활용하며) 디버깅하는 방법 - #line 지시자</h1> <p> 지난 글에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET Core/5+에서 C# 코드를 동적으로 컴파일/사용하는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12809'>https://www.sysnet.pe.kr/2/0/12809</a> </pre> <br /> C# 소스 코드를 동적으로 컴파일 및 사용하는 방법을 다뤘는데요, 사실 이에 대한 디버깅도 가능합니다. 어떻게 할 수 있는지 한번 다뤄볼까요? ^^<br /> <br /> 지난 글의 소스 코드를 활용해 바꿔볼 텐데요, 우선 C# 코드를 별도의 파일로 빼내야 합니다. 테스트를 위해 "dynamic_code.txt"라는 파일로 동적 코드를 빼내고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > string codeToCompile = File.ReadAllText("dynamic_code.txt"); </pre> <br /> 다음과 같이, 해당 파일은 "Build Action: None", "Copy to Output Directory: Copy if newer" 옵션을 설정해 줍니다. (위의 경우에는 "Build Action" 설정은 필요 없지만, 원래 C# 코드를 담은 파일의 확장자를 .cs로 주는 것이 일반적이기 때문에 그런 때를 위해 명시하는 것이 좋습니다.)<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='cs_dynamic_compile_debug_1.png' src='/SysWebRes/bbs/cs_dynamic_compile_debug_1.png' /><br /> <br /> 그리고, 디버깅이 가능하려면 debug 모드로 빌드해야 하므로 옵션을 조정하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > CSharpCompilationOptions options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary <span style='color: blue; font-weight: bold'>, optimizationLevel: OptimizationLevel.Debug</span>); </pre> <br /> 동적 빌드된 어셈블리의 바이너리뿐만 아니라 PDB 정보까지도 출력하도록 바꾸고, 사용할 때도 Symbol 정보를 로드하도록 바꿉니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>using (var symbolsStream = new MemoryStream())</span> using (var ms = new MemoryStream()) { EmitResult result = compilation.Emit(ms, <span style='color: blue; font-weight: bold'>pdbStream: symbolsStream</span>); if (result.Success) { ms.Seek(0, SeekOrigin.Begin); <span style='color: blue; font-weight: bold'>symbolsStream?.Seek(0, SeekOrigin.Begin);</span> Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(ms, <span style='color: blue; font-weight: bold'>symbolsStream</span>); var type = assembly.GetType("MyType"); var instance = assembly.CreateInstance("MyType"); var meth = type.GetMember("Print").First() as MethodInfo; meth.Invoke(instance, new[] { "World" }); } } </pre> <br /> 혹은 새로운 옵션인 embedded를 사용해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 닷넷 응용 프로그램을 위한 PDB 옵션 - full, pdbonly, portable, <a target='tab' href='https://www.sysnet.pe.kr/2/0/12733#prj_prop'>embedded</a> ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12554'>https://www.sysnet.pe.kr/2/0/12554</a> </pre> <br /> 다음과 같이 코드를 좀 더 간략하게 만들 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using (var ms = new MemoryStream()) { <span style='color: blue; font-weight: bold'>var emitOptions = new EmitOptions(debugInformationFormat: DebugInformationFormat.Embedded);</span> EmitResult result = compilation.Emit(ms, <span style='color: blue; font-weight: bold'>options: emitOptions</span>); 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" }); } } </pre> <br /> 기본적인 준비는 여기까지가 끝입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 자, 이제 마지막 남은 것이 하나 있는데요, 바로 비주얼 스튜디오에게 동적으로 컴파일한 MyType.Print 메서드가 불렸을 때 화면에 어떤 소스 코드 파일을 로드해 보여줄 것인지를 결정하는 것입니다.<br /> <br /> 그리고 바로 그런 역할을 하는 것이 #line 지시자입니다. 따라서 동적 소스 코드를 포함한 dynamic_code.txt 파일에 다음의 정보를 포함하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 1 2 <span style='color: blue; font-weight: bold'>#line 3 "dynamic_code.txt"</span> 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 } </pre> <br /> 빌드하면, 이제 C# 컴파일러는 해당 소스 코드가 "dynamic_code.txt" 파일에 있으며 기준점으로 3번째 라인에 매핑시켜야 한다는 것을 알 수 있습니다.<br /> <br /> 따라서, 이제 위의 소스 코드에 Breakpoint를 걸 수도 있고 실제로 디버그 모드로 실행하면 다음과 같이 BP도 잡히고 심지어 Watch 창에서 변숫값도 확인할 수 있습니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='cs_dynamic_compile_debug_2.png' src='/SysWebRes/bbs/cs_dynamic_compile_debug_2.png' /><br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1855&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 한 가지 가정을 해볼까요? 만약 제가 <a target='tab' href='https://docs.microsoft.com/ko-kr/aspnet/core/mvc/views/razor'>Razor 템플릿</a> 개발자이고 다음과 같은 코드 양식을 사용했을 때,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > @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> </pre> <br /> 어떻게 저 razor 파일 내에 있는 C# 코드에 BP를 걸게 만들 수 있었을까요? ^^ 그렇습니다. 자동 생성된 코드에서 저 파일을 지정하도록 하면 되는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // sample.cshtml.g.cs <span style='color: blue; font-weight: bold'>#line 3 "sample.cshtml"</span> // 위의 파일 이름이 sample.cshtml라고 가정 using System; using System.Text; // razor 파일을 C#으로 다루는 코드 </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1175
(왼쪽의 숫자를 입력해야 합니다.)