성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
글쓰기
제목
이름
암호
전자우편
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# - Marshal.GetNativeVariantForObject 사용 시 메모리 누수(Memory Leak) 발생 및 해결 방법</h1> <p> GetNativeVariantForObject 메서드는,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Marshal.GetNativeVariantForObject Method ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.getnativevariantforobject'>https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.getnativevariantforobject</a> </pre> <br /> 닷넷 객체를 COM 환경의 Variant 구조체로 바꿔주는 역할을 합니다. (<a target='tab' href='https://www.sysnet.pe.kr/2/0/617'>지난 글에 다룬 VARIANTARG</a>는 <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20171221-00/?p=97625'>VARIANT의 typedef</a>입니다.)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > typedef struct tagVARIANT { union { struct { VARTYPE vt; // typedef unsigned short VARTYPE; WORD wReserved1; WORD wReserved2; WORD wReserved3; union { LONGLONG llVal; LONG lVal; BYTE bVal; SHORT iVal; FLOAT fltVal; DOUBLE dblVal; VARIANT_BOOL boolVal; VARIANT_BOOL __OBSOLETE__VARIANT_BOOL; SCODE scode; CY cyVal; DATE date; BSTR bstrVal; IUnknown *punkVal; IDispatch *pdispVal; SAFEARRAY *parray; BYTE *pbVal; SHORT *piVal; LONG *plVal; LONGLONG *pllVal; FLOAT *pfltVal; DOUBLE *pdblVal; VARIANT_BOOL *pboolVal; VARIANT_BOOL *__OBSOLETE__VARIANT_PBOOL; SCODE *pscode; CY *pcyVal; DATE *pdate; BSTR *pbstrVal; IUnknown **ppunkVal; IDispatch **ppdispVal; SAFEARRAY **pparray; VARIANT *pvarVal; PVOID byref; CHAR cVal; USHORT uiVal; ULONG ulVal; ULONGLONG ullVal; INT intVal; UINT uintVal; DECIMAL *pdecVal; CHAR *pcVal; USHORT *puiVal; ULONG *pulVal; ULONGLONG *pullVal; INT *pintVal; UINT *puintVal; struct { PVOID pvRecord; IRecordInfo *pRecInfo; } __VARIANT_NAME_4; } __VARIANT_NAME_3; } __VARIANT_NAME_2; DECIMAL decVal; } __VARIANT_NAME_1; } VARIANT; </pre> <br /> 정의가 복잡한데, 중간의 union을 최대 크기인 16바이트로 놓고 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > typedef struct tagVARIANT { union { struct { <span style='color: blue; font-weight: bold'>VARTYPE vt; // == 2바이트 (typedef unsigned short VARTYPE;) WORD wReserved1; // == 2바이트 WORD wReserved2; // == 2바이트 WORD wReserved3; // == 2바이트</span> union { LONGLONG llVal; // ...[생략]... IUnknown **ppunkVal; // ...[생략]... <span style='color: blue; font-weight: bold'>struct { // x64의 경우 16바이트, x86의 경우 8바이트 PVOID pvRecord; IRecordInfo *pRecInfo; } __VARIANT_NAME_4;</span> } __VARIANT_NAME_3; } __VARIANT_NAME_2; DECIMAL decVal; } __VARIANT_NAME_1; } VARIANT; </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;' > [StructLayout(LayoutKind.Sequential)] public struct Variant { public ushort vt; public ushort wReserved1; public ushort wReserved2; public ushort wReserved3; public IntPtr data01; public IntPtr data02; } </pre> <br /> <a name='object_marshal'></a> 따라서 tagVARIANT의 크기는 x86의 경우 16바이트, x64의 경우 24바이트이기 때문에, GetNativeVariantForObject의 2번째 인자에 넘겨주는 메모리의 할당 크기는 당연히 16 또는 24바이트이기만 하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > object obj = new object(); int varSize = sizeof(Variant); // unsafe 문맥 필요 IntPtr pAlloc = <span style='color: blue; font-weight: bold'>Marshal.AllocHGlobal</span>(varSize); // 16 또는 24 { Marshal.GetNativeVariantForObject(obj, pAlloc); <span style='color: blue; font-weight: bold'>Marshal.FreeHGlobal</span>(pAlloc); } </pre> <br /> 실제로 pAlloc에 할당된 메모리를 조사해 보면, <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0x0000022D6923A900 <span style='color: blue; font-weight: bold'>09 00</span> 00 00 00 00 00 00 ........ 0x0000022D6923A908 <span style='color: blue; font-weight: bold'>18 00 35 69 2d 02 00 00</span> ..5i-... 0x0000022D6923A910 00 00 00 00 00 00 00 00 ........ </pre> <br /> 처음 2바이트의 (이 글의 하단에 실은 VARENUM으로 표현되는) vt 값(0009)은 VT_DISPATCH에 해당하는 VARENUM 값이므로 이후의 0000022d69350018 값은 "IDispatch *pdispVal" 값으로 해석할 수 있습니다.<br /> <br /> 반면, object가 아니라 상숫값을 넣어 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > int varSize = sizeof(Variant); IntPtr pAlloc = Marshal.AllocHGlobal(varSize); { Marshal.GetNativeVariantForObject(<span style='color: blue; font-weight: bold'>5</span>, pAlloc); Marshal.FreeHGlobal(pAlloc); } </pre> <br /> GetNativeVariantForObject는 pAlloc을 다음과 같이 채우고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0x00000218A1D60420 <span style='color: blue; font-weight: bold'>03 00</span> 00 00 00 00 00 00 ........ 0x00000218A1D60428 <span style='color: blue; font-weight: bold'>05 00 00 00</span> 00 00 00 00 ........ 0x00000218A1D60430 00 00 00 00 00 00 00 00 ........ </pre> <br /> vt == 3은 VT_I4에 해당하므로 5는 그대로 "LONG lVal" 필드에 값이 보존된 것을 확인할 수 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이제 GetNativeVariantForObject에서 어떻게 메모리 누수가 발생하는지 살펴볼까요? ^^ 우선, 간단하게 Variant 변수 내에 "값"을 담을 수 있는 경우라면 메모리 누수가 발생하지 않습니다. 일례로 다음과 같은 식으로 테스트해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > unsafe static int _varSize = sizeof(Variant); static unsafe void Main(string[] args) { while (true) // 무한 루프 { TestHandleWitInt(); } } private unsafe static void TestHandleWitInt() { int varSize = sizeof(Variant); IntPtr pAlloc = Marshal.AllocHGlobal(_varSize); { Marshal.GetNativeVariantForObject(<span style='color: blue; font-weight: bold'>5</span>, pAlloc); Marshal.FreeHGlobal(pAlloc); } } </pre> <br /> 작업 관리자를 통해 메모리가 안정적으로 유지되는 것을 확인할 수 있습니다. 반면, vt 필드가 VT_DISPATCH나 VT_IUNKNOWN 등으로 설정되는 경우에는 메모리 누수가 발생하는 것을 다음의 코드로 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static unsafe void Main(string[] args) { int i = 0; while (true) { TestHandleWithLeak(); i++; if (i > 1000) { <span style='color: blue; font-weight: bold'>GC.Collect(); GC.Collect();</span> i = 0; } } } private unsafe static void TestHandleWithLeak() { object obj = new object(); IntPtr pAlloc = Marshal.AllocHGlobal(_varSize); { <span style='color: blue; font-weight: bold'>Marshal.GetNativeVariantForObject(obj, pAlloc);</span> Marshal.FreeHGlobal(pAlloc); } } </pre> <br /> 여러가지로 테스트해 본 결과 이 문제를 해결하려면 oleaut32.dll의 VariantClear 함수를,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > VariantClear function ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/oleauto/nf-oleauto-variantclear'>https://learn.microsoft.com/en-us/windows/win32/api/oleauto/nf-oleauto-variantclear</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;' > [DllImport("oleaut32.dll", PreserveSig = true)] private static extern int VariantClear(IntPtr pObject); private unsafe static void TestHandleWithoutLeak() { object obj = new object(); IntPtr pAlloc = Marshal.AllocHGlobal(_varSize); { Marshal.GetNativeVariantForObject(obj, pAlloc); <span style='color: blue; font-weight: bold'>VariantClear(pAlloc);</span> Marshal.FreeHGlobal(pAlloc); } } </pre> <br /> 혹시, 다른 방법을 알고 계신 분은 덧글 부탁드립니다. ^^<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1561&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 참고로, windbg + sos를 이용하는 경우 이에 대한 진단을 !gchandles를 통해 알 수 있습니다. 가령 VariantClear를 호출하지 않아 메모리 누수가 발생하는 상황이라면 다음과 같은 식으로 "Ref Count Handles"가 GetNativeVariantForObject 메서드의 호출 횟수만큼 누적이 됩니다. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!gchandles -stat</span> Statistics: MT Count TotalSize Class Name 00007ffcd8745ef0 1 48 System.SharedStatics 00007ffcd8746bb0 1 64 System.Security.PermissionSet 00007ffc7ce468e0 1 64 Microsoft.VisualStudio.Debugger.Runtime.Main+_ThrowCrossThreadMessageException 00007ffcd8745cf8 1 160 System.ExecutionEngineException 00007ffcd8745c80 1 160 System.StackOverflowException 00007ffcd8745c08 1 160 System.OutOfMemoryException 00007ffcd8745b70 1 160 System.Exception 00007ffcd8745f60 1 216 System.AppDomain 00007ffcd8745d70 2 320 System.Threading.ThreadAbortException 00007ffcd87459c0 2 646 System.String 00007ffcd8745e70 4 35320 System.Object[] 00007ffcd8745dd8 <span style='color: blue; font-weight: bold'>125126</span> 3003024 System.Object Total 125142 objects Handles: Strong Handles: 11 Pinned Handles: 5 <span style='color: blue; font-weight: bold'>Ref Count Handles: 125125</span> Weak Long Handles: 1 </pre> <br /> <hr style='width: 50%' /><br /> <br /> 본문에서 vt 필드의 값은 (wtypes.h에 정의된 VARENUM과 동일한) System.Runtime.InteropServices의 VARENUM 값입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public enum VarEnum // C/C++ - typedef enum VARENUM { VT_EMPTY = 0, VT_NULL = 1, VT_I2 = 2, <span style='color: blue; font-weight: bold'>VT_I4 = 3,</span> VT_R4 = 4, VT_R8 = 5, VT_CY = 6, VT_DATE = 7, VT_BSTR = 8, <span style='color: blue; font-weight: bold'>VT_DISPATCH = 9,</span> VT_ERROR = 10, VT_BOOL = 11, VT_VARIANT = 12, <span style='color: blue; font-weight: bold'>VT_UNKNOWN = 13,</span> VT_DECIMAL = 14, VT_I1 = 0x10, VT_UI1 = 17, VT_UI2 = 18, VT_UI4 = 19, VT_I8 = 20, VT_UI8 = 21, VT_INT = 22, VT_UINT = 23, VT_VOID = 24, VT_HRESULT = 25, VT_PTR = 26, VT_SAFEARRAY = 27, VT_CARRAY = 28, VT_USERDEFINED = 29, VT_LPSTR = 30, VT_LPWSTR = 0x1F, VT_RECORD = 36, VT_FILETIME = 0x40, VT_BLOB = 65, VT_STREAM = 66, VT_STORAGE = 67, VT_STREAMED_OBJECT = 68, VT_STORED_OBJECT = 69, VT_BLOB_OBJECT = 70, VT_CF = 71, VT_CLSID = 72, VT_VECTOR = 0x1000, VT_ARRAY = 0x2000, VT_BYREF = 0x4000 } </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
9318
(왼쪽의 숫자를 입력해야 합니다.)