2010-07-29 9 views
4

salut ont une API COM visible en C# qui ressemble à ce qui suit:Le passage d'un objet de C++ C# si COM

public void DoSomething(string par1, string par2, object additionalParameter) 

L'idée est que basée sur la valeur des paramètres de chaîne je me attends à une autre classe comme le troisième paramètre et l'a jeté de manière appropriée dans l'implémentation (je sais que cette conception n'est pas optimale mais je n'ai pas beaucoup de flexibilité ici).

Supposons que pour une combinaison des paramètres de chaîne du type du paramètre supplémentaire est la suivante:

[ComVisible(true)] 
[Serializable] 
public class TestObject  
{ 
    public string String{ get; set; } 

    public long Long { get; set; } 
} 

Je dois ma méthode API à partir du code non géré; Cependant, j'ai des difficultés à créer le variant nécessaire pour le troisième paramètre.

J'utilise le CComVariant(...) en passant un IDispatch pointant vers un objet Test que je viens de construire.

Supposons que pTestObject est un pointeur IDispatch à mon TestObject, j'ai quelque chose comme ce qui suit:

CComVariant pObjectVariant(pTestObject); 
DoSomething(BSTR(L"first"), BSTR(L"second"), pObjectVariant); 

Cependant, lorsque la fonction C# est finalement invoqué, je vois que l'objet est de type bool au lieu de TestObject que je m'attendais.

Une idée?

Stefano

+0

Quelle est la valeur de pTestObject.vt lorsque vous regardez avec le débogueur non géré juste avant l'appel? –

+0

Il s'agit en fait de * est * de type VT_BOOL. Pas certain de pourquoi. Peut-être que c'est le défaut? –

Répondre

5

J'ai quelques suggestions. Tout d'abord, créez une interface pour tout ce que vous touchez dans COM, même si c'est juste un DTO standard qui n'a pas de méthodes et seulement des propriétés. COM aime les interfaces. Il les aime tellement, que tout vous touchez dans COM est une interface.

L'autre suggestion est que vous placez un GuidAttribute sur tout ce que vous touchez dans COM. Cela garantira que votre registre ne sera pas corrompu lorsque vous enregistrerez l'assembly géré avec COM. COM aime les GUID plus que les interfaces, et il se confond facilement si une interface est enregistrée pour plus d'un GUID, ce qui peut arriver si vous ne corrigez pas les GUID de l'interface dans le code. Les classes concrètes ont également besoin d'un GUIDAttribute.

Je sais que ça craint, mais c'est pourquoi MS fait de son mieux pour empêcher les gens d'utiliser COM.

Cela dit, vous voulez probablement C# comme ceci:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Runtime.InteropServices; 

namespace ClassLibrary1 
{ 

    [ComVisible(true)] 
    [Guid("1CEB24F2-BF13-417F-B5BC-DB8E82A56EAE")] 
    public interface ITestEntity1 //This is how TestEntity1 is visible to the COM code...so it needs a Guid. 
    { 
     bool Thing { get; set; } 
    } 


    [ComVisible(true)] 
    [Guid("C8B5A7C2-F67C-4271-A762-3642754F2233")] 
    public class TestEntity1 : ITestEntity1 //Created by the COM runtime...needs a Guid. 
    { 
     public bool Thing { get; set; } 
    } 

    [ComVisible(true)] 
    [Guid("8904A7EC-D865-4533-91EC-1F68524651D0")] 
    public interface ITestEntity2 
    { 
     string Description { get; set; } 
    } 

    [ComVisible(true)] 
    [Guid("668EE2E8-5A60-468B-8689-D9327090AA44")] 
    public class TestEntity2 : ITestEntity2 
    { 
     public string Description { get; set; } 
    } 

    [ComVisible(true)] 
    [Guid("2791082F-F505-49C4-8952-80C174E4FE96")] 
    public interface ITestGateway 
    { 
     //MarshalAsAttribute is somewhat important, it tells the tlbexp.exe tool to mark 
     // the comInputValue parameter as IUnknown* in the COM interface. 
     //This is good because VARIANTS kinda suck...You'll see what I mean in the C++ 
     // side. It also keeps some jack-wagon from passing a VARIANT_BOOL in 
     // on your object parameter. 
     void DoSomething(string a, [MarshalAs(UnmanagedType.Interface)]object comInputValue); 
    } 

    [ComVisible(true)] 
    [Guid("C3D079F3-7869-4B3E-A742-263775C6EA63")] 
    public class TestGateway : ITestGateway 
    { 
     public void DoSomething(string a, object comInputValue) 
     { 
      if (a == "yes") 
      { 
       var entity = (TestEntity1)comInputValue; 
      } 
      else 
      { 
       var entity = (TestEntity2) comInputValue; 
      } 
      //OR 

      if(comInputValue is TestEntity1) 
      { 
       //Do whatever here, and you don't need to test 
       // a string input value. 
      } 
      else if(comInputValue is TestEntity2) 
      { 
       //Other stuff is done here. 
      } 
      else 
      { 
       //Error condition?? 
      } 
     } 
    } 
} 

qui peut être appelé par le C suivant ++:

// ComClient.cpp : Defines the entry point for the console application. 
// 

#include "stdafx.h" 
#import <mscorlib.tlb> raw_interfaces_only 

//This creates the CLSID_ and IID_ constants, and 
// some strongly-typed interfaces. 
#import "..\Debug\ClassLibrary1.tlb" no_namespace named_guids 


int _tmain(int argc, _TCHAR* argv[]) 
{ 
    ITestGateway* test = NULL; 

    char buffer[50]; 
    gets(buffer); //Just a pause to attach the debugger in Managed + Native mode...hit enter in the console. 

    CoInitialize(NULL); 

    HRESULT hr = CoCreateInstance(CLSID_TestGateway, 
     NULL, 
     CLSCTX_INPROC_SERVER, 
     IID_ITestGateway, 
     reinterpret_cast<void**>(&test)); 

    if(FAILED(hr)) { 
     printf("Couldn't create the instance!... 0x%x\n", hr); 
     gets(buffer); 
    } else { 
     _bstr_t someString("yes"); 

     //Instead of fooling with CComVariant, 
     // just directly create a TestEntity1...which COM will return 
     // as an ITestEntity1. 
     ITestEntity1* testEntity1 = NULL; 
     HRESULT hr = CoCreateInstance(CLSID_TestEntity1, 
      NULL, 
      CLSCTX_INPROC_SERVER, 
      IID_ITestEntity1, 
      reinterpret_cast<void**>(&testEntity1)); 

     if(FAILED(hr)) { 
      printf("Entity was not created!... 0x%x\n", hr); 
      gets(buffer); 
      return 0; 
     } 

     //Set some kind of property just for show. 
     testEntity1->PutThing(VARIANT_FALSE); 



     //If you attached your debugger with Managed code & Native code, 
     // you should be able to hit a C# break point during this call. 
     //Also, notice that there is no cast for testEntity1. All interfaces 
     // in COM derive from IUnknown, so you can just pass it. 
     //IDispatch also derives from IUnknown, so if that's what you already have, 
     // you can just pass it as well, with no cast. 
     test->DoSomething(someString, testEntity1); 

     printf("Something was done."); 

     testEntity1->Release(); //Release anything you make through CoCreateInstance() 
    } 

    test->Release(); 

    return 0; 
} 
+0

Merci beaucoup, cela a fait l'affaire! Note: J'ai en fait une interface pour mon TestObject. Je n'ai simplement pas mis dans ma question de ne pas trop l'encombrer. Peut-être que je devrais avoir, pour plus de clarté. –