dll에서 std :: 객체 (벡터, 맵 등)를 포함하는 클래스 내보내기
std :: vectors 및 std :: strings와 같은 개체를 포함하는 DLL에서 클래스를 내보내려고합니다. 전체 클래스는 다음을 통해 dll 내보내기로 선언됩니다.
class DLL_EXPORT FontManager
{
문제는 복잡한 유형의 구성원에 대해 다음 경고가 표시된다는 것입니다.
경고 C4251 : 'FontManager :: m__fonts': 'std :: map <_Kty, _Ty>'클래스에는 'FontManager'클래스의 클라이언트가 [_Kty = std :: string, _Ty = tFontInfoRef와 함께 사용할 dll 인터페이스가 있어야합니다. ]
멤버 변수의 유형을 직접 변경하지 않더라도 다음과 같은 앞으로 클래스 선언을 추가하여 경고 중 일부를 제거 할 수 있습니다.
template class DLL_EXPORT std::allocator<tCharGlyphProviderRef>;
template class DLL_EXPORT std::vector<tCharGlyphProviderRef,std::allocator<tCharGlyphProviderRef> >;
std::vector<tCharGlyphProviderRef> m_glyphProviders;
멤버가 컴파일 될 때 전방 선언이 DLL_EXPORT를 "주입"하는 것처럼 보이지만 안전합니까? 클라이언트가이 헤더를 컴파일하고 그의 쪽에서 std 컨테이너를 사용할 때 실제로 변경됩니까? 향후 이러한 컨테이너 DLL_EXPORT (그리고 인라인이 아닐 수도 있음)를 모두 사용할 수 있습니까? 그리고 경고가 경고하려는 문제를 실제로 해결합니까?
이 경고는 내가 걱정해야 할 것이 있습니까? 아니면 이러한 구성의 범위에서 비활성화하는 것이 가장 좋습니까? 클라이언트와 dll은 항상 동일한 라이브러리 및 컴파일러 세트를 사용하여 빌드되며 헤더 전용 클래스입니다.
표준 STD 라이브러리와 함께 Visual Studio 2003을 사용하고 있습니다.
---- 업데이트 ----
답변이 일반적이고 여기에서는 std 컨테이너 및 유형 (예 : std :: string)에 대해 이야기하고 있지만 질문은 실제로 다음과 같습니다.
동일한 라이브러리 헤더를 통해 클라이언트와 dll 모두에 사용할 수있는 표준 컨테이너 및 유형에 대한 경고를 비활성화하고 int 또는 다른 내장 유형을 처리하는 것처럼 처리 할 수 있습니까? (제 쪽에서는 제대로 작동하는 것 같습니다.) 그렇다면 우리가이 작업을 수행 할 수있는 조건이어야합니까?
아니면 그러한 컨테이너를 사용하는 것이 금지되거나 할당 연산자, 복사 생성자 등이 dll 클라이언트에 인라인되지 않도록 최소한 세심한주의를 기울여야할까요?
일반적으로 이러한 개체를 포함하는 dll 인터페이스를 디자인하는 것이 좋은지 아닌지 알고 싶습니다 (예를 들어 반환 값 형식으로 클라이언트에 항목을 반환하는 데 사용). 이 기능에 대한 "높은 수준의"인터페이스 ... 아마도 가장 좋은 해결책은 Neil Butterworth가 제안한 것입니다. 정적 라이브러리를 만드는 것입니까?
클라이언트에서 클래스의 멤버를 터치 할 때 DLL 인터페이스를 제공해야합니다. DLL- 인터페이스는 컴파일러가 DLL 자체에 함수를 생성하여 가져올 수 있도록한다는 것을 의미합니다.
컴파일러는 DLL_EXPORTED 클래스의 클라이언트가 어떤 메서드를 사용하는지 모르기 때문에 모든 메서드가 dll로 내보내도록 강제해야합니다. 클라이언트가 액세스 할 수있는 모든 구성원은 자신의 기능도 dll-export해야합니다. 이는 컴파일러가 내 보내지 않은 메서드와 오류를 보내는 클라이언트의 링커에 대해 경고 할 때 발생합니다.
모든 구성원이 dll-export로 표시되어야하는 것은 아닙니다 (예 : 클라이언트가 만질 수없는 개인 구성원). 여기서 경고를 무시 / 비활성화 할 수 있습니다 (컴파일러가 생성 한 dtor / ctor에주의하십시오).
그렇지 않으면 멤버는 메서드를 내 보내야합니다. DLL_EXPORT로 전달 선언하면 이러한 클래스의 메서드가 내보내지지 않습니다. 컴파일 단위의 해당 클래스를 DLL_EXPORT로 표시해야합니다.
요약하면 ... (dll 내보내기 가능 멤버가 아닌 경우)
클라이언트가 사용할 수 없거나 사용할 수없는 회원이있는 경우 경고를 끕니다.
클라이언트에서 사용해야하는 멤버가있는 경우 dll 내보내기 래퍼를 만들거나 간접 메서드를 만듭니다.
template class DLL_EXPORT std::allocator<tCharGlyphProviderRef>;
이것은 현재 컴파일 단위에서 템플릿 전문화의 인스턴스화를 생성합니다. 따라서 이것은 dll에 std :: allocator의 메소드를 생성하고 해당 메소드를 내 보냅니다. 이것은 템플릿 클래스의 인스턴스 화일 뿐이므로 구체적인 클래스에서는 작동하지 않습니다.
이 경고는 DLL 사용자가 DLL 경계를 넘어 컨테이너 멤버 변수에 액세스 할 수 없음을 나타냅니다. 명시 적으로 내 보내면 사용할 수 있지만 좋은 생각입니까?
일반적으로 DLL에서 std 컨테이너를 내 보내지 않는 것이 좋습니다. DLL이 동일한 런타임 및 컴파일러 버전에서 사용된다는 것을 절대적으로 보장 할 수 있다면 안전 할 것입니다. DLL에 할당 된 메모리가 동일한 메모리 관리자를 사용하여 할당 해제되었는지 확인해야합니다. 그렇지 않으면 기껏해야 런타임에 어설 션합니다.
따라서 DLL 경계를 넘어 컨테이너를 직접 노출하지 마십시오. 컨테이너 요소를 노출해야하는 경우 접근 자 메서드를 통해 수행하세요. 제공 한 경우 인터페이스를 구현에서 분리하고 DLL 수준에서 인터페이스를 노출합니다. 표준 컨테이너 사용은 DLL의 클라이언트가 액세스 할 필요가없는 구현 세부 사항입니다.
또는 Neil이 제안한대로 DLL 대신 정적 라이브러리를 만듭니다. 런타임에 라이브러리를로드 할 수 없게되며 라이브러리 소비자는 라이브러리를 변경할 때마다 다시 연결해야합니다. 이것이 당신이 살 수있는 타협이라면, 정적 라이브러리는 적어도이 문제를 극복 할 수있을 것입니다. 여전히 불필요하게 구현 세부 정보를 노출하고 있다고 주장하지만 특정 라이브러리에는 의미가있을 수 있습니다.
다른 문제가 있습니다.
일부 STL 컨테이너는 내보내기에 "안전"하고 (예 : 벡터) 일부는 그렇지 않습니다 (예 :지도).
예를 들어 맵은 (어쨌든 MS STL 배포판에서) _Nil이라는 정적 멤버를 포함하고 있기 때문에 안전하지 않습니다. 그 값은 끝을 테스트하기 위해 반복에서 비교됩니다. STL로 컴파일 된 모든 모듈은 _Nil에 대해 다른 값을 갖기 때문에 한 모듈에서 생성 된 맵은 다른 모듈에서 반복 할 수 없습니다 (끝을 감지하지 못하고 폭발하지 않음).
이것은 lib에 정적으로 링크하더라도 적용됩니다. _Nil의 값이 무엇인지 보장 할 수 없기 때문입니다 (초기화되지 않음).
나는 STLPort가 이것을하지 않는다고 생각합니다.
이 시나리오를 처리하는 가장 좋은 방법은 다음과 같습니다.
라이브러리를 만들고 라이브러리 이름에 포함 된 컴파일러 및 stl 버전으로 이름을 지정하십시오. 부스트 라이브러리와 똑같이 말입니다.
예 :
-dll 버전의 경우 FontManager-msvc10-mt.dll , MSVC10 컴파일러에 고유하며 기본 stl.
-dll 버전 용 FontManager-msvc10_stlport-mt.dll , stl 포트가있는 MSVC10 컴파일러 전용.
-dll 버전의 경우 FontManager-msvc9-mt.dll , MSVC 2008 컴파일러 전용, 기본 stl 사용
-libFontManager-msvc10-mt.lib for static lib version, specific for MSVC10 컴파일러, with the default stl.
이 패턴을 따르면 다른 stl 구현과 관련된 문제를 피할 수 있습니다. vc2008의 stl 구현은 vc2010의 stl 구현과 다릅니다.
boost :: config 라이브러리를 사용하여 예제를 참조하십시오.
#include <boost/config.hpp>
#ifdef BOOST_MSVC
# pragma warning( push )
# pragma warning( disable: 4251 )
#endif
class DLL_EXPORT FontManager
{
public:
std::map<int, std::string> int2string_map;
}
#ifdef BOOST_MSVC
# pragma warning( pop )
#endif
One alternative that few people seem to consider is not to use a DLL at all but to link statically against a static .LIB library. If you do that, all the issues of exporting/importing go away (though you will still have name-mangling issues if you use different compilers). You do of course lose the features of the DLL architecture, such as run-time loading of functions, but this can be a small price to pay in many cases.
Found this article. In short Aaron has the 'real' answer above; Don't expose standard containers across library boundaries.
Though this thread is pretty old, I found a problem recently, which made me think again about having templates in my exported classes:
I wrote a class which had a private member of type std::map. Everything worked quite well untill it got compiled in release mode, Even when used in a build system, which ensures that all compiler settings are the same for all targets. The map was completely hidden and nothing was directly exposed to the clients.
As a result the code was just crashing in release mode. I gues, because different binary std::map instances were created for implementation and client code.
I guess the C++ Standard is not saying anaything about how this shall be handled for exported classes as this is pretty much compiler specific. So I guess the biggest portability rule is to just expose Interfaces and use the PIMPL idiom as much as possible.
Thanks for any enlightenment
Exporting classes containing std:: objects (vector, map, etc) from a dll
Also see Microsoft's KB 168958 article How to export an instantiation of a Standard Template Library (STL) class and a class that contains a data member that is an STL object. From the article:
To Export an STL Class
- In both the DLL and the .exe file, link with the same DLL version of the C run time. Either link both with Msvcrt.lib (release build) or link both with Msvcrtd.lib (debug build).
- In the DLL, provide the __declspec specifier in the template instantiation declaration to export the STL class instantiation from the DLL.
- In the .exe file, provide the extern and __declspec specifiers in the template instantiation declaration to import the class from the DLL. This results in a warning C4231 "nonstandard extension used : 'extern' before template explicit instantiation." You can ignore this warning.
And:
To Export a Class Containing a Data Member that Is an STL Object
- In both the DLL and the .exe file, link with the same DLL version of the C run time. Either link both with Msvcrt.lib (release build) or link both with Msvcrtd.lib (debug build).
- In the DLL, provide the __declspec specifier in the template instantiation declaration to export the STL class instantiation from the DLL.
NOTE: You cannot skip step 2. You must export the instantiation of the STL class that you use to create the data member.- In the DLL, provide the __declspec specifier in the declaration of the class to export the class from the DLL.
- In the .exe file, provide the __declspec specifier in the declaration of the class to import the class from the DLL. If the class you are exporting has one or more base classes, then you must export the base classes as well.
If the class you are exporting contains data members that are of class type, then you must export the classes of the data members as well.
In such cases, consider the uses of the pimpl idiom. Hide all the complex types behind a single void*. Compiler typically fails to notice that your members are private and all methods included in the DLL.
If you use a DLL make initialization of all objects at event "DLL PROCESS ATTACH" and export a pointer to its classes/objects.
You may provide specific functions to create and destroy objects and functions to obtain the pointer of the objects created, so you can encapsulate these calls in a wrapper class of access at include file.
none of the workarounds above are acceptable with MSVC because of the static data members inside template classes like stl containers
each module (dll/exe) gets its own copy of each static definition...wow! this will lead to terrible things if you somehow 'export' such data (as 'pointed' above)...so don't try this at home
see http://support.microsoft.com/kb/172396/en-us
The best approach to use in such scenarios is to use the PIMPL design pattern.
'program story' 카테고리의 다른 글
GROUP BY 및 COUNT (DISTINCT)를 사용하는 LINQ to SQL (0) | 2020.12.12 |
---|---|
관계형 데이터베이스에서 "튜플"이라는 용어는 무엇을 의미합니까? (0) | 2020.12.12 |
R로 지리 테마 맵 개발 (0) | 2020.12.12 |
jquery 문서 내부 또는 외부 함수 준비 (0) | 2020.12.12 |
파이썬의 정적 / 클래스 변수에 대한 __getattr__ (0) | 2020.12.12 |