PreserveSig

For managed signatures that represent unmanaged functions, the PreserveSig pseudo custom attribute controls an important aspect of run-time behavior.  In COM Interop scenarios, a C# method defined as follows:

  string GetName();

is treated like the following unmanaged method (in IDL syntax):

  HRESULT GetName([out, retval] BSTR* pRetVal);

When managed code calls a COM object implementing GetName, a managed exception is thrown if the COM object returns a failure HRESULT.  The type and contents of the exception can vary, but the details of this are probably best left for another day.  The absense of an exception means that the unmanaged method returned a success HRESULT.

Had the C# signature been marked with PreserveSigAttribute:

  [PreserveSig] string GetName();

then the CLR interprets the signature more literally, as if the corresponding unmanaged method is defined as follows:

  BSTR GetName();

which is pretty rare to see in COM components (excluding dispinterfaces).

Therefore, when a method is marked with PreserveSigAttribute, the managed signature must preserve the "shape" of the unmanaged signature.  The usual return value and HRESULT/Exception transformations are not done, even if the unmanaged signature returns an HRESULT.

So let's go back to the original unmanaged GetName method that returns an HRESULT.  We have essentially two ways to represent it in C#:

  string GetName();

or

[PreserveSig] int GetName(out string pRetVal);

The former representation is what you normally see, especially since the type library importer creates signatures this way.  Why would you want to use the latter representation?

1) If the method returns multiple success HRESULTs (such as S_OK and S_FALSE), this would be your only way to expose which got returned to callers.  IPersistStream::IsDirty is an example of such a method.

2) If the method often returns failure HRESULTs for non-exceptional conditions (which would be a poorly-designed COM API), using PreserveSig can boost performance.  That's because having the CLR return an integer is much faster than having it throw an exception.  Of course, managed callers should then do the tedious work of checking HRESULTs when calling such methods.

3) If a COM object exposes information from an error object via customized interfaces (in other words, implementing an interface like "IAdditionalErrorInfo" in addition to IErrorInfo on an object passed to SetErrorInfo), then marking methods with PreserveSig is necessary to prevent the CLR from calling GetErrorInfo and swallowing the custom error object when a failure HRESULT is returned.  After calling such a method marked with PreserveSig, managed code could then make a PInvoke call to GetErrorInfo, cast the returned object to a managed definition of the customized interface, then extract the desired information.  When error objects only expose information via IErrorInfo, changing signatures to use PreserveSig is not required because all the information (except the GUID returned from IErrorInfo::GetGUID) is copied to members of the exception thrown by the CLR.

Unlike COM Interop scenarios, PInvoke signatures have PreserveSig semantics by default.  So if you're using PInvoke to call an API that returns an HRESULT, like ProgIDFromCLSID, you'd typically expose the HRESULT in your managed signature:

  [DllImport("ole32.dll", CharSet=CharSet.Unicode)]

  static extern int ProgIDFromCLSID(

    [In] ref Guid clsid,

    out string lplpszProgID);

and call it like this:

  int hresult = ProgIDFromCLSID(ref g, out progID);

  if (hresult < 0)

    Console.WriteLine("The call failed: 0x{0:X}",

      hresult);

But using DllImportAttribute's boolean PreserveSig named parameter, you can turn off the PreserveSig semantics:

  [DllImport("ole32.dll", CharSet=CharSet.Unicode,

    PreserveSig=false)]

  static extern void ProgIDFromCLSID(

    [In] ref Guid clsid,

    out string lplpszProgID);

and call it like this:

  try { ProgIDFromCLSID(ref g, out progID); }

  catch (Exception ex) {

    Console.WriteLine("The call failed: " +

      ex.ToString()); }

This DllImport-specific PreserveSig mechanism is necessary since PInvoke's default behavior already matches what you'd get if you could mark a DllImport signature with PreserveSigAttribute.

Make sure that you only use PreserveSig=false with PInvoke signatures if the unmanaged function really returns an HRESULT.  You could mistakenly mark this on a signature that returns void, for example, and not notice on x86 plaforms if the value in EAX happens to be 0 after the call (which would be treated as if the function returned S_OK).  But will you be so lucky if you try running the code using the Shared Source CLI (aka Rotor) on FreeBSD?