원문 캡처 · devblogs.microsoft.com
원문 캡처 · devblogs.microsoft.com

기술적으로는 애자일하지만 원래 아파트 밖에서는 쓸모가 없는 가짜 애자일 래퍼를 만들려는 시도에 대해 루카 바치(Luca Bacci)는 여러 제안 중 하나로 Ro­Register­For­Apartment­Shutdown 함수를 사용하자고 했다. 이 함수는 유용하지만, 이번 용도에는 맞지 않는다.

아파트먼트 종료 시 등록할 객체는 애자일해야 한다. 애자일하지 않은 객체를 전달하면 호출이 실패한다. 따라서 래퍼도 애자일이어야 한다. 마샬링할 수 없는 비애자일 객체를 래퍼 안에 넣으면, 애자일 래퍼가 비애자일 원본 객체를 멤버로 보유하게 되고 래퍼가 잘못된 아파트먼트에서 소멸할 위험이 생긴다. 보통은 Ro­Get­Agile­Reference로 비애자일 객체를 감싸는 애자일 래퍼를 만든 뒤, 그 래퍼를 컨테이너 객체에 저장해 해결한다. 하지만 그러면 결국 우리가 만들려던 애자일 래퍼를 다시 구현해야 한다.

이 문제를 넘겨도 다른 걸림돌이 있다. 아파트먼트가 종료되면 COM은 래퍼 객체의 UnInitialize 메서드를 호출한다. 그런데 Ro­Unregister­For­Apartment­Shutdown 함수에는 OnUninitialize 콜백 안에서 호출하지 말라는 경고가 있다. 감싸고 있는 객체의 소멸자가 직접 또는 다른 객체를 거쳐 이 함수를 호출할 수도 있다. 예를 들어 다른 가짜 애자일 래퍼를 보유하고 있다면, 그 래퍼의 UnInitialize가 이미 호출된 뒤일 수 있다. 애자일 래퍼에서 비애자일 포인터를 비워 이중 해제를 막을 수는 있지만, 이 상황에 취약한 다른 객체가 있을 가능성도 있다.

Ro­Unregister­For­Apartment­Shutdown는 등록한 것과 같은 아파트먼트에서 호출해야 한다. 객체를 해제하려고 아파트먼트로 전환할 필요가 없도록 하는 것이 이번 시도의 목적이라, 이 조건은 구현을 더 복잡하게 만든다. IContext­Callback으로 원래 아파트먼트에 돌아가 객체를 해제하고 종료 콜백 등록을 취소하는 방법을 생각할 수 있다. 아파트먼트 전환이 실패하면 이미 아파트먼트가 종료된 것으로 보고 IApartment­Shutdown::On­Uninitialize가 객체를 해제했다고 간주하는 방식이다. 다만 이 경우에도 On­Uninitialize 콜백 안에서 정리 작업을 하게 되므로, 다른 객체가 자신의 아파트먼트 종료 콜백 등록을 취소하지 못할 수 있다.

마지막으로, 이 기능은 아파트먼트 종료 콜백이 많지 않다는 전제에 맞춰 구현됐다. 등록은 O(1)이지만, 등록 취소는 해당 아파트먼트의 콜백 목록을 선형 검색하므로 O(n)이다. 반면 전역 인터페이스 테이블(Global Interface Table)은 많은 객체를 관리하도록 설계돼 쿠키를 O(1)에 찾을 수 있다. 제안된 대안은 아파트먼트 종료 콜백을 대량으로 등록하고 취소하게 되므로, 이 기능이 최적화된 사용 방식과 맞지 않는다.