- 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, likeint main(){}
.- Right click just before the ending right brace
}
ofmain
, 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, namelyargc
,argv
andenvp
:
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 signaturesint main()
andint 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, namelyint 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
signatureThe 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 Cmain
perfectly: the language and the API are two sides of the same coin. But how well does thatmain
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 byCreateProcess
do not fit very well with the standard C and C++main
function’schar
-based arguments. After conversion from UTF-16 encoded text to Windows ANSI encodedmain
arguments, much critical information will generally have been lost, simply because it can’t be represented in Windows ANSI. For example, the standardmain
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 Properties → Debugging], 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-Windowsecho
command corresponding to the *nixecho
, 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, calledCommandLineToArgvW
. 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 whileGetCommandLine
returns a pointer to a statically allocated memory area,CommandLineToArgvW
returns a pointer to dynamically allocated memory, which must be deallocated viaLocalFree
.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 avector<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 aScopeGuard
class similar to the classicScopeGuard
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 thisScopeGuard
does intentionally not catch any exceptions (I got burned once by the catch-all in Marginean’sScopeGuard
). And another difference is that here the programmer has to choose a name for the local variable, e.g. calling itcleanup
, 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 theGetCurrentDirectory
function, and can be changed via theSetCurrentDirectory
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 toMAX_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 Properties → Linker → Input], 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 Properties → Debugging], 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
andSetEnvironmentVariable
, as well as theGetEnvironmentStrings
andFreeEnvironmentStrings
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
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
andthrowX
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 containshopefully
/throwX
as a special case.[…] recently discovered this style from Alf P. Steinback: Learn Windows API I’ve seen similar coding style before, but his examples are the most […]
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.
Used wrong library settings alternative, not using input.
Using input settings it works!
Thank you for posting “Lesson 3: The textual process arguments