简体   繁体   中英

How to declare a COM interface so it can be cast?

First, I must say I'm new to C#, .NET and COM Interop.

I get the following error message when I try to cast a COM object to an interface type I wrote :

Error Message: Unable to cast COM object of type 'System.__ComObject' to interface type 'Observer.IObserver'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{13478219-8C3B-4849-99D9-27CEF1A49A55}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

I use VS2010 (.NET Framework 3.5) on Windows 7.

Here is my interface (Observer classes library project) :

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

namespace Observer
{
    [ComImport]
    [Guid("2B2D0BC7-A7C6-4924-A3DE-42F7075E5947")]
    public interface IObservable
    {
        void attach(IObserver observer);
        void detach(IObserver observer);

        void notify();
    }

    [ComImport]
    [Guid("13478219-8C3B-4849-99D9-27CEF1A49A55")]
    public interface IObserver
    {
        void update(IObservable observable);
    }
}

When building this dll, I have this warning :
Observer.dll does not contain any types that can be registered for COM Interop (Surely because the interfaces are implemented in other dlls).
IObserver doesn't appear in my registry (absent from HKEY_CLASSES_ROOT\\Interface).

Here is the failing code (Syntheses classes library project) :

// Arguments de la méthode permettant de récupérer un objet selon son chemin
Object[] args;
// Objet représentant l'état technique de la synthèse
Observer.IObserver pEtatTechniqueSynthese;

args = new Object[] { m_sFullName + "/Etat_Technique" };

pEtatTechniqueSynthese = m_typeSite.InvokeMember("FindObject2",
                        BindingFlags.InvokeMethod,
                        null, m_pSite, args) as Observer.IObserver;

//Here pEtatTechniqueSynthese is null

//This call works fine without casting when pEtatTechniqueSynthese's type is Object
//but I need to cast it because my Observer.IObservable's attach method waits for an Observer.IObserver
//If I don't cast I get a "Exception has been thrown by the target of an invocation"
//=> InnerException : "La valeur n'est pas comprise dans la plage attendue"
//I don't know the exact translation, but it sort of means "Value not in expected range"
pEtatTechniqueSynthese = (Observer.IObserver)m_typeSite.InvokeMember("FindObject2",
                        BindingFlags.InvokeMethod,
                        null, m_pSite, args);

//Exception raised

The pEtatTechniqueSynthese real type, Etat_Technique (Syntheses classes library project), implements Observer.IObserver :

public class Etat_Technique : CODRA.SDK.DotNetUtils.COM.IObjectWithSite, Observer.IObservable, Observer.IObserver, ICalculateurEtatTechnique

All my assemblies are COM-Visible. My Observer project Build option "Register for COM Interop" is checked and I signed my assembly with a strong name key file.

I don't have access to the COM server code (third party component) but I'm sure the matter comes from my code.
Does anyone have a clue on what I'm missing?

=============================================

More information about the third party software :

This software manages objects.
The data structure seems to be an objects tree, the root node being called the site.
When a dll class want to access an object, it must call the "FindObject2" method from the site, passing it the object path. This method apparently returns a COM object, so we can call methods, get properties, ...

I can develop my own object types and add them to the software, describing the class (specifying the assembly, class, dll, properties, ...).

There, I want to get an object I developped and cast it back from COM object to the interface it implements.
Declaring the object as Object and calling its methods works just fine.
There I need an Observer to attach it to an Observable so I have to cast.
A solution would surely be to make the argument of the attach method being an Object, but if I do that, there is no interest using interfaces.

=============================================

More info based on Michael Edenfield's answer :

[ComImport parenthesis ON]

The ComImport attribute was a guess. I used it because I have access to a utils file given to interact with the third party code.
Here is an interface defined in this file. When I implement it the way the third party software documentation says, I have access to the site root object.
I guessed if they used it, I should have to use it too.

[ComVisible(true)]
[ComImport]
[Guid("FC4801A3-2BA9-11CF-A229-00AA003D7352") ]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
public interface IObjectWithSite
{
    void SetSite([MarshalAs(UnmanagedType.IUnknown)]
        [In] object pSite);
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")]
    void GetSite([In] ref Guid riid, [Out] out IntPtr pvSite);
}

I'll delete this attribute, you understand way better than me what's underlying. Furthermore, the problem is the same, so this attribute was useless.

[ComImport parenthesis OFF]

Badly, FindObject2 effectively returns a {System.__ComObject}, so casting doesn't work and I'm stuck with an InvalidCastException, again, and again, and again, ...
The good point is that I know what underlying class it should be.

The workaround I talked about in the "More information about the third party software" part works, ie making the attach method taking an Object parameter and then managing Objects instead of IObservers. Doing this I call the IObserver's update method using Reflection :

foreach (Object observer in m_observers)
{
    Object[] args = new Object[] { this };
    observer.GetType().InvokeMember("update",
                            BindingFlags.InvokeMethod,
                            null, observer, args);
}

Eeew, isn't it? But if I don't find anything cleaner, this crappy thing will make the job done.

I'm not really clear on what you're trying to accomplish, but it looks like you are confusing several different COM interop concepts here.

Importing a COM interface using ComImportAttribute is only meaningful when the interface is already defined in an external type library somewhere. If this is a new interface that you created in C# for your own classes, then declaring it as a COM Import attribute won't do any good. That's because no actual COM object will ever implement your interface, so you won't ever be able to get an pointer to one from QueryInterface . From your description of the problem, ComImport is completely the wrong way to go.

If you need to expose your own interfaces to COM clients, you just need to give them a GUID. If you leave off the ComImport attribute, any ComVisible classes or interfaces will be registered for interop, assuming you have that enabled. However, COM clients would need to know about your interface and be re-compiled to implement it. I also don't think this is going to solve your problem.

I don't know exactly how your third-party library is instantiating your custom class; if that is done via COM then you will need to export your CoClass and interface definitions for that to work. The fact that it's asking for assembly and class information leads me to suspect that this won't be needed.

It sounds to me like you are getting a managed C# object created somewhere, which implements your managed C# interface, and you just need to get it. If the FindObject method is actually returning an instance of your type, then you should first typecast the return value to a concrete C# type, then try to case it to your interface. If you try to use the typecast operators on a ComObject to cast to an interface, it's going to run QueryInterface and that's almost certainly going to fail.

I've never tried this, but as a first guess I would suggest typecasting to System.Object first. From there, C# should use the managed type metadata to figure out if your interface is available and cast appropriately.

Of course, if FindObject is returning an actual COM object that is somehow wrapped around your C# class, you'll need to figure out how to get the underlying class out of that return value first.

Note that the interface that's included in the documentation for the third party software is IObjectWithSite . This is a well-known interface defined by Windows, so you do need to use [ComImport] if you plan to implement this interface. In this case, the site root object would call IObjectWithSite::SetSite() on your custom object and pass itself in, which is how embedded objects "know" about their container. Because that interface is defined externally, you need to "Import" it so everyone is implementing the same one.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM