성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Reordering on an Alpha processor ;...
[정성태] 공유 감사합니다. ^^ 참고로, WPF에서 WindowsF...
[Tom Lee] 답변 감사합니다. 나름의 해결책 연구해보고 여기에도 공유해봅니다...
[정성태] 아래의 글을 보면, MoveWindow 하면 될 듯한데요. ^^...
[Tom Lee] 안녕하세요 올려주신 글 참고하여 WPF 어플리케이션 안에 Uni...
[정성태] A graphical depiction of the steps ...
[정성태] 질문을 주셔서 출판사 측에 문의를 했습니다. 약 한 달 정도 후...
[Thorondor
] @정성태 개인 블로그인데도 거의 커뮤니티 급 인 것 같아요. 요...
[정성태] Roll A Lisp In C - Reading ; https...
[정성태] Java - How to use the Foreign Funct...
글쓰기
제목
이름
암호
전자우편
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 Profiler - IMetaDataEmit2::DefineMethodSpec 사용법</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;' > namespace ConsoleApp1; internal class Program { static void Main(string[] args) { Program pg = new Program(); TestClass.InvokeLocalGeneric(pg); TestClass.InvokeLocalGeneric("TEST"); } } public class TestClass { <span style='color: blue; font-weight: bold'>public static void InvokeLocalGeneric<T>(T obj) { // ...[생략: 사용자 코드]... }</span> } </pre> <br /> <a target='tab' href='https://www.sysnet.pe.kr/2/0/10959'>Profiler가 InvokeLocalGeneric을 가로채 별도로 정의한 .NET DLL의 메서드를 호출</a>하도록 만들고 싶은 건데요, 만약 .NET DLL 측의 메서드 정의가 다음과 같이 돼 있다고 가정하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > namespace ClassLibrary1; public class Class1 { public static void <span style='color: blue; font-weight: bold'>InvokeMyMethod(object obj)</span> { Console.WriteLine(obj); } } </pre> <br /> 우리는 가로채고 싶은 InvokeLocalGeneric에 다음과 같은 식으로 코드를 추가해 주면 됩니다.<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 TestClass { public static void InvokeLocalGeneric<T>(T obj) { ldarg.0 box !!T // <a target='tab' href='https://www.sysnet.pe.kr/2/0/1848'>제네릭 인자를 object로 박싱</a> call void [ClassLibrary1]ClassLibrary1.Class1::InvokeMyMethod(object) // ...[생략: 사용자 코드]... } } </pre> <br /> 그런데, 만약 generic 인자 그대로 받고 싶다면 어떻게 해야 할까요? 즉, 다음과 같은 메서드로 호출을 하고 싶은 건데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > namespace ClassLibrary1; public class Class1 { public static void InvokeMyGeneric<span style='color: blue; font-weight: bold'><T>(T obj)</span> { Console.WriteLine(obj); } } </pre> <br /> 이를 위해 추가해야 할 IL 코드는 생각보다 단순합니다. object에 전달하는 것이 아니므로 box 연산자도 필요 없어 단순히 "ldarg.0" 코드만으로 끝입니다.<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 TestClass { public static void InvokeLocalGeneric<T>(T obj) { <span style='color: blue; font-weight: bold'>ldarg.0</span> call void [ClassLibrary1]ClassLibrary1.Class1::InvokeMyGeneric(!!T) // ...[생략: 사용자 코드]... } } </pre> <br /> 그런데, 단순히 저렇게 처리하고 실행하면 (개인적으로 정말 만나고 싶지 않은) InvalidProgramException 예외가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > System.InvalidProgramException: Common Language Runtime detected an invalid program. </pre> <br /> 이유가 뭘까요? ^^ 아마도, 뭔가 메타데이터 정의를 더 해야 하는 듯한데 뭐가 바뀌는 것인지 알아내야만 합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이를 위해 유용한 도구가 mdv입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > dotnet/metadata-tools ; <a target='tab' href='https://github.com/dotnet/metadata-tools'>https://github.com/dotnet/metadata-tools</a> </pre> <br /> mdv로 Generic 인자 관련한 테스트를 해본 결과, 제네릭 인자에 전달하는 타입마다 그에 해당하는 MethodSpec을 함께 정의해야 한다는 규칙이 나왔습니다. 예를 들어, 다음과 같은 제네릭 메서드가 있을 때,<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 Invoke<T>(T obj); </pre> <br /> T 인자에 대해 다음과 같이 다양한 타입으로 호출을 하게 되면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > string text = "TEST"; Invoke(text); // 제네릭 매개변수에 string 타입 전달 object obj = new object(); Invoke(obj); // 제네릭 매개변수에 object 타입 전달 int n = 50; Invoke(n); // 제네릭 매개변수에 Int32 타입 전달 Program pg = new Program(); Invoke(pg); // 제네릭 매개변수에 ConsoleApp2.Program 전달 </pre> <br /> 저 호출마다 MethdoSpec 메타데이터가 개별적으로 추가가 되는 것을 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > TypeDef (0x02): ====================================================================================================================================================================================== Name Namespace EnclosingType BaseType Interfaces Fields Methods Attributes ClassSize PackingSize ====================================================================================================================================================================================== 1: '<Module>' (#d) nil nil (TypeDef) nil (TypeDef) nil nil nil 0 n/a n/a <span style='color: blue; font-weight: bold'>2: 'Program' (#1b5) 'ConsoleApp2' (#1) nil (TypeDef) 0x0100000f (TypeRef) nil nil 0x06000001-0x06000003 0x00100000 (BeforeFieldInit) n/a n/a</span> Method (0x06, 0x1C): ==================================================================================================================================================================================================================================================== Name Signature RVA Parameters GenericParameters Attributes ImplAttributes ImportAttributes ImportName ImportModule ==================================================================================================================================================================================================================================================== 1: 'Main' (#1c4) void (string[]) (#3c) 0x00002050 0x08000001-0x08000001 nil 0x00000091 (PrivateScope, Private, Static, HideBySig) 0 0 nil nil (ModuleRef) <span style='color: blue; font-weight: bold'>2: 'Invoke' (#18) void (!!0) (#42) 0x0000208F 0x08000002-0x08000002 0x2a000001-0x2a000001 0x00000091 (PrivateScope, Private, Static, HideBySig) 0 0 nil nil (ModuleRef)</span> 3: '.ctor' (#1db) void () (#6) 0x00002092 nil nil 0x00001886 (PrivateScope, Public, HideBySig, SpecialName, RTSpecialName) 0 0 nil nil (ModuleRef) MethodSpec (0x2b): ======================================== Method Signature ======================================== <span style='color: blue; font-weight: bold'>1: 0x06000002 (MethodDef) string (#22) 2: 0x06000002 (MethodDef) object (#26) 3: 0x06000002 (MethodDef) int32 (#2a) 4: 0x06000002 (MethodDef) ConsoleApp2.Program (#2e)</span> #Blob (size = 264): ...[생략]... <span style='color: blue; font-weight: bold'> 22 (MethodSpec): 0A-01-0E 26 (MethodSpec): 0A-01-1C 2a (MethodSpec): 0A-01-08 2e (MethodSpec): 0A-01-12-08</span> ...[생략]... </pre> <br /> 추가된 MethodSpec의 #Blob 데이터는 다음과 같이 해석할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0A-01-0E // 0A == <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/corcallingconvention-enumeration'>IMAGE_CEE_CS_CALLCONV_GENERICINST</a> // 01 == # of generic args // 0E == <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/corelementtype-enumeration'>ELEMENT_TYPE_STRING</a> 0A-01-1C // 0A == <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/corcallingconvention-enumeration'>IMAGE_CEE_CS_CALLCONV_GENERICINST</a> // 01 == # of generic args // 1C == <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/corelementtype-enumeration'>ELEMENT_TYPE_OBJECT</a> 0A-01-08 // 0A == <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/corcallingconvention-enumeration'>IMAGE_CEE_CS_CALLCONV_GENERICINST</a> // 01 == # of generic args // 08 == <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/corelementtype-enumeration'>ELEMENT_TYPE_I4</a> 0A-01-12-08 // 0A == <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/corcallingconvention-enumeration'>IMAGE_CEE_CS_CALLCONV_GENERICINST</a> // 01 == # of generic args // 12 == <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/corelementtype-enumeration'>ELEMENT_TYPE_CLASS</a> // 08 == ConsoleApp2.Program 타입인 0x02000002에 대한 compressed token 표현 </pre> <br /> 그러니까, 제네릭 메서드의 호출에 대한 인자 대응을 signature로 표현해 MethodSpec으로 등록해 줘야 하는 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> MethodSpec의 등록은 Profiler에서 (당연히 제네릭을 지원하기 시작한) .NET Framework 2.0부터 제공하는 IMetaDataEmit2 인터페이스에 구현하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > IMetaDataEmit2 Interface ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/imetadataemit2-interface'>https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/imetadataemit2-interface</a> </pre> <br /> 제공하는 함수 중 DefineMethodSpec이 바로 신규 MethodSpec을 등록하는 기능을 제공하는데요, 처음 우리가 작성했던 예제의 경우라면,<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 TestClass { public static void InvokeLocalGeneric<T>(T obj) { ldarg.0 call void [ClassLibrary1]ClassLibrary1.Class1::InvokeMyGeneric(!!T) // ...[생략: 사용자 코드]... } } </pre> <br /> InvokeMyGeneric의 !!T에 호출 측의 메서드인 InvokeLocalGeneric 측에서 정의된 T 인자를 그대로 전달받는 것이기 때문에 MethodSpec에 연결할 호출 signature는 다음과 같이 정의할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0A-01-1E-00 // 0A == <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/corcallingconvention-enumeration'>IMAGE_CEE_CS_CALLCONV_GENERICINST</a> // 01 == # of generic args // 1E == <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/corelementtype-enumeration'>ELEMENT_TYPE_MVAR</a> // 00 == 메서드의 첫 번째 제네릭 인자 </pre> <br /> 따라서 DefineMethodSpec을 호출할 때는 다음과 같이 signature와 함께 제네릭 메서드의 토큰 값을 함께 전달하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > vector<BYTE> signatures; signatures.push_back(IMAGE_CEE_CS_CALLCONV_GENERICINST); signatures.push_back(1); signatures.push_back(ELEMENT_TYPE_MVAR); signatures.push_back(0); tokenMethod = [...ClassLibrary1.Class1::InvokeMyGeneric의 메서드 토큰...] mdMethodSpec methodSpec = mdTokenNil; HRESULT hr = pMetaDataEmit2->DefineMethodSpec(tokenMethod, signatures.data(), (ULONG)signatures.size(), &methodSpec); if (IsNilToken(methodSpec) == false) { // ...[생략]... } </pre> <br /> MethodSpec에 신규, 또는 기존에 동일한 항목이 있다면 이미 등록된 토큰 값을 4번째 인자로 전달한 methodSpec 변수로 받을 수 있습니다. 그럼, 이제 우리가 원래 호출하려고 했던 코드에서 "call void [ClassLibrary1]ClassLibrary1.Class1::InvokeMyGeneric(!!T)" 대신 methodSpec을 넣도록 변경합니다.<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 TestClass { public static void InvokeLocalGeneric<T>(T obj) { ldarg.0 <span style='color: blue; font-weight: bold'>call [...methodSepc...]</span> // ...[생략: 사용자 코드]... } } </pre> <br /> 이후 실행하면 잘 동작하는 것을 확인할 수 있습니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
5421
(왼쪽의 숫자를 입력해야 합니다.)