.NET Forum / Languages / Managed C++ / November 2007
Does anyone know what this text means? (related to C4251 warning)
|
|
Thread rating:  |
Anonymous - 01 Nov 2007 04:25 GMT On MS site: http://msdn2.microsoft.com/en-us/library/esew7y1w(VS.80).aspx
is the following garbled rambling:
"You can avoid exporting classes by defining a DLL that defines a class with virtual functions, and functions you can call to instantiate and delete objects of the type. You can then just call virtual functions on the type."
Does anyone actually understand what is being said?. A simple example would help.
Doug Harrison [MVP] - 01 Nov 2007 05:48 GMT >On MS site: http://msdn2.microsoft.com/en-us/library/esew7y1w(VS.80).aspx > [quoted text clipped - 7 lines] >Does anyone actually understand what is being said?. A simple example >would help. Off the top of my head:
#ifdef COMPILING_X_DLL #define X_EXPORT __declspec(dllexport) #else #define X_EXPORT __declspec(dllimport) #endif
class X { protected:
X(); virtual ~X();
private:
// Copyguard X(const X&); void operator=(const X&);
public:
virtual void f1(); virtual void f2(); virtual void f3();
private:
// Non-virtual implementation functions
public:
X_EXPORT static X* Create(); X_EXPORT static void Destroy(X*); };
Then in x.cpp, implement all the functions except those in the Copyguard section. If you need copying, you may have to implement those functions, and if you want users to be able to make copies, you'll have to expand the static section. You don't have to export the virtual functions, because they are represented by pointers in the vtbl, and each object contains a pointer (the vptr) to this table. Thus, you don't link to those functions by name, and that's why you don't have to export them. You do have to export the creation and deletion functions, because you have to be able to link to them by name.
Of course, the weakness of following the documentation you quoted to the letter is that derived classes can't call X::f1(), because that's a statically bound call and does require linking by name. You can work around that by adding X_EXPORT to the front of your virtual functions. But then you're just about at the point where you might as well have exported the whole class, which is what I recommend doing if you want the thing to act like a C++ class instead of a COM-style interface. As long as you link everyone to the same CRT DLL and consider this usage equivalent to static linking WRT compilation dependencies, for the most part, it'll work fine, and you can get rid of the Create/Destroy functions, as well as all the individual function exports, and it'll "just work".
 Signature Doug Harrison Visual C++ MVP
David Wilkinson - 01 Nov 2007 10:36 GMT > Off the top of my head: > [quoted text clipped - 54 lines] > and you can get rid of the Create/Destroy functions, as well as all the > individual function exports, and it'll "just work". Doug:
The way I do this is a bit more COM-like (I think; I don't really know COM):
// Interface (no constructor or destructor needed) class IBase { public: virtual void f1(); virtual void f2(); virtual void f3(); };
// Implementation class MyClass: public IBase { public: virtual void f1(); virtual void f2(); virtual void f3(); // other stuff };
The exported factory functions are stand-alone functions:
IBase* CreateClass() { return new MyClass; }
void DestroyClass(IBase* pBase) { delete (MyClass*)pBase; }
This method is compiler independent.
 Signature David Wilkinson Visual C++ MVP
Ben Voigt [C++ MVP] - 01 Nov 2007 14:38 GMT BTW David's way is what the article is referring to. Inheritance is done via aggregation and implementing the same public interface. The actual concrete class is not present in the public header file, only the pure interface (contains only pure virtual functions) is. The factory functions must be global to avoid name mangling issues, they would be prototyped in the public header file. Destruction should be a Dispose function in the v-table which calls "delete this;", avoiding the cast, or if reference counting is used then AddRef/Release functions in the v-table.
Doug's way, although it might avoid the warning, does all the things the warning is designed to discourage (exports class members, violates one-definition rule, etc).
Anonymous - 01 Nov 2007 17:31 GMT > BTW David's way is what the article is referring to. Inheritance is done > via aggregation and implementing the same public interface. The actual [quoted text clipped - 8 lines] > warning is designed to discourage (exports class members, violates > one-definition rule, etc). Just to make sure we are 'on the same page':
From what you say above, David's example header should include lines somewhat like this:
// Header IBase* DECLSPEC_MACRO CreateClass() ; void DECLSPEC_MACRO DestroyClass(IBase* pBase) ;
Where DECLSPEC_MACRO simply evaluates to a __declspec(dllimport/dllexport) as the case may be
Is my understanding correct ?
Also, could you please explain this paragraph in your response:
"Destruction should be a Dispose function in the v-table which calls "delete this;"
Surely, if the Construction/Destruction are global functions, then they can't have a 'this' pointer?
Dis you make a mistake, or am I missing something ?
Ben Voigt [C++ MVP] - 01 Nov 2007 19:16 GMT >> BTW David's way is what the article is referring to. Inheritance is done >> via aggregation and implementing the same public interface. The actual [quoted text clipped - 18 lines] > IBase* DECLSPEC_MACRO CreateClass() ; > void DECLSPEC_MACRO DestroyClass(IBase* pBase) ; Pretty much, unless you use self-destruction.
Also, it's common to use an out parameter instead of a return value for the new object, like:
HRESULT DECLSPEC_MACRO CreateClass(IBase** pRetval);
> Where DECLSPEC_MACRO simply evaluates to a __declspec(dllimport/dllexport) > as the case may be and MY_LIBRARY_DLL is a better name than DECLSPEC_MACRO
> Is my understanding correct ? > [quoted text clipped - 7 lines] > > Dis you make a mistake, or am I missing something ? This would be a virtual member function Dispose instead of a global destruction function, because the global destruction function has to do a cast, whereas a member function is pretty well assured that the object is the correct type.
Anonymous - 02 Nov 2007 01:34 GMT >>>BTW David's way is what the article is referring to. Inheritance is done >>>via aggregation and implementing the same public interface. The actual [quoted text clipped - 47 lines] > cast, whereas a member function is pretty well assured that the object is > the correct type. All this seems unnecessarily complicated - it sounds like reinventing the (COM) wheel. Maybe I should just use the PImpl Idiom (typesafe opaque ptr) to move all of the private data from the header file into the implementation - that will certainly make the compiler shut up about C4251 warnings ?
Doug Harrison [MVP] - 02 Nov 2007 03:52 GMT >All this seems unnecessarily complicated - it sounds like reinventing >the (COM) wheel. To a large extent, that's exactly what it is.
>Maybe I should just use the PImpl Idiom (typesafe >opaque ptr) to move all of the private data from the header file into >the implementation - that will certainly make the compiler shut up about >C4251 warnings ? Yes, using the PImpl idiom will avoid C4251, and so will #pragma warning(disable). For the most part, this warning can be ignored. It's an issue primarily when class templates that have static data are involved.[*] See my reply to Ben for more on that. Also, when exporting whole classes, you must do as I said in my first reply to you, namely, "link everyone to the same CRT DLL and consider this usage equivalent to static linking WRT compilation dependencies."
[*] Another way C4251 can be legitimate is if an inline function in a dllexported class calls a function in a non-exported class. ISTR that VC6 would inline those functions even though their class is exported, and that would lead to a linker error, because the non-exported function couldn't be found. VC2005 does not appear to inline such functions, so that leaves template static data as the only real issue.
 Signature Doug Harrison Visual C++ MVP
Doug Harrison [MVP] - 01 Nov 2007 21:55 GMT >BTW David's way is what the article is referring to. Inheritance is done >via aggregation and implementing the same public interface. That's not "inheritance" in the C++ sense of the word.
>The actual >concrete class is not present in the public header file, only the pure >interface (contains only pure virtual functions) is. The factory functions >must be global to avoid name mangling issues, they would be prototyped in >the public header file. They must also be exported, as the static member functions were in my class.
>Destruction should be a Dispose function in the >v-table which calls "delete this;", avoiding the cast, or if reference [quoted text clipped - 3 lines] >warning is designed to discourage (exports class members, violates >one-definition rule, etc). It doesn't just "avoid the warning". What I talked about in the last paragraph of my message lets you use the class as a real C++ class, instead of some clumsy COM simulation. Not everything is a nail.
I've just read the documentation the OP linked to, and it's confused in all its admonitions about "static data". What it's talking about is *template* static data, and that's only tangentially related to __declspec(dllexport), in that dllexported classes that contain or derive from templates that have static data can run into trouble due to duplication of template static data if two or more modules end up instantiating the template. (This is actually its own problem, existing independently of the __declspec interaction, e.g. in VC6, you couldn't pass a std::map object between two modules due to that class template's use of static data.) There are essentially two approaches to avoiding that problem:
1. You can ensure that the template instantiation only occurs in the module implementing the dllexported class.
2. You can explicitly instantiate all the template specializations you use and dllexport them from some DLL. The C4251 docs link to a KB article describing this.
Even if you apply these solutions, everyone has to play along with them, because if a module that applies the solution shares an affected object with a module that does not, you will have the same problem.
Where did what I posted violate the ODR, BTW?
 Signature Doug Harrison Visual C++ MVP
Ben Voigt [C++ MVP] - 01 Nov 2007 23:45 GMT >>BTW David's way is what the article is referring to. Inheritance is done >>via aggregation and implementing the same public interface. > > That's not "inheritance" in the C++ sense of the word. It isn't type derivation, but it is inheritance in the OOP sense of the word.
[snip]
>>Doug's way, although it might avoid the warning, does all the things the >>warning is designed to discourage (exports class members, violates [quoted text clipped - 4 lines] > instead > of some clumsy COM simulation. Not everything is a nail. That's misleading. What you could say is "it's a real Visual C++ 8.0 class dynamically linking the debug runtime library, with 4-byte packing, and STL_CHECKED_ITERATORS=0"... if those are in fact the compiler options used. Whereas the source code for a real C++ class can be used in any C++ program and compiled with any C++ standard-compliant compiler.
[snip]
> Where did what I posted violate the ODR, BTW? The definition of class X is different between the provider and consumer.
Every single use of dllimport/dllexport on a type violates the ODR. Sometimes you can get away with it, sometimes not.
Doug Harrison [MVP] - 02 Nov 2007 04:12 GMT >> Where did what I posted violate the ODR, BTW? > >The definition of class X is different between the provider and consumer. > >Every single use of dllimport/dllexport on a type violates the ODR. Can you write a program that can detect it and cares about it? Since "every single use violates the rule", please restrict yourself to the simplest type imaginable. :)
Listen, when you use __declspec(dllexport|dllimport), you're no longer writing in Standard C++. Someone who wants to use the feature doesn't care about inconsequential violations of the ODR; he cares only that the program behaves as he expects a C++ program to behave. I'm happy to talk about relevant ways in which expectations are violated, because they're important for people using the feature to understand.
 Signature Doug Harrison Visual C++ MVP
Ben Voigt [C++ MVP] - 06 Nov 2007 21:17 GMT >>> Where did what I posted violate the ODR, BTW? >> [quoted text clipped - 6 lines] > single use violates the rule", please restrict yourself to the simplest > type imaginable. :) #if BAD_DLL_BUILD #define BAD_DLL __declspec(dllexport) #else #define BAD_DLL __declspec(dllimport) #endif
BAD_DLL class Badness { char first; double second; public: double getValue() { return second; } };
First up, the ODR is violated because BAD_DLL is defined differently in each compilation unit.
Practically, because getValue is defined inside the class declaration, it is automatically marked for inlining. Therefore each compile unit will independently calculate the class layout. Since the class layout may not be the same for the DLL and EXE (only the v-table layout is part of the ABI and guaranteed to remain the same), there's a problem.
> Listen, when you use __declspec(dllexport|dllimport), you're no longer > writing in Standard C++. Someone who wants to use the feature doesn't care [quoted text clipped - 4 lines] > important > for people using the feature to understand. Doug Harrison [MVP] - 07 Nov 2007 00:38 GMT >#if BAD_DLL_BUILD >#define BAD_DLL __declspec(dllexport) [quoted text clipped - 12 lines] >First up, the ODR is violated because BAD_DLL is defined differently in each >compilation unit. I'll say it again: Listen, when you use __declspec(dllexport|dllimport), you're no longer writing in Standard C++. Someone who wants to use the feature doesn't care about inconsequential violations of the ODR; he cares only that the program behaves as he expects a C++ program to behave. I'm happy to talk about relevant ways in which expectations are violated, because they're important for people using the feature to understand.
(Key terms are "inconsequential violations" and "relevant ways". You are at best talking about the former, and in no way, the latter.)
>Practically, because getValue is defined inside the class declaration, it is >automatically marked for inlining. Therefore each compile unit will >independently calculate the class layout. Since the class layout may not be >the same for the DLL and EXE (only the v-table layout is part of the ABI and >guaranteed to remain the same), there's a problem. I'll ask you again: Can you write a program that can detect it and cares about it? ("It" being this purported ODR violation.) In case you don't realize it, you haven't demonstrated anything; you've just made some new unsubstantiated claims.
NB: You'll discover your glaringly obvious syntax error when you try to write this program. Anybody bold enough to categorically proclaim "BAD_DLL" ought to be able to write this program and demonstrate that there is an actual problem. It's not a lot to ask. However, I hope you realize anyone who understands the feature can write "GOOD_DLL"; indeed, MS uses __declspec(dllexport|dllimport) in the C and C++ DLLs to export the common basic_string specializations, std::exception, and various other classes. So even if you are able to contrive a "BAD_DLL", I expect to find you've used the feature incorrectly.
 Signature Doug Harrison Visual C++ MVP
Ben Voigt [C++ MVP] - 07 Nov 2007 14:22 GMT >>#if BAD_DLL_BUILD >>#define BAD_DLL __declspec(dllexport) [quoted text clipped - 38 lines] > realize it, you haven't demonstrated anything; you've just made some new > unsubstantiated claims. Yes, using pragma pack or any of a number of other options that affect the class layout but work correctly when used consistently within a DLL, except for dllexport types. As I said, only the v-table layout is part of the ABI and is guaranteed to be the same in different DLLs.
> NB: You'll discover your glaringly obvious syntax error when you try to > write this program. Anybody bold enough to categorically proclaim [quoted text clipped - 7 lines] > even if you are able to contrive a "BAD_DLL", I expect to find you've used > the feature incorrectly. I see I caused confusion by naming my library "Ben's Awkward Demonstration" because the acronym looks like the word bad.
Anyway, Microsoft using dllexport on types in the standard library causes no end of problems, which frequently appear in this newsgroup.
Doug Harrison [MVP] - 07 Nov 2007 17:40 GMT >Yes, using pragma pack or any of a number of other options that affect the >class layout but work correctly when used consistently within a DLL, except >for dllexport types. What do you mean, "except for dllexport types"?
If you use #pragma pack inconsistently on types for which it matters, you are causing undefined behavior:
1. You can easily observe this within a single module by using different packing in two translation units for a normal, non-dllexport type.
2. You can easily observe this for non-dllexport types shared between modules. For example, there were (once) bugs due to inconsistent packing affecting Windows's <winsock2.h> header and KEY_EVENT_RECORD type.
I hope you can see that problems like this exist independently of dllexport.
>I see I caused confusion by naming my library "Ben's Awkward Demonstration" >because the acronym looks like the word bad. The problem is, you didn't "demonstrate" anything.
>Anyway, Microsoft using dllexport on types in the standard library causes no >end of problems, which frequently appear in this newsgroup. Then you should be able to provide numerous examples of "Microsoft using dllexport on types in the standard library causing no end of problems".
 Signature Doug Harrison Visual C++ MVP
Ben Voigt [C++ MVP] - 13 Nov 2007 23:03 GMT >>Yes, using pragma pack or any of a number of other options that affect the >>class layout but work correctly when used consistently within a DLL, [quoted text clipped - 8 lines] > 1. You can easily observe this within a single module by using different > packing in two translation units for a normal, non-dllexport type. True, but this can be (and in the newest versions of VC++ it usually is) caught by the linker.
When you add dllexport to the mix, the difficult of matching settings increases exponentially because you now have to match multiple binaries from multiple vendors...
> 2. You can easily observe this for non-dllexport types shared between > modules. For example, there were (once) bugs due to inconsistent packing [quoted text clipped - 15 lines] > Then you should be able to provide numerous examples of "Microsoft using > dllexport on types in the standard library causing no end of problems". Specifically, people using binary third-party libraries that use STL objects in the public API, that then prevent the consumer (programmer, not end-user) from upgrading to a new compiler.
People wanting/needing to use two such libraries, that haven't used identical settings to each other.
Ben Voigt [C++ MVP] - 13 Nov 2007 23:36 GMT >> Then you should be able to provide numerous examples of "Microsoft using >> dllexport on types in the standard library causing no end of problems". There's a brand new example right now in microsoft.public.win32.programmer.kernel entitled "loading msvcr80.dll and msvcr80d.dll"
It may not be using __declspec(dllexport), but it is the same problem -- DLLs making assumptions about each other's runtime library and compiler settings.
Doug Harrison [MVP] - 14 Nov 2007 01:30 GMT >>> Then you should be able to provide numerous examples of "Microsoft using >>> dllexport on types in the standard library causing no end of problems". [quoted text clipped - 4 lines] > >It may not be using __declspec(dllexport) What you said was, "Anyway, Microsoft using dllexport on types in the standard library causes no end of problems, which frequently appear in this newsgroup." I can't believe you're trying to back this up with an "example" that doesn't involve dllexport and which comes from a different newsgroup, no less.
>but it is the same problem -- >DLLs making assumptions about each other's runtime library and compiler >settings. I was really hoping you would produce several links. Then I would have shown that there is a single answer to your "no end of problems," namely, what I've been saying for years (not to mention in my first post in this thread) about considering this use of DLLs equivalent to static linking for compilation dependency purposes.
 Signature Doug Harrison Visual C++ MVP
Ben Voigt [C++ MVP] - 14 Nov 2007 14:42 GMT > I was really hoping you would produce several links. Then I would have > shown that there is a single answer to your "no end of problems," namely, > what I've been saying for years (not to mention in my first post in this > thread) about considering this use of DLLs equivalent to static linking > for > compilation dependency purposes. I agree with that, except that "for compilation dependency purposes" confuses the issue without adding any information IMHO.
However, that adds a lot of limitations, and avoiding those limitations was the whole point of the text the OP quoted and asked about. Again, in my opinion the language you used suggested that dllexport was just as good or better than the method suggested by the MSDN article, and this just isn't true in many (possibly most) situations.
The real question is "what value does dllexport have over static linking" and aside from staggered loading, I can't think of anything. I'm pretty sure it has no value whatsoever to most projects.
Doug Harrison [MVP] - 15 Nov 2007 20:09 GMT >I agree with that, except that "for compilation dependency purposes" >confuses the issue without adding any information IMHO. How is it unclear? I've been answering C4251 questions for a long time, and I can't recall anyone asking for clarification, but that is the most concise way I've found to put it. I typically go into quite a bit more detail, along the lines of what I said in this thread in my first reply to you and in my second reply to the OP. I don't know any better way to capture the issue in one sentence.
>However, that adds a lot of limitations, and avoiding those limitations was >the whole point of the text the OP quoted and asked about. As I explained elsewhere in this thread, the documentation was concerned with template static data. That's the _sole_ "limitation" it talked about. As for the COM simulation suggestion, that's just one approach (the documentation actually links to a KB article that explains how to dllexport template specializations to avoid the problem), and as it prevents one from using the class as a C++ class, which is why dllexport was being used in the first place, it throws the baby out with the bath water. The OP would seem to agree, as he replied to you:
<q> All this seems unnecessarily complicated - it sounds like reinventing the (COM) wheel. Maybe I should just use the PImpl Idiom (typesafe opaque ptr) to move all of the private data from the header file into the implementation - that will certainly make the compiler shut up about C4251 warnings ? </q>
In my reply, I explained why using the pimpl idiom is often overkill just to silence C4251, when all you may need is #pragma warning(disable), subject, of course, to what I say in my one sentence summary you find so confusing.
>Again, in my >opinion the language you used suggested that dllexport was just as good or >better than the method suggested by the MSDN article, and this just isn't >true in many (possibly most) situations. Given that dllexport lets you use the class as a full-fledged C++ class instead of a poor COM simulation, the former is infinitely better than the latter if you want to use the class as a C++ class. That's effectively what I said in my first reply to the OP, whose last paragraph was:
<q> Of course, the weakness of following the documentation you quoted to the letter is that derived classes can't call X::f1(), because that's a statically bound call and does require linking by name. You can work around that by adding X_EXPORT to the front of your virtual functions. But then you're just about at the point where you might as well have exported the whole class, which is what I recommend doing if you want the thing to act like a C++ class instead of a COM-style interface. As long as you link everyone to the same CRT DLL and consider this usage equivalent to static linking WRT compilation dependencies, for the most part, it'll work fine, and you can get rid of the Create/Destroy functions, as well as all the individual function exports, and it'll "just work". </q>
I deliberately differentiated the approaches. I deliberately mentioned advantages and drawbacks of both, and I expanded at length in a subsequent message on the one major issue (template static data) concerning what you're trying to pass off as my "pet approach" or something. It's not my pet approach. It just bugs me when someone who clearly _has_ a pet approach, i.e. he thinks DLLs are only to be COM-style black boxes, and any other usage is evil, tries to pass off groundless opinions (still waiting on those examples) as if they were facts, when the facts are, lots (probably many thousands) of programs use dllexported classes without any problems. (See programs that use the C, C++, and MFC DLLs.)
>The real question is "what value does dllexport have over static linking" >and aside from staggered loading, I can't think of anything. I'm pretty >sure it has no value whatsoever to most projects. It has the same value as static libraries, with the DLL advantages of deterministic order of initialization of globals (DllMain restrictions apply, of course), run-time code sharing (smaller code, more efficient memory usage), and yes, the ability in some cases to fix bugs and make other enhancements merely by replacing a DLL. It also allows one to be a little more lax in controlling visibility of classes used internally by the DLL, as one needn't worry about violating ODR rules between modules for classes that aren't exposed in any way to DLL clients.
 Signature Doug Harrison Visual C++ MVP
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|