5,317,180 members and growing! (23,739 online)
Email Password   helpLost your password?
General Programming » Programming Tips » General     Advanced License: The Code Project Open License (CPOL)

C/C++ macros programming

By valdok

Sophisticated use of macros, never write things twice!
C++, C

Posted: 24 Apr 2008
Updated: 24 Apr 2008
Views: 5,470
Announcements
Want a new Job?



Search    
Advanced Search
Sitemap
7 votes for this Article.
Popularity: 2.85 Rating: 3.37 out of 5
1 vote, 14.3%
1
1 vote, 14.3%
2
1 vote, 14.3%
3
2 votes, 28.6%
4
2 votes, 28.6%
5
Note: This is an unedited contribution. If this article is inappropriate, needs attention or copies someone else's work without reference then please Report This Article

Introduction

All the C/C++ textbooks I've ever read criticize the use of macros. "Don't use them, they're dangerous because they conceal what you actually write. Especially function-looking macros."
Some even say there's no justification of using macros with the invention of C++ with its template classes.

Nevertheless macros are still used in some places.

For example - debug macros, such as ASSERT, VERIFY, TRACE, and etc. They're all function-looking macros which expand into different things under debug and release builds.

I know some people who never use those macros. Instead they just use functions that are implemented differently in debug and release builds. Why? Because they're afraid of the threat of using function-looking macros. That is, they prefer that the expression inside ASSERT is always evaluated, even in release build where its value is not used.
Well, this may be justified for some. Indeed placing some important code inside ASSERT (rather than VERIFY) is a pretty common error.

I personally always use macro-versions of those. That's because I use debug macros very heavily to get immediate indication when something goes wrong, whereas on the other side I don't want the final executable to come with all this crap.

Another example of widely used macros is related to Ansi/Unicode. This includes _T(), all the _tprintf-like macros. Also Windows API has many functions with either A or W sufix, such as GetMessageA, GetMessageW. That is, GetMessage is actually a macro, which expands into one of those depending on the build settings.

So that despite the criticism macros are still being used. One may argue if this is justified or not, but this is not the subject of the article. I want to show in this article really amazing things that can be done via sophisticated use of macros. Wether to use those techniques or not - the decision is up to you.

Communication protocol example

Suppose you have to implement some communication protocol. This protocol consists of 'messages' of different kinds, every message has its specific parameters.

Let's agree (for now) that every transferred message starts with its 4-byte size (thus restricting the largest message to order of 4GB), then comes its 2-byte code, and then come all its parameters which are message-dependent. Ordinal types (ULONG, UCHAR, USHORT, double, ...) are transferred as-is (without big/little endian conversion). Strings are transferred in the Unicode character set (Utf16), with preceding ULONG that specifies the length of the string in characters.

For beginning we want the following message types:

  1. Login. Contains the client version (ULONG), Username (string), Password (string).
  2. Login result. Contains the login result code (UCHAR). 0=ok, 1=invalid credentials, etc.
  3. Chat message. Contains the recipient username (string), chat text (string), some extra codes (ULONG).

So, how do we implement this ? For every message type we need the following:

  • Declaration of the message class/struct.
  • Code that writes this message into a strem (socket/file/etc.)
  • Code that parses messages from the stream.
  • Code that handles incoming messages.

For the purpose of this example we'll use the following abstract classes for streaming:

struct OutStream {
    virtual void Write(LPCVOID pPtr, size_t nSize) = 0;

    // ordinal types
    template <class T>
    void Write_T(const T& val)
    {
        Write(&val, sizeof(val));
    }
    // variable-sized strings
    void Write_Str(const CString& val)
    {
        ULONG nLen = val.GetLength();
        Write_T(nLen);
        Write((PCWSTR) val, nLen * sizeof(WCHAR));
    }
};
struct InStream {
    virtual size_t Read(LPVOID pPtr, size_t nSize) = 0;

    bool ReadExactTry(LPVOID pPtr, size_t nSize)
    {
        while (true)
        {
            size_t nPortion = Read(pPtr, nSize);
            if (nPortion == nSize)
                return true; // ok
            if (!nPortion)
                return false; // not enough data.
            nSize -= nPortion;
            if (pPtr)
                ((PBYTE&) pPtr) += nPortion;
        }
    }
    void ReadExact(LPVOID pPtr, size_t nSize)
    {
        if (!ReadExactTry(pPtr, nSize))
        {
            // not enough data, raise an appropriate exception
            throw _T("not enough data!");
        }
    }

    // ordinal types
    template <class T>
    void ReadExact_T(T& val)
    {
        ReadExact(&val, sizeof(val));
    }
    // variable-sized strings
    void ReadExact_Str(CString& val)
    {
        ULONG nLen;
        ReadExact_T(nLen);
        PWSTR szPtr = val.GetBuffer(nLen);
        ReadExact(szPtr, nLen * sizeof(WCHAR));
        val.ReleaseBuffer(nLen);
    }
};

Now let's implement our messages.
For example, the login message may be declared in the following manner:

struct MsgLogin
{
    // message fields
    ULONG m_Version;
    CString m_Username;
    CString m_Password;

    MsgLogin()
    {
        // zero-init members.
        m_Version = 0;
    }

    void Write(OutStream&);
    void Read(InStream&);
};


void MsgLogin::Write(OutStream& out)
{
    // first comes the message size (in bytes). Let's calculate it.
    ULONG nSize = 
        sizeof(ULONG) +        // message size
        sizeof(USHORT) +    // message code
        sizeof(m_Version) +
        sizeof(ULONG) + m_Username.GetLength() * sizeof(WCHAR) +
        sizeof(ULONG) + m_Password.GetLength() * sizeof(WCHAR);

    out.Write_T(nSize);
    out.Write_T((USHORT) 1); // the code of the login.
    out.Write_T(m_Version);
    out.Write_Str(m_Username);
    out.Write_Str(m_Password);
}
void MsgLogin::Read(InStream& in)
{
    in.ReadExact_T(m_Version);
    in.ReadExact_Str(m_Username);
    in.ReadExact_Str(m_Password);
}

Then in order to send/save this message you'll have to write it this way:

    MsgLogin login;
    login.m_Version = MAKELONG(1, 3);
    login.m_Username = _T("user");
    login.m_Password = _T("pass");
    login.Write(my_out_stream);

Message parsing code should be something like this:

    while (true)
    {
        ULONG nSize;
        if (!my_in_stream.ReadExactTry(&nSize, sizeof(nSize)))
            break; // no more messages so far.
        
        USHORT nCode;
        my_in_stream.ReadExact_T(nCode);
        switch (nCode)
        {
        case 1: // login
            {
                MsgLogin login;
                login.Read(my_in_stream);
                // process incoming message
                HandleMsg(login);
            }
            break;
        default:
            // unknown message, bypass it.
            for (nSize -= sizeof(ULONG) + sizeof(USHORT); nSize; )
            {
                BYTE pBuf[0x100];
                size_t nPortion = min(sizeof(pBuf), nSize);
                my_in_stream.ReadExact(pBuf, nPortion);
                nSize -= (ULONG) nPortion;
            }
        }
    }

Now we have to add other messages. For each of them we'll write their struct declaration, Write method, Read method, and extend the parser switch. Furthermore, every field of every message must be taken into account in several places: twice in the Write method (when calculating the size and actually storing), in the Read method, in the constructor (should be zero-initialized).

Writing all this is a pretty large routine work, with reasonable chance of typing/copy-paste mistakes. Now let's demonstrate how this can be done via macros.

First we declare the list of messages we want to have:

#define COMM_MSGS_All \
    COMM_MSG(1, Login) \
    COMM_MSG(2, LoginRes) \
    COMM_MSG(3, Chat)

What does this mean ? By now it actually means nothing. The macro COMM_MSGS_All just expands into a list of COMM_MSG with two parameters: the message code and its name. COMM_MSG is not yet defined, so that COMM_MSGS_All can't be used meanwhile.

Now let's write for every message the members we want in it:

#define COMM_MSG_Login(par) \
    par(ULONG, Version) \
    par(CString, Username) \
    par(CString, Password)

#define COMM_MSG_LoginRes(par) \
    par(UCHAR, Result)

#define COMM_MSG_Chat(par) \
    par(CString, Recipient) \
    par(CString, Test) \
    par(UCHAR, Flags)

Again, those 3 macros we've just defined don't mean too much. They just list in an abstract (not yet defined) way what our messages should contain. NOTE: Each of this macros needs a parameter (par), via which they list the message members.

And now let's breath life into our macros. First we said we need to declare a struct for every message type. Let's do it:

#define PAR_DECL(type, name) type m_##name;
#define PAR_ZERO(type, name) ZeroInit(m_##name);

template <class T>
void ZeroInit(T& val) { val = 0; }
template <>
void ZeroInit<CString>(CString& val) { }

#define COMM_MSG(code, name) \
struct Msg##name { \
    COMM_MSG_##name(PAR_DECL) \
    Msg##name() \
    { \
        COMM_MSG_##name(PAR_ZERO) \
    } \
    void Write(OutStream&); \
    void Read(InStream&); \
};

COMM_MSGS_All
#undef COMM_MSG

What does this mean ? Let's see.
As we said COMM_MSGS_All expands into a list of COMM_MSG per message. COMM_MSG in turn takes two parameters: the message code and its name.
We defined COMM_MSG(code, name) to expand into a declaration of Msg##name struct. (## means concatenation of tokens). So that COMM_MSGS_All in fact expands into declaration of a struct per message.

The first line inside the struct declaration is COMM_MSG_##name(PAR_DECL). It means that for every message type COMM_MSG_##name will turn into an appropriate message's parameter list. This list requires a parameter which we specified: PAR_DECL. Every message field is interpreted via this macro. Our PAR_DECL macro expands into type m_##name; So that for every field of the current message we declare a member in our struct of the appropriate type and name (with m_ prefix).

Next we have a constructor. It has COMM_MSG_##name(PAR_ZERO) statement, which passes all the message's fields via PAR_ZERO macro. This macro calls ZeroInit function for every member. This is a template function that should zero-init the member. For most of the types it just assigns 0 to it. The exception is CString (template function specialization). CString doesn't require explicit zero-init.

Next we declare Write and Read methods inside our struct. Let's now implement them:

template<class T>
ULONG CalcSizeOf(const T& val) { return sizeof(val); }
template <>
ULONG CalcSizeOf<CString>(const CString& val)
{
    return
        sizeof(ULONG) +
        val.GetLength() * sizeof(WCHAR);
}

template <class T>
void WriteToStream(OutStream& out, const T& val) { out.Write_T(val); }
template <>
void WriteToStream<CString>(OutStream& out, const CString& val)
{
    out.Write_Str(val);
}
template <class T>
void ReadFromStream(InStream& in, T& val) { in.ReadExact_T(val); }
template <>
void ReadFromStream<CString>(InStream& in, CString& val)
{
    in.ReadExact_Str(val);
}

#define PAR_CALCSIZE(type, name) +CalcSizeOf(m_##name)
#define PAR_WRITE(type, name) WriteToStream(out, m_##name);
#define PAR_READ(type, name) ReadFromStream(in, m_##name);

#define COMM_MSG(code, name) \
void Msg##name::Read(InStream& in) \
{ \
    COMM_MSG_##name(PAR_READ); \
} \
void Msg##name::Write(OutStream& out) \
{ \
    ULONG nSize = \
        sizeof(ULONG) + sizeof(USHORT) \
        COMM_MSG_##name(PAR_CALCSIZE); \
    out.Write_T(nSize); \
    out.Write_T((USHORT) code); \
    COMM_MSG_##name(PAR_WRITE) \
}

COMM_MSGS_All
#undef COMM_MSG

We use again COMM_MSGS_All, but this time we defined COMM_MSG to be something else (note that after every usage of COMM_MSGS_All we immediately undefine the COMM_MSG). This time we make it turn into Read and Write methods for the specified message.

Inside Read method we pass all our members through the PAR_READ macro, wich turns into ReadFromStream(in, m_##name); This is a template function that is implemented differently for CString and ordinal types.
The Write function makes use of the parameters list twice: once for calculating the message size and one for actual writing. For this purpose we prepared PAR_CALCSIZE and PAR_WRITE macros, which in turn call the relevant template functions that behave differently for ordinal types and CString.

Now the parser code turns into the following:

    while (true)
    {
        ULONG nSize;
        if (!my_in_stream.ReadExactTry(&nSize, sizeof(nSize)))
            break; // no more messages so far.
        
        USHORT nCode;
        my_in_stream.ReadExact_T(nCode);
        switch (nCode)
        {
#define COMM_MSG(code, name) case code: \
            { \
                Msg##name msg; \
                msg.Read(my_in_stream); \
                HandleMsg(msg); \
            } \
            break;
COMM_MSGS_All
#undef COMM_MSG

        default:
            // unknown message, bypass it.
            for (nSize -= sizeof(ULONG) + sizeof(USHORT); nSize; )
            {
                BYTE pBuf[0x100];
                size_t nPortion = min(sizeof(pBuf), nSize);
                my_in_stream.ReadExact(pBuf, nPortion);
                nSize -= (ULONG) nPortion;
            }
        }
    }

That is, for every known message we parse it and call the overloaded HandleMsg function. What still remains is implementing the appropraite HandleMsg functions for every message type. But this of course depends on the program logic. All other things are implemented automatically via our macros.

Let's make some conclusions.

What exactly did we achive, apart of making the program absolutely unreadable ?

The answer is that we made the generation of structs for messages, their serialization and parsing automatic. If you want to add another field to a message there is a *SINGLE* place you have to change: the appropriate COMM_MSG_xxxx macro.

If you add a new message then you'll have to list its parameters and append its entry into the COMM_MSGS_All macro. Plus you'll have to write a handling function for it. And this is all!

Compare this with what we had at the beginning: For every message type you write all the methods. When you have tens of different message types - it's a nightmare!

Now suppose we decided to change the protocol. For instance, we don't want to place the message size into the stream (thus making bypassing unknown messages impossible). You have tens places to fix !!!
And in our case we have *ONE* place to fix.

Let's go even further: for every message we want a run-time textual description of its members, which we can log/display. Let's implement it:

#define PAR_FMT_UCHAR "u"
#define PAR_FMT_USHORT "u"
#define PAR_FMT_ULONG "u"
#define PAR_FMT_CString "s"
#define PAR_FMT1(type, name) _T("\t") _T(#name) _T(" = %") _T(PAR_FMT_##type) _T("\r\n")
#define PAR_FMT2(type, name) ,msg.m_##name

#define COMM_MSG(code, name) \
void TxtDescr(const Msg##name& msg, CString& sOut) \
{ \
    sOut.Format(_T("Type=%s, Code=%u\r\n") \
        COMM_MSG_##name(PAR_FMT1) \
        ,_T(#name), code \
        COMM_MSG_##name(PAR_FMT2)); \
}

COMM_MSGS_All
#undef COMM_MSG

We generate TxtDescr for every message type, and we use CString's Format function. We pass through our parameters twice: the first time when we build the format string, the second time when we pass the appropriate parameter. When we build the format string for every parameter we use PAR_FMT_##type which expands to either PAR_FMT_USHORT, PAR_FMT_CString or etc. Thus for every such a thing we need to define the correct formatting flag.

And this is automatically done for all the messages.

You don't like this implementation ? Then rewrite it. There's absolutely no problem, because You have a *SIGNLE* place to rewrite.


Impressing, isn't it? So, let's think again is it worth to use macros.


Are they dangerous? Of course. You change one character in a macro definition - and the whole code for all the messages may become totally different.

But what is the alternative? Writing, writing, copy+pasting and etc.? From my personal experience rewriting the same thing multiple times, besides of demoralization, is much more dangerous than using macros.

If you write something wrong in the macro - most probably it just won't compile. And even if it will - it will work wrong probably for all kinds of messages. And if something works wrong - there's a *SINGLE* place that you have to fix.

And if you just write manually all the functions for all the message types - what are the chances to do it without mistakes? Pretty close to zero, unless you're a robot. And if you make a mistake in one message which is rarely used - you'll not know it until you get the surprise.

Yes, macros are very dangerous, they require good skills to write, it's very hard to read them, it's impossible to debug them. But they eliminate the need in rewriting the same thing several times.

This may sound crazy, but I think it's easier to maintain macros then dozens of almost identical code lines. You need to change something - go ahead, change *ONE* specific place.

Is there another way to implement messages without rewriting too many things and not using macros? In this specific example - yes. We could write it in this way:

struct ParamBase {
    PCTSTR m_szName;
    virtual void Write(OutStream&) = 0;
    virtual void Read(InStream&) = 0;
    virtual ULONG CalcSize() = 0;
};
struct Param_UCHAR :public ParamBase {
    UCHAR m_Val;
    virtual void Write(OutStream&);
    virtual void Read(InStream&);
    virtual ULONG CalcSize();
};
// ...

struct MsgBase {
    std::list m_lstParams;
};
struct MsgLogin :public MsgBase {
    MsgLogin()
    {
        m_lstParams.push_tail(new ParamULong(_T("Version")));
        m_lstParams.push_tail(new ParamString(_T("Username")));
        m_lstParams.push_tail(new ParamString(_T("Password")));
    }
};
// ...

That is, we arrange all our parameters in a list, which can be used for message serialization.
But I don't like this method. It means that instantiation, serialization, parsing and etc. is done in run-time instead of compile-time, plus it demands dynamic memory allocations; hence we have a performace hit. And anyway it doen't allow us to get rid of rewriting things. What if at some point we decide to replace stl list by another list implementation? Then we need to fix tens of places!

Macros on the other side give you the maximum flexibility.


Callback functions example.

Once I had to write a video wrapper (filter-like) driver.
At the initialization the video driver is requested to fill a table with its supported callback functions. My driver then had to call the initialization function of the original driver, get its functions. Some of them had to be filtered out, some replaced by mine, some taken as-is.

At some point I decided to use SEH (structured exception handling) in my filter functions, means - every such a top-level function has to be wrapped with the SEH handler.
Because I was already using macros to manipulate my functions - it took a minute for me to add the SEH handler for all my functions!

BTW instead of listing all the function by one single macro (like COMM_MSGS_All in the previous example) this time I had several macros for listing functions of a specific category: those that must always be hooked, those that must be replaced, and so on.

I'll not list the code here, it's too complicated and requires a lot of explanation.
But you can believe me, it was a pleasure. Instead of messing with tens of thousands of code lines of repititions I only had to write *ONE* elegant macro that does it all.

NOTE also that in this case there's no alternative with either C++ polymorphic stuff (run-time parameters list or etc.) or template functions.

Callback marshalling.

Marshalling - I mean I have callback functions that are called in one thread. I want them to post a message to another thread and return immediately. Then, in the other thread, I want an appropriate callback to be called with all the needed parameters.

Without too much getting into details that's how I've implemented it:

#define HANDLE_MY_EVTS \
    MARSHAL_IN(OnJoinSession) \
    MARSHAL_IN(OnClientUp) \
//...


#define MARSHAL_PARAMS_OnJoinSession(macro, sep) \
        macro(ULONG, ULONG, nUserSeq) \
        sep \
        macro(UCHAR, UCHAR, nState)
        sep \
        macro(PCTSTR, CString, sClientName) \
        sep \
        macro(const SYSTEMTIME&, SYSTEMTIME, tmOnline)


#define MARSHAL_PARAMS_OnClientUp(macro, sep) \
        macro(ULONG, ULONG, nUserSeq) \
        sep \
        macro(ULONG, ULONG, nRemoteID)

// ...

NOTE: Unlike the previous example, parameters list now has two types for every parameter: First is the type our callback function should receive according to the agreed prototype. And the second is the type of the variable that can be used to marshal this parameter. For ordinal types (ULONG, UCHAR, ...) it's the same. But for things such as PCTSTR it's different: you can't just take the pointer, you need to create a copy of the string.

Let's declare the functions that we want to be called:

#define THE_MACRO(type1, type2, val) type1 val
#define THE_SEP ,
#define MARSHAL_IN(method) \
    void method(MARSHAL_PARAMS_##method(THE_MACRO, THE_SEP));
    HANDLE_MY_EVTS
#undef MARSHAL_IN
#undef THE_MACRO
#undef THE_SEP

Another difference: after every parameter there goes sep (also passed via a macro parameter). Due to this thing we can separate the parameters by comma in the above function declaration.

Now, whenever our callback function is called we allocate a task structure with the needed parameters and post it to our dedicated thread. Let's write it:

#define THE_MACRO(type1, type2, val) type2 m_##val;
#define THE_SEP
#define MARSHAL_IN(method) \
struct CTaskGui_##method : public CTaskGui { \
    virtual ~CTaskGui_##method() {}\
    MARSHAL_PARAMS_##method(THE_MACRO, THE_SEP) \
    virtual void ExecuteUIGuarded(); \
};
HANDLE_MY_EVTS
#undef MARSHAL_IN
#undef THE_MACRO
#undef THE_SEP


// Callback functions:

#define THE_MACRO(type1, type2, val) type1 val
#define THE_SEP ,
#define ASSIGN_MACRO(type1, type2, val) spTask->m_##val = val;
#define ASSIGN_SEP
#define PASS_MACRO(type1, type2, val) m_##val
#define MARSHAL_IN(method) \
void THE_EVT::method(MARSHAL_PARAMS_##method(THE_MACRO, THE_SEP)) \
{ \
    if (IsEventAllowed()) \
    { \
        CComSPtrBase spTask = CTaskGui_##method::AllocOnHeap(); \
        MARSHAL_PARAMS_##method(ASSIGN_MACRO, ASSIGN_SEP) \
        m_spUIMarshaller->PostGui(spTask); \
    } \
} \
void CTaskGui_##method::ExecuteUIGuarded() \
{ \
    method(MARSHAL_PARAMS_##method(PASS_MACRO, THE_SEP)); \
}

and so on.

Some other examples.

I've seen tons of code written in this way:

    if (sfState.Flag & HOOK_CreatePort)
    {
        if (sfHList.pfnCreatePort != NULL)
        {
            sfHList.pfnOrgCreatePort = pGlobalProc[IDX_CreatePort];
            pGlobalProc[IDX_CreatePort] = sfHList.pfnCreatePort;

        }
        hooks_total++;
    }
    if (sfState.Flag & HOOK_CreateWaitablePort)
    {
        if (sfHList.pfnCreateWaitablePort != NULL)
        {
            sfHList.pfnOrgCreateWaitablePort = pGlobalProc[IDX_CreateWaitablePort];
            pGlobalProc[IDX_CreateWaitablePort] = sfHList.pfnCreateWaitablePort;

        }
        hooks_total++;
    }
    if (sfState.Flag & HOOK_ConnectPort)
    {
        if (sfHList.pfnConnectPort != NULL)
        {
            sfHList.pfnOrgConnectPort = pGlobalProc[IDX_ConnectPort];
            pGlobalProc[IDX_ConnectPort] = sfHList.pfnConnectPort;

        }
        hooks_total++;
    }
    // ... and tens more ...

Using macros we can turn it into:

#define HOOK_FUNC(func) \
    if (sfState.Flag & HOOK_##func) \
    { \
        if (sfHList.pfn##func != NULL) \
        { \
            sfHList.pfnOrg##func = pGlobalProc[IDX_##func]; \
            pGlobalProc[IDX_##func] = sfHList.pfn##func; \
        } \
        hooks_total++; \
    }
    HOOK_FUNC(CreatePort)
    HOOK_FUNC(CreateWaitablePort)
    HOOK_FUNC(ConnectPort)
    // ...

When you write MFC dialog-based applications you can put/get values of your controls via so-called DDX mechanism. For every such a control you can declare via wizard a variable, and the wizard will automatically add it to DoDataExchange function, and zero-initializa it in the constructor.

But I needed many times also something that the wizard didn't implement automatically: store those parameters in the registry and read them back.
Macros helped me again.


Once I had to construct/parse an XML (which I hate), and it demands 'special' characters encodings. I've declared those 'special' characters encoding rules:

#define XML_SPECIAL_BRACKETS \
    XML_SPECIAL_CHAR('>',  "gt") \
    XML_SPECIAL_CHAR('<',  "lt")

#define XML_SPECIAL_QUOTES \
    XML_SPECIAL_CHAR('\"', "quot") \
    XML_SPECIAL_CHAR('\'', "apos")

#define XML_SPECIAL_AMP \
    XML_SPECIAL_CHAR('&',  "amp")

#define XML_SPECIAL_BRACKETS_AMP \
    XML_SPECIAL_BRACKETS \
    XML_SPECIAL_AMP

#define XML_SPECIAL_BRACKETS_AMP_LEND \
    XML_SPECIAL_BRACKETS_AMP \
    XML_SPECIAL_CHAR('\r', "#xA") \
    XML_SPECIAL_CHAR('\n', "#xD")

They have to be separated into categories, because there're slightly different rules for encoding characters for attributes and regular XML nodes.

Now whenever I need to encode/decode XML strings I just declare XML_SPECIAL_CHAR to do the appropriate thing, and list all the special characters that are relevant for the case.

Another great thing that I managed to do via macros: we had a Direct3D application that does a lot of drawing. Once I needed to record all the drawings without affecting the performance too much.
By using macros I've wrapped all the Direct3D functions with the code that, apart of actually drawing, records (by the way similar to the messages example) all the function calls with all the relevant parameters. Then, by using the macros again, this stream can be parsed and all the functions 'replayed'.

Conclusions.

Sometimes template functions/classes may be used instead. If so - of course they're the preferred way to go. But usually the situation is more cruel.

By such a use of macros I have almost no repititions in my code. IMHO - repititions is the worst thing in programming. I'm aware of all the threats with using macros, and I still prefer macros to infinite code rewrites. Yes, macros are like puzzles, sometimes not trivial to understand. But they make the program very compact.

I also prefer using macros to re-arranging the code in a super-polimorphic way in order to minimize rewrites, but this is my opinion, I don't insist. All this is the matter of opinion after all.

Whereas using of macros is criticized in all the books and articles I've read till now, I'd like to point out some definite advantages of them. Is it worth using them - the decision is up to you.

Comments are welcome, as usual. Both positive and negative.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

valdok


My name is Vladislav Gelfer, I was born in Kiev (former Soviet Union), since 1993 I live in Israel.
In programming I'm interested mostly in low-level, OOP design, DSP and multimedia.
Besides of the programming I like physics, math, digital photography.

Occupation: Web Developer
Location: Israel Israel

Other popular Programming Tips articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 9 of 9 (Total in Forum: 9) (Refresh)FirstPrevNext
Subject  Author Date 
GeneralExactly right!memberDQNOK11:45 29 Apr '08  
GeneralRe: Exactly right!memberBob10000:56 30 Apr '08  
GeneralFlawed line of argumentationmemberStefan637:46 29 Apr '08  
GeneralRe: Flawed line of argumentationmembervaldok21:56 29 Apr '08  
GeneralYes - but think it does prove Macros can be difficult to follow!memberBob100015:57 28 Apr '08  
GeneralRemembervaldok22:08 29 Apr '08  
GeneralRe: RememberBob10000:47 30 Apr '08  
GeneralNice article, but your examples are not so much persuasivememberHuyong Zhao20:47 24 Apr '08  
GeneralOh yes they are!membervaldok22:49 26 Apr '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 24 Apr 2008
Editor:
Copyright 2008 by valdok
Everything else Copyright © CodeProject, 1999-2008
Web08 | Advertise on the Code Project