성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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'>C# - Native 메모리에 .NET 개체를 생성</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# - GC 힙이 아닌 Native 힙에 인스턴스 생성 - 0SuperComicLib.LowLevel 라이브러리 소개 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12538'>https://www.sysnet.pe.kr/2/0/12538</a> </pre> <br /> 저것이 가능한 이유는, .NET 개체에 대한 메모리 구조를 그대로 Native 메모리에 적용해서 닷넷 런타임으로 하여금 Managed 개체처럼 동일하게 접근할 수 있도록 만들었기 때문입니다.<br /> <br /> 이 과정을 간단하게 코드로 알아볼까요?<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;' > C#에서 확인해 보는 관리 힙의 인스턴스 구조 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/1176'>https://www.sysnet.pe.kr/2/0/1176</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;' > [일반 개체인 경우] Object Header (CPU Word 크기) Method Table (CPU Word 크기) ...[멤버 field]... [배열 개체인 경우] Object Header (CPU Word 크기) Method Table (CPU Word 크기) # of elements (배열 요소 크기) N 개의 요소 (연속된 배열 요소) </pre> <br /> 따라서, 우리가 만약 Int32 필드를 가진 다음과 같은 개체를 정의했다면,<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 MyObject { public int Value { get; set; } } </pre> <br /> 아래와 같이 24바이트만 채워주면 CLR은 그것을 MyObject로 인식할 수 있는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > | object header | method table | field Value | 0000000000000000 0000000000000000 0000000000000000 </pre> <br /> 재미 삼아 값을 하나씩 채워볼까요? 우선, object header는 0, field value는 초깃값을 주면 되므로 어렵지 않습니다. 남은 것은 Method Table인데요, 이에 대해서는 전에도 한 번 언급한 적이 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Method Table ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12142#method_table'>https://www.sysnet.pe.kr/2/0/12142#method_table</a> </pre> <br /> 그러니까, TypeHandle을 통해서도 구할 수 있고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > var pMethodTable = typeof(MyObject).TypeHandle.Value; </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;' > MyObject obj = new MyObject(); TypedReference tr = __makeref(obj); nint objectPtr = **(nint**)(&tr); IntPtr pMethodTable = *(nint*)objectPtr; </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 System.Runtime.InteropServices; namespace ConsoleApp1; internal class Program { static unsafe void Main(string[] args) { MyObject obj = new MyObject() { Value = 10 }; <span style='color: blue; font-weight: bold'>IntPtr allocated = Marshal.AllocHGlobal(MyObject.GetObjectSize()); MyObject objOnNative = MyObject.WriteObject(allocated, obj);</span> objOnNative.Value++; Console.WriteLine(obj); // 출력 결과: 10 Console.WriteLine(objOnNative); // 출력 결과: 11 Marshal.FreeHGlobal(allocated); } } public class MyObject { public int Value { get; set; } public override string ToString() { return $"{Value}"; } public unsafe static int GetObjectSize() { // return 24; // 0x18 var pMethodTable = typeof(MyObject).TypeHandle.Value; var methodTable = *(MethodTable*)pMethodTable; return methodTable.BaseSize; } public unsafe static MyObject WriteObject(IntPtr ptr, MyObject obj) { *(nint*)ptr = 0; // Object Header를 쓰고, ptr += sizeof(nint); IntPtr objAddress = ptr; var pMethodTable = typeof(MyObject).TypeHandle.Value; // TypedReference tr = __makeref(obj); // nint objectPtr = **(nint**)(&tr); // IntPtr pMethodTable = *(nint*)objectPtr; *(nint*)ptr = pMethodTable; // Method Table을 쓰고, ptr += sizeof(nint); *(int*)ptr = obj.Value; // 멤버 필드 값을 설정 // #pragma warning disable 8500 // warning CS8500: This takes the address of, gets the size of, or declares a pointer to a managed type ('MyObject') // MyObject objValue = *(MyObject*)&objAddress; // #pragma warning restore 8500 MyObject objValue = Unsafe.As<nint, MyObject>(ref objAddress); return objValue; } } // runtime/src/coreclr/vm/methodtable.h // ; <a target='tab' href='https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/methodtable.h#L584'>https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/methodtable.h#L584</a> [StructLayout(LayoutKind.Explicit)] public struct MethodTable { // Low WORD is component size for array and string types (HasComponentSize() returns true). // Used for flags otherwise. [FieldOffset(0)] public int Flags; [FieldOffset(0)] public ushort ComponentSize; // Base size of instance of this class when allocated on the heap [FieldOffset(4)] public int BaseSize; } </pre> <br /> 이런 식으로 개체를 만드는 것이 재미있긴 하지만, <a target='tab' href='https://www.sysnet.pe.kr/2/0/12538'>이전에 언급했던 것처럼</a> 현실적으로 사용하기에는 어려움이 있습니다.<br /> <br /> 가장 심한 제약이 바로, 저렇게 만든 MyObject는 GC Heap에 있는 참조 개체를 포함해서는 안 된다는 점입니다. 가령, 다음과 같은 식으로 문자열조차도 보관해서는 안 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > MyObject obj = new MyObject(); obj.Value = 5; obj.Name = "...."; // GC Heap에 있는 문자열을 보관한 경우 GC 이후 문제 발생 (<a target='tab' href='https://www.sysnet.pe.kr/2/0/13536'>.NET 8부터는 문자열 리터럴을 FOH에 위치</a>) public class MyObject { public int Value { get; set; } public string Name { get; set; } } </pre> <br /> 왜냐하면, "Name" 필드에 할당된 문자열이 GC Heap에 존재하는 경우 Garbage Collection이 동작한 후로는 아예 삭제되거나, 또는 메모리 Compaction 작업으로 인해 위치가 이동할 수 있기 때문입니다. 그렇게 되면, Native Heap에 있던 MyObject의 Name 필드는 GC가 발생하기 전의 위치 그대로를 담고 있으므로 GC 이후에는 쓰레기 위치를 가리키고 있는 것이나 다름없게 됩니다.<br /> <br /> 대충 어떤 식인지 감이 오시죠? ^^<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=2136&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1819
(왼쪽의 숫자를 입력해야 합니다.)