-1

I'm working on a native C++ application, and I am trying to utilize some managed classes from a separate assembly to read/write data containers that were originally developed in C#. The interop layer is basically loading the data, and then mirroring the managed data into a functionally equivalent native data container for use by the application (and then obviously back again for writing).

To be totally honest, it's not been super fun trying to do this interop, but it's mostly working at this point. There's one step of the translation that's holding everything up, though, and while debugging it, a managed exception (System.NullReferenceException) is being thrown on a line that is native C++ only. I don't understand at all why this is happening, and I'm hoping that someone can make sense of what's going on. I wouldn't even have expected it to be in a managed portion of the stack at the location where it's throwing...

This is a stripped down version of what our code is doing:

//  The native data types.
class cUnmanagedInterpolator
{
    virtual std::vector<std::pair<double, cUnmanagedInterpolator>& GetPoints() { return mPoints; }
    std::vector<std::pair<double, cUnmanagedInterpolator> mPoints;
};

class cUnmanagedInterpolator_Const : public cUnmanagedInterpolator
{
    virtual double GetValue() const { return mValue; }
    double mValue
};

//  The managed data types (please forgive syntax errors here; they're actually written in C# in our software, and this is just to get the point across).
class ManagedInterpolator
{
    property List<KeyValuePair<double, ManagedInterpolator^>^ Points = gcnew List<KeyValuePair<double, ManagedInterpolator^>();
};

class ManagedInterpolator_Const : public ManagedInterpolator
{
    property double DependentValue;
};

//  The function to mirror the managed data container into a native counterpart.
void CopyManagedPoints( ManagedInterpolator^ rhManagedInterpolator, cUnmanagedInterpolator* pUnmanagedInterpolator )
{
    //  Go through each managed point in the interpolator and add a corresponding unmanaged one.
    for each( auto point in rhManagedContainer->Points )
    {
        //  If this is a constant interpolator, just copy the values.
        if( dynamic_cast<ManagedInterpolator_Const^>( point->Value ) != nullptr )
        {
            //  Create a new unmanaged copy of the point.
            //  I even tried making x and r pointers and allocating the doubles on the heap with "new" to make sure they weren't somehow ending up as CLI types, but it didn't make a difference.
            double x = point->Key;
            double r = dynamic_cast<ManagedInterpolator_Const^>( point->Value )->DependentValue;
            std::pair<double, cUnmanagedInterpolator> newPoint( x, cUnmanagedInterpolator_Const( r ) );

            //  The unmanaged point data was looking weird, and this appeared to be where it was happening, so create a message with what it thinks the point is at this point.
            //  ***The next line is where the System.NullReferenceException is thrown.***
            std::string debugMessage = MakeString( newPoint.first ) + ", " + MakeString( dynamic_cast<cUnmanagedInterpolator_Const*>( &( newPoint.second ) )->GetValue() );

            //  Add the copy to the unmanaged interpolator.
            pUnmanagedInterpolator->GetPoints().push_back( newPoint );

            //  ***Trying to reference the newly created point by using pUnmanagedInterpolator->GetPoints().back() also results in an exception.

            //  Show the debug message to the user.
            AfxMessageBox( debugMessage.c_str() );
        }
        //  Otherwise, add a new base class interpolator.
        else
        {
            cUnmanagedInterpolator* pNewInterp = new cUnmanagedInterpolator();

            //  Recurse as deep as it goes.
            if( pNewInterp )
            {
                pUnmanagedInterpolator->GetPoints().push_back( std::make_pair( point->Key, std::move( *pNewInterp ) ) );
                CopyManagedPoints( point->Value, &( pUnmanagedInterpolator->GetPoints().back().second ) );
                delete pNewInterp;
                pNewInterp = nullptr;
            }
        }
    }
}

//  Roughly how the function would be used.
int main()
{
    ManagedInterpolator^ rhManagedInterpolator = gcnew ManagedInterpolator( /*initialization information*/ );

    cUnmanagedInterpolator* pNewInterp = new cUnmanagedInterpolator();

    CopyManagedPoints( rhManagedInterpolator, pNewInterp );

    //  Use the data...

    return 0;
}

The exception occurs in the inner if statement (there are comments preceded by three asterisks ("***") marking where the problems appear to be occurring).

A quick summary of the code in case it's not clear enough:

Basically, the data container is an "interpolator" that contains pairs of independent and dependent values ("points"). The dependent values can be either another interpolator or a scalar value. Dependent values that are interpolators can recurse as deeply as desired, but must end in a fixed value, which is a class derived from the interpolator.

1 Answers1

1

You're using handles (^) so this is not native code. The problem is that your pair newPoint has a cUnmanagedInterpolator object as its second value, which you try to dynamic_cast to a cUnmanagedInterpolator_Const on the line that generates the NullReferenceException. This cast will fail and return a nullptr, which when dereferenced will cause the exception.

Fundamentally, while you start with a cUnmanagedInterpolator_Const, it is sliced to a cUnmanagedInterpolator when you create the pair, losing its identity as a cUnmanagedInterpolator_Const and becoming a cUnmanagedInterpolator.

1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
  • Argh...Of course it doesn't remain polymorphic when it's passed by value (more specifically, when the copy constructor is called per [this answer](http://stackoverflow.com/a/8278824/6114796). Presumably this also applies to move constructors as well?)...I feel really stupid; that's probably like C++ 101 day one... Anyway, why is it throwing a CLR exception when accessing a native object (that entire line only uses native constructs)? Shouldn't it be throwing a C++ or SEH exception (or even an access violation for a function address that is presumably not there) instead? – BoatsBoatsBoats Jul 19 '16 at 07:23
  • 1
    You're compiling that with the `/clr` option, so it does not generate native code. – 1201ProgramAlarm Jul 20 '16 at 00:18
  • @BoatsBoatsBoats: AFAIK, it does throw a SEH exception, which is then translated into NullReferenceException somewhere in the handler chain (possibly only if your code doesn't handle it first). It's not like there's something anywhere to disable that translation when calling a native function, especially if it occurs in the "last" possible handler, converting SEH exceptions that would otherwise have been unhandled... – Medinoc Jul 21 '16 at 09:58