성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
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'>Roslyn 맛보기 - C# 소스 코드를 스크립트처럼 다루는 방법</h1> <p> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Roslyn 맛보기 (1) - C# 소스 코드를 스크립트 처럼 다루는 방법 Roslyn 맛보기 (2) - <a target='tab' href='http://www.sysnet.pe.kr/2/0/1154'>C# Interactive (1)</a> Roslyn 맛보기 (3) - <a target='tab' href='http://www.sysnet.pe.kr/2/0/1155'>C# Interactive (2)</a> Roslyn 맛보기 (4) - <a target='tab' href='http://www.sysnet.pe.kr/2/0/1156'>Roslyn Services APIs를 이용한 Code Issue 및 Code Action 기능 소개</a> Roslyn 맛보기 (5) - <a target='tab' href='http://www.sysnet.pe.kr/2/0/1157'>Syntax Analysis (Roslyn Syntax API)</a> Roslyn 맛보기 (6) - <a target='tab' href='http://www.sysnet.pe.kr/2/0/1158'>Roslyn Symbol / Binding API</a> Roslyn 맛보기 (7) - <a target='tab' href='http://www.sysnet.pe.kr/2/0/1159'>SyntaxTree 조작</a> </pre> <br /> Roslyn을 간단하게 설명하면, 컴파일러에 대한 Object Model 구조를 제공하는 거라고 보시면 됩니다. 예를 들어, <a target='tab' href='https://docs.microsoft.com/en-us/visualstudio/vsto/excel-object-model-overview'>Excel 프로그램에서 제공되는 Object Model</a>을 사용하면 Excel이 다루는 xls 문서 자체를 처리하는 것이 가능한데요. 마찬가지로, 컴파일러라는 것이 "소스 코드"를 다루는 것이기 때문에 Roslyn을 통해서 "소스 코드"에 대한 구문 분석 서비스까지 받을 수 있는 것입니다.<br /> <br /> 여기서는 ^^ 따분한 구문분석은 제쳐두고, 문자열로 구성된 C# 소스 코드를 C# 응용 프로그램에서 스크립트처럼 사용하는 활용 예를 소개해 보겠습니다.<br /> <br /> 실습을 위해서는 당연히 Roslyn을 설치해야 겠지요. ^^ 그 전에, 우선 "Visual Studio 2010 SP1 SDK"를 먼저 설치해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Visual Studio 2010 SP1 SDK ; http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=21835 ; <a target='tab' href='https://marketplace.visualstudio.com/items?itemName=VisualStudioProductTeam.VisualStudio2010SP1SDK'>https://marketplace.visualstudio.com/items?itemName=VisualStudioProductTeam.VisualStudio2010SP1SDK</a> </pre> <br /> 그다음 "Roslyn CTP" 버전을 다운로드 해서 설치하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Microsoft "Roslyn: CTP" ; http://www.microsoft.com/download/en/details.aspx?id=27746 dotnet/roslyn ; <a target='tab' href='https://github.com/dotnet/roslyn'>https://github.com/dotnet/roslyn</a> </pre> <br /> 설치 완료 이후, (x64의 경우) "C:\Program Files (x86)\Microsoft Codename Roslyn CTP" 폴더가 생성됩니다. 그 하위에 "Documentation" 폴더를 보면 "Step by step" 식으로 예제를 실습할 수 있는 워드 문서가 들어 있는데요, 이번에는 그중에서 "Interactive - Scripting Introduction.docx" 예제를 다룬다고 보면 될 것 같습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 간단하게 "C# Console Application"을 생성하고, 참조 추가 대화상자를 통해 다음과 같이 2개의 어셈블리를 추가합니다.<br /> <br /> <ul> <li>Roslyn.Compilers</li> <li>Roslyn.Compilers.CSharp</li> </ul> <br /> 준비는 다음과 같이 여러분의 코드에서 using 문을 추가하는 것으로 끝이 납니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using Roslyn.Compilers; using Roslyn.Scripting.CSharp; using Roslyn.Scripting; </pre> <br /> 이제 다음과 같이 C# 소스 코드를 스크립트 다루듯이 실행하는 것이 가능합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { Program pg = new Program(); pg.TestIt(); } private void TestIt() { var engine = new ScriptEngine(); engine.Execute("using System; Console.WriteLine(DateTime.Now);"); } </pre> <br /> 생각보다 간단하지요. ^^ <br /> <br /> <hr style='width: 50%' /><br /> <br /> 좀 더 나아가서, Session이라는 개념을 알아보기 위해 다음과 같이 바꿔보고 실행해 봅니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private void TestIt() { var engine = new ScriptEngine(); engine.Execute(<span style='color: blue; font-weight: bold'>"using System;"</span>); engine.Execute(<span style='color: blue; font-weight: bold'>"Console.WriteLine(DateTime.Now);"</span>); } </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;' > Unhandled Exception: Roslyn.Compilers.CompilationErrorException: (1,19): <span style='color: blue; font-weight: bold'>error CS0103: The name 'DateTime' does not exist in the current context</span> at Roslyn.Scripting.CommonScriptEngine.CompilationError(DiagnosticBag localDiagnostics, DiagnosticBag diagnostics) at Roslyn.Scripting.CSharp.ScriptEngine.Compile(String code, String fileName, DiagnosticBag diagnostics, Session session, Type delegateType, Type returnType, CancellationToken cancellationToken, Boolean isInteractive, Boolean isExecute, ICompilation& compilation, Delegate& factory) at Roslyn.Scripting.CommonScriptEngine.Execute[T](String code, String fileName, DiagnosticBag diagnostics, Session session, Boolean isInteractive) at Roslyn.Scripting.CommonScriptEngine.Execute(String code, Session session) at ConsoleApplication1.Program.TestIt() in D:\...\Program.cs:line 25 at ConsoleApplication1.Program.Main(String[] args) in D:\...\ConsoleAppl ication1\Program.cs:line 18 </pre> <br /> 그렇습니다. 스크립트 엔진 자체는 "using System;" 코드와 "Console.WriteLine(DateTime.Now);" 코드에 대해 전혀 다른 문맥을 적용해서 실행하기 때문에 위와 같은 예외가 발생하는 것입니다.<br /> <br /> 오류를 수정하려면 다음과 같이 코딩하는 것도 가능하지만,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > var engine = new ScriptEngine(); engine.Execute("<span style='color: blue; font-weight: bold'>System</span>.Console.WriteLine(<span style='color: blue; font-weight: bold'>System</span>.DateTime.Now);"); </pre> <br /> 일종의 연속될 수 있는 '문맥'을 적용해서 해결하는 방법도 있습니다. 바로 이런 개념으로 다가설 수 있는 것이 "Session"입니다. 그래서, 같은 세션 변수를 Execute 메서드에 전달해 줌으로써 연속적인 실행 결과를 동일한 문맥에서 처리할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > var engine = new ScriptEngine(); var <span style='color: blue; font-weight: bold'>session = Session.Create()</span>; engine.Execute("using System;", <span style='color: blue; font-weight: bold'>session</span>); engine.Execute("Console.WriteLine(DateTime.Now);", <span style='color: blue; font-weight: bold'>session</span>); </pre> <br /> <hr style='width: 50%' /><br /> <br /> 세션을 이해하셨으니, 이제 스크립트 내의 코드와 그것을 실행하는 주체, 즉 Host 측과의 코드를 연동하는 것을 알아보겠습니다.<br /> <br /> 이를 위해서는, 호스트 측을 하나의 '문맥' - 세션으로 지정해서 스크립트 측에 넘겨주면 됩니다.<br /> <br /> 예를 들어, 호스트 측에 정의된 "DoHostCode();"라는 메서드를 스크립트에서 실행하려면 다음과 같이 코드를 정의하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { Program pg = new Program(); pg.TestIt(); } private void TestIt() { var engine = new ScriptEngine(); <span style='color: blue; font-weight: bold'>var session = Session.Create(this);</span> engine.Execute("using System;", session); engine.Execute("Console.WriteLine(DateTime.Now);", session); <span style='color: blue; font-weight: bold'>engine.Execute("DoHostCode();", session);</span> } public void <span style='color: blue; font-weight: bold'>DoHostCode</span>() { Console.WriteLine("Program.DoHostCode called!"); } </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;' > Unhandled Exception: Roslyn.Compilers.CompilationErrorException: (1,1): <span style='color: blue; font-weight: bold'>error CS7012: The name 'DoHostCode' does not exist in the current context (are you missing a reference to assembly 'ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'?)</span> at Roslyn.Scripting.CommonScriptEngine.CompilationError(DiagnosticBag localDiagnostics, DiagnosticBag diagnostics) at Roslyn.Scripting.CSharp.ScriptEngine.Compile(String code, String fileName, DiagnosticBag diagnostics, Session sess ion, Type delegateType, Type returnType, CancellationToken cancellationToken, Boolean isInteractive, Boolean isExecute, ICompilation& compilation, Delegate& factory) at Roslyn.Scripting.CommonScriptEngine.Execute[T](String code, String fileName, DiagnosticBag diagnostics, Session session, Boolean isInteractive) at Roslyn.Scripting.CommonScriptEngine.Execute(String code, Session session) at ConsoleApplication1.Program.TestIt() in D:\...\Pr ogram.cs:line 29 at ConsoleApplication1.Program.Main(String[] args) in D:\...\Program.cs:line 18 Press any key to continue . . . </pre> <br /> 쉽게 생각해서, 스크립트 내부에서 실행하는 코드 역시 하나의 "DLL"을 만드는 "C# 프로젝트"라고 여기시면 됩니다. 만약, 여러분들이 위와 같은 코드를 수행하는 C# 프로젝트를 만들었다면, 당연히 ConsoleApplication1.exe를 참조 추가했을 것입니다.<br /> <br /> Roslyn의 스크립트 엔진에서는 다음과 같은 구문으로 가능합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > var engine = new ScriptEngine(new[] { this.GetType().Assembly.Location // ConsoleApplication1.exe 경로 }); </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;' > Unhandled Exception: System.TypeAccessException: <span style='color: blue; font-weight: bold'>Attempt by method 'Submission#2..ctor(Roslyn.Scripting.Session, System.Object ByRef)' to access type 'ConsoleApplication1.Program' failed.</span> at Submission#2..ctor(Session session, Object& submissionResult) at Submission#2.<Factory>(Session session) at Roslyn.Scripting.CommonScriptEngine.Execute[T](String code, String fileName, DiagnosticBag diagnostics, Session session, Boolean isInteractive) at Roslyn.Scripting.CommonScriptEngine.Execute(String code, Session session) at ConsoleApplication1.Program.TestIt() in D:\...\Program.cs:line 31 at ConsoleApplication1.Program.Main(String[] args) in D:\...\Program.cs:line 18 Press any key to continue . . . </pre> <br /> 아하... 다시 한번 스크립트 내부의 코드가 별도의 "C# 프로젝트"라고 생각해야 한다는 것을 상기시키는 군요. 외부에서 참조하는 것이니, 위의 코드에서 Program 타입이 public이 아니어서 이런 문제가 발생하는 것입니다.<br /> <br /> 모든 문제를 해결한 최종 코드는 다음과 같습니다.<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'>public</span> class Program { static void Main(string[] args) { Program pg = new Program(); pg.TestIt(); } private void TestIt() { <span style='color: blue; font-weight: bold'> var engine = new ScriptEngine(new[] { this.GetType().Assembly.Location }); </span> var session = Session.Create(this); engine.Execute("using System;", session); engine.Execute("Console.WriteLine(DateTime.Now);", session); engine.Execute("DoHostCode();", session); } public void DoHostCode() { Console.WriteLine("Program.DoHostCode called!"); } } ==== 실행 결과 ==== 2011-10-21 오전 12:10:30 Program.DoHostCode called! </pre> <br /> 실습 하나만 더 해볼까요? 스크립트 측에서 Host에서 제공하는 이벤트를 구독하는 것도 다음과 같이 가능합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class Program { <span style='color: blue; font-weight: bold'> public delegate void TimeChangedDelegate(object objState); public TimeChangedDelegate TimeChanged; </span> static void Main(string[] args) { Program pg = new Program(); pg.TestIt(); } private void TestIt() { var engine = new ScriptEngine(new[] { "System", this.GetType().Assembly.Location }); var session = Session.Create(this); engine.Execute("using System;", session); engine.Execute("Console.WriteLine(DateTime.Now);", session); engine.Execute("DoHostCode();", session); <span style='color: blue; font-weight: bold'> engine.Execute("void timeChanged(object state) { Console.WriteLine(state); }", session); engine.Execute("TimeChanged += new TimeChangedDelegate(timeChanged);", session); </span> Console.WriteLine(TimeChanged != null); <span style='color: blue; font-weight: bold'>TimeChanged(DateTime.Now);</span> } public void DoHostCode() { Console.WriteLine("Program.DoHostCode called!"); } } ==== 실행 결과 ==== 2011-10-21 오전 1:00:42 Program.DoHostCode called! True 2011-10-21 오전 1:00:42 </pre> <br /> 이 정도면... Roslyn에서 제공되는 스크립트 엔진에 대해서 대략 감을 잡으실 수 있겠죠? ^^<br /> <br /> 물론, 위의 작업들은 기존에도 csc.exe를 직접 호출한 후 Reflection을 통해서 이미 가능했었습니다. 하지만, Roslyn의 경우 좀 더 유기적으로 통합할 수 있는 길을 제공한다는 차이가 있는 것입니다.<br /> <br /> [<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=635&boardid=331301885'>첨부된 파일은 위의 코드를 포함한 예제 프로젝트</a>입니다.]<br /> <br /> 참고로, 참조된 2개의 어셈블리를 "Copy Local == True" 속성을 주어 exe 파일과 함께 배포하면 Roslyn이 설치되지 않은 PC에서도 정상적으로 실행됩니다. 적어도 스크립트 엔진 기능에 대해서는 Roslyn.Compilers.CSharp.dll, Roslyn.Compilers.dll 2개의 어셈블리에서 끝을 내준다는 의미입니다. (2개의 파일을 더해서 3MB가 채 안되는 군요.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 기타 사항이라면, 아쉽게도 아직은 CTP 버전이기 때문에 제법 많은 기능들이 누락되어 있는 문제가 있습니다. 이에 대해서는 다음의 문서에 정리되어 있으니 한번쯤 확인해 보시는 것도 좋겠지요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Microsoft “Roslyn” CTP - Readme.mht ; <a target='tab' href='http://www.microsoft.com/download/en/details.aspx?id=27746'>http://www.microsoft.com/download/en/details.aspx?id=27746</a> </pre> <br /> 일례로, 아직은 지원되지 않는 unchecked 구문을 사용하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > engine.Execute("int _value = unchecked((int)5);", session); </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;' > Unhandled Exception: Roslyn.Compilers.CompilationErrorException: (1,14): error CS8000: <span style='color: blue; font-weight: bold'>This language feature ('UncheckedExpression') is not yet implemented in Roslyn.</span> at Roslyn.Scripting.CommonScriptEngine.CompilationError(DiagnosticBag localDiagnostics, DiagnosticBag diagnostics) at Roslyn.Scripting.CSharp.ScriptEngine.Compile(String code, String fileName, DiagnosticBag diagnostics, Session session, Type delegateType, Type returnType, CancellationToken cancellationToken, Boolean isInteractive, Boolean isExecute, ICompilation& compilation, Delegate& factory) at Roslyn.Scripting.CommonScriptEngine.Execute[T](String code, String fileName, DiagnosticBag diagnostics, Session session, Boolean isInteractive) at Roslyn.Scripting.CommonScriptEngine.Execute(String code, Session session) at ConsoleApplication1.Program.TestIt() in D:\...\Program.cs:line 39 at ConsoleApplication1.Program.Main(String[] args) in D:\...\Program.cs:line 21 Press any key to continue . . . </pre> </p><br /> <br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
6840
(왼쪽의 숫자를 입력해야 합니다.)