none
CAnimationController::AnimateGroup 호출 후 종료시 액세스 위반

    질문

  • CWnd를 상속받은 클래스에서 CAnimationController를 사용해 효과를 적용하려고 합니다.

    연습: MFC 프로젝트에 애니메이션 추가

    위 링크의 예제는 정상적으로 실행되었고 종료도 되었습니다.

    다이얼로그 기반의 MFC 프로그램에서 CWnd를 상속받은 클래스에 적용하는중에 문제가 발생하였습니다.

    아래는 애니메이션을 설정하는 부분의 코드입니다.

    	m_animController.SetRelatedWnd(this);
    	m_animController.AddAnimationObject(&m_animPointX);
    	m_animController.AddAnimationObject(&m_animPointY);
    
    	m_animController.EnableAnimationTimerEventHandler();
    
    	CRect rc;
    	GetWindowRect(rc);
    
    	center = Gdiplus::PointF{ rc.Width() / 2.f, rc.Height() / 2.f };
    
    	m_animPointX = 0;
    	m_animPointY = 0;
    
    	CAccelerateDecelerateTransition* pPointx = new CAccelerateDecelerateTransition(30, 3600);
    	CAccelerateDecelerateTransition* pPointy = new CAccelerateDecelerateTransition(30, 3600);
    
    	m_animPointX.AddTransition(pPointx);
    	m_animPointY.AddTransition(pPointy);
    
    	CBaseKeyFrame* pKeyframeStartPoint = CAnimationController::GetKeyframeStoryboardStart();
    	pPointx->SetKeyframes(pKeyframeStartPoint);
    	pPointy->SetKeyframes(pKeyframeStartPoint);

    애니메이션을 정지하는 코드입니다. 위의 예제에서 그대로 따와서 사용하였습니다.

    IUIAnimationManager* pManager = m_animController.GetUIAnimationManager();
    	if (pManager != NULL)
    	{
    		pManager->AbandonAllStoryboards();
    	}

    아래 코드로 설정된 애니메이션을 실행합니다.

    m_animController.AnimateGroup(0);


    애니메이션을 실행하면 정상적으로 동작하고 예제코드의 설정부분을 그대로 가져와도 정상적으로 동작합니다.

    하지만 두 코드 모두 프로그램이 닫힐때 소멸자가 호출되면 

     ~CComPtrBase() throw()
        {
            if (p)
                p->Release();
        }

    p->Release();에서 액세스 위반이 발생합니다.

    예제코드를 실행시에는 나타나지않고 다이얼로그 기반에서만 나타납니다.

    어떻게하면 정상적으로 소멸시킬수있을까요?



    • 편집됨 YongSoon 2018년 10월 22일 월요일 오전 7:22
    2018년 10월 22일 월요일 오전 7:21

모든 응답

  • 다이얼로그 기반으로 마이그레이션 하신 풀 코드를 제공해주시면 무엇이 문제인지 알 수 있을 것 같지만 이렇게 봐서는 조금 어려운 점이 있습니다. 예제 코드를 차용하셨겠지만 다이얼로그 기반의 틀로 옮기면서 코드에 구조적으로 변화가 좀 있었을 것이라고 생각합니다.

    기본적인 것만 되짚어보자면, 보통 이런 경우는 COM 기반 객체에 대한 포인터를 CoUnitialize 호출된 이후에 접근하려고 할 때 발생할 수 있습니다. 따라서 프로그램을 닫으면서 CoUnitialize가 호출되기 전에 해당 포인터를 모두 릴리즈 하는 것이 좋을 것입니다. 코드를 변경하면서 원래 자연스럽게 릴리즈 되었어야 하는 값이 어떤 이유에 의하여 붙잡혀 있는 경우가 있는지 검토하셔야 할 것 같습니다.

    잘은 모르겠지만 위의 코드를 포함하여 사용하신 어떤 값 중에 CComptr 기반의 객체를 이용하는 경우가 있어서 이런 문제가 발생할 것으로 보이는데, 조금 오래된 포스트긴 하지만 아래와 같은 내용이 있어서 참고해보자면, 특정 COM 객체를 글로벌 변수로 사용하는 경우에 특히 메모리 해제 순서에 대해서 주의하라고 하는군요.

    https://blogs.msdn.microsoft.com/larryosterman/2004/04/22/when-global-destructors-bite/ 

    2018년 10월 22일 월요일 오후 3:56
  • 문제가 발생하지 않게 하는방법은 찾았지만 이게 정상적인지는 잘 모르겠습니다 ...

    생성자에서 CoInitialize를 호출하고 CoUninitialize를 호출하지 않을때 정상적으로 종료됩니다만

    CoInitialize를 호출하지 않아도 CoUninitialize를 호출해도 Access Denied가발생합니다.

    어떤것을 하더라도 동작자체에는 문제가 없고 소멸시에만 영향이 있습니다.

    이해가 되지않는 동작인데 이건 버그일까요?

    + Access denied가 아니고 violation이었습니다.
    • 편집됨 YongSoon 2018년 10월 23일 화요일 오전 1:17
    2018년 10월 23일 화요일 오전 12:41
  • 말씀하시는 것만 봐서는 UI 스레드에서 COM 초기화 관련 문제가 있었던 것으로 보입니다만, 코드에서 무엇이 사용되었는지 알 수 없어서 답을 드리기가 좀 어렵습니다. CoUnitialize 단독 호출은 의미가 없을 것 같습니다.

    제품 개발 코드가 아니라 연습 목적이라면 해당 프로젝트를 OneDrive 같은 곳에 한번 공유해주시는 것이 어떨까요?

    2018년 10월 23일 화요일 오전 1:26
  • 다운로드 링크

    감사합니다.  실험중인 소스코드 다운로드 링크입니다.

    실행하시면 Start / Stop / Restore 3가지 버튼이 있습니다.

    Start버튼이 애니메이션을 실행하는 버튼입니다. 

    Start를 한 뒤 X버튼을 눌러 프로그램을 닫으면 해당 증상이 나타납니다.

    메인 다이얼로그에서는 Static Control을 하나 생성하여 CCtrlCircle클래스에 연결해두었습니다.

    애니메이션에 대한 설정은 CCtrlCircle::setDefaultAnimation() 함수에서 수행하고 

    애니메이션 시작은 CCtrlCircle::StartAnimation() 함수를 사용합니다.

    2018년 10월 23일 화요일 오전 2:22
  • 확인해보니 그리는 기능을 CCtrlCircle 라는 클래스로 정리하셨더군요.

    이를 가져와서 사용하는 메인 창이 닫히는 시점, CCtrlCircle 객체가 릴리즈 되는 시점, 객체 내의 CAnimationController 등의 멤버 객체가 릴리즈 되는 시점 등이 타이밍 문제가 있는 것으로 보여서 CCtrlCircle Circle을 포인터 형식으로 선언하도록 변경하고 void CMFCApplication1Dlg::OnClose()에서 명시적으로 Circle를 삭제하도록 바꾼 후에 문제가 사라졌습니다.

    MFCApplication1Dlg.h

    class CMFCApplication1Dlg : public CDialogEx
    {
    
    ...
    
    public:
    	afx_msg void OnBnClickedButton1();
    	
    	afx_msg BOOL OnEraseBkgnd(CDC* pDC);
    	afx_msg void OnBnClickedButton2();
    
    	CCtrlCircle* Circle;
    	//std::shared_ptr<CCtlCircle<double>> pCircle;
    	std::shared_ptr<CStatic> pStatic;
    	afx_msg void OnClose();
    	afx_msg void OnBnClickedButton3();
    	afx_msg void OnDestroy();
    
    };

    MFCApplication1Dlg.cpp

    ...
    
    CMFCApplication1Dlg::CMFCApplication1Dlg(CWnd* pParent /*=NULL*/)
    	: CDialogEx(CMFCApplication1Dlg::IDD, pParent)
    {
    	Circle = new CCtrlCircle();
    	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    }
    
    ...
    
    BOOL CMFCApplication1Dlg::OnInitDialog()
    {
    	CDialogEx::OnInitDialog();
    
    ...
    
    	CRect rc;
    	GetClientRect(rc);
    	rc.top = 50;
    	Circle->MoveWindow(rc);
    	Circle->CreateDefaultDC();
    	return TRUE;  // 포커스를 컨트롤에 설정하지 않으면 TRUE를 반환합니다.
    }
    
    ...
    
    void CMFCApplication1Dlg::OnBnClickedButton1()
    {
    	CAnimationController &anim = Circle->GetAnimationController();
    	anim.EnableAnimationTimerEventHandler();
    	BOOL breturn = Circle->StartAnimation();
    
    	return;
    }
    
    ...
    
    void CMFCApplication1Dlg::OnBnClickedButton2()
    {
    	Circle->StopAnimation();
    }
    
    void CMFCApplication1Dlg::OnClose()
    {
    	delete Circle;
    	CDialogEx::OnClose();
    }
    
    void CMFCApplication1Dlg::OnBnClickedButton3()
    {
    	Circle->RestoreDC();
    }

    그리고 CoInitialize() / CoUninitialize()는 멤버 변수로 사용되는 CAnimationController 같은 일부 타입의 메모리 처리 문제로 이상 상황에서 걸린 것으로 보이므로 더 이상 사용하지 않아도 됩니다.

    2018년 10월 30일 화요일 오전 6:05