C++ Standard Library Gotcha: Include Headers Explicitly

tcppplI was using the boost::log library component and when compiling my code, I ran into a rather odd error converting a derived shared pointer to a base class shared pointer, i.e. implicitly converting a shared_ptr<std::ofstream> to a const shared_ptr<std::ostream> &. Here’s the code snippet, with the line causing the compiler error highlighed. Note that code has been simplified for clarity.

using namespace boost::log;

typedef sinks::asynchoronous_sink<
    sinks::text_ostream_backend> TextSink;

bool Initialize(const std::string &logFile)
{
    bool didSucceed = false;

    try
    {
        auto sink = boost::make_shared<TextSink>();
        auto lfstream 
            = boost::make_shared<std::ofstream>(logFile);
        sink->locked_backend()->add_stream(lfstream);
        core::get()->add_sink(sink);
        BOOST_LOG(lgr_gtl) << "LogSystem initialized...";
        core::get()->flush();
 
        didSucceed = true;
    }
    catch (const std::exception& ex)
    {
        std::cout
            << "LogSystem::Initialize() failed."
            << std::endl;
        std::cout << "Details:" << std::endl;
        std::cout << ex.what() << std::endl;
    }
    return didSucceed;
}

The compiler, Visual Studio 2015 Update 1, spat out the error pointing to the bold line above:

error C2664: 'void boost::log::v2s_mt_nt6::sinks::basic_text_ostream_backend<char>::add_stream(const boost::shared_ptr<std::basic_ostream<char,std::char_traits<char>>> &)': cannot convert argument 1 from 'boost::shared_ptr<T>' to 'const boost::shared_ptr<std::basic_ostream<char,std::char_traits<char>>> &'
    with
    [
        T=std::ofstream
    ]
note: Reason: cannot convert from 'boost::shared_ptr<T>' to 'const boost::shared_ptr<std::basic_ostream<char,std::char_traits<char>>>'
    with
    [
        T=std::ofstream
    ]
note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

I was flummoxed. std::ofstream is a typedef for std::basic_ofstream, which is a class derived from basic_ostream. So, there should be no reason why the error should be thrown. The conversion from non-const to const shouldn’t be an issue either, though it’s a red herring and would cause most folks to look at that first and maybe add a const qualifier. Adding the const qualifier won’t make a jot of difference.

I looked up the definition of std::ofstream and sure enough, it looked like this:

typedef basic_ofstream<char, char_traits<char> > ofstream;

and a little further up in the same file is a forward declaration for basic_ofstream, which looks like this:

template<class _Elem, class _Traits = char_traits<_Elem> >
    class basic_ofstream;

The one odd thing was that Intellisense found this in the iosfwd header file and was a forward declaration. But I wanted the definition, which Intellisense stubbornly refused to show me. So, I tried finding all references which only yielded the earlier mentioned ofstream definition basic_ostream forward declaration. Even more odd.

The standard says std::ofstream is defined in the fstream header. So, I cracked that open to find the definition of std::basic_ofstream, which was just as I had expected:

template<class _Elem, class _Traits> class basic_ofstream
    : public basic_ostream<_Elem, _Traits>
{ // output stream associated with a C stream
...
...
...
};

Now, I didn’t #include <iosfwd> explicitly anywhere. It was getting included by one of the standard C++ headers or one of the boost headers. MSDN says, of iosfwd:

Declares forward references to several template classes used throughout iostreams. All such template classes are defined in other standard headers. You include this header explicitly only when you need one of its declarations, but not its definition.

I figured, more along the lines of “Let me throw this at the wall and see if it sticks.”, that I should include the fstream header explicitly. In hindsight, seeing that Intellisense could only show me the forward declaration, it should’ve been the first thing to try.

So, I explicitly added a #include <fstream> to my code. Voilà! The error disappeared and the code compiled just fine.

It’s a tad irritating (Who am I kidding? It’s outright frustrating.) when the compiler throws out an error like the one above, but the definitions and declarations seem to be present – yeah, in this case a forward declaration only, but I would expect the compiler to complain that it didn’t have enough information (in this case the definition) to determine whether ofstream / basic_ofstream was derived from basic_ostream.

Lesson learned: Explicitly include headers that contain the definitions, do not rely on indirect inclusions.

Powershell HOWTO: Pass Command Line Arguments From One Script to Another

Scenario

PowerShell_5.0_iconI have to invoke different sets of scripts conditionally for setting up the build environment based on which project(s) I want to build. This is related to porting my build environment scripts from bash to Powershell.

Let’s say, push0.ps1 is the main script and there are three other scripts, viz. quake.push0.ps1, doom.push0.ps1 and wolf.push0.ps1. I want to invoke push0.ps1 with some arguments from the command line and have push0.ps1 invoke one of the *.push0.ps1 scripts while passing all the command line arguments that push0.ps1 was invoked with.

A bit of code would help make the example concrete:

push0.ps1
# $args is the array of input arguments to any script
# Do some work here common to all projects
# like pushing code to the master repo for developer scripts, etc.

# invoke project specific scripts here
# $Env:Project will be set to quake, doom or wolf
# call project specific push0 script if it exists 
# and pass all arguments to it
$scriptName = $Env:Project + ".push0.ps1"
if (Test-Path $scriptName) {
    & $scriptName $args
}

quake.push0.ps1
# do quake specific work here
$pushFolder = Join-Path $Env:QuakeBaseDir "win32"
cd $pushFolder
hg push

$winSpecificFolder = Join-Path $pushFolder $Env:QuakeWinSubDir
cd $winSpecificFolder
hg push

# now push any external sub-repos
$quakeExtBaseDir = Join-Path $Env:ExtBaseDir $QuakeExtWin32SubDir
$args | % {
    $extFolder = Join-Path $quakeExtBaseDir $_
    cd $extFolder
    hg push
}

doom.push0.ps1
# do doom specific work here
$pushFolder = Join-Path $Env:DoomBaseDir "win16"
cd $pushFolder
hg push

$win16SpecificFolder = Join-Path $pushFolder $Env:DoomWin16SubDir
cd $win16SpecificFolder
hg push

# now push assembly language library repos
$doomAsmBaseDir = Join-Path $Env:AsmLibBaseDir $doomAsmSubDir
$args | % {
    $asmFolder = Join-Path $doomAsmBaseDir $_
    cd $asmFolder
    hg push
}

wolf.push0.ps1
# do wolf specific work here
$pushFolder = Join-Path $Env:WolfBaseDir "dos"
cd $pushFolder
hg push

$dosSpecificFolder = Join-Path $pushFolder $env:WolfDosSubDir
cd $dosSpecificFolder
hg push

# now push dos assembly language library repos
$wolfAsmBaseDir = Join-Path $Env:AsmLibBaseDir $wolfAsmSubDir
$args | % {
    $asmFolder = Join-Path $wolfAsmBaseDir $_
    cd $asmFolder
    hg push
}

As you can see from the example above, the three project specific scripts push different sub-repos that are passed in as parameters.

Problem

The three project specific scripts {quake|doom|wolf}.push0.ps1, work fine as they are now, if they are individually invoked from Powershell. But if they are  invoked from another Powershell script, then they fail processing the arguments passed in via $args.

The issue is that when $args is passed to push0.ps1, it’s an array of parameters, but when you invoke another script from within push0.ps1, the $args array (i.e. all the parameters) is treated as a single parameter passed to the callee script. If I add a line to print out the arguments in quake.push0.ps1 like this:

quake.push0.ps1
...
...
Write-Host "Quake:- args[=" $args.Count "]: " $args
...
...

And invoke push0.ps1 passing in some arguments, then the output looks like this:

$> .\push0.ps1 boost libssl lua
Quake:- args[=1]: boost libssl lua

As you can see, even though all 3 parameters are passed in, they are seen as a single parameter.

Essentially it would appear, that when the callee script is invoked, all 3 parameters as passed in as $args[0]. Now, Powershell is an object scripting language and it seems to pass an array of strings from the caller script as the first and only parameter to the callee script.

Knowing that since Powershell is built on top of .NET and deals with objects, we can verify this by checking the type of $args[0] like this:

quake.push0.ps1
...
...
Write-Host $args[0].GetType().IsArray
...
...

OUTPUT
True

And we’re in business! :-)

Solution

What we need to do in the callees, is check if the incoming $args has only 1 parameter and if it’s an array. If so, then simply use $args[0] as the actual list of arguments and process it below.

Since this bit of the code is common to multiple scripts, I put it in a function and stick it in the $PROFILE file or some other file that can be dot-sourced. Here’s the function:

function UnwrapArguments($params) {
    $unwrapped = @()
    if ($params.Count -gt 0) {
        if ($params.Count -eq 1 -and $params[0].GetType().IsArray) {
            $unwrapped = $params[0]
        } else {
            $unwrapped = $params
        }
    }
    return $unwrapped
}

So our scripts end up looking like this (changes in blue):

push0.ps1
# NOTE: No changes to the caller.
# $args is the array of input arguments to any script
# Do some work here common to all projects
# like pushing code to the master repo for developer scripts, etc.

# invoke project specific scripts here
# $Env:Project will be set to quake, doom or wolf
# call project specific push0 script if it exists 
# and pass all arguments to it
$scriptName = $Env:Project + ".push0.ps1"
if (Test-Path $scriptName) {
    & $scriptName $args
}

quake.push0.ps1
$myargs = UnwrapArguments($args)

# do quake specific work here
$pushFolder = Join-Path $Env:QuakeBaseDir "win32"
cd $pushFolder
hg push

$winSpecificFolder = Join-Path $pushFolder $Env:QuakeWinSubDir
cd $winSpecificFolder
hg push

# now push any external sub-repos
$quakeExtBaseDir = Join-Path $Env:ExtBaseDir $QuakeExtWin32SubDir
$myargs | % {
    $extFolder = Join-Path $quakeExtBaseDir $_
    cd $extFolder
    hg push
}

doom.push0.ps1
$myargs = UnwrapArguments($args)

# do doom specific work here
$pushFolder = Join-Path $Env:DoomBaseDir "win16"
cd $pushFolder
hg push

$win16SpecificFolder = Join-Path $pushFolder $Env:DoomWin16SubDir
cd $win16SpecificFolder
hg push

# now push assembly language library repos
$doomAsmBaseDir = Join-Path $Env:AsmLibBaseDir $doomAsmSubDir
$myargs | % {
    $asmFolder = Join-Path $doomAsmBaseDir $_
    cd $asmFolder
    hg push
}

wolf.push0.ps1
$myargs = UnwrapArguments($args)

# do wolf specific work here
$pushFolder = Join-Path $Env:WolfBaseDir "dos"
cd $pushFolder
hg push

$dosSpecificFolder = Join-Path $pushFolder $env:WolfDosSubDir
cd $dosSpecificFolder
hg push

# now push dos assembly language library repos
$wolfAsmBaseDir = Join-Path $Env:AsmLibBaseDir $wolfAsmSubDir
$myargs | % {
    $asmFolder = Join-Path $wolfAsmBaseDir $_
    cd $asmFolder
    hg push
}

That’s it. We’re golden.

[SOLVED] Cocos Studio: “Cocos Framework is not installed” Error

The Problem

cocos2d-xInstall Cocos (without the framework) and cocos2d-x separately. Cocos2d-x works fine independently – I can create C++ (as well as Lua and JavaScript) projects just fine and build the C++ projects in Visual Studio 2015. Cocos Studio, unfortunately doesn’t recognize that cocos2d-x is installed and won’t let me create C++ projects that I can export and use in my game written in C++.

In Cocos Studio, I get the error:

Cocos Framework is not installed

The Preference menu item in the Edit menu looks like this:

cocos-framework-is-not-installed

When I try to create a project and then add C++ as an option later, it looks like this (no engine is recognized and C++ radio option in the Project Language section is grayed out):

cocos-studio-no-engine

Easy Solution

On Windows (7 and 10), the Cocos for Win with framework 2.3.3 installation works right out of the box. So, if you want to get on with your gamedev life, just install that version and you’ll be golden.

TL;DR Version

Note: I had troubles with earlier Cocos Studio versions (2.3.2.3, both with and without the framwork included) and jumped through a bunch of hoops with the non-framework installation package in order to try and get things working. Also, I realized that it’s better to use the non-framework installation package and install multiple framework versions separately, in order to be able to use them (side-by-side, if needed) with Cocos Studio. So, I’m documenting (a) what needs to be done to getting things working and (b) how I figured out what needed to be done.

How to get Cocos Studio to Recognize cocos2d-x

  1. Download and install Cocos for Windows V2.3.3 (the one without framework) to C:\Cocos
  2. Download and unzip cocos2d-x-3.9.zip to C:\Cocos\frameworks\cocos2d-x-3.9
  3. Download this .REG file, change the name from cocos-framework.reg_.txt to cocos-framework.reg and run it. Alternately, you can manually create the following registry entries on Windows (64 bit):
    1. HKLM\Software\Wow6432Node\CocosFramework\InstallDir as a REG_SZ and set it to C:\Cocos
    2. HKLM\Software\Wow6432Node\CocosFramework\3.9\EngineVersion as a REG_SZ and set it to cocos2d-x-3.9
    3. HKLM\Software\Wow6432Node\CocosFramework\3.9\Path as a REG_SZ and set it to C:\Cocos\frameworks\cocos2d-x-3.9
  4. Create a file named version with the contents cocos2d-x-3.9 in C:\Cocos\frameworks\cocos2d-x-3.9
  5. If you also want to do Android development, then Android SDK, Android NDK, Ant and JDK paths should be set in the Platform section of the Edit/Preferences menu item, else skip this step.

The Ugly

Note that none of this is documented anywhere on the cocos2d-x site or its forums. So, I’m posting this here and will include this information and a link to this post in my answer to my own question in the cocos2d-x forum.

How I Figured it out

I used 2 computers to sort this out. I removed all previous Cocos package installations completely from both – this includes both the Cocos Studio and the cocos2d-x framework.

On my first computer (Windows 7), I installed Cocos Studio without the framework and extracted the framework to the appropriate sub-directory. Cocos Studio did not recognize that the framework was installed. I tried various suggestions that were provided in answers to identical questions in the forums. These included:

  • editing the C:\Cocos\Cocos\CocosStudio2\Frameworks\FrameworkVersionList.xml to ensure that the framework version I was using was listed there. It was already there. Note that this file doesn’t even exist in the installation package that works out of the box. Total red herring.
  • editing the C:\Cocos\frameworks\cocos2d-x-3.9\tools\console\bin\cocos2d.ini file to correctly point to the cocos2d-x installation and the templates folders. They weren’t, but fixing them made no difference.
  • removing some and then all cocos environment variables. This made no difference.
  • adding a version file with cocos2d-x-3.9 in the C:\Cocos\frameworks\cocos2d-x-3.9 directory. This is a necessary step, but it didn’t work by itself.

I then installed the Cocos for Windows with framework v2.3.3 on my second computer (Windows 10). Then I proceeded to compare the directory layout and files and their differences. Other than the version file, I didn’t find anything significant.

I figured as a last, desperate (since Cocos Studio is cross platform) step, I’d do a registry search on the (second) computer, the one that had package installed of Cocos Studio with the framework included. That yielded the three registry entries that were not present on the first computer. I added those entries to the first computer and Cocos Studio recognized that the cocos2d-x framework was installed.

Last set of steps was to revert each of the changes I’d made one by one to determine which ones were actually needed and which ones were not. It turned out that the only change from the forum answers that was actually needed in addition to the registry change was the creation of the version file (#4 in the “How to get Cocos Studio to Recognize cocos2d-x” section above).

Hope this helps anyone else looking to figure this out.