성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Roll A Lisp In C - Reading ; https...
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
글쓰기
제목
이름
암호
전자우편
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'>C# - 컴파일 시점에 참조할 수 없는 타입을 포함한 이벤트 핸들러를 Reflection을 이용해 구독하는 방법</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;' > C# - 런타임 시점에 이벤트 핸들러를 만들어 Reflection을 이용해 구독하는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12609'>https://www.sysnet.pe.kr/2/0/12609</a> </pre> <br /> 한 가지 상황을 더 가정해 보겠습니다. 그러니까, 여기서 만약 EventHandler의 TempEventArgs 타입을 .NET Framework 4.6.2부터 제공하는 EventSourceCreatedEventArgs 타입이라고 가정해 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > EventSourceCreatedEventArgs ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.tracing.eventsourcecreatedeventargs'>https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.tracing.eventsourcecreatedeventargs</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class MyTemp { public void Create() { Created(this, new EventSourceCreatedEventArgs()); } public event EventHandler<<span style='color: blue; font-weight: bold'>EventSourceCreatedEventArgs</span>> Created; } </pre> <br /> 그리고 저 이벤트를 구독해야 할 코드가 정의된 곳은 .NET 4.0 대상의 DLL 프로젝트라는 제약을 두겠습니다. 즉, 다음의 코드는 .NET 4.6.2 이상의 ConsoleApp1 EXE 프로젝트에 있고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Diagnostics.Tracing; class Program { static void Main(string[] args) { MyTemp instance = new MyTemp(); <span style='color: blue; font-weight: bold'>ClassLibrary1.Class1 cl = new ClassLibrary1.Class1(); cl.Subscribe(instance);</span> instance.Create(); } } public class MyTemp { public void Create() { Created(this, new EventSourceCreatedEventArgs()); } public event EventHandler<EventSourceCreatedEventArgs> Created; } </pre> <br /> Class1의 코드는 .NET 4.6.2 미만의 DLL 프로젝트에 놓여 있는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Reflection; // .NET 4.6.2 미만의 Framework을 대상으로 하는 프로젝트에서는 EventSourceCreatedEventArgs 참조 오류 발생 namespace ClassLibrary1 { public class Class1 { public void Subscribe(object instance) { Type targetType = instance.GetType(); Type arg2Type = typeof(<span style='color: blue; font-weight: bold'>EventSourceCreatedEventArgs</span>); EventInfo ei = targetType.GetEvent("Created", BindingFlags.Public | BindingFlags.Instance); ei.AddEventHandler(instance, (EventHandler<<span style='color: blue; font-weight: bold'>EventSourceCreatedEventArgs</span>>)Instance_Created); } private static void Instance_Created(object sender, <span style='color: blue; font-weight: bold'>EventSourceCreatedEventArgs</span> e) { Console.WriteLine(e.ToString()); } } } </pre> <br /> 그럼, 당연히 위의 코드에서 EventSourceCreatedEventArgs 타입은 정의되어 있지 않으므로 컴파일 오류가 발생합니다. 이럴 때는 EventSourceCreatedEventArgs 타입에 대한 모든 처리를 Reflection으로 제어해야 하는데요. 여기서 또 한가지 문제라면, 우리가 작성해야 할 EventHandler 조차도,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private static void Instance_Created(object sender, <span style='color: blue; font-weight: bold'>EventSourceCreatedEventArgs</span> e) { Console.WriteLine(e.ToString()); } </pre> <br /> EventSourceCreatedEventArgs 타입에 대해 정적 바인딩을 하고 있으므로 역시나 사용할 수 없다는 점입니다. 그래서 이런 경우라면, <a target='tab' href='https://www.sysnet.pe.kr/2/0/12609#sample_code_2'>지난 글</a>에 제시한 두 번째 방법을 이용해 동적으로 EventSourceCreatedEventArgs 타입에 대한 이벤트 핸들러 역할을 하는 메서드를 생성해야 합니다. 그렇긴 한데, 사실 DynamicMethod와 IL 코드의 조합으로 프로그래밍하는 것이 꽤나 귀찮은 작업이므로, 해당 이벤트 핸들러에 들어갈 코드를 C# 코드로 만든 EventArgs 타입의 인자를 갖는 이벤트 핸들러에 넣고 그 메서드를 동적 메서드에서 호출하도록 할 예정입니다. 즉, 다음과 같은 구조를 갖게 되는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public void Subscribe(object instance) { Type targetType = instance.GetType(); /* EventSourceCreatedEventArgs 타입 사용 불가 Type arg2Type = typeof(EventSourceCreatedEventArgs); EventInfo ei = targetType.GetEvent("Created", BindingFlags.Public | BindingFlags.Instance); ei.AddEventHandler(instance, (EventHandler<EventSourceCreatedEventArgs>)Proxy_Created); */ // 동적으로 Proxy_Created 메서드를 만들고 이벤트 핸들러로 추가 } /* 동적 생성 private static void Proxy_Created(object sender, EventSourceCreatedEventArgs e) { Instance_Created(sender, e); // C#으로 만든 메서드로 호출 중계 } */ private static void Instance_Created(object sender, EventArgs e) { Console.WriteLine(e.ToString()); } </pre> <br /> 이를 위해 Subscribe 코드를 다음과 같이 만들 수 있는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; namespace ClassLibrary1 { public class Class1 { public void Subscribe(object instance) { Type targetType = instance.GetType(); Type arg2Type; <span style='color: blue; font-weight: bold'>Assembly asm = TryGetType("System.Diagnostics.Tracing.EventWrittenEventArgs", out arg2Type);</span> if (asm == null) { return; } EventInfo ei = targetType.GetEvent("Created", BindingFlags.Public | BindingFlags.Instance); <span style='color: blue; font-weight: bold'>MethodInfo proxyMethodInfo = typeof(Class1).GetMethod("Instance_Created", BindingFlags.NonPublic | BindingFlags.Static);</span> DynamicMethod proxyMethod = new DynamicMethod( "Proxy_Created", typeof(void), new Type[] { typeof(object), arg2Type }); ILGenerator il = proxyMethod.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); <span style='color: blue; font-weight: bold'>il.Emit(OpCodes.Call, proxyMethodInfo);</span> il.Emit(OpCodes.Ret); // 아래의 코드에서 실행 시 System.ArgumentException 예외 발생 <span style='color: blue; font-weight: bold'>Delegate proxyDelegate = proxyMethod.CreateDelegate(ei.EventHandlerType);</span> <span style='color: blue; font-weight: bold'>ei.AddEventHandler(instance, proxyDelegate);</span> } private static void Instance_Created(object sender, EventArgs e) { Console.WriteLine(e.ToString()); } private Assembly TryGetType(string typeName, out Type targetType) { targetType = null; foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) { Type type = asm.GetType(typeName); if (type != null) { targetType = type; return asm; } } return null; } } } </pre> <br /> 재미있는 것은, 위의 코드를 실행하면 proxyMethod.CreateDelegate 호출에서 예외가 발생한다는 점입니다.<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.ArgumentException: Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type. at System.Delegate.CreateDelegateNoSecurityCheck(Type type, Object target, RuntimeMethodHandle method) at System.Reflection.Emit.DynamicMethod.CreateDelegate(Type delegateType) at ClassLibrary1.Class1.Subscribe(Object instance) at Program.Main(String[] args) </pre> <br /> 오류 메시지를 보면, signature 또는 <a target='tab' href='https://www.sysnet.pe.kr/2/0/1680'>security transparency</a>의 문제로 보이는데 해당 코드를 .NET Core에서 실행시켜 보면 이것이 signature 문제임을 알 수 있습니다.<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 환경에서의 오류 메시지 System.ArgumentException HResult=0x80070057 Message=Cannot bind to the target method because its signature is not compatible with that of the delegate type. Source=System.Private.CoreLib StackTrace: at System.Delegate.CreateDelegateNoSecurityCheck(Type type, Object target, RuntimeMethodHandle method) at System.Reflection.Emit.DynamicMethod.CreateDelegate(Type delegateType) at ClassLibrary1.Class1.Subscribe(Object instance) at Program.Main(String[] args) </pre> <br /> 이때의 ei.EventHandlerType 타입은 "System.EventHandler`1[System.Diagnostics.Tracing.EventSourceCreatedEventArgs]"인데, 개인적으로 왜 여기서 오류가 발생하는지 잘 모르겠습니다. (아시다시피 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12609#sample_code_2'>지난 예제</a>에서는 위의 코드가 잘 동작했습니다.)<br /> <br /> 혹시 원인을 아시는 분은 덧글 부탁드립니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이유는 알 수 없지만, 어떻게서든지 우회로를 찾아야 합니다. 이런 경우 한 가지 방법이 있다면, 어차피 모든 EventHandler의 인자가 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.eventargs'>System.EventArgs</a>로부터 상속을 받기 때문에 동적 메서드의 인자를 System.Diagnostics.Tracing.EventWrittenEventArgs에서 EventArgs로 돌리는 것을 고려할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public void Subscribe(object instance) { Type targetType = instance.GetType(); <span style='color: blue; font-weight: bold'>Type arg2Type = typeof(System.EventArgs);</span> EventInfo ei = targetType.GetEvent("Created", BindingFlags.Public | BindingFlags.Instance); MethodInfo proxyMethodInfo = typeof(Class1).GetMethod("Instance_Created", BindingFlags.NonPublic | BindingFlags.Static); DynamicMethod proxyMethod = new DynamicMethod( "Proxy_Created", typeof(void), new Type[] { typeof(object), arg2Type }); ILGenerator il = proxyMethod.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Call, proxyMethodInfo); il.Emit(OpCodes.Ret); Delegate proxyDelegate = proxyMethod.CreateDelegate(ei.EventHandlerType); ei.AddEventHandler(instance, proxyDelegate); } </pre> <br /> 저렇게 바꾸면 일단 CreateDelegate 단계에서의 오류는 없어지지만, 대신 동적 생성 메서드 내에서의 "OpCodes.Call, proxyMethodInfo" 단계에서 (해당 이벤트가 발생한 실행 시점에) 다음과 같은 오류가 발생합니다.<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.MethodAccessException: Attempt by method 'DynamicClass.Proxy_Created(System.Object, System.EventArgs)' to access method 'ClassLibrary1.Class1.Instance_Created(System.Object, System.EventArgs)' failed. at Proxy_Created(Object , EventArgs ) at MyTemp.Create() at Program.Main(String[] args) </pre> <br /> 직접적인 원인은, 동적 생성한 메서드가 호출할 ClassLibrary1.Class1.Instance_Created의 접근 제한자가 "private"이기 때문에 발생하는 문제입니다. (그런데 여기서도 재미있는 점이 있다면, 위의 오류는 .NET Framework 환경에서만 발생하고 동일한 소스 코드를 .NET 5에서 실행하면 정상적으로 실행이 된다는 점입니다.)<br /> <br /> 어쨌든, .NET Framework에서도 동작하고 싶다면 ClassLibrary1.Class1.Instance_Created 메서드의 접근자를 public으로 명시하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public void Subscribe(object instance) { Type targetType = instance.GetType(); Type arg2Type = typeof(System.EventArgs); EventInfo ei = targetType.GetEvent("Created", BindingFlags.Public | BindingFlags.Instance); MethodInfo proxyMethodInfo = typeof(Class1).GetMethod("Instance_Created", <span style='color: blue; font-weight: bold'>BindingFlags.Public</span> | BindingFlags.Static); DynamicMethod proxyMethod = new DynamicMethod( "Proxy_Created", typeof(void), new Type[] { typeof(object), arg2Type }); ILGenerator il = proxyMethod.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Call, proxyMethodInfo); il.Emit(OpCodes.Ret); Delegate proxyDelegate = proxyMethod.CreateDelegate(ei.EventHandlerType); ei.AddEventHandler(instance, proxyDelegate); } <span style='color: blue; font-weight: bold'>public</span> static void Instance_Created(object sender, EventArgs e) { Console.WriteLine("Instance_Created: " + e?.ToString()); } </pre> <br /> 휴~~~ 이제야 끝났군요. ^^ 실행해 보면 화면에 "Instance_Created: System.Diagnostics.Tracing.EventSourceCreatedEventArgs" 메시지가 출력되는 것을 확인할 수 있습니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1750&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그나저나, 기왕에 EventArgs 인자를 담고 있는 메서드를 사용할 예정이라면 굳이 저렇게 중간에 동적 메서드(Proxy_Created)를 끼지 않고 Instance_Created 이벤트 핸들러를 바로 구독하는 것도 가능할지 모릅니다.<br /> <br /> 그래서 본문의 예제를 DynamicMethod 생성 없이 곧바로 다음과 같이 Instance_Created 메서드를 이벤트 핸들러에 추가하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > EventInfo ei = targetType.GetEvent("Created", BindingFlags.Public | BindingFlags.Instance); <span style='color: blue; font-weight: bold'>ei.AddEventHandler(instance, (EventHandler<EventArgs>)Instance_Created);</span> // 또는, MethodInfo miAdd = ei.GetAddMethod(); <span style='color: blue; font-weight: bold'>miAdd.Invoke(instance, new object[] { (EventHandler<EventArgs>)Instance_Created });</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: System.ArgumentException: Object of type 'System.EventHandler`1[System.EventArgs]' cannot be converted to type 'System.EventHandler`1[System.Diagnostics.Tracing.EventSourceCreatedEventArgs]'. at System.RuntimeType.TryChangeType(Object value, Binder binder, CultureInfo culture, Boolean needsSpecialCast) at System.Reflection.MethodBase.CheckArguments(Object[] parameters, Binder binder, BindingFlags invokeAttr, CultureInfo culture, Signature sig) at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at System.Reflection.EventInfo.AddEventHandler(Object target, Delegate handler) at ClassLibrary1.Class1.Subscribe(Object instance) at Program.Main(String[] args) </pre> <br /> 어쩔 수 없습니다. ^^ 동적 메서드를 만들어 중계해야 합니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1791
(왼쪽의 숫자를 입력해야 합니다.)