views:

38

answers:

1

First, note that this is just a learning exercise for me. The intent is to produce a .NET messagebox using nothing but pure C++ (not C++/CLI) and late binding as necessary.

This works fine with just a title and text in the messagebox.

However, when I try to specify buttons I get back 2147942487 "The parameter is incorrect". On the .NET side MessageBox.Show has this as an enum type argument. I am guessing that somehow the integer supplied as argument from C++, isn't automatically converted typewise.

I've tried to obtain an "object" of the enum type by calling Enum.ToObject. However, what's returned back to the COM/C++ side of things is just a VARIANT with type I4, i.e. a 32-bit integer.

Also, I've tried to Google this, but all I've found has been about how to obtain numerical values for .NET enum values. I have the numerical values. The problem appears to be how to get those numerical values automatically converted to enum-type arguments over in .NET?

However, I could be totally wrong about what the problem is.

So any help appreciated!

Code below (the hardcoded path will probably need adjustment on ur system):

/*
    // C# code that this C++ program should implement:
    using System.Windows.Forms;

    namespace hello
    {
     class Startup
     {
      static void Main( string[] args )
      {
       MessageBox.Show(
        "Hello, world!",
        ".NET app:",
        MessageBoxButtons.OK,
        MessageBoxIcon.Information
        );
      }
     }
    }
*/

#include <assert.h>
#include <algorithm>        // std::swap
#include <iostream>
#include <stddef.h>         // ptrdiff_t
#include <sstream>
#include <stdexcept>
#include <stdlib.h>         // EXIT_SUCCESS, EXIT_FAILURE
#include <string>
using std::swap;

//#undef  UNICODE
//#define UNICODE
//#undef  NOMINMAX
//#define NOMINMAX
//#undef  STRICT
//#define STRICT
//#include <windows.h>

#include <Mscoree.h>
#include <comdef.h>
_COM_SMARTPTR_TYPEDEF( ICorRuntimeHost, IID_ICorRuntimeHost );      // ICorRuntimeHostPtr

// #import is an MS extension, generates a header file. Will be replaced with #include.
#import "C:\\WINDOWS\\Microsoft.NET\\Framework\\v1.1.4322\\mscorlib.tlb" \
    raw_interfaces_only rename( "ReportEvent", "reportEvent" )

typedef mscorlib::_AppDomainPtr     AppDomainPtr;
typedef mscorlib::_AssemblyPtr      AssemblyPtr;
typedef mscorlib::_AssemblyNamePtr  AssemblyNamePtr;
typedef mscorlib::_MethodInfoPtr    MethodInfoPtr;
typedef mscorlib::_ObjectPtr        ObjectPtr;
typedef mscorlib::_TypePtr          TypePtr;


typedef ptrdiff_t   Size;
typedef Size        Index;

bool throwX( std::string const& s ) { throw std::runtime_error( s ); }


bool startsWith( wchar_t const prefix[], wchar_t const s[] )
{
    while( *prefix != 0 && *prefix == *s ) { ++prefix; ++s; }
    return (*prefix == 0);
}

template< class Predicate >
struct Is: Predicate
{};

template< class Type, class Predicate >
bool operator>>( Type const& v, Is< Predicate > const& check )
{
    return check( v );
}

struct HrSuccess
{
    bool operator()( HRESULT hr ) const
    {
        ::SetLastError( hr );
        return SUCCEEDED( hr );
    }
};

struct Text
{
private:
    std::ostringstream  stream;

    Text( Text const& );
    Text& operator=( Text const& );

public:
    Text() {}

    template< class Type >
    Text& operator<<( Type const& v )
    {
        stream << v;
        return *this;
    }

    operator std::string () const
    {
        return stream.str();
    }
};

template< class Type >
struct VariantType;

template<>
struct VariantType< IUnknown* >
{
    static VARENUM const    id  = VT_UNKNOWN;
};

template<>
struct VariantType< VARIANT >
{
    static VARENUM const    id  = VT_VARIANT;
};

class OleVector
{
private:
    typedef OleVector   ThisClass;
    SAFEARRAY*  descriptor_;
    Index       lowerBound_;
    Index       upperBound_;

    ThisClass( ThisClass const& );
    ThisClass& operator=( ThisClass const& );

    Index lowerBound() const
    {
        if( descriptor_ == 0 ) { return 0; }
        long result;
        SafeArrayGetLBound( descriptor_, 1, &result )
            >> Is< HrSuccess >()
            || throwX( "OleVector::lowerBound: SafeArrayGetLBound failed" );
        return result;
    }

    Index upperBound() const
    {
        if( descriptor_ == 0 ) { return 0; }
        long result;
        SafeArrayGetUBound( descriptor_, 1, &result )
            >> Is< HrSuccess >()
            || throwX( "OleVector::upperBound: SafeArrayGetUBound failed" );
        return result;
    }

public:
    OleVector(): descriptor_( 0 ) {}

    explicit OleVector( SAFEARRAY* descriptor )
        : descriptor_( descriptor )
        , lowerBound_( 0 )
        , upperBound_( 0 )
    {
        assert( descriptor_ == 0 || ::SafeArrayGetDim( descriptor_ ) == 1 );
        lowerBound_ = lowerBound();
        upperBound_ = upperBound();
    }

    template< class ElemType >
    OleVector( Size size, VariantType< ElemType > )
        : descriptor_( ::SafeArrayCreateVector( VariantType< ElemType >::id, 0, size ) )
        , lowerBound_( 0 )
        , upperBound_( size - 1 )
    {
        assert( descriptor_ != 0 && ::SafeArrayGetDim( descriptor_ ) == 1 );
        lowerBound_ = lowerBound();
        upperBound_ = upperBound();
    }

    ~OleVector()
    {
        if( descriptor_ != 0 )
        {
            ::SafeArrayDestroy( descriptor_ );
        }
    }

    SAFEARRAY* asSafeArray() const
    {
        return descriptor_;
    }

    VARENUM elemType() const
    {
        VARTYPE     result  = 0;
        if( descriptor_ != 0 )
        {
            ::SafeArrayGetVartype( descriptor_, &result )
                >> Is< HrSuccess >()
                || throwX( "OleVector::elemType: SafeArrayGetVartype failed" );
        }
        return VARENUM( result );
    }

    void swapWith( OleVector& other )
    {
        swap( descriptor_, other.descriptor_ );
        swap( lowerBound_, other.lowerBound_ );
        swap( upperBound_, other.upperBound_ );
    }

    void clear()
    {
        OleVector().swapWith( *this );
    }

    Size count() const
    {
        return (upperBound_ + 1) - lowerBound_;
    }

    Size elemSize() const
    {
        return (descriptor_ == 0? 0 : ::SafeArrayGetElemsize( descriptor_ ));
    }

    void getElement( Index i, void* pResult ) const
    {
        long internalIndex  = i + lowerBound_;
        ::SafeArrayGetElement( descriptor_, &internalIndex, pResult )
            >> Is< HrSuccess >()
            || throwX( "OleVector::getElement: SafeArrayGetElement failed" );
    }

    void setElement( Index i, void* pData )
    {
        long internalIndex  = i + lowerBound_;
        ::SafeArrayPutElement( descriptor_, &internalIndex, pData )
            >> Is< HrSuccess >()
            || throwX( "OleVector::setElement: SafeArrayPutElement failed" );
    }
};

template< class ElemType >
class ElemAccess
{
private:
    OleVector*  v_;

    template< class T >
    static void* safeArrayPutArg( T& v ) { return &v; }

    template<>
    static void* safeArrayPutArg( IUnknown*& v ) { return v; }

public:
    ElemAccess( OleVector& v )
        : v_( &v )
    {
        assert( VariantType< ElemType >::id == v_->elemType() );
        assert( v_->elemSize() == sizeof( ElemType ) );
    }

    ElemType elem( Index i ) const
    {
        ElemType    result;
        v_->getElement( i, &result );
        return result;
    }

    void setElem( Index i, ElemType v )
    {
        v_->setElement( i, safeArrayPutArg( v ) );
    }
};


void cppMain()
{
    ICorRuntimeHostPtr  pCorRuntimeHost;
    CorBindToRuntimeEx(
        L"v1.1.4322",         // LPWSTR pwszVersion,  // RELEVANT .NET VERSION.
        L"wks",                 // LPWSTR pwszBuildFlavor, // "wks" or "svr"
        0,                      // DWORD flags,
        CLSID_CorRuntimeHost,   // REFCLSID rclsid,
        IID_ICorRuntimeHost,    // REFIID riid,
        reinterpret_cast<void**>( &pCorRuntimeHost )
        )
        >> Is< HrSuccess >()
        || throwX( "CorBindToRuntimeEx failed" );

    pCorRuntimeHost->Start()    // Without this GetDefaultDomain fails.
        >> Is< HrSuccess >()
        || throwX( "CorRuntimeHost::Start failed" );

    IUnknownPtr     pAppDomainIUnknown;
    pCorRuntimeHost->GetDefaultDomain( &pAppDomainIUnknown )
        >> Is< HrSuccess >()
        || throwX( "CorRuntimeHost::GetDefaultDomain failed" );

    AppDomainPtr    pAppDomain  = pAppDomainIUnknown;
    (pAppDomain != 0)
        || throwX( "Obtaining _AppDomain interface failed" );

    AssemblyPtr     pCoreLibAssembly;
    {
        SAFEARRAY*  rawAssembliesArray;
        pAppDomain->GetAssemblies( &rawAssembliesArray )
            >> Is< HrSuccess >()
            || throwX( "_AppDomain::GetAssemblies failed" );

        OleVector   assemblies( rawAssembliesArray );
        Size const  n       = assemblies.count();

        std::cout << n << " assemblies loaded." << std::endl;
        if( n > 0 )
        {
            std::cout << "Array elemtype " << assemblies.elemType() << "." << std::endl;

            ElemAccess< IUnknown* >     elems( assemblies );
            for( Index i = 0;  i < n;  ++i )
            {
                IUnknownPtr         pUnknown( elems.elem( i ) );
                AssemblyPtr         pAssembly( pUnknown );

                _bstr_t     displayName;
                pAssembly->get_ToString( displayName.GetAddress() )
                    >> Is< HrSuccess >()
                    || throwX( "_Assembly::get_ToString failed" );
                std::cout
                    << i + 1 << ": "
                    << "\"" << displayName.operator char const*() << "\""
                    << std::endl;
                if( startsWith( L"mscorlib,", displayName ) )
                {
                    pCoreLibAssembly = pAssembly;
                }
            }
        }
    }
    (pCoreLibAssembly != 0)
        || throwX( "mscorlib was not loaded by the .NET initialization" );

    TypePtr     pStringType;
    pCoreLibAssembly->GetType_2( _bstr_t( L"System.String" ), &pStringType )
        >> Is< HrSuccess >()
        || throwX( "_Assembly::GetType failed (for System.String)" );
    (pStringType != 0 )
        || throwX( "System.String type not found" );

    TypePtr     pEnumType;
    pCoreLibAssembly->GetType_2( _bstr_t( L"System.Enum" ), &pEnumType )
        >> Is< HrSuccess >()
        || throwX( "_Assembly::GetType failed (for System.Enum)" );
    (pEnumType != 0 )
        || throwX( "System.Enum type not found" );

    AssemblyPtr     pFormsAssembly;
    pAppDomain->Load_2( _bstr_t( L"System.Windows.Forms, Version=1.1.4322.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" ), &pFormsAssembly )
        >> Is< HrSuccess >()
        || throwX( "Loading System.Windows.Forms assembly failed" );
    std::cout << "Loaded the System.Windows.Forms assembly." << std::endl;

    _bstr_t     formsAssemblyDisplayName;
    pFormsAssembly->get_ToString( formsAssemblyDisplayName.GetAddress() )
        >> Is< HrSuccess >()
        || throwX( "_Assembly::get_ToString failed" );
    std::cout << "\"" << formsAssemblyDisplayName.operator char const*() << "\"" << std::endl;

    TypePtr     pMessageBoxButtonsType;
    pFormsAssembly->GetType_2( _bstr_t( L"System.Windows.Forms.MessageBoxButtons" ), &pMessageBoxButtonsType )
        >> Is< HrSuccess >()
        || throwX( "_Assembly::GetType failed (for System.Windows.Forms.MessageBoxButtons)" );
    (pMessageBoxButtonsType != 0 )
        || throwX( "System.Windows.Forms.MessageBoxButtons type not found" );

    TypePtr     pMessageBoxIconType;
    pFormsAssembly->GetType_2( _bstr_t( L"System.Windows.Forms.MessageBoxIcon" ), &pMessageBoxIconType )
        >> Is< HrSuccess >()
        || throwX( "_Assembly::GetType failed (for System.Windows.Forms.MessageBoxIcon)" );
    (pMessageBoxIconType != 0 )
        || throwX( "System.Windows.Forms.MessageBoxIcon type not found" );

    TypePtr     pMessageBoxType;
    pFormsAssembly->GetType_2( _bstr_t( L"System.Windows.Forms.MessageBox" ), &pMessageBoxType )
        >> Is< HrSuccess >()
        || throwX( "_Assembly::GetType failed" );
    (pMessageBoxType != 0 )
        || throwX( "System.Windows.Forms.MessageBox type not found" );


    Size const  nArgs = 2;      // With 3 args (3rd is a button spec), the invocation fails.

    OleVector                   argTypesVec( nArgs, VariantType< IUnknown* >() );
    ElemAccess< IUnknown*>      argTypes( argTypesVec );
    argTypes.setElem( 0, static_cast<IUnknown*>( pStringType ) );
    argTypes.setElem( 1, static_cast<IUnknown*>( pStringType ) );
    if( nArgs > 2 ) { argTypes.setElem( 2, static_cast<IUnknown*>( pMessageBoxButtonsType ) ); }
    if( nArgs > 3 ) { argTypes.setElem( 3, static_cast<IUnknown*>( pMessageBoxIconType ) ); }

    MethodInfoPtr   pShowMethod;
    pMessageBoxType->GetMethod_5(
        _bstr_t( L"Show" ), argTypesVec.asSafeArray(), &pShowMethod
        )
        >> Is< HrSuccess >()
        || throwX( "MessageBox::GetMethod failed for method Show" );

    _bstr_t     showMethodDescription;
    pShowMethod->get_ToString( showMethodDescription.GetAddress() )
        >> Is< HrSuccess >()
        || throwX( "_MethodInfo::get_ToString failed" );
    std::cout << "MethodInfo: \"" << showMethodDescription.operator char const*() << "\"" << std::endl;

    _variant_t      okButtonIdAsVariant;



    //OleVector               toObjectArgsVec( 2, VariantType< VARIANT >() );
    //ElemAccess< VARIANT >   toObjectArgs( toObjectArgsVec );

    //toObjectArgs.setElem( 0, _variant_t( static_cast<IUnknown*>( pMessageBoxButtonsType ) ).GetVARIANT() );
    //toObjectArgs.setElem( 1, _variant_t( MB_OKCANCEL ) );
    //pEnumType->InvokeMember_3(
    //    _bstr_t( L"ToObject" ),
    //    mscorlib::BindingFlags(
    //        mscorlib::BindingFlags_Public | mscorlib::BindingFlags_Static | mscorlib::BindingFlags_InvokeMethod
    //        ),
    //    0,      // Binder
    //    _variant_t(), //targetObject.GetVARIANT(),
    //    toObjectArgsVec.asSafeArray(),
    //    &okButtonIdAsVariant
    //    )
    //    >> Is< HrSuccess >()
    //    || throwX( Text() << "_Type::InvokeMember failed, member = ToObject, code = " << GetLastError() );

    //ObjectPtr   pOkButtonId( okButtonIdAsVariant );   // The variant is just I4 with the id value.

    _variant_t              targetObject( static_cast< IUnknown* >( pMessageBoxType ) );
    _variant_t              result;
    OleVector               argsVec( nArgs, VariantType< VARIANT >() );
    ElemAccess< VARIANT >   args( argsVec );

    args.setElem( 0, _variant_t( L"Hello, world!" ).GetVARIANT() );
    args.setElem( 1, _variant_t( L"C++ app using .NET:" ).GetVARIANT() );
    if( nArgs > 2 ) { args.setElem( 2, _variant_t( MB_OK ) ); }
    if( nArgs > 3 ) { args.setElem( 3, _variant_t( MB_ICONINFORMATION ) ); }
    std::cout << argsVec.count() << " args." << std::endl;

    //pMessageBoxType->InvokeMember_3(
    //    _bstr_t( L"Show" ),
    //    mscorlib::BindingFlags(
    //        mscorlib::BindingFlags_Public | mscorlib::BindingFlags_Static | mscorlib::BindingFlags_InvokeMethod
    //        ),
    //    0,      // Binder
    //    _variant_t(), //targetObject.GetVARIANT(),
    //    argsVec.asSafeArray(),
    //    &result
    //    )
    //    >> Is< HrSuccess >()
    //    || throwX( Text() << "_Type::InvokeMember failed, code = " << GetLastError() );

    pShowMethod->Invoke_3( targetObject.GetVARIANT(), argsVec.asSafeArray(), &result )
        >> Is< HrSuccess >()
        || throwX( Text() << "MethodInfo::Invoke failed for MessageBox::Show, code = " << GetLastError() );

    std::cout << "Result type " << result.vt << std::endl;
    std::cout << "Finished!" << std::endl;
}

int main()
{
    try
    {
        cppMain();
        return EXIT_SUCCESS;
    }
    catch( std::exception const& x )
    {
        std::cerr << "!" << x.what() << std::endl;
    }
    return EXIT_FAILURE;
}
+1  A: 

I think your problem is that you're invoking an older version of the .Net framework (v1.1 is over 7 years old), and I suspect that its marshalling or method binding is a bit restrictive. The versions I tested (v2.0, v4.0) let me just pass an int for any enum types to Invoke, which should be sufficient for your little test.

Gabe
Yup. Thanks! :-)
Alf P. Steinbach