2010-10-24 5 views
-2

Tout d'abord, notez qu'il ne s'agit que d'un exercice d'apprentissage pour moi. L'intention est de produire une boîte de message .NET en utilisant uniquement du C++ pur (pas C++/CLI) et une liaison tardive si nécessaire.Liaison tardive de l'appel COM-> NET avec argument (s) de la valeur enum

Cela fonctionne correctement avec juste un titre et du texte dans la boîte de message. Cependant, lorsque j'essaie de spécifier des boutons, je reviens 2147942487 "Le paramètre est incorrect". Du côté .NET MessageBox.Show l'a comme un argument de type enum. Je suppose que d'une manière ou d'une autre l'entier fourni en argument de C++ n'est pas automatiquement converti en type. J'ai essayé d'obtenir un "objet" du type enum en appelant Enum.ToObject. Cependant, ce qui est retourné au côté COM/C++ des choses est juste un VARIANT avec le type I4, c'est-à-dire un entier de 32 bits.

Aussi, j'ai essayé de Google ceci, mais tout ce que j'ai trouvé a été sur la façon d'obtenir des valeurs numériques pour les valeurs enum .NET. J'ai les valeurs numériques. Le problème apparaît pour être comment convertir automatiquement ces valeurs numériques en arguments de type enum dans .NET?

Cependant, Je peux me tromper totalement sur le problème.

Alors toute aide appréciée!

code ci-dessous (le chemin hardcoded aura probablement besoin d'ajustement sur ur système):

/* 
    // 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

Il est difficile de savoir quel est le problème, et je ne peux pas essayer votre code parce que je n'ai pas C++ ou .Net 1.1 sur ma machine. Est-ce que le fait de passer le résultat de 'Enum.ToObject' renvoie la même erreur que de passer dans' MB_OK'? Est-ce que changer la version du framework change quelque chose? – Gabe

+0

@Gabe: merci d'avoir regardé cela. Oui, même erreur, car 'Enum.ToObject' retourne juste un entier au coté C++. .NET 4.0 fonctionne bien (pour deux arguments) en remplaçant "1.1.4322" par "4.0.30319". Cependant, sans un compilateur C++ vraisemblablement cela ne fonctionne pas ... :-) Cheers, –

+0

@Gabe: re "Est-ce que changer la version du framework change quelque chose?" C'est CERTAINEMENT! Quatre arguments fonctionne bien avec .NET 4.0! Maintenant, je n'ai pas vérifié .NET 3.5 ou 2.0, exactement où cela a été corrigé, mais si vous écrivez ce commentaire en guise de réponse, je le coche comme "la" bonne réponse ... Merci! À la vôtre, –

Répondre

3

Je pense que votre problème est que vous invoquez une ancienne version du framework .Net (version 1.1 est terminée 7 ans), et je soupçonne que sa liaison ou méthode de liaison est un peu restrictive. Les versions que j'ai testées (v2.0, v4.0) me laissent juste passer un int pour tous les types enum à Invoke, ce qui devrait être suffisant pour votre petit test.

+0

Yup. Merci! :-) –

Questions connexes