Implicit method signature translations in .NET interop
To stay programming language agnostic, the Windows COM system and many Windows APIs return a 4 byte integer type called an HRESULT
to indicate whether an API succeeded or failed, along with some information about the failure. Other values that need to be passed to the caller are "returned" via pointer parameters that act as "out" parameters, and are typically the last parameter in the signature. Languages like C# and Visual Basic traditionally translate a failure code to an exception to match how failures are usually propagated in the language, and expect interop method signatures to not include the HRESULT
. To translate the method signature to a native signature, the runtime moves the return value of the method to an additional "out" parameter with one more level of indirection (in other words, makes it a pointer to the managed signature's return type), and assumes an HRESULT
return value. If the managed method returns void
, no additional parameter is added and the return value becomes an HRESULT
. For example, see the following two C# COM methods that translate to the same native signature:
int Add(int a, int b);
void Add(int a, int b, out int sum);
HRESULT Add(int a, int b, /* out */ int* sum);
PreserveSig in COM
All COM methods in C# are expected to use the translated signature by default. To use and export methods without the signature translation and handling of HRESULT
values, add the PreserveSigAttribute to a COM interface method. When the attribute is applied to a method, no translation is done to the signature, and exceptions aren't thrown for failing HRESULT
values. This applies to both built-in COM and source-generated COM. For example, see the following C# method signature with a PreserveSig
attribute and its corresponding native signature.
[PreserveSig]
int Add(int a, int b, out int sum);
HRESULT Add(int a, int b, int* sum);
This can be useful if the method might return different HRESULT
values that aren't failures, but must be handled differently. For example, some methods might return the value S_FALSE
when a method doesn't fail but only returns partial results, and S_OK
when it returns all results.
PreserveSig
with P/Invokes
The DllImportAttribute attribute also has the bool PreserveSig
field that works similarly to the PreserveSigAttribute
, but defaults to true
. To indicate that the runtime should translate the managed signature and handle the HRESULT
that is returned, set the PreserveSig
field to false
in the DllImportAttribute
. For example, see the following signatures of two P/Invokes to the same native method, one with PreserveSig
set to false
, and one with it left to the default true
value.
[DllImport("shlwapi.dll", EntryPoint = "SHAutoComplete", ExactSpelling = true, PreserveSig = false)]
public static extern void SHAutoComplete(IntPtr hwndEdit, SHAutoCompleteFlags dwFlags);
[DllImport("shlwapi.dll", EntryPoint = "SHAutoComplete", ExactSpelling = true)]
public static extern int SHAutoCompleteHRESULT(IntPtr hwndEdit, SHAutoCompleteFlags dwFlags);
Note
Source-generated P/Invokes, which use the LibraryImportAttribute, have no PreserveSig
field. The generated code always assumes the native and managed signature are identical. For more information, see Source-generated P/Invokes.
Manually handle HRESULT
values
When calling a PreserveSig
method that returns an HRESULT
, you can use the ThrowExceptionForHR method to throw the corresponding exception if the HRESULT
indicates a failure. Similarly, when implementing a PreserveSig
method, you can use the GetHRForException method to return the HRESULT
that indicates a corresponding value for the exception.
Marshal HRESULTs as structs
When using a PreserveSig
method, int
is expected to be the managed type for HRESULT
. However, using a custom 4-byte struct as the return type allows you to define helper methods and properties that can simplify working with the HRESULT
. In built-in marshalling, this works automatically. To use a struct in place of int
as the managed representation of HRESULT
in source-generated marshalling, add the MarshalAsAttribute attribute with Error as the argument. The presence of this attribute reinterprets the bits of the HRESULT
as the struct.