2009-12-30 3 views
1

우리는 각각 다른 개발자가 개발 한 3 개의 다른 라이브러리를 가지고 있으며 각각은 (아마도) 잘 설계되었습니다. 그러나 일부 라이브러리는 RAII를 사용하고 일부 라이브러리는 RAII를 사용하지 않기 때문에 일부 라이브러리는 동적으로로드되고 다른 라이브러리는 그렇지 않습니다. 작동하지 않습니다.C++에서 RAII 코드와 비 RAII 코드를 함께 사용하는 문제를 해결하는 방법은 무엇입니까?

각 개발자는 자신이 한 일이 옳다고 말하고이 경우에만 방법론을 변경하면 (예 : B에서 RAII 싱글 톤 만들기) 문제를 해결할 수 있지만 추악한 패치로 보입니다.

이 문제를 해결하려면 어떻게 권장합니까?


내 코드 :

static A* Singleton::GetA() 
{ 
    static A* pA = NULL; 
    if (pA == NULL) 
    { 
     pA = CreateA(); 
    } 
    return pA; 
} 

Singleton::~Singleton() // <-- static object's destructor, 
         // executed at the unloading of My Dll. 
{ 
    if (pA != NULL) 
    { 
     DestroyA(); 
     pA = NULL; 
    } 
} 

(다른 DLL에서 내의 DLL에 정적으로 링크)

"A"코드 :

문제를 이해하기 위해 코드를 참조하십시오

A* CreateA() 
{ 
    // Load B Dll library dynamically 
    // do all other initializations and return A* 
} 
void DestroyA() 
{ 
    DestroyB(); 
} 

(A에서 동적으로로드 된 다른 DLL에서)

"B"코드 :

static SomeIfc* pSomeIfc; 
void DestroyB() 
{ 
    if (pSomeIfc != NULL) 
    { 
     delete pSomeIfc; // <-- crashes because the Dll B was unloaded already, 
          // since it was loaded dynamically, so it is unloaded 
          // before the static Dlls are unloaded. 
     pSomeIfc = NULL; 
    } 
} 
+1

"B"DLL의 조기 언로드를 일으키는 원인이 무엇인지 명확하지 않습니다. 분명히 할 수 있습니까? Offhand는 B DLL이 언로드되어 라이브러리의 사용 범위를 더 적절하게 일치시키는 지점을 수정하는 것으로 보입니다. 예를 들어 DLL이 CreateA에로드 된 경우 DestroyA에서 언로드해야합니다. –

+3

RAII 래퍼를 만드는 것은 결코 못생긴 해킹이 아닙니다. – avakar

답변

3

처음에는 이것이 결투 API의 문제처럼 보였지만 실제로는 또 다른 정적 소멸자 문제 일뿐입니다.

일반적으로 전역 또는 정적 소멸자가 아닌 이유는 다른 이유로도하지 않는 것이 좋습니다.

특히 : Windows에서는 DLL의 전역 및 정적 개체에 대한 소멸자가 특수한 상황에서 호출되며 수행 할 수있는 작업에 제한이 있습니다.

DLL이 C 런타임 라이브러리 (CRT)와 연결된 경우 CRT에서 제공하는 진입 점은 전역 및 정적 C++ 객체의 생성자와 소멸자를 호출합니다. 따라서 DllMain에 대한 이러한 제한은 생성자와 소멸자 및 해당 소집 자로부터 호출 된 모든 코드에도 적용됩니다.

http://msdn.microsoft.com/en-us/library/ms682583%28VS.85%29.aspx

제한 사항은 해당 페이지에 설명되어 있습니다

,하지만 아주 잘. 나는이 문제를 피하려고한다. 아마도 싱글 톤을 사용하는 대신에 A의 API를 흉내 낸다.

+0

정확하게 싱글 톤과 dll이 잘 섞이지 않는다는 것을 알았습니다. – iain

1

간단한 대답은 동적으로로드 자신의 코드를 작성하지 않는 것입니다/DLL을 언로드합니다.
모든 세부 정보를 처리하는 프레임 워크를 사용하십시오.

일단 DLL이로드되면 일반적으로 수동으로 언로드하는 것이 좋지 않습니다.
발견 한 것만 큼 너무 많은 문제가 있습니다.
가장 간단한 해결책은 DLL을 언로드하지 않는 것입니다 (로드 된 후). 해당 OS에서 응용 프로그램을 종료 할 때 사용하십시오.

+1

B Dll이 수동으로 언로드되지 않습니다. 싱글 톤 (정적 객체) 소멸자는 My Dll이 언로드 될 때 호출됩니다. 그래서 이것은 모든 DLL이 OS에 의해 언로드 될 때입니다. 자, 내가 아는 한, DLL은 로딩 순서의 반대 순서로 언로드됩니다. 따라서 Dll B가 마지막으로로드 된 (동적으로로드 된) 이후로, 내 Dll 및 A Dll을 언로드하기 전에 먼저 언로드됩니다. –

+0

귀하의 설명은 DLL이 시작/종료시 OS에 의해 자동으로로드되는 방법에 대한 정확한 설명이라고 생각하지만 수동으로 수행 할 때 DLL이 작동하는 방식이라고 생각하지 않습니다. DLL이로드 될 때마다 카운트를 증가시키는 특정 호출이 있습니다. 그리고 요청이 내려지면 카운트를 감소시키는 다른 것도 있습니다. 카운트가 0에 도달하면 DLL이 언로드됩니다. 그러나 (나는 어려운 일을하기 위해) : 카운트를 증가/감소시키지 않고 DLL을로드/언로드 할 수있는 다른 (저급 APIS) 호출도 있습니다. –

5

먼저, 주어진 예제에서, 나는 당신이 Singleton::getA()에서 정적 지역 변수로 pA 선언 이후 Singleton::~Singleton()pA에 액세스 할 수있는 방식을 볼 수 실패합니다.

그런 다음 "B"라이브러리가 A* Create();에 동적으로로드되었다고 설명했습니다. 그 때 어디에서 내릴 수 있습니까? DestroyB()을 호출하기 전에 void DestroyA()에서 "B"라이브러리를 언로드하지 않는 이유는 무엇입니까?

"DLL을 정적으로 연결하는 것"은 무엇이라고합니까?

마지막으로 나는 거칠고 싶지는 않지만 어디에서나 싱글 톤을 배치하는 더 나은 디자인이 있습니다. 즉, "A"의 수명을 관리하는 객체가 실제로는 싱글 톤이되어야합니까? 응용 프로그램을 시작할 때 CreateA()을 호출하고 atexit을 사용하여 DestroyA() 전화를 걸 수 있습니다.

편집 : OS에 의존하여 "B"라이브러리를 언로드하므로 DestroyA() 구현에서 DestroyB() 전화를 삭제하지 않는 것이 좋습니다. 그런 다음 RAII (또는 Windows에서 DllMain에서 호출하고 Linux 또는 Mac에서는 __attribute__((destructor))으로 표시하는 것과 같은 OS 관련 메커니즘)을 사용하여 "B"라이브러리를 언로드 할 때 DestroyB() 호출을 만듭니다.

EDIT2 : 분명히 귀하의 의견에 따르면 프로그램에는 많은 독신이 있습니다. 그들이 정적 싱글 톤 (Meyers 싱글 톤으로 알려짐)이고 서로 의존하고 있다면 조만간에을 깨뜨릴 것입니다. 파괴 순서 (따라서 싱글 톤 수명)를 제어하려면 Alexandrescu의 책이나 @Martin이 주석에서 언급 한 링크를 참조하십시오.


참조 (약간 관련, 가치 읽기)

Clean Code Talks - Global State and Singletons

Once Is Not Enough

Performant Singletons

Modern C++ Design, Implementing Singletons

+0

어쨌든 "A"의 수명을 관리하는 객체가 필요합니다. 예를 들어, 내 Dll에 더 많은 싱글 톤이 있으므로 A에 액세스해야하는 소멸자에서 일을하기 때문에 가능합니다. Dll의 다른 모든 싱글 톤이 파괴 된 후 파괴되는 싱글 톤으로 A를 관리합니다. –

+1

그럼 Alexei Alexandrescu의 Modern C++ Design book을 읽는 것이 좋습니다. 여기에는 싱글 톤을 구현할 때의 위험에 대한 부분이 포함되어 있습니다. 싱글 톤은 가장 싫어하는 디자인 패턴이며, GOF가 소개하지 않았 으면 좋겠다. –

+0

싱글 톤을 사용하는 이유는 "싱글 톤이 많습니다"입니까? 더 나은 해결책은 "모든 싱글 톤을 제거"하는 것입니다. 당신이보고있는 것은 싱글 톤으로 인한 문제의 부작용입니다. – jalf

11

내 대답은 샘입니다 사람들이 싱글 톤 (singleton)에 대해 갈 때마다 : 그것을하지 마십시오!

당신의 싱글 톤은 어떤 라이브러리가 언로드 된 후 소멸자가 너무 늦게 호출되도록합니다. "정적"을 삭제하고 일반 객체로 만들고 더 엄격한 범위에서 인스턴스화하면 모든 라이브러리가 언로드되기 전에 파괴되어 모든 것이 다시 작동해야합니다.

싱글 톤을 사용하지 마십시오.

+3

나는 여기서 너와 함께 있고, 내 자신의 코드에서 싱글 톤을 결코 사용할 필요가 없다. –

+0

RAII의 전체 요점은 범위 관리 규칙을 활용하여 리소스를 관리하는 것입니다. 싱글 톤은 리소스가 "항상"있는지 확인함으로써 회피합니다 (최소한 언로드 된 라이브러리와 같이 구현 정의 이벤트가 발생할 때까지) – jalf

+1

일반적으로 "하지 마십시오"라는 대답은 좋아하지만 C++의 경우 때로는 유일하게 제정신이 아닙니다. +1. –

2

MS C 런타임에서 atexit 작동 방식의 부작용이 나타납니다.실행 모든 모든 DLL을에 정의 된 핸들러

  • 전화 위해서는 atexit 핸들러를 처리
  • 무료 링크 된 실행 파일에 정의 된 핸들러와 정적 라이브러리에 대한

    • 전화 위해서는 atexit 핸들러 :

      는 순서입니다

    그래서 핸들을 통해 B에 대한 dll을 수동으로로드하면이 핸들은 해제되고 d는로드 된 A에 대한 소멸자에서 B를 사용할 수 없습니다 자동으로

    필자는 과거 싱글 톤에서 중요한 섹션, 소켓 및 db 연결을 정리하는 비슷한 문제를 겪었습니다.

    해결책은 싱글 톤을 사용하지 않거나, 메인 아웃점 전에 정리해야한다는 것입니다.

    int main(int argc, char * argv []) 
    { 
        A* a = Singleton::GetA(); 
    
        // Do stuff; 
    
        Singleton::cleanup(); 
        return 0; 
    } 
    

    또는 더 나은 예를 들어

    내가 완전히 ESP가 시험 지옥을하기 때문에이를 피하려고 싱글 톤에 대한 문제의 결과로 당신

    int main(int argc, char * argv []) 
    { 
        Singleton::Autoclean singletoncleaner; // cleans up singleton when it goes out of scope. 
    
        A* a = Singleton::GetA(); 
    
        // Do stuff; 
    
        Singleton::cleanup(); 
        return 0; 
    } 
    

    후 청소 RIIA를 사용합니다. 어디서나 동일한 객체에 대한 액세스가 필요하고 주위에 참조를 전달하지 않으려면 main의 인스턴스로 생성자를 생성 한 다음 생성자를 인스턴스로 등록하고 다른 모든 곳의 싱글 톤처럼 액세스합니다. 그것을 알아야만하는 유일한 장소는 싱글 톤이 아니라는 것입니다.

    Class FauxSingleton 
    { 
    public: 
        FauxSingleton() { 
         // Some construction 
         if (theInstance == 0) { 
          theInstance = this; 
         } else { 
          // could throw an exception here if it makes sense. 
          // I generaly don't as I might use two instances in a unit test 
         } 
        } 
    
        ~FauxSingleton() { 
         if (theInstance == this) { 
          theInstance = 0; 
         } 
        } 
    
        static FauxSingleton * instance() { 
         return theInstance; 
        } 
    
        static FauxSingleton * theInstance; 
    }; 
    
    
    int main(int argc, char * argv []) 
    { 
        FauxSingleton fauxSingleton; 
    
        // Do stuff; 
    } 
    
    // Somewhere else in the application; 
    void foo() 
    { 
        FauxSingleton faux = FauxSingleton::instance(); 
        // Do stuff with faux; 
    } 
    

    는 물론 생성자와 소멸자는 스레드로부터 안전하지 않지만 어떤 스레드가 양산되기 전에 일반적으로 그들은 주에 호출됩니다. 이것은 응용 프로그램의 수명 동안 구를 필요로하는 CORBA 응용 프로그램과 관련이없는 많은 장소에서 구를 액세스해야하는 경우에 매우 유용합니다.