-
Notifications
You must be signed in to change notification settings - Fork 992
Interop guidelines
READ First: Native interoperability best practices
A: No, interop imports should all be internal to match the CoreCLR/FX convention.
A: BOOL
requires no marshaling, bool
does.
bool
is marshaled as BOOL
(UnmangedType.Bool
) in P/Invokes, and VARIANT_BOOL
(UnmanagedType.VariantBool
) in COM imports.
.NET bool is never blitted, it is always marshalled, and by default as 'int'. You can add the [MarshalAs]
to tell it otherwise, or use our "native" wrapper enums.
A: The differences between blitting structs and classic marshalling.
-
Marshal.SizeOf
for classic marshalling size -
Marshal.OffsetOf
for classic marshalling offsets -
sizeof
keyword for blitting size - pointer arithmetic for blitting offsets
Example where it makes a difference:
public struct Sample
{
public int a;
public bool b;
public short c;
}
var sample = new Sample();
var blittingSize = sizeof(Sample); // 8
var blittingOffset = (byte*)&sample.c - (byte*)&sample; // 6
var marshalSize = Marshal.SizeOf<Sample>(); // 12
var marshalOffset = Marshal.OffsetOf<Sample>(nameof(Sample.c)); // 8
A: If there aren't any strings
or char
s then Charset
isn't useful.
Charset
is Ansi
by default so we should always be setting it as we'll almost always want it and we'll want to be super explicit when we don't. For the APIs that have a A/W
it's best to be explicit to avoid the probing.
A: For the APIs that have a A/W
it's best to be explicit to avoid the probing. As a general rule, we set ExactSpelling=true
for all p/invokes.
A: Everything in COM derives from IUnknown
. Only some classes support IDispatch
(which derives from IUnknown
).
The documentation goes into detail here, but the gist is that object by default marshals as VARIANT
, which is almost always not what we want.
If we don't use object
and use a ComImport
interface you don't need anything.
Q: So what is the practical difference then between things like .Interface
, .IUnknown
and .IDispatch
marshalling? Surely if all interfaces implement IUnknown
then there is no difference?
A: Which of the 3 you use depends on the C signature:
- C signature is IUnknown, use
IUnknown
- C signature is IDispatch, use
IDispatch
- C signature is some interface, use
Interface
and make sure the C# type is actually a matching COM import
IUnknown
vs. IDispatch
depends on the C signature. Some methods explicitely have an IDispatch
in the signature, so if you pass an IUnknown
you get an access violation if the method actually calls into it because it has less methods than IDispatch
. As said above not all COM objects implement IDispatch
so I'd only use it when the signature actually demands it, though passing an IDispatch
when only IUnknown
is demanded doesn't harm anything (assuming the thing you pass actually implements IDispatch
).
When a C signature demands an explicit interface (and not just an IUnknown
or IDispatch
) then you as the caller need to do the QueryInterface for that interface, which is when you need to use Interface
- it will look at the C# type (which must be a ComImport interface) and QueryInterface for the GUID on the interface. If you don't do that and use IUnknown
or IDispatch
then you'll get access violations when the called method tries to call anything on the interface.
Note that the reverse is less problematic, for example you can always pass a more specific interface when the caller is just expecting IUnknown
.
A: You only need to do it for object
, which marshals as variant by default. If you're returning/passing a COM interface it marshals as interface.
object
its equivalent to [MarshalAs(IUnknown)]
because you can't specify the IID to query for:
This member produces the same behavior as IUnknown when you apply it to the Object data type.
A: Strictly speaking it is not necessary if a delegate doesn't outlive a method call. Marshaling a Delegate as a Callback Method says:
When you use a delegate inside a call, the common language runtime protects the delegate from being garbage collected for the duration of that call.
However we do want to use GC.KeepAlive
until after the native call is returned. See for more details.
- Dependencies
- ARM64