Tech Tips

When Shared Data Segments Aren't Shared
by Larry Hamilton Preventing Class Derivation in Visual C++ .NET
by Ehsan Akhgari

Do/Wile Macros Souped Up and Revisited (First Movement)
by Cindy Ross

Do/While Macros Souped Up and Revisited (Second Movement)
by Petter Hesselberg



October 01, 2003
URL:http://drdobbs.com/tech-tips/184416696

Tech Tips

When Shared Data Segments Aren't Shared

There are several ways to share data between instances of DLLs. One of the methods is to use Shared Data segments that are set up in the .def file for the DLL. This method is much easier than going through all the work that memory-mapped files require. There is, however, a potentially time-consuming surprise in store for you if you do not understand one caveat about the shared memory segments.

Shared data segments are only shared between copies of a DLL loaded from the same location in the filesystem. This can be quite surprising when trying to debug a DLL with shared data segments in a system with many programs using the DLL. It is not uncommon to have the DLL loaded by parts of the system from its normal location and to also have the DLL loaded from the Debug directory in the project location. This results in two copies of the shared data segments.

To demonstrate this problem, I have written a very simple DLL that supports reading and writing a simple integer number between multiple applications. The DLL simply defines three access functions and a shared data segment with an integer. The application (included in this month's code archive) shows where the DLL is loaded from and allows the user to read and write integer values. The projects to build the DLL and application have a "Custom Build Step" that copies the target to both the bin and bin2 directories.

By running two copies of TheApp.exe from the bin directory, you can see that writing a value in one instance of the application will cause that value to be read in the other instance, just as would be expected.

Now without closing either of these instances, go to the bin2 directory and start an instance of TheApp.exe from there. Notice that when doing a read from this instance, you do not get the value written by the instances run from bin. As an additional step, you could run another instance of TheApp.exe from bin2 and see that the instances run from the same directory use the same shared memory.

Preventing Class Derivation in Visual C++ .NET

By Ehsan Akhgari

[email protected]

Here is a tip for preventing derivation from a class in C++. Suppose you have a class B, and you don't want any class to derive from this class. C++, unlike other languages such as Java, doesn't have any built-in support for this task, but this can be done using virtual inheritance. You should create and use a Lock class as this example shows:

class Lock {
             Lock() {}
             friend class B;
};

class B : private virtual Lock
{
public:
             B() {}
};

class D : public B
{
public:
             D() {}
};

int main()
{
             B b;
             D d;

             return 0;
}

B is a friend of Lock, so B::B() has access to Lock::Lock() and can initialize the virtual base class. On the other hand, D is not a friend of B, so D::D() can't initialize the virtual base class due to its inability to call Lock::Lock(). Thus, attempting to compile the aforementioned program will lead to an error. Microsoft Visual C++ .NET 2003 produces the following error messages when compiling this application:

h:\Projects\VC++.NET\test\test\test\test.cpp(17)   : error C2248: 
'Lock::Lock' : cannot access    private member declared in class 'Lock'
h:\Projects\VC++.NET\test\test\test\test.cpp(3)    : see
declaration of 'Lock::Lock'
h:\Projects\VC++.NET\test\test\test\test.cpp(2)    : see
declaration of 'Lock'
h:\Projects\VC++.NET\test\test\test\test.cpp(22)   : error C2248:
'Lock::Lock' : cannot access private member    declared in class 'Lock'
h:\Projects\VC++.NET\test\test\test\test.cpp(3)    : see
declaration of 'Lock::Lock'
h:\Projects\VC++.NET\test\test\test\test.cpp(2)   : see
declaration of 'Lock'

Do/While Macros Souped Up and Revisited (First Movement)

By Cindy Ross

[email protected]

In the June 2003 issue, John Szakmeister suggested that the macro wrapper (previously presented by Raja Venkataraman) could be improved by removing the do/while construct. But the original version with the do/while construct is better, as it can be used in any context where a C statement can be used.

For example, using the macro from the previous article, this code compiles fine:

   #define MY_ASSERT_ONE(x) do { \       if (!(x)) { \
         _asm int 3 \
      } \
      } while (0)

   main()
   {
      if ( 1 )
         MY_ASSERT_ONE(1);
      else
         return 1;
   }

but we get a syntax error if we omit the do/while construct:

   #define MY_ASSERT_ONE(x) { \       if (!(x)) { \
         _asm int 3 \
      } \
      }

   main()
   {
      if ( 1 )
         MY_ASSERT_ONE(1);
      else
         return 1;
   }

Do/While Macros Souped Up and Revisited (Second Movement)

by Petter Hesselberg

[email protected]

Regarding John Szakmeister's June 2003 Tech Tip ("A Better Macro Wrapper for Visual C++"), which was a response to Raja Venkataraman's February 2003 Tech Tip ("do/while Macros for C++").

Venkataraman's original Tech Tip starts by showing the problems associated with a simplistic ASSERT implementation such as this:

#define MY_ASSERT_ONE( x )  if ( !( x ) ) { \     _asm int 3 \
}

Using the aforementioned definition, the following code generates an "illegal else without matching if" compiler error:

if ( x )
    MY_ASSERT_ONE( y );
else
    MY_ASSERT_ONE( z );

This is fixable in two ways: Either remove the semicolons (a strange-looking and error-prone solution) or insert braces (which is a good practice in any case):

if ( x ) {     MY_ASSERT_ONE( y );
} else {
    MY_ASSERT_ONE( z );
}

Venkataraman then goes on to show the MFC/ATL solution, in which a semantically NULL do/while loop makes the semicolon required rather than forbidden:

#define MY_ASSERT_TWO( x )  do { \        if ( !( x ) ) { \
        _asm int 3 \
    } while ( 0 )

This code is, in any case, wrong because it has two opening braces but only one closing brace. The correct version looks like this:

#define MY_ASSERT_TWO( x )  do { \        if ( !( x ) ) { \
        _asm int 3 \
    } } while ( 0 )

Szakmeister's Tech Tip update then "improves" on Venkataraman's by going back to the original version. He does not discuss the semicolon problem, except to note that his version is "harmful if not used correctly" — this makes you wonder if he actually read all of the original Tech Tip.

The standard header file, assert.h, has a different solution:

#define assert(exp) \     (void)( (exp) || \
    (_assert(#exp, __FILE__, __LINE__), 0) )

This approach doesn't help if you insist on using an inline assembler, though; there's no way of making an assembly statement part of an expression. The closest you get is something like this:

int failedAssert( void ) {     __asm int 3
    return 0;
}

#define MY_ASSERT_THREE( x ) \
    (void)( (x) || failedAssert())

This works well enough — except that the interrupt occurs in failedAssert() rather than at its point of call, and you have to go manually one step up the call stack in the debugger. But inline assembly is not a necessity in Windows programming; there's DebugBreak() to the rescue:

#define MY_ASSERT_FOUR( x ) \
    (void)( (x) || DebugBreak())

But oops — DebugBreak() is a void function, and you can't have void as an operand to the || operator. Let's try the ternary conditional operator instead:

#define MY_ASSERT_FIVE( x ) \

    ((x) ? (void) 0 : DebugBreak())

The trick to safe macro programming in C/C++ is to avoid the outer braces — you need an expression rather than a statement. Consider the comma operator, also called the "sequential-evaluation operator," which lets you evaluate two or more expressions in a context where only one expression is allowed. This allows cool macros like the following, good for trashing a pointer you've just released (the purpose being to root out dangling pointer errors):

#define reset_pointer( p )  \
    ( memset( &( p ), 0xacACacAC, sizeof( p )    ), \
        assert( 4 == sizeof( p ) ) )

This is pretty cool, but can be confusing — the comma operator is easily confused with the comma that separates function or macro arguments; it is emphatically not the same thing. The following example shows how you might call a function with three parameters; the value of the second parameter passed is 3; the value of b after the function call is 1:

myFunction( a, (b = 1, b + 2), c );

If you absolutely must have inline assembly, I suspect that the do/while technique really is the safest approach — in spite of its clunkiness.

Editor's note: I hope our reader's have enjoyed this ongoing debate about do/while macros (and I'm sure we'll get the big e-mail gong if not). Kudos to Raja Venkataraman, who started the thread, and to everyone who vigorously threw in their opinion. Have an opinion of your own about any of our tips? Please send them to me at [email protected].


George Frazier is a software engineer in the System Design and Verification group at Cadence Design Systems Inc. and has been programming for Windows since 1991. He can be reached at georgefrazier@ yahoo.com.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.