Search code examples
c#pinvoke

Can PInvoke convert [out] parameter into return value?


I'm new to PInvoke and I need some help.

I want to access IEnumGUID using PInvoke, and I found a code block on pinvoke.net.

 [ComImport, Guid("0002E000-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), ComVisible(false)]
 public interface IEnumGUID
 {
     int Next(int celt, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0)]Guid[] rgelt);
     void Skip(int celt);
     void Reset();
     [return: MarshalAs(UnmanagedType.Interface)]
     IEnumGUID Clone();
 }

The Clone function here gives IEnumGUID as a return value, while the function in the the original C++ interface gives it as an out parameter.

HRESULT Clone(
  [out]  IEnumGUID **ppenum
);

I have learnt that PInvoke automatically converts HRESULT into COMException, but I don't know how PInvoke works to convert the out parameter to a return value.

Please give some explanation about this, so that I can correctly use this method later.


Solution

  • The pinvoke.net website isn't exactly perfect. It has a lot of mistakes, inversely proportional to the likelihood that anybody uses the declaration in their code. Low odds for this one, the declaration you found is badly broken.

    This does not have anything to do with pinvoke, COM interop is a distinctive feature in .NET. The ability to transform an argument to a return value is highly specific to COM Automation and highly specific to an argument that's decorated with the [out, retval] attribute. It is [retval] that provides the hint to the COM client runtime support library that the argument can be treated as a return value. Very common, any scripting language takes advantage of that for example. So does any .NET language, unless that conversion is explicitly suppressed with the [PreverseSig] attribute.

    IEnumGUID::Clone()'s argument doesn't have the [retval] attribute so should not be declared like it has one. That's not the only mistake, the other members require the [PerserveSig] attribute since you need to know their return value to use them correctly. The proper declaration is:

    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("0002E000-0000-0000-C000-000000000046")]
    public interface IEnumGUID
    {
        [PreserveSig]
        int Next(int celt, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0)] Guid[] rgelt, out int pceltFetched);
        [PreserveSig]
        int Skip(int celt);
        [PreserveSig]
        int Reset();
        void Clone(out IEnumGUID ppenum);
    }
    

    COM IEnumXxxx interfaces are very common, distinguished only by the type of element returned by the enumerator. Several of them are present in the .NET Framework, compare to this one for example.