Intro
In this blog post I want to share some Visual Studio macros that can be handy for the C++ software developers for optimizing and debugging your code.
Note that I am assuming that you have a full access to the Visual Studio C++ source code and that you can compile it.
So in no particular order.
Table Of Contents
Here's the table of contents for a quick access:
- Macro to See What Other Macros Expand To
- Macro to Get a Mangled (or Decorated) Name of a Function
- Macro to Create a "C" Exported Function
- Macro to Check printf-Type Specifiers
For those that don't like reading blog posts, make sure to check my video recap at the end.
Macro to See What Other Macros Expand To
This one can be particularly handy if you have some complex macro and you are not sure what exactly it expands to. For instance, this is not a very complicated example, but just to illustrate the point. Let's assume you have this snippet of code:
case WM_APPCOMMAND:
{
UINT cmd = GET_APPCOMMAND_LPARAM(lParam);
UINT uDevice = GET_DEVICE_LPARAM(lParam);
UINT dwKeys = GET_KEYSTATE_LPARAM(lParam);
//...
}
break;
And in that case you want to know what does GET_APPCOMMAND_LPARAM
macro translate into?
One way that is supported by the latest version of Visual Studio is to hover the mouse over it, and the built-in Intellisense should give you a user prompt with what that macro expands to, as such:
This is all good if you need a quick glance, but that user prompt unfortunately doesn't seem to allow you to copy-and-paste from it. Nor that it is available in older versions of the Visual Studio.
Instead, let's declare the EXPAND_MACRO
somewhere in your globally accessible .h
file:
#ifndef _CRT_STRINGIZE
#define _CRT_STRINGIZE_(x) #x
#define _CRT_STRINGIZE(x) _CRT_STRINGIZE_(x)
#endif
//Expands to macro:
#define EXPAND_MACRO(x) __pragma(message(__FILE__ _CRT_STRINGIZE((__LINE__): \nmacro\t)#x" expands to:\n" _CRT_STRINGIZE(x)))
And then use it in your code as such:
Note that EXPAND_MACRO
will not compile into any code, it will simply instruct the Visual Studio C++ compiler to output the result into the
"Output" debugging window during the build. So you will get something like this:
1>path-to\source-code.cpp(101):
1>macro GET_APPCOMMAND_LPARAM(lParam) expands to:
1>((short)(((WORD)((((DWORD_PTR)(lParam)) >> 16) & 0xffff)) & ~0xF000))
Note that the first line of the output will give you the location where EXPAND_MACRO
was called from, i.e. the source code file and the line of text in it.
You can double-click it in the "Output" window to have the Visual Studio IDE take you to that line of code in the editor.
This handy macro was suggested by Rbmm and is available in his GitHub.
Macro to Get a Mangled (or Decorated) Name of a Function
You normally wouldn't need to do this, unless you are adding an Assembly language code snippet to your C++ project.
In that case to be able to call, or even reference C++ functions from your Assembly code, you will have to either
declare those as extern "C"
, which may not be very convenient from the standpoint of your C++ code,
or get and use their mangled names
(or decorated names, as Microsoft calls it.)
There is no documented way, or an API, to decorate a function name. There's only a somewhat limited but documented API, called
UnDecorateSymbolName
, to do the opposite.
To obtain a decorated name for a function from the Visual Studio compiler, first declare the following macro:
#ifndef _CRT_STRINGIZE
#define _CRT_STRINGIZE_(x) #x
#define _CRT_STRINGIZE(x) _CRT_STRINGIZE_(x)
#endif
//Get mangled name for a function
#define GET_MANGLED_FUNCTION_NAME __pragma(message(__FILE__ _CRT_STRINGIZE((__LINE__): \nfunction:\t) __FUNCSIG__ " is mangled to: " __FUNCDNAME__))
And then if you want to get a mangled name of some specific function, simply add the GET_MANGLED_FUNCTION_NAME
macro somewhere inside
that function definition:
HRESULT SomeFunctionName(int n, const char* pName, int* pOutType = NULL)
{
GET_MANGLED_FUNCTION_NAME;
//...
return S_OK;
}
When you build the project, Visual Studio compiler will output the resulting mangled name of that function into the "Output" debugging window:
1>path-to\source-code.cpp(101):
1>function: long __cdecl SomeFunctionName(int,const char *,int *) is mangled to: ?SomeFunctionName@@YAJHPEBDPEAH@Z
Note that the first line of the output will give you the location where GET_MANGLED_FUNCTION_NAME
was called from, i.e. the source code file and the line of text in it.
Note that although mangled names look like random strings, they are not. They follow a strict, although undocumented, naming convention specific to a C++ compiler. Thus a mangled name of a function will not change from compilation to compilation. It will change only if you change some of the function declaration.
So in this case, if you want to call the C++ function named SomeFunctionName
from your x86-64
Assembly code, you can do it as such:
EXTERN ?SomeFunctionName@@YAJHPEBDPEAH@Z : PROC
.data
msg1 db "Hello world!",0
var1 dq 123
.code
some_Asm_proc PROC
mov ecx, 1
lea rdx, [msg1]
lea r8, [var1]
; SomeFunctionName(int, const char*, int*)
jmp ?SomeFunctionName@@YAJHPEBDPEAH@Z
some_Asm_proc ENDP
END
Note that in our Assembly code sample above we're passing input parameters for the SomeFunctionName
in the rcx
, rdx
and r8
registers,
following the x64 calling convention for Windows.
Macro to Create a "C" Exported Function
This is a very handy way to quickly export a function from your DLL using the "C" naming convention.
First define the following macro somewhere in your globally accessible .h
file:
//Macro to export a function
#define EXPORTED_C_FUNCTION __pragma(comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__))
And, if you want to export some global function, simply do:
HRESULT SomeFunctionName(int n, const char* pName, int* pOutType = NULL)
{
//Export this function
EXPORTED_C_FUNCTION;
//...
return S_OK;
}
The resulting compiled binary will have the SomeFunctionName
symbol added to the Export
table in its PE header:
Thus an outside process can map that binary file dynamically using the
LoadLibrary
API
and then retrieve a pointer to our function using the
GetProcAddress
API, as such:
Macro to Check printf-Type Specifiers
Unfortunately, this feature is available only in the latest Visual Studio compilers. But, it is by far, the handiest one in eliminating printf specifier bugs. Let me explain.
Say, you have some functions that take variadic parameters. For instance, for diagnostic logging. Like these ones:
#include <tchar.h>
void LogDiagnosticMsg(LPCTSTR pszFormat, ...)
{
va_list argList;
va_start(argList, pszFormat);
int nchLen = _vsctprintf(pszFormat, argList);
//... write into log
va_end(argList);
}
void LogDiagnosticMsgInt(int n, LPCTSTR pszFormat, ...)
{
va_list argList;
va_start(argList, pszFormat);
int nchLen = _vsctprintf(pszFormat, argList);
//... write into log
va_end(argList);
}
But unfortunately, it's way too easy to specify the printf format string for such variadic functions incorrectly. Here's an example:
hFile = CreateFile(pFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
LogDiagnosticMsg(L"Failed to open file. Error (%s): %s", GetLastError(), pFilePath); //*** Contains a bug, DO NOT USE! ***
return FALSE;
}
The code above will compile and run just fine for as long as the file specified by the pFilePath
can be opened by the CreateFile
function.
The latent bug lies inside the LogDiagnosticMsg
function call that will crash the process in the form presented above. Why so, you may ask?
If you look at the printf specifiers, or L"Failed to open file. Error (%s): %s"
string, you will notice that they both are set to %s
,
which expects a string pointer, while on the input for that function we provide an int
and a string.
As a result of that, the call to _vsctprintf
inside the LogDiagnosticMsg
function will most certainly cause an exception:
0xC0000005: Access violation reading location ...
To make matters worse, it is also quite easy to type %s
instead of %d
, as the letter s is very close to the letter d on the keyboard.
Unfortunately though, there was no way to catch these conditions at a compilation time, other than just by manually verifying all printf specifiers, which was quite time consuming and error prone. On top of that, the fact that some logging functions may not be even called during a normal testing phase can make the situation with such bugs even worse.
Luckily though, since VC++ version 14.16
(or Visual Studio 2017 version 15.9.0
), the compiler can now automatically check printf format
specifiers for the following functions, and issue a warning during the compilation, if they don't match provided parameters:
_snprintf_s_l
_snprintf_s
_swprintf_l
swprintf
_swprintf_s_l
swprintf_s
_swprintf_p_l
_swprintf_p
_swprintf_c_l
_swprintf_c
_snwprintf_l
_snwprintf
_sprintf_s_l
sprintf_s
_sprintf_p_l
_sprintf_p
_snprintf_l
snprintf
_snprintf
_snprintf_c_l
_snprintf_c
__swprintf_l
_swprintf
_sprintf_l
sprintf
_fwprintf_l
fwprintf
_fwprintf_s_l
fwprintf_s
_fwprintf_p_l
_fwprintf_p
_fprintf_l
fprintf
_fprintf_s_l
fprintf_s
_fprintf_p_l
_fprintf_p
_wprintf_l
wprintf
_wprintf_s_l
wprintf_s
_wprintf_p_l
_wprintf_p
_scwprintf_l
_scwprintf
_scwprintf_p_l
_scwprintf_p
_printf_l
_printf_s_l
printf_s
_printf_p_l
_printf_p
_scprintf_l
_scprintf
_scprintf_p_l
_scprintf_p
printf
That is perfect! But unfortunately though, the compiler cannot yet check the printf specifiers for a custom variadic function, such as our LogDiagnosticMsg
.
Solution
Rbmm and I had to come up with our own macro to take advantage of the functionality of the new compiler.
But first, we need to slightly modify our original variadic functions by adding an underscore to their names (such is needed to merely differentiate them from the debugging macros that we will later define under their original names):
void LogDiagnosticMsg_(LPCTSTR pszFormat, ...)
{
va_list argList;
va_start(argList, pszFormat);
int nchLen = _vsctprintf(pszFormat, argList);
//... write into log
va_end(argList);
}
void LogDiagnosticMsgInt_(int n, LPCTSTR pszFormat, ...)
{
va_list argList;
va_start(argList, pszFormat);
int nchLen = _vsctprintf(pszFormat, argList);
//... write into log
va_end(argList);
}
Then define the following macros that do the actual testing of variadic parameters:
//Macros for testing variadic functions
#if _MSC_VER >= 1916
#define CHECK_VARIADIC(f, ...) (_sntprintf_s(NULL, 0, 0, __VA_ARGS__), f(__VA_ARGS__))
#define CHECK_VARIADIC_P1(f, p, ...) (_sntprintf_s(NULL, 0, 0, __VA_ARGS__), f(p, __VA_ARGS__))
#else
#error Your_compiler_doesnt_support_it
#endif
And finally re-define the variadic functions with our macros:
#ifdef _DEBUG
//Debug
#define LogDiagnosticMsg(...) CHECK_VARIADIC(LogDiagnosticMsg_, __VA_ARGS__)
#define LogDiagnosticMsgWithInt(p, ...) CHECK_VARIADIC_P1(LogDiagnosticMsgInt_, p, __VA_ARGS__)
#else
//Release
#define LogDiagnosticMsg(...) LogDiagnosticMsg_(__VA_ARGS__)
#define LogDiagnosticMsgWithInt(p, ...) LogDiagnosticMsgInt_(p, __VA_ARGS__)
#endif
Note that in the macro definitions above we perform our checks of the variadic functions only in theDebug
configuration build, as the performance cost of inserting ourCHECK_VARIADIC*
macros into the code is quite high.
So now when we build our original buggy code under Debug
configuration, the compiler will output the following warnings
that will help us pinpoint the problem line of code:
1>path-to\source-code.cpp(101,3): warning C4477: '_snwprintf_s' : format string '%s' requires an argument of type 'wchar_t *', but variadic argument 1 has type 'DWORD'
1>path-to\source-code.cpp(101,3): warning C4313: '_snwprintf_s': '%s' in format string conflicts with argument 1 of type 'DWORD'
At that point it becomes trivial to correct the bug:
hFile = CreateFile(pFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
LogDiagnosticMsg(L"Failed to open file. Error (%d): %s", GetLastError(), pFilePath);
return FALSE;
}
Explanation
So how did our variadic macro trickery work?
At first, we needed to override the function declarations with a macro. That's when we added an underscore to the original function name. There's no real rule here, you can modify your function name any (allowed) way you please. I chose to use an underscore at the end.
Then by defining two sets of macros, we can differentiate how our final macro behaves when we build a Debug
configuration
(or the one that will perform all the checks) versus the Release
, or production configuration (that will omit all the checks.)
Finally, the checks themselves are done in the CHECK_VARIADIC*
macros.
Each of those macros simply substitutes every variadic function call with a call to _sntprintf_s
and then with the actual function, using the
comma operator.
To illustrate, the LogDiagnosticMsg
macro call in our buggy sample will unfold into the following sequence of functions during compilation:
(
_sntprintf_s(NULL, 0, 0, L"Failed to open file. Error (%s): %s", GetLastError(), pFilePath),
LogDiagnosticMsg_(L"Failed to open file. Error (%s): %s", GetLastError(), pFilePath)
);
I took a liberty to add spaces and to move each function call to a new line. But the basic principle is this: besides our original LogDiagnosticMsg_
function call,
we're also adding a call to _sntprintf_s
with the same parameters. And _sntprintf_s
is there only to make compiler perform variadic parameter checks during compilation.
The reasons I chose the_sntprintf_s
function are the following:
- It is obviously a variadic function that is checked by the compiler.
- It does not do anything if we pass NULLs into its first parameters.
But, you don't have to use
_sntprintf_s
. You're free to choose some other variadic function from the list of supported by the compiler.
There's one warning I need to give you though!If you are calling functions inside the variadic parameters, repeated calls of which could alter the code-flow of the program, make sure to move them outside first. For example, this snippet of code:
Needs to be re-written into:
C++[Copy]{ int v = AppendValueToFile(pFileName); LogDiagnosticMsg(L"Value is %d", v); return FALSE; }
Because of the
AppendValueToFile
function call inside the variadic parameters, that otherwise would be called twice in theCHECK_VARIADIC
macro.In case of the
GetLastError
call though, in our example above, that function can be called repeatedly without altering the code-flow of the program.
Note that to take advantage of the proposed variadic parameter checks, you do no need to run your compiled code. All the checks will be performed
during the compilation time. Just make sure that the warning C4477
and warning C4313
are not disabled during compilation!
Do not leave the variadic parameter checks described above in your production code, as it will impact its performance!
Video Overview
And lastly, here's a video demonstration of what I described above:
Conclusion
If you feel like you know your own handy debugging macros for the Visual Studio, feel free to share them in the comments below.