Lessons in Windows API programming (C++)

An introduction to Windows API level GUI programming in C++. Doing things at the API level teaches you the fundamentals with most everything concrete and possible to understand. It also allows you to build your own C++ abstractions, which furthers your understanding of how common C++ GUI frameworks work behind the scenes, which in turn makes it easier to use such frameworks.

Lesson 3: The textual process arguments < December 20, 2011

Filed under: c++ programming,programming,tutorial,windows api programming — Alf P. Steinbach @ 4:48 am
  • Contents of this lesson:
  • > The C++ main function signature
  • > Process arguments versus the standard main signature
  • > The command line
  • > The process’s current directories
  • > The process environment

This lesson’s main points:

  • The standard main arguments are ungood for serious Windows programming.
  • Each Windows process has multiple current directories, one per drive, just like in DOS.
  • Much Windows programming support code is roughly the same in many programs (think reuse).

One reaction to this posting was that it must surely be irrelevant to Windows GUI programming. Command line, main arguments? Hey?

Well that’s where it all starts, e.g. when you double-click a file to just “open” it: the program then receives the file name via a command line, which in standard C++ ends up as individual arguments to main.

And one interesting fact is that the C++ standard main signature is utterly useless for reliably transferring such file names in Windows.

> The C++ main function signature

Many C programmers believe that the program execution starts in main. In C++ dynamic initialization of global objects has to be done first, so C++ programmers often have a more realistic view of it, but alas, it’s also often a very unclear view (like, “yes, now that you mention it, there is some magic up there, I think”)… Anyway, the C or C++ startup function main is called from some process initialization code provided by the runtime library, which in turn is called from some machine code level entry point code, e.g. Microsoft’s mainCRTStartup as in lesson 1.

That seems crystal clear – until you think more about it… Then you see that even that can’t be the whole story, because main can have different signatures in different programs. E.g., the C++98 through C++11 standards require that any implementation must support the two signatures int main() and int main( int argc, char* argv[] ). So, OK, the implementation can use magic that’s unavailable to application programmers who are fixated on producing “portable” code. But how does this actually work, how can the common process initialization code know the actual signature of your particular main function – or, doesn’t it need to know?

Let’s check it out:

TODO #1:

  • In Visual C++ Express create a console program project or an ordinary GUI subsystem program project with entry point mainCRTStartup.
  • Add a main C++ source code file with an empty standard main function, like int main(){}.
  • Right click just before the ending right brace } of main, and in the pop up menu select Run to Cursor, or use the keypress equivalent.
  • In the debugger’s call stack pane the first line denotes the current execution position, and the second line denotes the call that put it there. Also the third, fourth etc. lines denote function calls, each a stack frame in the call chain that led down to the current position. Double click the second line (the last executed call), or, for the same effect, right click it and in the pop up menu select Switch to Frame:
  • And here in the calling stack frame you see that with this C++ implementation (Visual C++ 10.0) your no-arguments main function was called with three arguments, namely argc, argv and envp:

I.e., with Visual C++ 10.0 your argument-free main function was called like …

mainret = main(argc, argv, envp);

The main function uses the C calling convention, which means that (1) arguments are passed on the machine stack, and (2) are pushed on the stack in order from right to left (so that the leftmost = first argument is pushed last, just before the function call return address is pushed), and where (3) it is the caller’s responsibility to clean up the stack. The last point means that no matter how many of these arguments the actual implementation declares, it does not matter for the stack cleanup: it is the caller that adjusts the stack pointer up again, by the amount corresponding to the on-stack size of the pushed items. Also, no matter how many of these arguments the actual implementation declares, looking into the machine stack it will find values for them where it expects those values to be – so that it Just Works™.

With Visual C++ 10.0 you can therefore declare main as e.g. int main( int argc ) and expect that to work, unless the compiler goes to unreasonable lengths to detect invalid signatures. And it does work :-). However, since the C++ standard only requires that the implementation supports the two signatures int main() and int main( int argc, char* argv[] ), any other signature is generally non-portable. The call from the Microsoft runtime library code, shown above, corresponds to a third common signature, namely int main( int argc, char* argv[], char* envp[] ), which for some time was used for *nix C programs, and which no longer is fully supported by *nix, but which (ironically) was adopted in Windows and is documented as being supported by Visual C++.

> Process arguments versus the standard main signature

The extended main signature that we encountered above, …

int main( int argc, char* argv[], char* envp[] );

is a near perfect receiver for the process arguments passed to the *nix API function execve, which creates a new process:

int execve( const char *filename, char *const argv[], char *const envp[] );

It’s not surprising that the C main fits perfectly with the *nix API requirements. One might as well say that the *nix API fits the C main perfectly: the language and the API are two sides of the same coin. But how well does that main signature fit with the Windows API?

At the lowest level exposed by [windows.h] a new Windows process is created by a call to the CreateProcess function

BOOL WINAPI CreateProcess(
    LPCTSTR                     lpApplicationName,
    LPTSTR                      lpCommandLine,
    LPSECURITY_ATTRIBUTES       lpProcessAttributes,
    LPSECURITY_ATTRIBUTES       lpThreadAttributes,
    BOOL                        bInheritHandles,
    DWORD                       dwCreationFlags,
    LPVOID                      lpEnvironment,
    LPCTSTR                     lpCurrentDirectory,
    LPSTARTUPINFO               lpStartupInfo,
    LPPROCESS_INFORMATION       lpProcessInformation
    );

where the passed-in textual information is comprised of (1) a command line (corresponding to the argc+argv arguments collection in *nix); (2) a set of environment variables, where each environment variable is a name+value pair of strings; and (3) a current directory path.

All this textual information is UTF-16 encoded, and since char in Windows means Windows ANSI encoding, the process arguments supported by CreateProcess do not fit very well with the standard C and C++ main function’s char-based arguments. After conversion from UTF-16 encoded text to Windows ANSI encoded main arguments, much critical information will generally have been lost, simply because it can’t be represented in Windows ANSI. For example, the standard main arguments can tell the program that the user wants to open a file named [?????.txt] (literally!), when the user actually specified [кошка.txt], e.g. by dragging that file onto the executable…

#include <sstream>          // std::wostringstream
using namespace std;

#include <winapi/wrapper/windows_h.h>   // Just a wrapper for [windows.h].

int main( int argc, char* argv[] )
{
    wostringstream  text;
    if( argc == 0 )
    {
        text << L">> no arguments given <<";
    }
    else
    {
        for( int i = 0;  i < argc;  ++i )
        {
            text << L"#" << i << L":\t[" << argv[i] << L"]\n";
        }
    }

    DWORD const infoBoxOptions = MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND;
    MessageBox( 0, text.str().c_str(), L"main function arguments:", infoBoxOptions );
}

TODO #2:

  • In Visual C++ Express create a console program project or an ordinary GUI subsystem program project with entry point mainCRTStartup.
  • Add a main C++ source code file with the C++ code shown above, except that it may be necessary to use your own way of including [windows.h].
  • Make it compile and run OK, with result like
  • In the project settings dialog, drill down to [Configuration PropertiesDebugging], click in the Command Arguments value’s edit field, and add some text with probably non-Windows-ANSI characters in it such as “Look, it’s a 日本国 кошка that likes Norwegian blåbærsyltetøy!”:
  • It doesn’t matter if you now run the program in the debugger (e.g. keypress [F5]) or stand-alone (e.g. keypress [Ctrl F5]. The specified command line arguments will be used. And the message box will then report how they were received via the main function’s arguments:

A lesser problem is that parsing the command line into individual arguments, as main requires, loses information about the whitespace between the arguments. Thus, a for-Windows echo command corresponding to the *nix echo, serving to tell the user exactly what would be passed to a process, cannot be written in pure C++ that only uses the C++ standard library. Gasp! 🙂

But the inability to write a faithful Windows echo in only standard C++ is really just a symptom of a design mismatch, that the C++ main is designed for the process arguments of a different operating system than Windows, namely *nix.

This design mismatch results in the much more serious [кошка.txt] → [?????.txt] problem exemplified above, where the user can’t use our standard C++ program to process files that have non-Windows-ANSI characters in their names.

> The command line

Happily, the (Unicode version of the) Windows API function GetCommandLine returns a pointer to the UTF-16 encoded command line as it’s stored in statically allocated memory. There is also a function that provides a “standard” parse of a command line, called CommandLineToArgvW. This standard command line parser function does not expand wildcards like * and ?.

Unlike GetCommandLine, CommandLineToArgvW is only available in Unicode version. Presumably the idea is that if you need parsing then maybe you are expecting filenames, and then you should better not rely on the results of an ANSI parsing function… Another difference is that while GetCommandLine returns a pointer to a statically allocated memory area, CommandLineToArgvW returns a pointer to dynamically allocated memory, which must be deallocated via LocalFree.

The deallocation requirement makes CommandLineToArgvW a bit unsafe; one might end up leaking memory. However, if one but ignores efficiency concerns then it’s quite easy to copy the result to a vector<wstring> and provide exception safe guaranteed deallocation of the original. For example, in C++11 (or for C++03 with the Boost library) it’s incredibly easy to define a ScopeGuard class similar to the classic ScopeGuard class by Petru Marginean and Andrei Alexandrescu, where the main idea is to execute a specified cleanup function in a local variable’s destructor. One difference here is that this ScopeGuard does intentionally not catch any exceptions (I got burned once by the catch-all in Marginean’s ScopeGuard). And another difference is that here the programmer has to choose a name for the local variable, e.g. calling it cleanup, instead of relying on automatic name generation in a macro.

#include <assert.h>         // assert
#include <functional>       // std::function
#include <sstream>          // std::wostringstream
#include <stddef.h>         // ptrdiff_t
#include <stdexcept>        // std::runtime_error, std::exception
#include <stdlib.h>         // EXIT_FAILURE, EXIT_SUCCESS
#include <string>           // std::wstring
#include <vector>           // std::vector
using namespace std;

#include <winapi/wrapper/windows_h.h>   // A [windows.h] wrapper -- u can use your own!
#include <shellapi.h>                   // CommandLineToArgvW

// General C++ support:
namespace cpp {
    typedef ptrdiff_t       Size;
    typedef Size            Index;

    class ScopeGuard
    {
    private:
        function< void() >  cleanup_;

        ScopeGuard( ScopeGuard const& );                    // No such.
        ScopeGuard& operator=( ScopeGuard const& );         // No such.

    public:
        ScopeGuard( function< void() > const& f ): cleanup_( f ) {}
        ~ScopeGuard() { cleanup_(); }
    };  // ScopeGuard

    bool hopefully( bool const condition )  { return condition; }
    bool throwX( string const& s )          { throw std::runtime_error( s ); }
}    // namespace cpp


class CommandLineArgs
{
private:
    vector< wstring >   arguments_;

public:
    cpp::Size count() const { return arguments_.size(); }
    wstring const& operator[]( cpp::Index i ) const { return arguments_[i]; }
    wstring const& at( cpp::Index i ) const { return arguments_.at( i ); }

    wstring const*  begin() const   { return &arguments_[0]; }
    wstring const*  end() const     { return begin() + count(); }

    explicit CommandLineArgs( wstring const& commandLine = GetCommandLine() )
    {
        using namespace cpp;        // hopefully, throwX, ScopeGuard

        int                     n;
        wchar_t const* const*   a   = ::CommandLineToArgvW( commandLine.c_str(), &n );
        hopefully( a != 0 )
            || throwX( "CommandLineArgs::<init>: CommandLineToArgvW failed" );

        ScopeGuard cleanup( [a](){ ::LocalFree( const_cast< wchar_t** >( a ) ); } );
        arguments_.reserve( n );
        for( int i = 0;  i < n;  ++i ) { arguments_.push_back( a[i] ); }
    }
};


int main()
{
    wstring const           commandLine = GetCommandLine();
    CommandLineArgs const   args( commandLine );

    wostringstream  text;
    text
        << L"Command line:\n"
        << L"[" << commandLine << L"]\n"
        << L"\n";
    if( args.count() == 0 )
    {
        text << L">> no arguments given <<";
    }
    else
    {
        text << L"As individual arguments:\n";
        for( cpp::Index i = 0;  i < args.count();  ++i )
        {
            text << L"#" << i << L":\t[" << args[i] << L"]\n";
        }
    }

    DWORD const infoBoxOptions = MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND;
    MessageBox( 0, text.str().c_str(), L"Command line info:", infoBoxOptions );
}

Yay! It works!

TODO #3:

  • Try the above program with command line arguments “blah "di dah" """uh oh""" """"he he""""”.
  • Find documentation of the parsing that CommandLineToArgvW does.
  • Can you construct a command line that yields any arbitrary set of arguments? Note: the author does not know the answer to this question. It may be trivial, or difficult, or perhaps just undocumented.

If you want a little challenge, think about designing a different safe C++ wrapper for CommandLineToArgvW, one that maybe sacrifices some functionality in order to retain the sheer raw efficiency of the API function.

> The process’s current directories

Windows’s file system is multi-rooted, with each drive as a separate root. The drives that are accessible via the ordinary path notation have each a drive letter. For example, the main drive where Windows is installed is usually drive C (with drive letters A and B reserved for archaic floppy disk drives), and to refer to that drive one appends a colon to the drive letter, like “C:”.

Each process maintains a separate set of current directories, one for each drive. Each drive’s current directory is used to complete any relative path on that drive. A relative path such as “.”, that doesn’t refer directly to any drive, is assumed to refer to the process’ current drive. Since the current directories are per process settings this scheme is in theory not particularly thread-safe. But as a practical matter it is not much of a problem either.

The process’ current drive + the current directory on that drive, is called “the current directory” for the process (and yes, the terminology here is severely lacking! :-)). For now let’s just ignore how the per drive current directories are passed to a new process. But the current directory path is passed in as a direct argument to CreateProcess, and is then available in the new process via the GetCurrentDirectory function, and can be changed via the SetCurrentDirectory function.

#include <bitset>           // std::bitset
#include <sstream>          // std::wostringstream
#include <stdexcept>        // std::runtime_error, std::exception
#include <string>           // std::wstring
#include <vector>           // std::vector
using namespace std;

#include <winapi/wrapper/windows_h.h>   // A [windows.h] wrapper -- u can use your own!

// General C++ support:
namespace cpp {
    bool hopefully( bool const condition )  { return condition; }
    bool throwX( string const& s )          { throw std::runtime_error( s ); }
}    // namespace cpp

namespace winapi {
    using cpp::hopefully;
    using cpp::throwX;

    wstring currentDirectory()
    {
        wstring     result( MAX_PATH, L'#' );
        DWORD const nCharacters = GetCurrentDirectory( result.size(), &result[0] );
        hopefully( nCharacters > 0 )
            || throwX( "winapi::currentDirectory: GetCurrentDirectory failed" );
        result.resize( nCharacters );
        return result;
    }

    wstring fullPathFor( wstring const& path )
    {
        wstring     result( MAX_PATH, L'#' );
        DWORD const nCharacters = GetFullPathName(
            path.c_str(), result.size(), &result[0], nullptr
            );
        hopefully( nCharacters > 0 )
            || throwX( "winapi::fullPathFor: GetFullPathName failed" );
        result.resize( nCharacters );
        return result;
    }

    bitset<32> logicalDrives()
    {
        // GetLogicalDrives() produces a 32-bit DWORD. It needs to be casted
        // to either int or long long due to a bug in the Visual C++ 10.0
        // std::bitset. Ironically, that bug was introduced due to a bugfix.
        return static_cast<int>( GetLogicalDrives() );
    }
}  // namespace winapi

int main()
{
    wostringstream  text;

    text << L".\t→ [" << winapi::fullPathFor( L"." ) << L"]\n";
    text << L"\n";
    bitset<32> const    drives  = winapi::logicalDrives();
    for( int i = 0;  i < int( drives.size() );  ++i )
    {
        if( drives[i] != 0 )
        {
            wchar_t const driveLetter   = wchar_t( 'A' + i );
            wstring const relativePath  = wstring() + driveLetter + L":.";
            wstring const absolutePath  = winapi::fullPathFor( relativePath );

            text << relativePath << L"\t→ [" << absolutePath << L"]\n";
        }
    }
    text << L"\n";
    text << L"GetCurrentDirectory() → [" << winapi::currentDirectory() << L"]";

    DWORD const infoBoxOptions  = MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND;
    wchar_t const title[]       = L"Current directories for this process:";
    MessageBox( 0, text.str().c_str(), title, infoBoxOptions );
}

The MAX_PATH constant in the code above is about 260 characters. Longer paths, up to about 32K characters, can be used by prepending a special prefix, but most applications (and in particular Windows Explorer) can’t deal with such very long paths, so it is a good idea to restrict oneself to MAX_PATH character paths.

A common novice question is how one can find the executable’s directory, which is not necessarily the same as the process’ initial current directory?

Well, that’s easy: simply call GetModuleFileName with a null argument:

#include <sstream>          // std::wostringstream
#include <stdexcept>        // std::runtime_error, std::exception
#include <string>           // std::wstring
using namespace std;

#include <winapi/wrapper/windows_h.h>   // A [windows.h] wrapper -- u can use your own!
#include <shlwapi.h>                    // PathRemoveFileSpec

// General C++ support:
namespace cpp {
    bool hopefully( bool const condition )  { return condition; }
    bool throwX( string const& s )          { throw std::runtime_error( s ); }
}    // namespace cpp

namespace winapi {
    using cpp::hopefully;
    using cpp::throwX;

    wstring currentDirectory()
    {
        wstring     result( MAX_PATH, L'#' );
        DWORD const nCharacters = GetCurrentDirectory( result.size(), &result[0] );
        hopefully( nCharacters > 0 )
            || throwX( "winapi::currentDirectory: GetCurrentDirectory failed" );
        result.resize( nCharacters );
        return result;
    }

    wstring directoryPartOf( wstring path )
    {
        path += L'\0';
        if( PathRemoveFileSpec( &path[0] ) )
        {
            path.resize( wcslen( &path[0] ) );
        }
        return path;
    }

    wstring moduleFilePath( HMODULE moduleHandle = 0 )
    {
        wstring     result( MAX_PATH, L'#' );
        DWORD const nCharacters = GetModuleFileName(
            moduleHandle, &result[0], result.size()
            );
        hopefully( nCharacters > 0 )
            || throwX( "winapi::moduleFilePath: GetModuleFileName failed" );
        result.resize( nCharacters );
        return result;
    }
}  // namespace winapi

int main()
{
    wstring const   exeFilePath = winapi::moduleFilePath();
    wstring const   exeDirPath  = winapi::directoryPartOf( exeFilePath );

    wostringstream  text;

    text << L"exe\t→ [" << exeDirPath << L"]\n";
    text << L"\n";
    text << L".\t→ [" << winapi::currentDirectory() << L"]\n";

    DWORD const infoBoxOptions  = MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND;
    wchar_t const title[]       = L"Executable and current directories for this process:";
    MessageBox( 0, text.str().c_str(), title, infoBoxOptions );
}

When you try out the program above you may get some linker error message. The problem is that the [shlwapi.lib] DLL import library, which provides the PathRemoveFileSpec function, is not linked in by default. In the project settings simply drill down to [Configuration PropertiesLinkerInput], click on the value name Additional Dependencies (or in its edit field), in the edit field’s drop down list select Edit…, and in the dialog that pops up just add “shlwapi.lib”, without the quotes, of course.

> The process environment

Now, you’ve been running around in circles waiting for the explanation of how the set of current directories is transferred into a new process. Well, they are apparently transferred as special environment variables, with weird names like “=C:”. Anyway they were transferred that way in old DOS, and those special environment variables are still there in modern 32-bit and 64-bit Windows.

Unfortunately the set of environment variables can easily be too large to present in a simple message box.

Hence, in the program below I chose to simply write them to a text file (they’re just text strings, after all!), and open that file in its default handler, which will typically be the Notepad editor:

#include <codecvt>          // std::codecvt_utf8
#include <fstream>          // std::wofstream
#include <memory>           // std::unique_ptr
#include <stdexcept>        // std::runtime_error, std::exception
#include <stdlib.h>         // system
#include <string>           // std::wstring
using namespace std;

#include <winapi/wrapper/windows_h.h>   // A [windows.h] wrapper -- u can use your own!

// General C++ support:
namespace cpp {
    template< class Type >
    Type implicitCast( Type const v ) { return v; }

    bool hopefully( bool const condition )  { return condition; }
    bool throwX( string const& s )          { throw std::runtime_error( s ); }
}    // namespace cpp

namespace winapi {
    using namespace cpp;        // implicitCast, hopefully, throwX

    class Env
    {
    private:
        struct Deleter
        {
            void operator()( wchar_t const* pBlock )
            {
                ::FreeEnvironmentStrings( const_cast< wchar_t* >( pBlock ) );
            }
        };

        unique_ptr< wchar_t const, Deleter >    pBlock_;

    public:
        Env()
            : pBlock_( implicitCast< wchar_t const* >( ::GetEnvironmentStrings() ) )
        {
            hopefully( pBlock_.get() != 0 )
                || throwX( "winapi::Env::<init>: GetEnvironmentStrings failed" );
        }

        class It
        {
        friend class Env;
        private:
            wchar_t const*  p_;

            It( wchar_t const* const p ): p_( p ) {}

        public:
            wchar_t const* string() const   { return p_; }
            bool isAtEnd() const            { return !*p_; }
            void advance()                  { p_ += wcslen( p_ ) + 1; }
        };

        It first() const { return It( pBlock_.get() ); }
    };
}  // namespace winapi

int main()
{
    using namespace cpp;        // hopefully, throwX

    string const    filename    = "environment.txt";
    wofstream   text( filename.c_str() );
    hopefully( !text.fail() )
        || throwX( "main: failed to open file \"" + filename + "\" for writing." );
    text.imbue( locale( locale(), new codecvt_utf8<wchar_t>() ) );

    winapi::Env environment;
    for( auto it = environment.first();  !it.isAtEnd();  it.advance() )
    {
        text << it.string() << endl;
    }
    text.close();

    string const command = string() + "start \"\" \"" + filename + "\"";
    system( command.c_str() );
}

As with the command line, the standard C++ machinery such as the getenv function from the C library, is not adequate to handle Windows’ environment strings:

#ifdef  _MSC_VER
#   pragma warning( disable: 4996 )     // The C++ standard library is deprecated!
#endif

#include <sstream>          // std::wostringstream
#include <stdlib.h>         // getenv
#include <string>           // std::wstring
using namespace std;

#include <winapi/wrapper/windows_h.h>   // A [windows.h] wrapper -- u can use your own!

// General C++ support:
namespace cpp {
    wstring widened( char const* s )
    {
        wostringstream      stream;

        stream << s;
        return stream.str();
    }
}    // namespace cpp

int main()
{
    using cpp::widened;

    wstring const   text    =
        widened( getenv( "SILLYTEXT" ) ).c_str();
    wstring const   title   =
        L"Hi " + widened( getenv( "USERNAME" ) ) + L"!" +
        L" It's the \"getenv\" function reporting here!";
    
    DWORD const infoBoxOptions  = MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND;
    MessageBox( 0, text.c_str(), title.c_str(), infoBoxOptions );
}

One way to reproduce the above result (there are many different ways to do it!) is to tell Visual Studio to define the SILLYTEXT environment variable when it runs your program:

TODO #4:

  • Create a Visual C++ project with entry point mainCRTStartup and add the above code, except possibly doing your own include of [windows.h].
  • In the project settings dialog drill down to [Configuration PropertiesDebugging], click the Environment value (or its edit field) in that list, and in the edit field’s drop down list, select Edit…;
  • In the dialog that pops up, define the SILLYTEXT environment variable as e.g. “SILLYTEXT=Maybe it’s a 日本国 кошка that likes Norwegian blåbærsyltetøy?”;
  • Click the OK button; you’ll get back to the main project settings dialog with the edit field updated:
  • Run the program.

So, the standard C++ library’s getenv function is not quite up to the task. In Windows. And besides it’s a bit dangerous, returning a pointer to a memory area that it may overwrite in the next call.

The Windows API provides a more complete and reliable set of functions for accessing the environment variables, namely GetEnvironmentVariable and SetEnvironmentVariable, as well as the GetEnvironmentStrings and FreeEnvironmentStrings functions used in the general listing program.

#include <assert.h>         // assert
#include <sstream>          // std::wostringstream
#include <stdexcept>        // std::exception, std::runtime_error
#include <stdlib.h>         // getenv, EXIT_SUCCESS, EXIT_FAILURE
#include <string>           // std::wstring
using namespace std;

#include <winapi/wrapper/windows_h.h>   // A [windows.h] wrapper -- u can use your own!

// General C++ support:
namespace cpp {
    bool hopefully( bool const cond )   { return cond; }
    bool throwX( string const& s )      { throw std::runtime_error( s ); }
}    // namespace cpp

namespace winapi {
    using namespace cpp;        // hopefully, throwX

    wstring environmentVar( wchar_t const* const name )
    {
        DWORD const bufSize = GetEnvironmentVariable( name, nullptr, 0 );

        wstring result( bufSize, L'#' );
        DWORD const nCharacters = GetEnvironmentVariable( name, &result[0], bufSize );
        assert( nCharacters <= bufSize );
        hopefully( nCharacters > 0 )
            || throwX( "winapi::EnvironmentVar: GetEnvironmentVariable failed" );
        result.resize( nCharacters );
        return result;
    }

}  // namespace winapi

int main()
{
    try
    {
        wstring const   text    = winapi::environmentVar( L"SILLYTEXT" );

        wstring const   title   =
            L"Hi " + winapi::environmentVar( L"USERNAME" ) + L"!" +
            L" It's the \"GetEnvironmentVariable\" function reporting here!";

        DWORD const infoBoxOptions  = MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND;
        MessageBox( 0, text.c_str(), title.c_str(), infoBoxOptions );
        return EXIT_SUCCESS;
    }
    catch( exception const& x )
    {
        DWORD const errorBoxOptions = MB_OK | MB_ICONERROR | MB_SETFOREGROUND;
        char const* const title = "Sorry, failure:";
        MessageBoxA( 0, x.what(), title, errorBoxOptions );
    }
    return EXIT_FAILURE;
}

TODO #5:

  • Find all examples of wrapping API functions in this blog posting. What has been the main purpose or advantage in each case? Are there any common techniques and patterns, perhaps?
  • Test your understanding: what exactly can go wrong if you use standard C++ main arguments instead of Windows’ GetCommandLine and friends?
  • Write a concise summary of this blog posting! 🙂

Cheers, & enjoy!,
– Alf

 

6 Responses to “Lesson 3: The textual process arguments <”

  1. MFC Tips Says:

    Alf, your hopefully and throwX functions are the best! Are they your invention or from a coding standard? The syntax in which you use them, although it looks unusual at first, really help make clean and easy-to-follow code. These functions, and the way you use them, deserve an article all to themselves.

    FYI: when attempting to use these with Windows CE, I got an unresolved external symbol with string::npos. Adding the following fixes that, but I’m sure there is a better way:

    template std::wstring::size_type std::wstring::npos;

    Thanks

    • Maybe you have run into an issue with the Windows CE UPnP string class, which is also called wstring?

      Regarding credit, I find it difficult to honestly take credit for anything. Every idea under the sun has been thought of earlier by someone else, and the main concept and pattern of “do X successfully || report failure” is common in many scripting languages, e.g. in Perl. I started using this kind of pattern in the late 1990’s, then with a >> operator to propagate a function result into a success/failure checker.

      Maybe hopefully and throwX is at the right level of simplicity for practical use. I did write a blog article about a more general scheme, with a >> operator. That more general scheme contains hopefully/throwX as a special case.

  2. […] recently discovered this style from Alf P. Steinback: Learn Windows API I’ve seen similar coding style before, but his examples are the most […]

  3. Seems I could not link in the function PathRemoveFileSpec when running with VS Pro 2010 and Windows 7 Home Premium 64 bit, what could be the matter – I get:

    >main.2.obj : error LNK2019: unresolved external symbol __imp__PathRemoveFileSpecW…

    using the code in this document and following rec’s about linkage.

  4. Used wrong library settings alternative, not using input.
    Using input settings it works!

  5. Thank you for posting “Lesson 3: The textual process arguments


Leave a comment