When defining a Visual Studio COM interface in C#,

2019-08-23 17:25发布

问题:

VS 2017 defines this interface in Microsoft.VisualStudio.Shell.Interop.15.0.DesignTime.dll:

[Guid("A459C228-5617-4136-BCBE-C282DF6D9A62")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IVsSolutionEvents7
{
    void OnAfterCloseFolder(string folderPath);
    void OnAfterLoadAllDeferredProjects();
    void OnAfterOpenFolder(string folderPath);
    void OnBeforeCloseFolder(string folderPath);
    void OnQueryCloseFolder(string folderPath, ref int pfCancel);
}

I want to implement that interface in my extension so that I can respond to those events, but I want the same extension assembly to be compatible with Visual Studio 2015, so I don't want a dependency on that VS 2017 DLL. Therefore, I copy-paste that definition into my code.

I can get that definition from the documentation, or from Visual Studio itself via F12 when I add a reference to that DLL, or from JustDecompile. They all give roughly the same definition of the interface.

But that interface definition doesn't work:

  • The methods are in the wrong order, so the wrong ones get called.
  • The strings aren't passed in properly - my guess is that they are assumed to be BStrs but these are LPWStrs.
  • I often get an access violation after a call - my guess is an incorrect calling convention.

If I apply a pile of attributes to the interface, so it becomes this:

[ComVisible(true)]
[ComImport]
[Guid("A459C228-5617-4136-BCBE-C282DF6D9A62")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IVsSolutionEvents7
{
    [PreserveSig, MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    int OnAfterOpenFolder([In, MarshalAs(UnmanagedType.LPWStr)] string folderPath);
    ...

and put the methods in the right order, then it works.

How are you supposed to know which of those attributes to use, or which order to to put the methods in? The documentation is not necessarily useful - the documentation for that interface, for example, adds nothing to the simple broken definition above.

Even when I have access to an assembly that defines the interface (Microsoft.VisualStudio.Shell.Interop.15.0.DesignTime.dll in this case), where do these attributes go? When I look at the definition in that DLL in either Visual Studio or JustDecompile, I only see that simple definition, and yet if I use the interface via a reference to that DLL, it works. The attributes are somehow there but invisible, or are being defaulted differently from when I define the interface myself.

I cobbled together the attributes I'm using from cargo-cult copying, examining the IDL, and blind trial and error, and as such I don't really trust them. How should I have done it?

回答1:

It's just that JustDecompile does a bad job here. Other tools such as DotPeek, DnSpy and Reflector (commercial) all seem to work fine. Visual Studio F12 is only useful because it's integrated, but useless for interop.

Another option is to use the C/C++/H/IDL files when they are available. The IDL is available here <programfiles>Microsoft Visual Studio\2017\<sku>\VSSDK\VisualStudioIntegration\Common\IDL\vsshell150.idl and the resulting .h header file here <programfiles>Microsoft Visual Studio\2017\<sku>\VSSDK\VisualStudioIntegration\Common\inc\vsshell150.h

They are the law. When in doubt, refer to one of them (I prefer the .h, the lowest binary level).

This is how IVsSolutionEvents7 is defined in the .h file:

MIDL_INTERFACE("A459C228-5617-4136-BCBE-C282DF6D9A62")
IVsSolutionEvents7 : public IUnknown
{
public:
    virtual HRESULT STDMETHODCALLTYPE OnAfterOpenFolder( 
        /* [in] */ __RPC__in LPCOLESTR folderPath) = 0;

    virtual HRESULT STDMETHODCALLTYPE OnBeforeCloseFolder( 
        /* [in] */ __RPC__in LPCOLESTR folderPath) = 0;

    virtual HRESULT STDMETHODCALLTYPE OnQueryCloseFolder( 
        /* [in] */ __RPC__in LPCOLESTR folderPath,
        /* [out][in] */ __RPC__inout BOOL *pfCancel) = 0;

    virtual HRESULT STDMETHODCALLTYPE OnAfterCloseFolder( 
        /* [in] */ __RPC__in LPCOLESTR folderPath) = 0;

    virtual HRESULT STDMETHODCALLTYPE OnAfterLoadAllDeferredProjects( void) = 0;

};


回答2:

Just use Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll, all newer VS version have binding redirects