Содержание
- P/Invoke and COM imports
- Q: Should interop imports be declared as public?
- Q: Should interop imports use BOOL vs bool?
- Q: What's the difference between sizeof(T) and Marshal.SizeOf<T>()?
- Q: When to declare Charset in DllImportAttributes?
- Q: When to use the ExactSpelling field and an explicit W in DllImportAttributes?
- Q: What's the difference between MarshalAs for .IUnknown, .IDispatch and .Interface?
- 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?
- Q: In what scenario do we need to decorate members with MarshalAs(Interface)?
- Q: Do I need to keep a callback alive while it is marshaled to a native call?
P/Invoke and COM imports
READ First: Native interoperability best practices
Q: Should interop imports be declared as public
?
A: No, interop imports should all be internal to match the CoreCLR/FX convention.
Q: Should interop imports use BOOL
vs bool
?
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.
Q: What's the difference between sizeof(T)
and Marshal.SizeOf<T>()
?
A: The differences between blitting structs and classic marshalling.
Marshal.SizeOf
for classic marshalling sizeMarshal.OffsetOf
for classic marshalling offsetssizeof
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
Q: When to declare Charset
in DllImportAttribute
s?
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.
Q: When to use the ExactSpelling
field and an explicit W
in DllImportAttribute
s?
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.
Q: What's the difference between MarshalAs for .IUnknown
, .IDispatch
and .Interface
?
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
.
Q: In what scenario do we need to decorate members with MarshalAs(Interface)
?
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.
Q: Do I need to keep a callback alive while it is marshaled to a native call?
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.
Guidelines
Build
- Dependencies
- ARM64