荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: tang (独孤九剑〖玄铁重剑〗), 信区: Program
标  题: Office Source Code Style Guide
发信站: BBS 荔园晨风站 (Wed Jun 30 21:47:23 1999), 转信

Office Source Code Style Guide
Dave Parker, 6/30/95

Abstract
This document outlines a general style guide for C and C++ source code in Office
Development.  The main purpose here is to list features of C++ which we will use
and which we will avoid, along with the basic rationale for doing so.  There are
also standards for basic coding issues for the sake of consistency within the
code and robust constructs.  This is not a complete list of C/C++ language
features with commentary.  Rather, it mentions only the issues we consider
important.  Knowledge of C++ is assumed.
Contents
1. GENERAL GOALS        3
2. CLASSES      3
2.1 CLASS VS. STRUCT    4
2.2 PUBLIC, PRIVATE, AND PROTECTED MEMBERS      4
2.3 DATA MEMBERS        4
2.4 VIRTUAL FUNCTIONS   5
2.5 CONSTRUCTORS        5
2.6 DESTRUCTORS 6
2.7 NEW AND DELETE      7
2.8 OPERATORS   7
2.9 INHERITANCE 8
2.9.1 Inheritance of Interface vs. Implementation       8
2.9.2 Inheritance vs. Containment       10
2.9.3 Multiple Inheritance      11
3. OTHER C++ FEATURES   11
3.1 CONSTANTS AND ENUMERATIONS  12
3.2 REFERENCES  12
3.3 CONST PARAMETERS AND FUNCTIONS      13
3.4 DEFAULT ARGUMENTS   13
3.5 FUNCTION OVERLOADING        14
3.6 OPERATOR OVERLOADING        14
4. COMMON C/C++ ISSUES  14
4.1 #IFDEFS     14
4.2 GLOBAL VARIABLES    15
4.3 MACROS AND INLINE FUNCTIONS 16
4.4 OPTIMIZATION        16
4.5 WARNINGS    17
4.6 PRIVATE DATA AND FUNCTIONS  17
4.7 TYPEDEFS    17
4.8 BASIC DATA TYPES    17
4.9 POINTERS    18
4.10 SWITCH STATEMENTS  19
4.11 ASSERTS    19
4.12 ERRORS AND EXCEPTIONS      19
5. FORMATTING CONVENTIONS       20
5.1 NAMING CONVENTIONS  20
5.2 FUNCTION PROTOTYPES 21
5.3 VARIABLE DECLARATIONS       22
5.4 CLASS DECLARATIONS  22
5.5 COMMENTS    23
5.5.1 File Headers and Section Separators       23
5.5.2 Function Headers  24
5.5.3 In-Code Comments  25
5.5.4 Attention Markers 25
5.6 MISC. FORMATTING CONVENTIONS        26
5.7 SOURCE FILE ORGANIZATION    27
5.7.1 Public Interface Files    27
5.7.2 Private Interface Files   28
5.7.3 Implementation Files      28
5.7.4 Base Filenames    29
6. INTERFACES TO DLLS   29
6.1 C FUNCTIONS AND GLOBAL VARIABLES    29
6.2 COMMON C/C++ PUBLIC HEADER FILES    29
6.3 LIGHTWEIGHT COM OBJECTS AND ISIMPLEUNKNOWN  30
7. APPENDIX A: BASIC HUNGARIAN REFERENCE        33
7.1 MAKING HUNGARIAN NAMES      33
7.2 STANDARD BASE TAGS  33
7.3 STANDARD PREFIXES   34
7.4 STANDARD QUALIFIERS 35

 1. General Goals
C++ is a complex language that provides many ways to do things, and going 搘hole
hog" on all of its features can lead to confusion, inefficiency, or maintenance
problems.  All Office developers need to become experts on the features we will
use, and avoid the others in order to form solid conventions within the group
that we are all comfortable with.  Our use of C++ features will be fairly
conservative.  We抎 much rather err on the side of just dealing with C, which
we抮e all used to, then screwing up our app with a new concept that not all of
us are used to.
Underlying the choice of all of the style decisions are a few basic goals, as
listed below.  When in doubt about a particular issue, always think about the
spirit of these goals.  Sometimes these goals will conflict, of course, and in
these cases we try to either prioritize the tradeoffs or use experience (either
our own or from other groups that have used C++ extensively).
1.      Simplicity.  When in doubt, keep it simple.  Bugs are related mostly to
complexity, not code.
2.      Clarity.  The code should do what it looks like it抯 doing.  Other
people
need to be able to understand your code.
3.      Efficiency.  Speed and size are important.  Using C++ does not imply
big and
slow.  There are plenty of perfectly reasonable ways to make things as fast or
faster than the normal C way.  Speed and size often trade off, and most people
probably err on the side of choosing speed too often.  Remember that 20% of the
code is responsible for 80% of the time.  In most cases, we抮e more concerned
about fitting comfortably in less RAM.
4.      Appropriateness.  Use the language construct that is appropriate for the
abstraction or operation you are trying to do.  Do not abuse the language.
Don抰 use a construct just because it happens to work.  Definitely don抰 use a
strange construct to amaze and confuse your friends to try to show how smart you
are.
5.      Natural transition from C to C++.  We all used to be C programmers.
Others
that look at our code are still C programmers (e.g. Word and Excel).  When
possible, avoid C++ constructs where a C programmer抯 instinct causes a wrong
assumption.
6.      Catch Errors Early.  Having the compiler catch an error is ideal.
Having
debug code (e.g. Asserts) catch it is the next best thing, etc.  Declare things
in such as way as to give the compiler the best chance at catching errors.
7.      Fast builds.  Total generality and modularity can cause lots of
inter-dependencies between files, which can have a dramatic impact on build
times.  This is a constant time sink for everyone.  It is often worth
rearranging things a little to make incremental builds faster.
8.      Consistency.  The whole point of having a style guide is that
programmers are
never totally autonomous, even when the group has strong code ownership.  Other
people need to read and understand your code.  Everyone has to give a little to
have a consistent style guide, but everyone gains it back when they read or
debug other people抯 code.
2. Classes
C++ classes are a nice way to encapsulate code and data into a single unit,
which provides a good paradigm for object-oriented implementations as well other
features such as flexible access control, convenient and type-safe polymorphism,
and the possibility of code reuse via inheritance.
At the most general, classes are an extension to the built-in typing of C which
allows you to define your own types along with the operations on that type.
Taken to the extreme, every piece of data in a program could be an instance of a
class.  However, we will not go nearly this far in Office.  We will use classes
when there is a good reason to, such as the concept being implemented is
inherently object-oriented or polymorphism is required.  It has been the
experience of many people that programs that use classes for everything evolve
into systems that are complex and inefficient.  Although this may not be the
fault of any particular class, complex class hierarchies can lead to needless
complexity, and overly abstracted concepts can easily lead to inefficiency.
In general, we will avoid allocating classes on the stack and passing classes by
value, because this is where the use of constructors and destructors gets you
into the most trouble.  Most classes should be allocated via new, freed by
delete, and passed by pointer.  In addition, we will never declare a global
variable which is an instance of a class that has a constructor, because this
causes a bunch of C runtime stuff to get linked in and stuff to happen at boot
time to construct the thing, which is a big performance hit.  Using only
heap-allocated classes implies we抣l probably use classes only for relatively
complex objects that you would normally have in the heap anyway, not simple
things like basic data types.  Beyond this, it is a judgment call when to use a
class.  Use one if there is a good reason, but not if a more straightforward
solution is just as good.
Summary:
· Use classes to encapsulate the implementation of an object-oriented concept.
· Use classes to implement polymorphism.
· Avoid allocating class instances on the stack and passing them by value.  Use
new and delete, and pass them by pointer.  This implies not using classes for
simple data types.
· Never declare a global instance of a class that has a constructor.
· Not everything is as class.  Use them only when you gain something.
2.1 Class vs. Struct
In C++, a struct can also have member functions and operators and everything
else that a class can have.  In fact, the only difference between a class and a
struct is that all members default to public access in a struct but private
access in a class.  However, we will not use this as the deciding point between
using a class vs. a struct.  To match the normal intuition, we will use a class
if and only if there are member functions included.
Summary:
· Use a class instead of a struct if and only if there are member functions.
2.2 Public, Private, and Protected members
As stated above, structs default to public access and classes default to private
access.  However, we will depend on the default only in the case of structs
(where we leave all the data implicitly public).  For a class, we will declare
all members (both data and code) explicitly as public, protected, or private,
and group them into sections in that order.  For example:
class Foo
        {
public:
        Foo();
        ~Foo();
        void Hey(int I);
        void Ack();
protected:
        int m_iValue;
private:
        int m_iStuff;
        void LocalHelperSub();
        };

Summary:
· Declare all class members explicitly as public, protected, or private, in
groups in that order.
2.3 Data Members
Data members should use the naming convention m_name where name is a normal
Hungarian local variable name.  This makes member function implementations
easier to read (no confusion about member vs. local data), and allows the use of
the same Hungarian name for, e.g., parameters and members.  See the example
below.
Data members should normally not be declared public because this usually defeats
the purpose of the class abstraction.  To efficiently export a data member,
declare inline get and set member functions.  This will get optimized into the
same code as a public data member.  For example:
class Counter
        {
public:
        int CItems() const { return m_cItems; }
        void SetCItems(int cItems) { m_cItems = cItems; }
private:
        int m_cItems;
        };

Summary:
· Data members use the naming convention m_name.
· Do not declare public data members.  Use inline accessor functions for
performance.
2.4 Virtual Functions
Virtual functions are used to allow derived classes to override a method in a
base class by providing their own implementation in a way that always causes the
most-derived version to be called whenever a method is called through an object
pointer, even if that pointer is declared as a pointer to the base class.  This
is usually done to implement polymorphism, and that抯 when we抣l use them.  For
example, all COM interface methods are virtual because you are always going for
polymorphism via a standard interface.
Unlike simple member functions, virtual functions incur some overhead due to
need to call through the vtable.  If a class contains at least one virtual
function then the data size of each instantiated object will be 4 bytes larger
than the combined size of the declared data in order to hold the vtable
pointer.
After the first virtual function, each additional one only adds another entry to
the class vtable, which is static and per-class (nothing per object), so the
main concern here is whether a class has any virtual functions at all.  In
addition to the memory overhead, there is the overhead to indirect a pointer
twice before calling the function.  This is fairly fast and compact in 32-bit
code, but affects speed and size nevertheless. Perhaps the worst part is that
virtual functions cannot be inlined, so there will always be a function call,
even when the work is trivial.
Because they have overhead, you should not use virtual functions in a class
unless you need to.  However, make sure you do use them when it makes sense.  In
particular, if you have a base class which requires a destructor, then the
destructor should definitely be virtual to allow derived classes to destruct any
added members properly.  If the destructor were not virtual, then in a context
where polymorphism is being used (so the object pointer is declared as a pointer
to the base class), the base class destructor will always get called, even for
an object of a derived class that added data members and declared its own
destructor in an attempt to free them.  The derived class抯 destructor will only
get called if the base class destructor is declared virtual.  This scenario
applies to many other kinds of methods that you will add to your classes.  In
fact, most of the methods in a base class might be this way if polymorphism is
intended.  This issues is discussed in more detail in the Inheritance section
below.
Note that although virtual functions have a performance penalty over regular
member functions, they are often the most efficient way to implement a concept
such as polymorphism where the alternative would be large switch statements (not
to mention the benefits of the object-oriented encapsulation).
Summary:
· Use virtual functions to implement polymorphism.
· Virtual functions have overhead, so don抰 use them unless you really should.
· A destructor in a base class should always be virtual if polymorphism is
intended.
2.5 Constructors
Ah, constructors.  Every new C++ programmer抯 nightmare.  This is one reason to
try to minimize the use of constructors -- C programmers aren抰 used to them and
will get confused.  Another reason is the infamous performance overhead of
calling a function (unless it抯 inline) and doing work at possibly unexpected
and/or redundant times.
However, using constructors can eliminate the dangers of uninitialized data and
can also made the code simpler to read (if you抮e used to it).  Judicious use of
destructors (see below) which match the constructors can also help prevent
memory leaks and other resource management problems.
Fortunately, the issue is mainly one when classes are declared on the stack or
passed by value, both of which we will avoid.  Most of our classes should be
dynamic memory objects  which will be passed around by pointer.  In this case,
the constructor is essentially just a helper function for the functions that
create these dynamic objects.  Using a constructor for this purpose is
reasonable to ensure a clean and consistent initialization (if you make sure to
initialize all data members), but to prevent potential performance problems due
to redundant initialization the constructor should not do anything expensive.
Simply assigning a constant or a parameter value to each data field is about
right.  Very simple constructors can be made inline.
Most importantly, a constructor should never be able to fail, because lacking a
fancy exception handling mechanism, the caller has no way to handle this in some
cases.  Any initialization that can fail (e.g. memory allocations) should be put
in a separate initialization member function (called, e.g., FInit).  When this
is the case, it is often useful to encapsulate the creation of an object in a
function (a global function or a member of another class) that calls new and
then FInit for the object, and returns the result of FInit.  For example:
class Foo
        {
public:
        Foo(int cLines) { m_hwnd = NULL; m_cLines = cLines}
        virtual ~Foo();
        BOOL FInit();
        void DoSomething();
private:
        HWND m_hwnd;
        int m_cLines;
        };

BOOL FCreateFoo(int cLines, Foo **ppfoo)
{
        if ((*ppfoo = new Foo(cLines)) == NULL)
                return FALSE;
        if (*ppFoo->FInit())
                return TRUE;
        delete *ppFoo;
        *ppFoo = NULL;
        return FALSE;
}

BOOL Foo::FInit()
{
        m_hwnd = CreateWindow(...);
        return (m_hwnd != NULL);
}

Summary:
· Do not do expensive work in a constructor.
· If you do make a constructor, make sure to initialize all data members.
· Very simple constructors can be made inline
· A constructor should never fail.  Do memory allocations and other potential
failures in an FInit method.
· Consider making a creation function that encapsulates the new and FInit
operations.
2.6 Destructors
If a class has resources that need to be freed, then the destructor is a
convenient place to put this.  The normal case for us will be that this is just
the central place to free resources for an object that is freed via delete (see
below).  The trickier use of destructors is for stack-allocated classes, but
we抮e going to avoid that by not using classes on the stack.
A destructor should be careful to destroy an object properly regardless of how
it was created or used.  Furthermore, if you choose to implement a method that
frees any resources before the actual destruction, make sure to reset those
fields (e.g. set pointers to NULL) so that a destructor will not try to free
them twice.  It is not necessary for the destructor to reset any fields, though,
because the object cannot be used after it is destructed.
Like a constructor, a destructor can never fail.  Also, as stated above, a
destructor in a base class should always be declared virtual to make
polymorphism work.
The destructor for the above example would be defined as:
Foo:~Foo()
{
        if (m_hwnd != NULL)
                DestroyWindow(m_hwnd);
}

Summary:
· Use a destructor to centralize the resource cleanup of a class which is freed
via delete.
· If resources are freed before destruction, make sure the fields are reset
(e.g. set pointers to NULL) so that a destructor will not try to free them
again.
· A destructor should never fail.
· A destructor in a base class should always be declared virtual if
polymorphism might be used.
2.7 New and Delete
The operators new and delete should be used to allocate and free classes
(instead of the low-level malloc-like function in your app) so that the
constructor and destructor, if any are called properly.  We will implement our
own global new and delete so that they in turn call our favorite low-level
memory manager, so the only difference is really that new does the sizeof
automatically and also calls a constructor, and delete calls the destructor.
Note that there must be some mechanism for detecting failed memory allocations.
For new, the calling code is responsible for checking.  Our memory manager
simply returns 0 for a failed allocation, and this will in turn be returned from
new (and the constructor will not be called).  It is therefore up to the caller
of new to check for a 0 return value, as in the example above in the
Constructors section.
You should avoid defining any other new and delete (i.e. class-level) operators
and stick to the global one to avoid mixed memory models, which complicates
things like help optimization and memory leak checking and makes it risky to
have the routines that allocate and free a block be different (although this is
normally bad structure anyway).
Summary:
· Use new and delete to allocate and free classes.
· We will implement our own global new and delete in terms of the Office
infrastructure memory manager.
· Check the return value of new for failure.
· Avoid defining any other new and delete operators (use the global ones
defined by Office).
2.8 Operators
Ideally, you will never need to define an operator for a class.  If you did one,
it might be operator=, but don抰 define one unless you really think you want
this capability.  Next might be operator== and operator!=, but the same applies
here, only define them if really needed.  We抮e not in the business of providing
a general class library that might be used in a certain way, we抮e just
implementing code that we actually expect to use ourselves (as explained in a
later section, we will not export a real C++ class to anyone).  And if you do
define these operators, make sure they are efficient so that you are not hiding
an expensive operation.
By all means, never define standard operators such as operator+ to do anything
other than the standard semantics for built-in objects.  Don抰 even push it by
doing things like defining, say, operator+ to do a union or concatenation
operation.  In addition to causing confusion, this hides potentially expensive
work behind an innocent-looking operator.
Summary:
· Ideally, classes shouldn抰 need to define any operators.
· Define 搑easonable" operators such as =, ==, and != only if you really want
and use this capability yourself, and if you do they should be super-efficient
(ideally inline).
· Never define an operator to do anything other than the standard semantics for
built-in types.
· Never hide expensive work behind an operator.  If it抯 not super efficient
then make it an explicit method call.
· When in doubt, just make a member function to do the work so that the
operation is explicit.
2.9 Inheritance
Inheritance is a powerful technique, but it is often misused and can lead to
complex class hierarchies that are hard to understand and hard to change.  The
following sections describe the various uses of inheritance, compare them to
other techniques and try to provide rules of thumb about when to use it.
Beyond being appropriate in a particular case, however, just because inheritance
can be appropriate does not mean it should be used everywhere.  A deep or wide
inheritance tree gets hard to understand, hard to browse, and eventually hard to
maintain.  Keep inheritance limited to a few 搒ilver bullets" where you really
win from it.
Summary:
· Don抰 use inheritance just because it will work.  Use it sparingly and
judiciously.
2.9.1 Inheritance of Interface vs. Implementation
Most people think about inheritance as a way to share code.  However, one of the
most useful ways to use it is simply as a way to ensure working polymorphism by
inheriting interface only.  The classic example is to have an interface class
which is entirely abstract (all methods are pure virtual), and then one or more
implementation classes that inherit the interface and implement it in different
ways.  The OLE COM model is an example of this.  A COM interface is expressed in
C++ as an abstract base class, and then a separate implementation class inherits
from the interface class and implements the interface methods for that object.
Here the inheritance is simply a convenient way to ensure that the object speaks
the exact interface it is supposed to (has the right methods in the right order
in the vtable with the right parameters and the right return types).  This is
ensured by having each implementation class inherit from the same interface
class, which is only declared once in a common header file.  Note than when an
interface class implements an inherited pure virtual method, it must redeclare
it because from a language point of view, it is still considered to 搊verride"
the base method.  For example:
// The interface base class provides interface only
class FooInterface
        {
public:
        virtual void DoThis() = 0;              // pure virtual
        virtual void DoThat(int i) = 0; // pure virtual
        };

// The implementation class implements the FooInterface interface
class FooImplementation: public FooInterface
        {
public:
        virtual void DoThis();
        virtual void DoThat();
        }

void FooImplementation::DoThis()
{
        ...
}
...

The above example shows the case where the entire base class is interface only.
However, inheritance of interface only can also happen at the level of an
individual member function in a base class which also includes some
implementation.  This is the case when any member function is declared pure
virtual.  An example of this is shown below with the DrawObj::Draw method.
The above example does not use inheritance to share code.  However, inheritance
can also be used for this, and this is done by providing implementations of
methods in a base class that inherited classes can use.  There are two
interesting cases here.  If the base class defines an implementation of a method
which can either be used or overridden, then the base method is defining an
interface with a default implementation.  In this case, the method should be
defined as virtual so that any class which overrides the method will get the
right result when polymorphism is used.  Alternately, if the base class method
provides an implementation of a method which is not meant to be overridden
(because it does a standard action on data which is private to the base class),
then the base method is defining an interface and a required implementation.  In
this case, the method should not be declared virtual.  The converse of this is
that when inheriting from a class, do not override any non-virtual functions
because this could lead to maintenance problems when the base class is changed.
In general, the two cases of inheritance of implementation outlined above as
well as the case of inheritance of interface only can all be combined in a
single class by having different methods do different things.  The key is to
decide, for each method, whether the goal of the base method is to provide
interface only, interface plus default implementation, or interface plus
required implementation.  For example:
// A base class for drawing objects
class DrawObj
        {
public:
        virtual void Draw() = 0;          // interface only
        virtual BOOL FHitTest(POINT pt);  // default implementation
        void GetBounds(RECT *pr);         // required implementation
private:
        Rect m_rBounds; // bounding rectangle
        };

BOOL DrawObj::FHitTest()
{
        return PtInRect(pt, m_rBounds);
}

void DrawObj::GetBounds(RECT *pr)
{
        *pr = m_rBounds;
}

In this example, the Draw method is pure virtual because it is only specifying
an interface for polymorphic use.  Any derived class that can be instantiated
must define the Draw method.  The FHitTest method is defining interface (for
polymorphism) as well as a default implementation.  Any derived classes that
don't need non-rectangular hit-testing can just use the default implementation
(no code or declaration required), but other classes can simply override this
method and do special hit-testing.  The GetBounds method is an example of a
required implementation.  The base class requires that "bounds" be defined in
the same way for all objects, and it doesn't make sense for anyone to change
it.
In this case, the member does not need to be virtual (and should not be for
clarity and efficiency) because the base class implementation is always used.
Summary:
· Inheritance of interface can be used for ensuring a consistent (e.g.
polymorphic) interface.
· An implementation class can inherit its interface from an interface class
where the interface class has only pure virtual methods.
· When using inheritance of implementation to share code in a base class,
        ·  Use pure virtual functions to provide interface only.
        ·  Use virtual functions to provide interface and a default
implementation.
        ·  Use non-virtual functions to provide interface and a required
implementation.
2.9.2 Inheritance vs. Containment
The most common misuse of inheritance is to view inheritance as a way to share
code among 搒imilar" objects and to use it in a context where there is no real
搃s a" relationship.  There are several ways to share code, and the simpler
technique of containment and delegation (one class contains another and
delegates the relevant functionality to it), which we抮e all used to from
traditional structured programming, works fine in most cases.  In this case, the
relationship is described as 揾as a".
The primary reason to use inheritance instead of containment is to achieve
polymorphism (in conjunction with the use of virtual functions).  The easiest
way to test for an 搃s a" relationship is to think whether polymorphism is what
is desired.  If so, then inheritance could be appropriate (assuming any other
practical concerns are met).  Another way to test 搃s a" vs. 揾as a" is to ask
yourself if it could make sense to have more than one of the base class in the
derived class.  If so, then 揾as a" (containment) is the right model.  For
example, if you were implementing a scrolling window and you already have a
scrollbar class, you would notice that a window could have two scrollbars
(horizontal and vertical) even if you weren抰 planning on that feature in the
first version, so a window should contain (揾as") a scrollbar, not inherit from
(搃s") one.
Even when you do decide to use inheritance from another class, it is often the
case that you should split the original class into a base class and a derived
class and inherit only from the base class.  This allows you to split off only
the stuff that is really shared.  For example, say you had a Rectangle drawing
object, and now you want an 揙val" object.  You convince yourself that
polymorphism is desired (e.g. drawing and hit-testing code in the caller wants
to treat all objects the same), and that an Oval would never want two
Rectangles.  Now you might decide to have the Oval inherit from the Rectangle,
but probably what you really want is to split the Rectangle class into a base
DrawingObject class and a separated derived Rectangle class, and then Oval would
inherit from DrawingObject, not Rectangle.  This allows later changes to the
Rectangle object that are specific only to it, even if this isn抰 needed now.
As in the example from the previous section, the DrawingObject base class will
probably have a combination of pure virtual methods to enforce the polymorphic
interface, virtual methods to provide a standard interface as well as a default
implementation for all 搒imple objects", and non-virtual methods to provide
required implementation of stuff that is common to all objects and assumed to be
constant in the common code.
Note that containment forces you to use the contained object抯 public interface,
whereas inheritance allows use of protected members also.  This is another way
of saying that containment is more encapsulated than inheritance.  In fact, it
is often said that inheritance breaks encapsulation because it can create
dependencies on the implementation of the base class.  This is particularly true
in the case of overridden functions, where a change to the base class might not
have the right effect on all derived classes.
Summary:
· Be careful with inheritance vs. containment.  When in doubt, use containment.
· Inheritance is an 搃s a" relationship, whereas containment is a 揾as a"
relationship.
· Test for 搃s a" by seeing if polymorphism is desired or makes sense.
· Test for 揾as a" by asking yourself if one class could ever use more than one
of the other class.
2.9.3 Multiple Inheritance
We will avoid multiple inheritance altogether.  Multiple inheritance has a
number of problems including resolution of name conflicts, efficiency concerns
of some operations (functionality is hidden from you), maintenance problems, and
general confusion about what the heck is going on.
If you are building a large and complex inheritance hierarchy (to be avoided as
noted above), you might find yourself wanting multiple inheritance to share code
from two different places. In the case of literally sharing code from two
different places, this is the most dangerous form of multiple inheritance
because it leads to the trickiest dependencies.  There are other forms of
multiple inheritance, though.  The safest is multiple inheritance of only
interfaces (no code from any base class), but even this has problems with things
like name conflicts.  So, we will avoid it altogether.
Every time you think you need multiple inheritance, you should consider that
maybe you are over-using inheritance and you should switch to containment in
some cases.  Inheritance is a silver bullet that you have only one of.  Once
you抳e used it for a given class, you need to use containment to get anything
else.  Note that you can use containment as much as you want within a given
class with no problems.
Summary:
· Don抰 use multiple inheritance.
· Given only single inheritance, inheritance is a 搒ilver bullet" which you
have only one of, so use it sparingly and judiciously.
3. Other C++ Features
The following sections comment on various new features of C++ that aren抰
directly related to classes.
3.1 Constants and Enumerations
C++ adds the concept of true constants to C.  In C, you had the choice of using
a #define or declaring a "const" global variable.  However, the #define will not
be type safe, and the const variable takes up real memory and isn't optimized.
For example:
// C alternatives:
#define dxMin  0                // not type safe
const DX dxMin = 0;     // just a real global variable

In C++, the const syntax declares a real constant of the specified type that the
compiler will type-check, and then substitute the actual value in-line and
optimize (fold constants, etc).  As a bonus, the debugger will even know about
this symbol.  For example,
// C++ solution:
const DX dxMin = 0;     // type safe, optimized, and debug symbol

So true C++ constants are preferred to the traditional C++ #define.  Note that
they cannot be used in shared C/C++ header files, though, because a C compiler
will just allocate memory for them.
C++ also makes the existing C concept of an enum type safe.  An enum in C++
defines a type and declares constants of that type.  You can then use that type
as, say, a parameter to a function, and the compiler can then enforce that you
pass one of the symbols defined in the enumeration (or you can type cast to get
around this if you need to).  An enum can also be made local to a class so that
its scope is limited.  For example,
class Foo
        {
public:
        enum GMODE { gmNo = 0, gmYes = 1, gmMaybe = -1 };
        void InsertGNode(GMODE gm);
        };

Summary:
· Use const or enum instead of #define for constants that are only used in C++.
3.2 References
C++ adds the ability to express references to objects, and the primary use of
them is to pass classes as parameters without the overhead of the copy
constructor being called.  This is a worthy goal, but a more straightforward
method to do this is to just to pass classes by pointer, which is what we抮e all
used to from C.  For someone used to C, seeing something being passed by
reference looks like it抯 being passed by value, so you might wonder if the
constructor is being called, or whatever.  Furthermore, when using a reference,
the illusion is that you have a local copy of the object that you can reference
cheaply, but in fact you just have a pointer to the object (this is how the
compiler does it), and every access is an indirection.  We should just make this
indirection explicit by actually using pointers.  Typing "*" or "->" instead of
"." is not a big deal, and it makes it more clear what is going on.  The one
real advantage of references over pointers is that they are guaranteed to be
initialized (they cannot be NULL or point to garbage).  But this advantage is
not worth the above problems for us.
Also note that when you do pass objects by pointer, use const to mark formal
parameters that are read-only (see the "Const" section below).  This is related
to references because some C++ programmers will use the convention that
read-only objects are passed by references and other objects are passed by
pointer (to make this safe you still need to the declare the reference const
because C++ will let you change a parameter through a reference).  This is a
reasonable convention, but it still has the problem of looking foreign and
confusing to programmers with a C background.  So, we will use const to get the
safety but pass every object by pointer.
There are other more exotic uses of references, such as being able to return an
lvalue from an operator function, and sometimes this is necessary if you've
defined such an operator.  But since we don抰 plan to use operators much if at
all (because we won抰 use stack-based classes), we should be able to avoid
references in most cases.
Summary:
· Avoid references.  Pass objects that are larger than an 搃nt" by pointer.
3.3 Const Parameters and Functions
As mentioned above, you should use const to mark formal parameters that are
read-only.  This allows the compiler to check that you actually obey this,
serves as a form of documentation to users of your function, and also allows the
compiler to produce better code in some cases.  For example:
/* Copy the contents of 'fooSrc' into 'fooDst'. */
void CopyFoo(const FOO *fooSrc, FOO *fooDst);

You can also declare non-pointer formal parameters as const (as well as the
actual pointer portion of a pointer parameter, rather than what it points at, in
which case the word "const" may appear twice for that parameter), but this is
not as much of a win and it can make the prototype harder to read, so it's
optional.  This just makes sure that you don't reuse the parameter itself as a
local variable and change its value.  Of course, sometimes a function will do
this as a way of avoiding declaring and setting up a local variable, so in this
case you can't use const (not that this is not great programming style, but
we're not going to disallow it).  On the other hand, if you don't change the
value of the parameter within the function, declaring it as const may allow the
compiler to generate better code.  Note that doing this does not give any useful
documentation to the caller, though.  For example:
/* Copy 'cb' bytes of the contents of 'fooSrc' into 'fooDst'.
   In addition to not changing what 'fooSrc' points at, my implementation
   promises not to change the values of any of the local parameters
   within the function (like you care...). */
void CopyFooCb(const FOO *const fooSrc, FOO *const fooDst, const int cb);

In addition to declaring parameters const, you can also declare a member
function const to indicate that the function does not modify the object.  Again,
this allows compiler checks and possible optimization as well as a form of
documentation.  For example:
class Counter
        {
public:
        int CItems() const { return m_cItems; }
        void SetCItems(int cItems) { m_cItems = cItems; }
private:
        int m_cItems;
        };

Summary:
· Use const to mark read-only pointer parameters (what the pointer points at,
not the pointer itself).
· Use const to mark member functions that don't change the object
· Use const to mark parameters themselves only if you care about the possible
performance gains in the implementation.
3.4 Default Arguments
Having default arguments seems like a cool feature.  It seems like a way to add
a parameter which only some calls to a function will need to pass, so that the
simple cases will be kept simple.  Well unfortunately, there is no efficiency
gain here, and instead the compiler is just hiding work from you.  If you have a
function with one required argument and four optional arguments, every call to
this function will push all five arguments, so you are getting the code size and
time hit in every case.  Furthermore, you can抰 even use default arguments just
to try something new without bothering with the old calls because you still have
to find all the old calls in order to rebuild those files (if you do an
incremental build of just one use after adding a default argument, the other
calls will screw up).  Finally, default arguments can be easily confused with
overloading (which we抣l also avoid).
There are cases, however, where a certain parameter is totally irrelevant in a
call (because, for example, the value of another parameter tells you all you
need to know).  Note that this is somewhat different from a default argument
because there is no real default value for this parameter.  In these cases, it
is nice to have the untyped constant "NA" , #defined to be 0, to stand for "Not
applicable" which can be passed to indicate this for any actual parameter.  This
is better than passing, say, NULL for a pointer or FALSE for a Boolean because
it makes it clear that the value is not important at all.  For example:
#define NA  0           // universal "not applicable" parameter value

/* If fShow then show the object, else hide it.  If showing then
   redraw it only if fRedraw.
void Show(BOOL fShow, BOOL fRedraw);

void Ack()
{
        Show(TRUE, TRUE);
        ...
        Show(FALSE, NA);
}

Summary:
· Don't use default arguments.
· Use "NA" (#defined to be 0) for "not applicable" parameters in a call.
3.5 Function Overloading
Overloading functions is just a lazy naming scheme.  It seems like a form of
polymorphism, but it shouldn抰 be confused with real polymorphism because all
the decisions must be made staticly at compile time.  It抯 just a way to keep
搒imple" function names and reuse them for different cases.  Such a lazy naming
scheme just causes more confusion than it抯 worth (trying to determine which
function is relevant, changing the wrong one by accident, etc.) and can also
interfere with proper use of Hungarian in some cases.  Finally, the combination
of function overloading and type coercion can be quite confusing indeed.
Summary:
· Don抰 overload functions
3.6 Operator Overloading
The main use of operators is in classes.  This is discussed in a previous
section.  Operators can also be overloaded at global scope.  For example, you
can define what operator+ should do when it finds a Foo on the left side and a
Bar on the right side.  Unlike the use within a class, this allows control over
the left-hand side operand.  Anyway, all the same problems apply and more (due
to the larger scope). Functionality is hidden (a possible efficiency problem)
and confusion can result, so we will avoid this.
Summary:
· Don抰 overload operators, especially at global scope.
4. Common C/C++ Issues
The following sections comment of features of regular C that we also try to
maintain a consistent use of.
4.1 #ifdefs
First and foremost, everyone should try really hard to minimize the use of
#ifdefs.  Programs with lots of #ifdefs are really hard to read.  It is often
possible to either invent the right abstraction or to isolate the #ifdefs to
small places in header files, or make the right definitions in target-specific
headers so as to make #ifdefs unnecessary in the main code.  The main argument
for #ifdefs (over, say, a regular 搃f") is to minimize the code size.  However,
everyone should be aware that the optimizer is perfectly capable of simplifying
statements that evaluate to a constant.  For example,
// Wrong:
#ifdef MAC
if (x == 3 || Foo() || FSomeMacMode())
#else
if (x == 3 || Foo())
#endif

// Right:
// In a header file for each non-Mac platform, there is
#define FSomeMacMode()  FALSE

// Then the using code can just be
if (x == 3 || Foo() || FSomeMacMode())

In this example, the compiler is perfectly capable of eliminating what amounts
to (搢| FALSE") at compile-time.  Furthermore, if the entire "if" were to always
evaluate to FALSE at compile time, then the optimizer will also remove all of
the code inside the "if" and remove the test.
If you must use an #ifdef, we prefer to use #if instead because it's shorter and
allows logical operations, as in:
int Foo()
{
        int x = 3
        #if MAC && !DEBUG
                x = 0;
        #endif
        return x;
}

Note that we will still leave flags such as DEBUG undefined when false, but the
compiler does the right thing here (treats it the same as being defined to be
0).  Leaving these flags undefined means that #ifdef will also work, in case
this is used by accident anywhere.
Also, as this example shows, #ifs should be properly indented so that they read
easily when nested.  Yes, this works;  C Compilers have accepted this for years.
Aside from the standard identifiers defined by our build process (e.g. DEBUG,
MAC), we will also use the identifiers UNUSED and LATER, which are never
defined, to mark code which is currently unused but kept in for some reason, and
code which cannot be activated yet but will eventually, respectively.
Summary:
· Minimize #ifdefs by defining good abstractions, partitioning files better, or
defining appropriate constants or macros in header files for the compiler to
figure out.
· Prefer #if over #ifdef.
· Indent an #if with the code so that it reads better when nested within
others.
· Use #if UNUSED for code that is on longer used but kept in for some reason.
· Use #if LATER for code that cannot be used yet but will eventually.
4.2 Global Variables
Global variables are usually bad programming style.  More specifically, they are
often trouble when used to cache 揷urrent" state because it is so easy to get
them out of sync.  Everybody knows this from their past experience, so there抯
no point in going into gory detail.  In addition to these problems, globals make
things such as multiple uses of a DLL in the same process, reentrancy, and
multi-threading very hard to achieve, and we need to worry about all of these in
Office.
Due to the DLL/process/thread problem (all instances of the same DLL in a
process as well as all threads share the same copy of the globals), most things
that you might normally of as global should go in an instance data structure.
In Office, this structure is the MSOINST struct, which is allocated and returned
for each caller of MsoFInitOffice.
When you do use a global (e.g. for data which is truly shared among all uses in
a given process), use the Hungarian 搗" prefix before the type tag to make the
global clear.
Summary:
· Minimize global variables.  Most per-client read/write storage should go in
the MSOINST structure.
· Use the Hungarian 搗" prefix at the beginning of global variables.
4.3 Macros and Inline Functions
In C++, what would be a functional or procedural macro in C is usually better
expressed as an inline function because this makes it type safe and avoids
problems with multiple evaluation of macro parameters.  For example:
//Wrong
#define XOutset(x)  ((x) + 1)
#define XMin(x1, x2)  ((x1) < (x2) ? (x1) : (x2))

//Right
inline XY XOutset(XY x)  { return x + 1; }
inline XY XMin(XY x1, XY x2)  { return x1 < x2 ? x1 : x2; }

In addition, inline functions can be limited to the scope of a class if
appropriate in order to make them more specific.
Note that #define must still be used in public header files that are exported to
C clients.  Actually, some C compilers do implement inline functions (via
__inline), though, so this may be possible in C also if all of your relevant
compilers support it.
When you do use #define to write functional macros with arguments, be sure to
remember to enclose each instance of an argument in parentheses, and also the
entire expression if necessary, to avoid precedence problems.
Summary:
· Use inline functions instead of #define macros when possible.
· In functional macros, enclose all argument instances in parentheses as well
as the entire expression if necessary.
4.4     Optimization
Always know what code the compiler generates for your favorite constructs, and
what things typically get optimized.  Trying to hand-optimize your code can
cause bugs and can also actually make the generated code worse.  For example,
introducing temporary variables may cause something that would have been
pre-computed and cached in a register to be moved into a stack variable.  It is
worth it for everyone to play around with sample constructs, compile with /Fc,
and look at the .cod file to see what gets generated.  Finally, remember that
the expanded code you see when debugging is not the optimized code; it is
totally unoptimized for debugging purposes (e.g. to make it line up exactly with
the source statements).
Another issue to be aware of is that in order to get maximum benefit from our
optimizer, we want to enable lots of different kinds of optimization.  Some
coding constructs can confuse the optimizer and cause bugs (for example,
depending on subtle aliasing behavior).  The rule of thumb here is that the more
straightforward your code is, the better the optimizer will be able to improve
it and the less chance of an optimization bug.  This is always good practice
anyway, because it makes your code easier for people too.
Summary:
· Know what your compiler and optimizer produce.  Compile with /Fc and look at
the .cod files to see for yourself.  Don抰 make theoretical arguments about what
you think is generated, and don抰 base it on the debug (unoptimized) version.
Check it for yourself in a ship compile.
· Don抰 use strange coding techniques (e.g. depending on aliasing) that confuse
the optimizer.
4.5 Warnings
We will compile our code at warning level 3, and all code should be free of
warnings.  If you absolutely have to code something that generates a warning,
disable the warning with a #pragma.  We may disable some that we don抰 like
globally (e.g. unused parameter).
Summary:
· Make your code warning level 3 clean.
4.6 Private Data and Functions
Data and functions that are local to a module or class should be kept as such as
not exported from the module or class.  This prevents name conflicts (and
therefore allows for shorter, simpler names), and also makes the linker do less
work, which speeds up the builds.  In C, data and functions that are local to a
module should be marked with 搒tatic".  In C++, many 搈odule" local data and
functions can be encapsulated in classes.  In this case, class static data
should be declared with static and the s_name naming convention.  Local
functions should just be member functions if they logically operate on an
instance of the object.
Summary:
· Prototypes for module-local data and functions in C files are marked with
搒tatic".
· In C++, module-specific data can often be encapsulated in classes with
class-static data and member functions.
4.7 Typedefs
In C++, a struct or class tag defines a new type automatically, so don抰
explicitly typedef it.
Don抰 use typedefs to define pointers to other types.  Just use the normal '*'
notation itself.  This keeps the Hungarian simpler and makes the pointer
explicit.
We will use 搃nt" for integer data whose exact size is not important in memory
(we will assume an 搃nt" is at least 32 bits).  However, you should use typedefs
to define types for data where the size is important because it is being saved
to disk or packed in a tight data structure.
Summary:
· Don抰 typedef classes or structs in C++
· Don抰 define typedefs for pointers to other types, just use the '*' notation.
· Use 搃nt" for 搉atural sized" machine integers (assume at least 32 bits), but
use typedefs for size-sensitive data.
4.8 Basic Data Types
Use the basic data types provided by the language, by Windows, and by OLE where
appropriate.  Try not to define special basic types such as 揑nteger" or
揃oolean" that are redundant (and possibly different than) the basic system
types, unless there is a good reason to.  Win32 is our API, so we don抰 need
損ortability" abstractions on top of this.  Defining our own types also makes it
harder for client apps to integrate our header files and APIs (they have to
figure our what each one is, and there is the potential for name conflicts with
their definitions).
Following is a list of the most common basic types that we will use, along with
the default Hungarian tag.  Note that the actual hungarian tag used will usually
depend on the usage (see Appendix A).
Type    Defined by      Meaning Default Tag
void    C++     No type v
int     C++     Signed natural machine-size integer, at least 32 bits   (none)
BOOL    Win32   Natural machine-sized integer holding TRUE or FALSE.    f
UINT    Win32   Unsigned natural machine-size integer, at least 32 bits (none)
BYTE    Win32   Unsigned 8-bit value    b
CHAR    Win32   Signed 8-bit value (usually used for ANSI characters)   ch
WCHAR   Win32   16-bit (wide) character used for UNICODE characters     wch
USHORT, WORD    Win32   Unsigned 16-bit value.  USHORT is preferred when the
type is
a number.       w
SHORT   Win32   Signed 16-bit value     w
ULONG, DWORD    Win32   Unsigned 32-bit value.  ULONG is preferred when the
type is a
number. u, dw
LONG    Win32   Signed 32-bit value     l
HRESULT OLE     OLE general purpose error/result code   hr

For bitfields, the type given to each field will depend on how the struct and
bitfields are used.  Usually a bitfield will be used in a memory-only data
structure because this eliminates concerns over bit-ordering in a file format.
If you usage is in memory and you don't care about the exact size of the
bitfield, each field can simply be declared as "int".  UINT can also be used for
multi-bit fields that are to be interpreted as unsigned integers.  The most
common type BOOL should be declared as BOOL (which the same as int) for
increased readability.  If you care about the total size of the bitfield, then
you could take advantage of the Microsoft C extension and declare each field as
a sized-type such as WORD.  For example:
// A typical bitfield
struct foo
        {
        BOOL fVisible   :1;
        BOOL fEnabled   :1;
        BOOL fPushed    :1;
        int     unused  :13;            // used to ensure reasonable alignment
        int iBar                :16;
        };

// A size-sensitive bitfield
struct bar
        {
        WORD fVisible   :1;
        WORD fEnabled   :1;
        WORD fPushed    :1;
        WORD unused     :13;            // to help enfore proper size
        };

Summary:
· Use the basic types provided by C/C++, Windows, and OLE.  See the table
above.
· Use "BOOL", "int", or "UINT" in most bitfields, unless size is important,
then use a size-specific type such as WORD for all fields.  Use "unused" fields
to force alignment or to help enforce a specified size.
4.9 Pointers
We抮e 32-bit flat everywhere now (finally!), so use just plain '*' everywhere.
Don抰 use 搉ear", 揻ar", or 揾uge".
Summary:
· Use flat 32-bit pointers everywhere (declared using just plain "*").
4.10 Switch Statements
Some switch statements can be avoided by using classes and virtual functions in
C++, and often the result will be smaller and faster as well as simpler.  A
switch statement can take a long time to find its target (depending on the
values involved, the compiler may not generate a good lookup table).
When you do use a switch statement, cases that do something and then fall
through to the next case should be explicitly marked with a "// Fall through"
comment, since this is a common source of mis-read code.
Summary:
· Consider using classes and virtual functions instead of switch statements in
some cases.
· Switch cases that do something and then fall through to the next case should
be explicitly marked with a "// Fall through" comment.
4.11 Asserts
Use plenty of asserts to verify correct program operation.  It抯 better to catch
errors early.  We will have a smart assert layer that allows you to break,
continue, or cancel all asserts and continue.
There are two kinds of asserts we will use.  The first, named 揂ssert",
evaluates its expression only in DEBUG compiles, so don抰 put any needed
side-effects inside!  The second, named "AssertDo", always evaluates its
expression, but only checks the final condition and possibly generates an error
in DEBUG builds.  Note that the 揷ompare" portion of an AssertDo statement, if
any, will be removed by the optimizer.  For example.
Assert(hwnd != NULL);
Assert(HwndTop() == hwnd);      // don't want HwndTop() call in ship
AssertDo(FInitFooBar());                // must init in ship too!
AssertDo(pObj->Release() == 1); // release and verify ref count

Summary:
· Use Assert for debug-only checks, use AssertDo to execute always but check
only in debug.
4.12 Errors and Exceptions
There are two basic ways to handle unexpected errors (e.g. memory allocation
failure) in code.  One is to return failure codes and check them (bubbling them
back to the caller as necessary), and another is to catch and throw exceptions.
Exceptions can be useful and more efficient in some cases, but they can also be
harder to understand and cause unexpected results.  The root of the whole
problem is that you often can抰 tell what exception handling code will do by
just looking at it.  There may be places where we will use exceptions (unclear
at this point whether it will be C++ exceptions or something else we create),
but the default should be to use normal return codes in order to make the code
more straightforward.  In any case, it is not possible to throw an exception
over a DLL boundary, so all DLL-exported APIs or methods that can fail must
return error codes.
When doing many things in a row that can fail, it is perfectly reasonable to use
a goto to direct all the return code-based functions to a common error handler
(which recovers in a robust way based on consistent initialization of the data.
This is probably the best use of a goto in C/C++.  For example
{
        BOOL fRet = FALSE;
        FOO *pfoo = NULL;
        BAR *pbar = NULL;
        ACK *pack = NULL;

        if (!FCreateFoo(&pfoo))
                goto Error;
        //...
        if (!FCreateBar(&pbar))
                goto Error;
        //...
        if (!FCreateAck(&pack))
                goto Error;

        // Use data in normal way...
        fRet = TRUE;    // Success return flag

Error:
        /* This is common error and success cleanup code. */
        if (pack != NULL)
                DeleteAck(pack);
        if (pbar != NULL)
                DeleteBar(pbar);
        if (pfoo != NULL)
                DeleteFoo(pfoo);
        return fRet;
}

A convention we will use for functions that return pointers and can fail (e.g.
because the object is being created and returned) is that the function will
return a BOOL, and return the pointer in a parameter.  This makes explicit the
fact that the function can fail and prevents errors where NULL is returned and
indirected without checking by a client.  To contrast, pointer-fetching
functions that cannot fail can return the pointer directly.  For example,
BOOL FCreateFoo(FOO *pfoo);             // can fail
FOO *PfooGet(BAR *pbar);                // get a FOO from a BAR, cannot fail

Summary:
· Prefer error code return values to exception handling in most cases, for
simplicity.
· Always use error codes from DLL-exported APIs or methods.
· Use goto to implement common cleanup code when using return values in complex
cases.
· Functions that return pointers but can fail should return a BOOL and return
the pointer in a parameter instead of returning NULL to indicate failure.
5. Formatting Conventions
The following sections describe various formatting conventions that we will use
to produce common-looking code for easy readability throughout the group.
5.1 Naming Conventions
We will use the Microsoft Hungarian naming conventions for identifiers (see
Appendix A below).  We will also keep an alphabetized text file reference to
Hungarian tags that we invent ourselves and keep this checked into the project.
Whenever you add a Hungarian tag, add it to this file.  It may also be helpful
to show the suggested hungarian naming at the declaration of a class or type for
variables of that type, but this is not required.
When using Hungarian, remember to preserve abstraction where appropriate.  Don抰
say that something is a pointer to an integer if you really want to say it抯 a
pointer to, say, a 揷ell index".  Invent abstract tags where appropriate and use
them consistently.  For real global variables, use the Hungarian 憊' prefix
before the type tag to make the global nature clear.
In addition to the Hungarian convention, we will add a standard prefix "Mso"
(Microsoft Office) to most identifers that we export from our DLLs. This
provides a convenient way for the apps to tell where this function comes from
and prevents name conflicts with symbols they already define. The only
exceptions are for interface member function names and parameter names, because
these don抰 need their scope limited.  This 揗so" prefix goes before the normal
Hungarian tag, and the capitalization depends on the type of identifier (for
example, 揗so" for a function, 揗SO" for a typedef, and 搈so" for a constant).
Variable and parameter names derived from types prefixed with 揗SO" do not
usually include 搈so" in the name, however, because it is not usually necessary
(except perhaps to disambiguate special cases) and clutters the names, and as a
result of this uses of the hungarian tag for a type within a function name does
not include 搈so" in front of that type either (see examples below).
COM interface names are prefixed with 揑Mso".  Variables that hold interface
pointers are named with 損i" for 損ointer to interface", then a short
abbreviation of the interface name (using the first letter of each word is good
if this will work).  The variable does not include 搈so" (see examples below).
The following examples show this prefix for various kinds of identifiers:
void *MsoPvAlloc(int cb);       // exported API has Mso
MSOTBCR tbcr;                           // exported type has MSO, but variable
doesn抰
#define msotbbsPushed 1 // enum/bit constant has mso and another tag
#define msoCUsersMax  99        // plain constant has mso then hungarian
IMsoToolbarSet *pits;           // interface name has IMso, variable doesn抰
pits->FUpdate();                        // interface member has plain Hungarian
pit->PitsGetToolbarSet();       // member returning IMsoToolbarSet

One additional note about typedefs is that they should be minimized in exported
headers.  When possible, use already-defined types from Windows such as BOOL and
RECT (see the Basic Data Types section).  Also, do not define types for simple
enumerations -- just use 搃nt" as the type and #define constants.  The constants
should have a meaningful tag in addition to the 搈so" prefix, though (e.g.
搈sotbbsPushed").
For C++ class data members (internally in our implementation), we will use the
m_name convention because this makes it easier to distinguish members from
locals and allows consistent use of Hungarian (the name portion is the normal
Hungarian name and often matches parameter or local names).  A modification of
this rule is that for class static members, use a name of the form s_name in
order to make the static (global) nature clear.
Summary:
· Use Hungarian naming conventions.  Invent Hungarian tags and use them
consistently where appropriate to preserve abstraction.  Add all new tags to the
checked-in Hungarian tag reference file.
· Use the 憊' prefix for global variables.
· Use the 揗so" prefix before a Hungarian name when the identifer is exported
to clients in a header file at global scope, as follows:
        1.      Exported API functions use 揗so" (but interface member
functions don抰)
        2.      Typdefs use 揗SO" (but variables and parameters of that type
don抰)
        3.      Constants use 搈so" in addition to any enum/bit tag they may
have.
        4.      COM-style interface names use 揑Mso".  Interface pointer
variables use 損i"
(no 搈so"),
                and then a short abbreviation of the interface name (such as
the first letter
of each word).
· Minimize use of typedefs in exported headers.  Use basic types when possible,
and 搃nt" for enumerations.
· Use m_name for C++ class member data, and s_name for static class data.
5.2 Function Prototypes
Always declare full function prototypes so that you get good type checking and
warning level 3 clean code.  Put the names and types of the parameters inside
the argument list in both this declaration and the definition, because this
makes it particularly easy to keep these two in sync.  All function prototypes
should also include a comment before the prototype that describes what the
function does, how it uses its parameters and what it returns.  This is best
done in a comment that describes this in complete sentences and referencing each
parameter by name (use single quotes around the name to make it easier to read
if necessary), rather than a big block comment with a line for each parameter,
because these never seem to be kept up to date as well.  For example,
/* Return the index of the foo element at position (x, y) in the
   fooset at 'pfs', or 0 if no element is found.  If more than one
   is found, return the index of the most recently created one. */
int IFooFromXY(const FOOSET *pfs, int x, int y);

An exception to the normal prototype comment is for the declaration of
overridden virtual functions in C++ classes.  The declaration of the override
does not have to repeat the whole interface contract, but it should mention that
it is overriding a base method (see Class Declarations below).  An additional
point here is that, although the overridden virtual function will automatically
be made virtual, the virtual keyword is specified again anyway for clarity.
For exported functions, the prototype will appear in a header (.h) file.  For
internal functions, they can appear at the top of the implementation (.c or
.cpp) file, or in an internal interface (.i) file (see below under Source File
Organization).
Summary:
· Declare full function prototypes with argument names and types.
· Define function implementation using argument names and types directly in the
argument list to match the prototype (it抯 exactly the same minus the
semicolon).
· Precede each function prototype with a comment describing the function
(operation, parameters, return type, side effects, etc.) in complete sentences.
Mention each parameter by name somewhere in the comment.
· The declaration of an overridden virtual function in a derived class does not
have to repeat the whole interface comment, but should mention that it is
overriding a base method.
· An overridden virtual method is explicitly declared virtual.
5.3 Variable Declarations
Variables can be declared either C-style (at the top of a function or block) or
C++ style (in with the code).  The C style is preferred for variables that are
used throughout the function or used for more than one thing in the function.
The C++ style is preferred for temporary variables or those limited in scope.
When using the C++ style, always provide an initialization for the variable.
For the C style, it is optional, but if you do provide one, make it something
simple and fast such as just assigning to FALSE or NULL.  Don抰 do anything
complex or expensive because code at a C-style declaration is sometimes
overlooked when reading the code.
Variables should be declared one per line, to prevent bugs with pointers,
arrays, and initializers doing the wrong thing by accident.  Exceptions can be
made in extremely simple cases such as int x, y;
Summary:
· Declare and initialize variables C-style (top of block) if used throughout
the function or for more then one thing, use C++ style (declare and initialize
at first use) for temporary and limited-scope variables.
· Don抰 do anything expensive in a C-style variable initialization
· Declare variables one per line, except in extremely simple cases such as int
x, y;
5.4 Class Declarations
A class declaration should provide prototypes with comments for function members
and comments for data members.  Function members that are overriding those in a
base class which are declared and commented elsewhere in our code or in a
standard place such as OLE need not have a duplicate comment, but they should be
grouped into sections with a comment stating where they came from. The
constructor(s) and destructor should be declared first, followed by any
overridden inherited methods, followed by other methods.  The constructor(s) and
destructor do not require comments.  For example,
// The Ellipse object implmements an Ellipical drawing object
class Ellipse: public DrawObj
        {
public:
        Ellipse();
        virtual ~Ellipse();

        // DrawObj methods
        virtual BOOL FHitTest(int x, int y);
        virtual void Draw();

        /* Return the x radius of the ellipse. */
        int XRadius();

        /* Return the y radius of the ellipse. */
        int YRadius();

        /* Set the shading parameters for the ellipse to use shading type
           'st' and color slopes 'xSlope' and 'ySlope' in the x and y
           directions, respectively. */
        void SetShading(ShadeType st, int xSlope, int ySlope);

private:
        RECT m_rBounds; // bounding rectangle of ellipse
        SHADING sd;             // the shading parameters for 3D effects
        };
5.5 Comments
Contrary to some old Microsoft notions, comments are not bad.  Comments that are
out of date are bad, but they only get that way because the programmers didn抰
think maintaining them was important in the first place.  Comments are part of
the code, so maintain them.  Comments can appear at several levels and serve
different purposes.  Some serve to explain the contract of an interface (e.g. in
a header file). Others serve to summarize the following code so that it can be
read and understood faster.  And some just explain a strange construct or the
reason for doing something. All of these types are important.  The following
sections give examples of some different kinds of comments.
Summary:
· Write comments both to define interface contracts and to summarize and
explain implementation.
· Keep comments up to date.  They are part of the code.
5.5.1 File Headers and Section Separators
Each file has a header at the top that specifies the name of the file, the
owner, a copyright, and an explanation of the contents.  The header is done with
a banner comment of asterisk characters, for example:
/***********************************************************************
   Foo.c

   Owner: DavePa
   Copyright (c) 1994 Microsoft Corporation

   The implementation of the Foo object
***********************************************************************/

In addition, to separate major parts of a file, banner comments of asterisk
characters with an explanation of the following section are used.  These bars
can also include the email name of the primary owner of that section of code.
For example:
/***********************************************************************
   The Foo object
************************************************************** DAVEPA */

void Foo::Hey()
{
}

void Foo::Ack()
{
}
...


/***********************************************************************
   The Bar object
************************************************************** DAVEPA */

void Bar::Glorp()
{
}

void Bar::Blob()
{
}
...

Summary:
· Each file starts with a file header comment made with a banner of asterisk
characters.  The header includes the filename, owner, Microsoft copyright, and
an explanation of the contents.
· Separate major parts of a file with banner comments of asterisk characters
with an explanation of the following section.
5.5.2 Function Headers
In addition to the comment at a function prototype in a header file (see the
above section 揊unction Prototypes"), the definition of a function also has a
comment.  This can be exactly the same comment as in the interface file (this is
the usually the most convenient thing), but it may differ in that it may
reference some implementation-specific interactions.  This is a controversial
issue because of the resulting duplicated comment that needs to be maintained,
but this effort is worth it to get a clear description of the function in the
implementation itself, which is what you spend most of your time reading (and
the description in the header file is required when you are exporting the
interface to people who don抰 normally read your implementation code).
The comment at the function definition has a more pronounced banner form to help
visually separate the various function implementations.  The banner includes the
email name of the person who owns the function for easy reference.  We will have
editor macros to generate the standard function header comment templates.
The bars of the banner comment will be a series of '-' characters.  For example,
/*-----------------------------------------------------------------
   IFooFromXY

   Return the index of the foo element at position (x, y) in the
   fooset at 'pfs', or 0 if no element is found.  If more than one
   is found, return the index of the most recently created one.
-------------------------------------------------------- DAVEPA -*/
int IFooFromXY(const FOOSET *pfs, int x, int y)
{
        // ...
}

Summary:
· A function definition has a comment similar to (or identical to) the function
prototype comment.
· Function definition comments are marked with a distinctive banner using '-'
characters (see example above) which includes the name of the function, the
description, and the email name of the owner.
5.5.3 In-Code Comments
Comments that appear directly in the code are usually trying to make the code
easier to understand.  Summarize big operations by preceding them with a blank
line and a comment on a line (or more) by itself.  Explain strange code with
line-trailing comments or larger ones if necessary.  Feel free to reference RAID
bug numbers in a comment if the code is directly related to a bug and not
obviously correct otherwise.  Comments that take more than one line are often
easier to write using /*...*/ rather than //, but // is clearly easier and
better for end-of-line comments.  Single-line comments can go either way, but //
is preferred.  For example:
pTarget = PObjGetTarget();

/* Find the beginning of the sub-list where the target object and
   its children start. */
for (p = pFirst; p != NULL; p = p->next)
        {
        if (p == pTarget)
                break;
        if (p == pSpecial)  // bug 1234: pSpecial denotes gag mode
                {
                pTarget = pSpecial
                break;
                }
        }

// Process target node if found.
if (p != null)
        ...

Summary:
· Use in-code comments to summarize big sections of code and also to explain
weird code.
· Reference RAID bug numbers in comments that are totally specific to that bug
fix.
5.5.4 Attention Markers
It is useful to have standard markers in comments that call attention to
incomplete or wring code and can be searched for.  Use "// TODO:" to mark
incomplete code and "// BUG:" to mark wrong code. Follow each by an appropriate
comment.  Adding your email name to the comment is an optional addition that
makes it easier for others to see who added it.
Summary:
· Use "// TODO: comment" to mark incomplete code.
· Use "// BUG: comment" to mark wrong code.
· Adding your email name to the comment in an attention marker is an optional
addition that makes it easier for others to see who added it.
5.6 Misc. Formatting Conventions
Low-level details of code formatting such as where the spaces go aren抰 that
important, but a group that can standardize on these details saves time by being
able to read code faster, and no time is spent reformatting copied code to match
another environment.  Like Hungarian, less time spent thinking about details
that don抰 matter means more time to think about the things that do.
Code should be indented with tabs set at 3 space intervals (so that typing 3
spaces at the beginning of a line is equivalent to typing a tab).  This keeps
the files smaller than using spaces and makes some editing operations easier.
Ideally, you would also avoid using tabs after the first non-blank character on
a line (switch to spaces) to ensure alignment even when tabs change, but this is
difficult to maintain so it is not required.  Make sure your editor does not
change existing lines by replacing tabs with spaces or vice-versa, or deleting
trailing blanks because this makes bogus SLM diffs.  Line length should be kept
within 78 characters when possible so that horizontal scrolling is not required
in text editors that support 80 columns plus borders.  Blank lines should be
used before large code blocks (usually with a comment before the next block),
and two blank lines are used between function implementations.
Curly braces are done in the Word and Excel style (braces on their own line,
flush with the indented code), and spaces are placed as in English (e.g. space
after comma, no space after parentheses, space after keywords, space before and
after most operators).  Labels for switch statements line up with the switch
keyword.  These conventions are shown in the code below.
x = XGetFirst(p);
y = YGetFirst(p);

// Determine the z value for this coordinate
if (x == 0 && FBar(x, y + 1))
   z = -1
else
   {
   for (p = pFirst; p != NULL; p = p->next)
      {
      x = 3;
      if (FAck(p))
                z = 0;
      }
   x = 0;
   }

// Process according to the face type
switch (z)
        {
case 0:
        ft = ftNone;
        RemoveFace(p, x, y, z);
        break;
case -1:
        ft = ftReverse;
        FlipFace(p, x, y, z);
        break;
default:
        DrawFace(p, x, y ,z);
        break;
        }

Summary:
· Use a blank line between blocks of code separated by a summary comment.
· Try to keep line length to within 78 characters.
· Indent using 3 space tabs.
· Make sure your editor doesn't muck with blank characters on lines that you
didn't change.
· Curly braces go on their own line, flush with the indented code (like Word
and Excel).
· Labels for switch statements line up with the switch keyword.
· Place spaces as in English (after keywords and commas, not after open
parentheses, between many operators).
5.7 Source File Organization
The following sections describe a basic methodology for organizing source code
into different files based on interface and implementation.  The basic idea here
is to organize the files into modules where each module has its own interface
file.  This makes it easier to publish and learn the interface for a module and
has other benefits such as reducing checkin merges (the owner of a module is
much more likely to be the only one to change its interface file).
5.7.1 Public Interface Files
Public interface files are those exported to (and will be #included by) clients
who use a module.  The public interface contains constants, typedefs, function
prototypes, etc.  For Office, there may sometimes be two levels of public
interface.  An external public interface is exported to users of our DLLs and
must compile correctly for either C and C++ (meaning it must use all regular C
declarations or macros such as the OLE COM declarations in objbase.h that expand
to different things in C and C++).  Another type of public interface possible is
an internal public interface.  This would be a formal interface to a module for
use by other modules, but only within Office.  This interface could include C++
constructs such as class declarations.
Public interface files end in a .h extension.  This applies to both exported and
inter-Office interface files.  Exported header files will be put in an inc
subdirectory so that it's clear which ones are exported.  In addition, files
within Office that #include public headers will use #include <file.h> for
exported headers (as well as system headers), and #include "file.h" for
non-exported headers.
A public interface should not declare access to data structures that it doesn抰
have to.  Don抰 just put every declaration in the public header file.  The whole
point of an interface is to provide controlled and documented access to the
functionality.  On the other hand, an overly strict and controlled interface can
cause performance problems by introducing overhead.  Export what you have to in
order to make the interface simple and fast, but no more.  Inline functions can
often help you have your cake and eat it too in C++ interfaces when efficient
access to member data is of concern.
When efficiency is not as important in an interface, another worthy goal
(besides clarity) is to reduce compile dependencies.  By moving the declaration
of data members into an implementation file or internal interface and only
exporting a pointer type, you allow an easy incremental compile of the
implementation file when the data members change.  This sometimes sacrifices a
small amount of speed due to the extra level of indirection in the using
modules, but often this doesn抰 make any real difference.
Summary:
· Public interface files end in a .h extension.
· Exported public header files (#included by consuming applications) must
compile properly for both C and C++, and are put in the inc subdirectory.
· Use #include <file.h> for exported headers (as well as system headers), and
#include "file.h" for non-exported headers.
· Export what you have to in order to make the interface simple and fast, but
no more.
· Reduce compile dependencies by moving non-speed critical data declarations
into implementation files and only export an opaque pointer in the public
interface.
5.7.2 Private Interface Files
There are two common instances where it becomes necessary to factor out
declarations that are used by more than one implementation file but not exported
outside of the module to users.  One is when there is more than one .c or .cpp
file involved in the implementation, which is common for complex modules or
classes.  Another is when inheritance of classes is used in C++ to inherit the
implementation class of one module into another.  In these cases, the common
declarations should be factored out into an internal interface file.
Internal interface files end in a .i extension.  This is done in order to easily
distinguish internal and external interface files, and it also makes it easy to
use the same base name in both interfaces (e.g. foo.h and foo.i).  Internal
interface files always live in the normal source directory (along with the cpp
files) and are included with #include "file.i".
Summary:
· An internal interface file contains declarations that are common to multiple
.c or .cpp files implementing the same module or inheriting from the same base
implementation class.
· An internal interface file ends in a .i extension, lives in the cpp source
directory, and is included by #include "file.i".
5.7.3 Implementation Files
Implementation files contain the actual function bodies for global functions,
local functions, and class method functions.  An implementation file has the
extension .c or .cpp.  Note that an implementation file does not have to
implement an entire module.  It can be split up and #include a common internal
interface.  Also note that declarations that are not explicitly exported to
users of the module should be kept inside the implementation file itself for
increased encapsulation and fewer cases where you have to search for and/or fix
and recompile callers of a module when you change something.
Summary:
· Implementation files end in a .c or .cpp extension.
· Split large implementations into multiple files which all include a common
internal interface (.i) file.
· Keep declarations which don抰 have to be exported inside the implementation
file.
5.7.4 Base Filenames
The filename extension conventions are given above.  Then there抯 the question
of the base part of the filename.  First of all, the name should be 8 or fewer
characters and live up to all other DOS restrictions.  Even though we抮e using
NT for everything, some tools (e.g. SLM) have a DOS legacy and will not handle
long filenames.  As far as the name itself, we will use a two-character prefix
denoting the major feature team (e.g. 搕b" for Toolbars, 揹r for Drawing", etc.)
followed by some abbreviation of the functionality of the module.  Use the same
base filename for the implementation, public interface, and internal interface
(if any) when possible (e.g. tb.h, tb.i, tb.cpp).
Summary:
· Use at most 8 characters for the base filename, and obey other DOS
restrictions even on NT.
· Use a two-character prefix denoting the major feature team (e.g. 搕b" for
Toolbars, 揹r for Drawing", etc.) followed by some abbreviation of the
functionality of the module.
· Use the same base filename for the implementation, public interface, and
internal interface (if any) when possible (e.g. tb.h, tb.i, tb.cpp).
6. Interfaces to DLLs
It is possible but difficult and impractical to export classes from a DLL.  But
more importantly, we need to be able to deliver to C applications.  This means
that we must use a standard C external public interface even for implementations
that are in C++.  The following sections describe how to do this.
6.1 C Functions and Global variables
All function interfaces that go across the DLL boundary must be standard C
functions.  In C++, this is done by declaring the function as extern "C", which
prevents the normal 揹ecorated" function names.  If Office, we do this with the
MSOAPI macro, which is similar to the STDAPI macro on OLE抯 objbase.h.  MSOAPI
ensures both extern 揅" and the _stdcall calling convention. DLL-exported
functions that are part of a COM-style interface will be declared with the
MSOMETHOD and MSOMETHODIMP macros, which are just like OLE's STDMETHOD and
STDMETHODIMP macros, except that this explicitly indicates exporting from the
DLL.
Global variables also need to be declared as extern "C" when exported from the
DLL, but this should be rare if at all, because we don抰 plan to export global
data from our DLL.
Summary:
· Declare DLL-exported functions using MSOAPI if global, and MSOMETHOD(IMP)
inside COM interfaces.
· DLL-exported data should be avoided, but must use extern "C"  if needed.
6.2 Common C/C++ Public Header Files
Since some of our clients are using C (e.g. Word and Excel), and some are using
C++ (e.g. PowerPoint, Access, and Ren), our header files must compile properly
for both clients, and also produce consistent names and calling conventions.
This is normally done by using a block extern "C" declaration around the whole
file protected by #ifdef __cplusplus, as shown below.  Note that this is even
necessary for plain C header files because when #included by a C++ client, the
names will become decorated in the client
#ifdef __cplusplus
extern "C" {
#endif

void Foo(int x);
int Ibar(int y);
...

#ifdef __cplusplus
}
#endif

The OLE header file objbase.h also defines a number of macros that are
convenient for this purpose (especially when declaring COM object interfaces).
See objbase.h for details.
Finally, note that you can抰 declare anything using new C++ features in a shared
header file (e.g. classes)
Summary:
· Exported public header files need to work for both C and C++ clients.
· Use the #ifdef __cplusplus  extern "C" construct to declare an entire header
file as the C naming convention.
· Use the macros defined in OLE抯 objbase.h where appropriate to assist in
shared C/C++ declarations, especially when declaring COM interfaces.
· Don抰 use C++ specific declarations in a shared header file (unless
specifically protected and declared both ways via #ifdefs).
6.3 Lightweight COM Objects and ISimpleUnknown
For C++ fans it seems like a pain to have to export all functionality in
standard C form.  Furthermore, standard C function APIs are not a great way to
export object-oriented functionality.  And in addition, exposing lots of
separate functions makes the DLL load more expensive due to the larger number of
dynamic fixups required.  Fortunately,  there is a pretty good solution to all
of these problems.  That solution is part of what the OLE COM (Component Object
Model) specifies.
Object-oriented functionality lends itself to using object pointers and vtables
inside the object.  Since most of the functions are put in the vtable, all you
need global APIs for is basic creation functions to get the object in the first
place.  In fact, some creations can be done from methods on other objects, so
this reduces the need even for global APIs further.  The OLE COM model specifies
a standard way to define objects and vtables so that the declarations and
implementations can be easily mixed and matched between C and C++.  The OLE
header file objbase.h defines a number of macros that make it easy to declare
object interfaces (and also just functions and data) in a common header file
that can be shared by C and C++.  See objbase.h for details.
The OLE COM model defines more than just C/C++ object/vtable interactions,
however.  The true COM model also includes support for multiple interfaces per
object and dynamic interface negotiation (QueryInterface), multiple references
to objects via reference counts (AddRef and Release), and also external creation
of objects (with automatic loading of the DLL if necessary) via the system
registry (CoCreateInstance).  Not all of these will be of interest to parts of
Office which are just trying to implement and declare a standard object-oriented
piece of functionality, however.  In particular, support for creation via the
registry is often not desired because it is much slower then a creation API, and
reference counts are often not desired for simple objects.  If you take COM and
remove the registry creation support, you have what I call 搇ightweight COM" or
just an 揑Unknown object".  if you go further and remove reference counts, then
you have what we call an 揑SimpleUnknown" object.  ISimpleUnknown is a base
interface that has only QueryInterface (no AddRef or Release) and no
reference-counting semantics:
/**************************************************************************
        The ISimpleUnknown interface is a variant on IUnknown which supports
        QueryInterface but not reference counts.  All objects of this type
        are owned by their primary user and freed in an object-specific way.
        Objects are allowed to extend themselves by supporting other interfaces
        (or other versions of the primary interface), but these interfaces
        cannot be freed without the knowledge and cooperation of the object's
        owner.  Hey, it's just like a good old fashioned data structure except
        now you can extend the interfaces.
**************************************************************** DAVEPA **/

#undef  INTERFACE
#define INTERFACE  ISimpleUnknown

DECLARE_INTERFACE(ISimpleUnknown)
{
        /* ISimpleUnknown's QueryInterface has the same semantics as the one
           in IUnknown, except that QI(IUnknown) succeeds if and only if the
           object also supports any real IUnknown interfaces,
           QI(ISimpleUnknown) always succeeds, and there is no implicit AddRef
           when an non-IUnknown-derived interface is requested.  If an object
           supports both IUnknown-derived and ISimpleUnknown-derived
           interfaces, then it must implement a reference count, but all active
           ISimpleUnknown-derived interfaces count as a single reference
           count. */
        MSOMETHOD(QueryInterface) (THIS_ REFIID riid, void **ppvObj) PURE;
};

An example of some simple IUnknown objects with a C++ implementation and shared
C/C++ interface files is in \\daddev\filesys\usr\davepa\com\*.*.  See the
README.TXT file for information.  There are plenty of examples of ISimpleUnknown
objects in the Office '96 code (e.g. the Toolbar objects).  There are some
general points about COM interfaces that should be stated, though, and these are
summarized below.
First, it is more efficient and also easier to understand if you make fewer,
more complete interfaces rather than using lots of small ones. There are 4 bytes
of overhead per object for each interface supported, and this can get out of
control quickly if lots of small interfaces are used.  For example, an OCX
object has 20 interfaces and therefore 80 bytes of overhead per object just for
vtable pointers!  Ideally, an object will have only one interface.  This makes
the implementation much cleaner, as shown in the example implementation
referenced above.  The "reuse of interface" gained by using lots of small,
standard interfaces is not a big benefit unless explicit polymorphism is desired
between your object and others via one of the interfaces.  There are cases when
having only one interface just doesn't make sense, however, such as when
polymorphism is desired based on one interface but the object still has other
functionality needed by other objects.  Sometimes it is possible to handle this
by implementing a set of objects connected by internal interfaces (or friend
classes), but this may be less efficient and more complex to arrange.
Second, use a C++ class to implement a COM object and use inheritance of
interface from an interface class to get the interface right.  When more than
one interface is implemented by a single object, use nested classes instead of
multiple inheritance in the implementation class.  Although the multiple
inheritance declaration is shorter, you have to do special work to resolve
naming conflicts between interfaces (which can be common when interfaces are
versioned), it's not possible to have interface-level reference counts, which
are useful for debugging and can make the release process more efficient, and
there is simply general confusion about multiple inheritance (how it works and
what it's doing for you).  As stated earlier, we want to avoid multiple
inheritance altogether.  Also, an implementation using nested classes can be as
efficient as desired, especially when an inline function is defined to compute
the backpointer instead of an explicit backpointer, as shown in the example
implementation.
Finally, use IUnknown-based COM object only if you want reference counts and
ISimpleUnknown-based objects (see below) if you don抰 want reference counts.  Be
very careful with reference counts if you use them.  Reference counts are the
root of much evil because they are easy to get wrong and cause bugs that are
hard to track down.  Some reference count bugs are crashing bugs (serious), and
some are memory leaks (hard to find).
Summary:
· A COM object is a convenient way to export object-oriented functionality from
a DLL to both C and C++ clients at the same time.
· A "lightweight COM" or just an 揑Unknown" object is a COM object that does
not support CoCreateInstance.
· Use a C++ class to implement a COM object, and use inheritance of interface
from an interface class to get the interface right.
· Use a small number of interfaces that are generally complete rather than
using a bunch of small ones.  Ideally, an object would have only one interface.
· When a COM object does support multiple interfaces, use nested classes in the
implementation object to implement it rather than multiple inheritance.
· Use ISimpleUnknown instead of IUnknown if you don抰 need reference counts.
Be very careful with reference counts if you do use them.
· Study the sample implementation of IUnknown-based COM objects in C++ in
\\daddev\filesys\usr\davepa\com\*.*.  There are plenty of examples of
ISimpleUnknown-based objects in the Office '96 code (e.g. the Toolbar objects).
 7. Appendix A: Basic Hungarian Reference
This section gives a brief summary of the basic Hungarian naming elements that
all implementations will use.  The Office project itself will invent abstract
type tags where necessary and keep a record of these in a text file checked into
the project.  The rationale and exact mechanism for Hungarian naming is not
described here.  See the vintage 1988 document HGARIAN.DOC by Doug Klunder, or
read chapter 2 of Charles Simonyi's even finer vintage "Meta-Programming" thesis
if interested.
7.1 Making Hungarian Names
A general Hungarian name is formed by appending together one or more prefixes, a
base tag, and a qualifier.  The base tag indicates the type of the variable
(e.g. "co" for a color), the prefixes modify that type (e.g. "rg" for an array,
giving "rgco" for an array of colors), and the qualifier describes the use of
this particular type (e.g. "rgcoGray", for an array of colors used as grays).
Not all of these are used for all names.  A prefix is often not relevant, and
the qualifier can be omitted if the use is obvious, such as when there is only
one of the relevant type involved in the code.
It is important to note that many (perhaps most) base tags and qualifiers will
be application-specific because they are denoting application-defined types and
uses.  There are various standard tags for basic types (several are given
below), but it is a mistake to use these when the abstract type is more
appropriate.  For example, if a color happened to be implemented as a long, then
one might say "rgl" for an array of colors, but this breaks the abstraction of a
color.
When Hungarian is used for function names, the qualifier usually just describes
the action of the function.  After the basic qualifier, it is sometimes useful
to describe the parameters to the function by appending Hungarian types for
them.  And, of course, the first letter of a function name is always
capitalized.  For example, "FInitFooFromHwndXY" might be the name of a function
which initializes a "foo" structure from parameters of type "Hwnd", "X", and
"Y", and returns a Boolean success code.  This is not a requirement, though.
Use it only when it makes the name easier to understand.
7.2 Standard Base Tags
The following list gives standard base tags for basic types.  As stated above,
note that an application will define many of its own tags for its own internal
types.
f       A flag (BOOL).  The value should be TRUE or FALSE .  The qualifier
should
describe when the flag is TRUE, for example "fError" is true if an error
occurred.
ch      A one-byte character (a CHAR).
sz      A zero-terminated string of CHARs (a classic C string).  Sometimes a
maximum
length is specified, as in "sz255", which indicates that the actual string part
can have 255 characters, so that there must be 256 characters allocated.
st      A length-preceded string of CHARs.  This type of string is limited to
255
characters because the length must fit in a byte.  As with sz, the length might
be specified as in "st32", which would need to have 33 CHARs allocated in order
to hold the length and the specified length of real characters.
stz     A length-preceded string of CHARs that is also zero-terminated.  Note
that
if a length is specified, then there must be (length + 2) CHARs allocated.
chw, wt, wz, wtz        Wide-character (WCHAR) versions of ch, sz, st, and stz.
 Wide
strings (UNICODE) should be used for all user-visible strings.
fn      A function.  This is usually used with the "p" prefix (see below) to
make
"pfn" because in C you can only hold the address of a function in a variable.
b       A BYTE.
w       A 16-bit quantity (WORD, SHORT, or USHORT).
dw      A 32-bit unsigned double-word (DWORD).
l       A LONG (a signed 32-bit quantity).
u       An unsigned long (ULONG).  In classic Hungarian, this was an unsigned
word.
In Office, this will be an unsigned 32-bit quantity and is therefore the same as
"dw", but the "u" tag (and the ULONG type) is preferred when the type is a
number.
v       A void.  This is always used with the "p" prefix to make "pv", a
pointer to an
unknown type.
sc      An OLE SCODE.
hr      An OLE HRESULT.
var     An OLE VARIANT.
varg    An OLE VARIANTARG.
7.3 Standard Prefixes
The following list gives standard prefixes that are used to modify a base tag
type.  More than one prefix can be used.  It is possible, but probably unusual
for an application to define its own prefixes (typically an application will
define only base tags and qualifiers).
p       A pointer.  For example, "pch" for a pointer to a character.  In classic
Microsoft Hungarian, "p" meant a near pointer, and "lp" and/or "hp" were used
for long (far) pointer and huge pointer, respectively.  In a 32-bit world this
is no longer an issue.
rg      An array (comes from "range").  For example, "rgch" for an array of
characters.  As used in C/C++, this may be the name of an allocated array, or
just a pointer to an array.
i       An index into an array of the base type.  For example, "ich" for in
index into
an array of characters.
c       A count of items of the base type.  For example, "cch" for a count of
characters.
n       Another use for a count of items, (for "number of"), but "c" is
preferred.
d       A difference or delta between values of the base type.  For example,
"dx" for
a difference between two values of type x.
h       A handle.  An opaque reference to an item of the base type that cannot
be
indirected by the user (this definition has been loosened in the past due to a
somewhat different use for a movable memory block).  For example, "hwnd" is a
handle to a window ("wnd") that you are not allowed to indirect and access the
fields of because it's not in your address space (this also preserves the
abstraction of the opaque reference).  An application module might export a
handle to an abstract data type (typically declared as a void *) so that clients
can only use the reference and never see or know about the fields.  However, C++
makes his less necessary by due to the ability to export a pointer to a class
with private data members.
pl      A 損lex" of objects.  This is an alternative to a simple array (搑g")
that is
resizable in a standard way using the plex abstraction (see inc\msoalloc.h).
mp      An array used to map an index or other scalar to a value.  The index and
value tags are appended, as in "mpchdx" to map a character value (used as the
array index) to a dx value for that character.
v       A global variable.  When used, this is always the first prefix.

In addition, there are the following special prefixes that we will add before
any Hungarian prefix when appropriate:
m_      A C++ class data member.
s_      A C++ class static data member.
Mso     An exported global function.
MSO     An exported typedef
mso     An exported global constant or global variable.
7.4 Standard Qualifiers
Most qualifiers are defined by the situation where the name is used.  However,
there are a few standard ones, as given below.
First   The first item in a set, or the first one of interest (e.g. pchFirst)
Last    The last item in a set, or the last one of interest (e.g. pchLast).
When
used as an index, Last represents the last valid/desired value, so a loop might
read:
for (ich = ichFirst; ich <= ichLast; ich++)
Lim     The upper limit of elements in a set.  Unlike Last, Lim does not
represent a
valid value; Lim is one beyond the last valid value, so a loop might read:
for (ich = ichFirst; ich < ichLim; ich++)
Min     The minimum element in a set.  Similar to First, but usually represents
the
first valid value, not just the first one to be dealt with.
Max     The upper limit of elements in a set (same as Lim).  Unfortunately, a
normal
English reading of "Max" usually implies the last valid value, but the Max
qualifier is not a valid value; it is one beyond.  Like Lim, a typical use would
be:
for (ich = ichMin; ich < ichMax; ich++)
Be very careful with this one.
Mac     Like Max, but sometimes used when the "current" maximum can change over
time.  Note that Mac is also one beyond the "last" valid value.
Mic     Like Min, but sometimes used when the "current" minimum can change over
time.
T       A temporary value.  This qualifier is probably overused as a way to
avoid
coming up with a good new name for something, but sometimes a brief temporary is
OK, such as in a classic swap operation.
TT, T3, etc.    Further abuses of the T = temporary convention when more unique
names are needed.  These should be avoided.
Sav     A temporary value used to save a value so that it can be restored
later.
For example:
hwndSav = hwnd; ...; hwnd = hwndSav;
Null    The special 0 value, always equal to 0 but used for documentation
purposes
(e.g. hwndNull).
Nil     A special invalid value, not necessarily equal to 0 (might be -1, or
anything else).  To avoid confusion, it's best to not have both Null and Nil
values for the same type.
Src     The source of an operation, typically paired with Dest as the
destination,
as in:
*pchDest = *pchSrc
Dest    A destination.  See Src.


--
        there are these few times I feel lonely for you
     but I know this shade of blue will soon pass through
            I must be strong without you by my side
               I can see forever you are my light

※ 来源:.BBS 荔园晨风站 bbs.szu.edu.cn.[FROM: 192.168.10.250]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店