성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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'>CComPtr/CComQIPtr과 Conformance mode 옵션의 충돌</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;' > class MyType { public: int f = 100; MyType() { } }; class MyDervied : public MyType { public: bool result = true; MyDervied() { f = 200; } }; </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;' > MyDervied test; MyType t = test; printf("test = %d, t = %d\n", t.f, test.f); // t = 200, test = 200 </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;' > MyType t; MyDervied test = t; // Error C2440 'initializing': cannot convert from 'MyType' to 'MyDervied' </pre> <br /> 보는 바와 같이 오류가 발생합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 위의 코드를 개선해 볼까요? 우선 자식을 부모 클래스에 대입하는 경우는 비록 컴파일은 가능하지만 Visual Studio의 코드 편집기 상에서 "t = test" 코드에 밑줄이 표시되면서 "<a target='tab' href='https://learn.microsoft.com/en-us/cpp/code-quality/c26437'>C26437: Do not slice (es.63)</a>" 경고를 알려줍니다. 이 경고를 없애려면, 명시적으로 부모 타입에 생성자를 하나 추가해 주면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > class MyType { public: int f = 100; MyType() { } <span style='color: blue; font-weight: bold'>MyType(MyType& inst) { this->f = inst.f; }</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;' > class MyDervied : public MyType { public: bool result = true; MyDervied() { this->f = 200; } <span style='color: blue; font-weight: bold'>MyDervied(MyType& inst) { this->f = inst.f; }</span> }; MyType t; MyDervied <span style='color: blue; font-weight: bold'>test = t</span>; // 정상적으로 컴파일 </pre> <br /> 일단, 여기까지는 다들 아시는 그대로입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이 상황에서 template을 사용하면 어떻게 될까요?<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'>template <class T></span> class MyType { public: int f = 100; MyType() { } MyType(MyType& inst) { this->f = inst.f; } }; <span style='color: blue; font-weight: bold'>template <class T></span> class MyDervied : public MyType<T> { public: bool result = true; MyDervied() { this->f = 200; } MyDervied(MyType<T>& inst) { this->f = inst.f; } }; </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;' > { MyDervied<int> test; MyType<int> t = test; printf("t = %d, test = %d\n", t.f, test.f); } { MyType<int> t; MyDervied<int> test = t; printf("t = %d, test = %d\n", t.f, test.f); } </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;' > { MyDervied<<span style='color: blue; font-weight: bold'>int</span>> test; MyType<<span style='color: blue; font-weight: bold'>long</span>> t = test; // Error C2440 'initializing': cannot convert from 'MyDervied<int>' to 'MyType<long>' } </pre> <br /> 이런 경우, 컴파일러는 타입이 다르기 때문에 당연히 오류가 발생합니다.<br /> <br /> <hr style='width: 50%' /><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;' > class Root { }; class Node : public Root { }; class Terminal : public Root { }; template <class T> class MyType { public: MyType() { } <span style='color: blue; font-weight: bold'>operator T& () { return p; }</span> T p; }; template <class T> class MyDerived : public MyType<T> { public: MyDerived() { } MyDerived(<span style='color: blue; font-weight: bold'>Root& inst</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;' > { MyType<Terminal> t; MyDerived<Node> test = t; } </pre> <br /> 잘 보시면, t 인스턴스는 MyType의 T&() 연산자로 인해 Terminal 타입에 해당하는 인스턴스를 반환합니다. 그리고 MyDerived의 생성자는 Root& 타입을 인자로 정의했으므로 Terminal 타입의 인스턴스를 받을 수 있습니다.<br /> <a name='conformance_mode'></a> <br /> 여기서 재미있는 것은, 저 코드가 Visual C++의 "Conformance mode"를 적용하는 경우에는 컴파일 오류가 발생한다는 점입니다. 실제로 프로젝트 속성 창에서 "Configuration Properties" / "C/C++" / "Language" 범주로 들어가 "Conformance mode" 옵션을 "Yes (/permissive-)"로 켜면,<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='atl_cpp_conformance_mode_0.png' src='/SysWebRes/bbs/atl_cpp_conformance_mode_0.png' /><br /> <br /> 이후 "test = t;" 코드에서 다음과 같은 컴파일 오류가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Error C2440 'initializing': cannot convert from 'MyType<Terminal>' to 'MyDerived<Node>' </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;' > { MyType<<span style='color: blue; font-weight: bold'>Terminal</span>> t; MyDerived<<span style='color: blue; font-weight: bold'>Terminal</span>> test = t; // Error C2440 'initializing': cannot convert from 'MyType<T>' to 'MyDerived<Terminal>' } </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;' > { MyDerived<Terminal> test; MyType<Terminal> t = test; } </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;' > template <class T> class MyType { public: MyType() { } }; template <class T> class MyDerived : public MyType<T> { public: MyDerived() { } }; </pre> <br /> <hr style='width: 50%' /><br /> <br /> 아니... 그나저나 도대체 저런 이상한 형태의 클래스 상속을 누가 사용한단 말입니까? ^^; 자, 서론이 길었군요. 바로 저런 식으로 사용하는 사례가 CComPtr과 CComQIPtr입니다.<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;' > #include <d3d11.h> #include <Unknwnbase.h> template <class T> class CComPtr { public: CComPtr() { p = nullptr; } CComPtr(T* lp) { p = lp; } operator T* () { return p; } T* p; }; template <class T> class CComQIPtr : public CComPtr<T> { public: CComQIPtr() { } CComQIPtr(IUnknown* lp) { } }; </pre> <br /> 보시면, CComQIPtr 생성자에서 IUnknown* 인자를 받아들이고, CComPtr에서는 T*() 연산자 오버로드로 포인터를 반환하고 있습니다. 즉, IUnknown을 상속한 타입을 템플릿 인자로 지정한 CComPtr 인스턴스는 CComQIPtr에 이렇게 대입하는 것이 가능합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // IDXGIResource와 ID3D11Texture2D는 서로 다른 타입이지만, IUnknown을 상속 { CComPtr<<span style='color: blue; font-weight: bold'>IDXGIResource</span>> pUnk; CComQIPtr<<span style='color: blue; font-weight: bold'>ID3D11Texture2D</span>> pDisp = pUnk; } </pre> <br /> 어차피 내부에서 IUnknown의 QueryInterface 함수를 호출하기 때문에 저렇게 사용하는 것은 어떠한 버그도 발생시키지 않습니다. 하지만, 저 코드 역시 마찬가지로 "Conformance mode"를 켜면 이렇게 오류가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Error C2440 'initializing': cannot convert from 'CComPtr<IDXGIResource>' to 'CComQIPtr<ID3D11Texture2D>' </pre> <br /> <hr style='width: 50%' /><br /> <br /> 이 상황을 해결하려면 간단하게는 "Conformance" 모드를 끄면 됩니다. 하지만, 근래의 C++ 표준 준수를 위한 분위기를 고려한다면 좋은 선택이 아닙니다.<br /> <br /> 그렇다면, 다시 원론적으로 문제를 해결하는 수밖에는 없습니다. 일단, 대입은 안 되므로 다음과 같은 식으로 QueryInterface를 풀어쓰면서 스마트 포인터는 나름대로 그와 연동해 사용하는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 이 한 줄의 코드를, // CComQIPtr<ID3D11Texture2D> spTextureResource = spDXGIResource; // 이렇게 풀어서 적용 CComQIPtr<ID3D11Texture2D> spTextureResource; { ID3D11Texture2D* pTextureResource; hr = spDXGIResource->QueryInterface(__uuidof(ID3D11Texture2D), (LPVOID*)&pTextureResource); if (hr == S_OK) { spTextureResource.Attach(pTextureResource); } } </pre> <br /> 좀 멋은 없어졌지만... 뭐 그래도... ^^; <br /> <br /> <hr style='width: 50%' /><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;' > /permissive- (Standards conformance) ; <a target='tab' href='https://learn.microsoft.com/en-us/cpp/build/reference/permissive-standards-conformance'>https://learn.microsoft.com/en-us/cpp/build/reference/permissive-standards-conformance</a> </pre> <br /> 이 기능은 Visual Studio 2017부터 15.5 버전 이후로 기본값으로 바뀌었다고 합니다. 그러니까, "<a target='tab' href='https://www.sysnet.pe.kr/2/0/11385'>DXGI를 이용한 윈도우 화면 캡처 소스 코드(Visual C++)</a>" 글을 썼던 게 2017-12-05일이니까 아마도 그때 당시에는 /permissive+ 상태가 기본값이어서 아무런 문제가 없었던 것입니다.<br /> <br /> 하지만, 15.5 버전 패치가 적용된 이후부터는 새 프로젝트를 만들어 저 소스코드를 복사해 적용한 사용자들의 경우에는 /permissive- 옵션의 영향으로 컴파일 오류가 발생했던 것입니다.<br /> <br /> 참고로, /permissive- 옵션은 C++ 표준 옵션을 "Preview - Features from the Latest C++ Working Draft (/std:c++latest)" 또는 "/std:c++20"으로 설정하면 암시적으로 켜진다고 합니다.<br /> <br /> <hr style='width: 50%' /><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;' > afxhtml.h fails to compile when disabling permissive mode ; <a target='tab' href='https://developercommunity.visualstudio.com/t/afxhtmlh-fails-to-compile-when-disabling-permissiv/471981'>https://developercommunity.visualstudio.com/t/afxhtmlh-fails-to-compile-when-disabling-permissiv/471981</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;' > #include <atlbase.h> int main() { CComPtr<IUnknown> pUnk; CComQIPtr<IDispatch> pDisp = pUnk; } // After importing a TLB, how do I convert from one type of _com_ptr_t to another? // <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20221228-00/?p=107621'>https://devblogs.microsoft.com/oldnewthing/20221228-00/?p=107621</a> </pre> <br /> 이슈 대응을 보면 "Visual Studio 2019 버전 16.3"부터 해결되었다고 나옵니다. 이상하군요, 전혀 해결되지 않았는데... 혹시 제가 모르는 또 다른 옵션이 있을까요? ^^ 암튼, 근래에는 아무래도 ATL COM의 분위기가 많이 식었기 때문에 그에 따라 CComPtr과 CComQIPtr을 사용하는 경우도 많지 않은 듯합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 참고로, 예전에 Conformance 모드 관련해 써 놓은 글이 하나 있군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C++의 연산자 동의어(operator synonyms), 대체 토큰 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12424'>https://www.sysnet.pe.kr/2/0/12424</a> </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1637
(왼쪽의 숫자를 입력해야 합니다.)