ElektroStudios ElektroStudios - 6 months ago 66
Vb.net Question

How to use win32 'IOpenControlPanel' interface from managed code?

I'm trying to implement the IOpenControlPanel interface, which is not documented in sites like pinvoke.net, so for this task I built the definitions from scratch as I think they should be, then I tried to manually retrieve the interface CLSID from registry, which seems to be

D11AD862-66DE-4DF4-BF6C-1F5621996AF1
, and a Class that implements that inferface, which seems to be
06622D85-6856-4460-8DE1-A81921B41C4B
.

The problem is that in the following code If I call
GetCurrentView
function I don't get the expected value, and a call to
Open
function does nothing (I'm using a proper canonical name like
Microsoft.DefaultPrograms
as explained in this MSDN article from this list of canonical names.)

Dim cp As New COpenControlPanel
Dim view As ControlPanelView
DirectCast(cp, IOpenControlPanel).GetCurrentView(view)
DirectCast(cp, IOpenControlPanel).Open("Microsoft.DefaultPrograms", "", Nothing)


So, I think that my definitions are wrong in some way, I need help to fix it.




These are the the definitions:

VB.Net:

Friend NotInheritable Class NativeMethods

Enum ControlPanelView As Integer
Classic = 0
Category = 1
End Enum

<ComImport()>
<Guid("06622D85-6856-4460-8DE1-A81921B41C4B")>
Class COpenControlPanel
End Class

<ComImport()>
<InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
<Guid("D11AD862-66DE-4DF4-BF6C-1F5621996AF1")>
Interface IOpenControlPanel

<PreserveSig()>
Function GetCurrentView(ByRef refView As ControlPanelView
) As Integer

<PreserveSig()>
Function GetPath(<MarshalAs(UnmanagedType.LPWStr)> ByVal name As String,
<MarshalAs(UnmanagedType.LPWStr)> ByRef refPath As StringBuilder,
ByVal bufferSize As Integer
) As Integer

<PreserveSig()>
Function Open(<MarshalAs(UnmanagedType.LPWStr)> ByVal name As String,
<MarshalAs(UnmanagedType.LPWStr)> ByVal page As String,
ByVal punkSite As IntPtr
) As Integer

End Interface

End Class


C# (online translation):

internal sealed class NativeMethods {

public enum ControlPanelView : int {
Classic = 0,
Category = 1
}

[ComImport()]
[Guid("06622D85-6856-4460-8DE1-A81921B41C4B")]
class COpenControlPanel {}

[ComImport()]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[GuidAttribute("D11AD862-66DE-4DF4-BF6C-1F5621996AF1")]
public interface IOpenControlPanel {

[PreserveSig()]
int GetCurrentView(ref ControlPanelView refView);

[PreserveSig()]
int GetPath([MarshalAs(UnmanagedType.LPWStr)] string name,
[MarshalAs(UnmanagedType.LPWStr)] ref StringBuilder refPath,
int bufferSize);

[PreserveSig()]
int Open([MarshalAs(UnmanagedType.LPWStr)] string name,
[MarshalAs(UnmanagedType.LPWStr)] string page,
IntPtr punkSite);
}
}

Answer

Your interface definition is wrong because you defined methods in the same order as MSDN does (in fact, names are not important, what's important is the interface methods layout: matching binary signatures in the correct order). The order must be exactly what's defined in .h files available with the Windows SDK, not what MSDN displays - this is actually misleading :-). In this case, the header file is Shobjidl.h. This is how it's defined in C/C++:

MIDL_INTERFACE("D11AD862-66DE-4DF4-BF6C-1F5621996AF1")
IOpenControlPanel : public IUnknown
{
public:
    virtual HRESULT STDMETHODCALLTYPE Open( 
        /* [string][unique][in] */ __RPC__in_opt_string LPCWSTR pszName,
        /* [string][unique][in] */ __RPC__in_opt_string LPCWSTR pszPage,
        /* [unique][in] */ __RPC__in_opt IUnknown *punkSite) = 0;

    virtual HRESULT STDMETHODCALLTYPE GetPath( 
        /* [string][unique][in] */ __RPC__in_opt_string LPCWSTR pszName,
        /* [size_is][string][out] */ __RPC__out_ecount_full_string(cchPath) LPWSTR pszPath,
        /* [in] */ UINT cchPath) = 0;

    virtual HRESULT STDMETHODCALLTYPE GetCurrentView( 
        /* [out] */ __RPC__out CPVIEW *pView) = 0;

};

There are multiple equivalent definition in .NET, C#, but here is one that should work:

    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("D11AD862-66DE-4DF4-BF6C-1F5621996AF1")]
    public interface IOpenControlPanel
    {
        [PreserveSig]
        int Open([MarshalAs(UnmanagedType.LPWStr)] string name,
                 [MarshalAs(UnmanagedType.LPWStr)] string page,
                                                   IntPtr punkSite);
        [PreserveSig]
        int GetPath([MarshalAs(UnmanagedType.BStr)] string name,
                    [MarshalAs(UnmanagedType.LPWStr)] StringBuilder refPath,
                                                      int bufferSize);

        // if you remove PreserveSig, you can return the [out] param directly
        // note in this case, the function could throw instead of returning an error int like with PreserveSig
        ControlPanelView GetCurrentView();
    }
Comments