Tuesday, October 10, 2006

My second CodeGuru article, the Message Only Window & friends

Aptly named: Message Only Window - On message only windows, thunks, threads, pimpls, encapsulation and other great practices
http://www.codeguru.com/cpp/w-p/win32/tutorials/article.php/c12689/

Have a look, and be sure to tell me what you think. Corrections are more than welcome :)

Monday, October 9, 2006

Oddly sized variables in VC++

Seeing as VC++ will not pack bit-fields with non bit-fields, even when the pack pragma is used, you can find yourself in a jam if you need, say, a 24 bit number. If you want a 24 bit value following or followed by an 8 bit value, you can make both bit-fields, and it will pack. If the surrounding fields are larger, they wouldn't fit in the holding type (e.g. a 32 bit long), and would therefore not be packed. The following MSDN forum thread tells the tale of one who did need such a struct http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=801265&SiteID=1.

Having a couple of extra minutes on my hands, I refurbished a quick workaround example I wrote up as a response to the post.

Here goes
/********************************************************************
** Helper Template meta programs
*/

// TMP compile time assert, based on boost::STATIC_ASSERT
#define COMBINE(x, y) x##y
#define LINETAG(x) COMBINE(x, __LINE__)
template<bool> struct STATIC_ASSERTION_FAILURE;
template<> struct STATIC_ASSERTION_FAILURE<true> { enum { v = 1 }; };
template<int x> struct static_assert_test {};
#define STATIC_ASSERT(expr) \
typedef static_assert_test< \
sizeof(STATIC_ASSERTION_FAILURE<(bool)(expr)>) \
> \
LINETAG(static_assert)

/********************************************************************
** SizedType
** Generates an oddly sized variable, which simulates the given type.
** The int size is the number of bytes to use, such as '3' for
** 24 bit.

*/


template<typename T, int size>
class SizedType
{
// Make sure that the size requested isn't larger than
// that of the underlying type.
STATIC_ASSERT(size < sizeof(T));
public:
T operator=(const T& rhs)
{
_ASSERT((rhs >> 8 * size) == 0);
memcpy(&v, &rhs, size);
return v;
}

operator T()
{
T x = 0;
memcpy(&x, &v, size);
return v;
}

private:
#pragma pack(push, 1)
union
{
unsigned char padding[size];
unsigned char v;
};
#pragma pack(pop)
};

/********************************************************************
** A test struct with a few packed members
*/

#pragma pack(push, 1)
struct TestStruct
{
char field1;
char field2;
short field3;
char field4;
SizedType<unsigned long, 3> field5;
SizedType<unsigned __int64, 5> field6;
unsigned long field7;
};
#pragma pack(pop)

int main()
{
TestStruct t;
memset(&t, 0xFF, sizeof(t));
t.field1 = 0;
t.field2 = 1;
t.field3 = 2;
t.field4 = 3;
// This commented line will generate a runtime assert when run in
// debug mode, since the size is larger than that supported by the
// SizedType declared in TestStruct.
// t.field5 = 0xAAAAAAAA;
t.field5 = 0xAAAAAA; // 3 bytes, 24 bit
t.field6 = 0xBBBBBBBBBBBB; // 6 bytes, 48 bit
t.field7 = 7;

cout << indev::hexdump(&t, sizeof(TestStruct)) << endl;
return 0;
}



The above app should output (when run with my string helper library in place, anyway)
00000000  00 01 02 00 03 aa aa aa - bb bb bb bb bb bb 06 00  |.....¬¬¬╗╗╗╗╗╗..|
00000010 00 00 |..|
00000012

... Which shows field5 taking up 3 bytes and field6 6 bytes. Since no type will hold the combined 72 bit present here, they would not be packed together if they were both made bit fields, the approach demonstrated here really is the only way of doing it.

Here's the output if field5 and field6 are made 24- and 48 bit fields.

00000000  00 01 02 00 03 aa aa aa - ff bb bb bb bb bb bb ff  |.....¬¬¬ ╗╗╗╗╗╗ |
00000010 ff 06 00 00 00 | ....|
00000015

As you can see, the structure is now a total of 3 bytes larger, with added FF padding between field5 (the AA's) and field6 (the BB's) and between field6 and field7.