├── .gitignore
├── README.md
└── doc
└── interop-doc.md
/.gitignore:
--------------------------------------------------------------------------------
1 | *.*~
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # .NET Interop Document Respository
2 |
3 | This repository is inspired by [mem-doc](https://github.com/Maoni0/mem-doc). Its purpose is to provide insight and guidance on how to be successful when interoperating between .NET and another virtual machine or non-managed language (for example, C/C++). This repository is living so is by definition "incomplete". Contributions are welcome.
4 |
5 | **Disclaimer**: Details in this repository are based on the author's experience. Information contained within should not be taken as official Microsoft or .NET recommendations/guidance. For official documentation see below.
6 |
7 | ## Official Links
8 |
9 | [.NET organization](https://github.com/dotnet) - the GitHub organization for .NET.
10 |
11 | [.NET runtime](https://github.com/dotnet/runtime) - the .NET runtime source repository.
12 |
13 | [Native interoperability with .NET](https://docs.microsoft.com/dotnet/standard/native-interop/) - official documentation for interoperability with .NET.
14 |
--------------------------------------------------------------------------------
/doc/interop-doc.md:
--------------------------------------------------------------------------------
1 | # .NET Interop Document
2 |
3 | **Context for this document is always relative to the [current release of the .NET platform.](https://dotnet.microsoft.com/download)**
4 |
5 | ### Contents
6 |
7 | - [Introduction](#intro) – Introduction to how interoperability works in .NET.
8 | - [Terminology](#terms).
9 | - [Transitioning between Managed and Native](#transition).
10 | - [Concepts](#concepts) – Fundamental ideas in .NET interoperability.
11 | - [Garbage Collection](#gc) – Pinning and `GCHandle`s.
12 | - [Referencing Memory](#referencingmemory).
13 | - [Blittable vs Unmanaged types](#blittablevsunmanaged).
14 | - [IL Stubs and Reverse IL Stubs](#ilstubs) – Generation of IL Stubs for marshalling support.
15 | - [C++/CLI](#cppcli) – Merging .NET and C++ into a single language.
16 | - [C++ language extensions](#cppcli_cpplangext).
17 | - [Activation of the .NET runtime](#cppcli_activation).
18 | - [COM and `IUnknown`](#comiunknown) – Working with COM and the `IUnknown` ABI.
19 | - [WinRT](#winrt) - **TODO**
20 | - [Diagnostics](#diagnostics) – Understanding what is happening.
21 | - [Tooling](#tooling) – Tools that can help building interop scenarios.
22 | - [FAQs](#faqs) – Common interop questions.
23 |
24 |
27 |
28 | ## Introduction
29 |
30 | The .NET interop system is an intrinsic part of the .NET platform and its impact can be observed down to the binary level – defined in [ECMA-335][spec_ecma335]. The representation of C language function exports with the common [x86 calling conventions][wiki_x86callconv] at the time of writing are literally able to be encoded at the lowest readable level. The lowest level in this case is termed "Intermediate Language" or "IL". The point of interop in .NET is to express in IL how software _not_ defined in IL and _not_ the .NET platform itself should work with logic and data owned and managed by a .NET runtime implementation.
31 |
32 | An example is warranted. Imagine wanting to call the following C function from a .NET language (for example, C#).
33 |
34 | ```C
35 | // Return the sum of the supplied array of integers.
36 | int32_t sum_ints(int32_t length, int32_t* integers)
37 | {
38 | int32_t acc = 0;
39 | for (int32_t i = 0; i < length; ++i)
40 | acc += integers[i];
41 | return acc;
42 | }
43 | ```
44 |
45 | In order to call this function it must first be declared in a manner that permits being called in C#. The .NET runtime will also need to know the name of the C library the function is defined in. The arguments to this function must also be considered. Since the first argument is a primitive type in both C and .NET (that is, a 4 byte integer) how it is passed to this function is relatively straight forward. The second argument is more complicated because it is an array. In order to pass a C# array naturally to this function, the .NET runtime must convert an `int[]` to a C `int32_t*`. This is the nature of .NET interoperability – working with a system that is not .NET.
46 |
47 | ### Terminology
48 |
49 | Understanding the .NET interop domain is difficult without defining common jargon. Below is a list of terms used throughout this document. Terms are **bolded** in definitions when also defined in this section.
50 |
51 | [**Blittable**][doc_blittable] – Data that has the same representation within and outside the .NET platform. Blittable data doesn't need to be **marshalled**. This term is a concept and not explicitly defined by the ECMA-335 specification or any .NET language.
52 |
53 | **IL Stub** – A function generated by the .NET runtime at run time to facilitate a **managed** to **native** transition.
54 |
55 | **Managed** – Code running under the control of a virtual machine where memory is managed automatically by a Garbage Collector (for example, .NET runtime). When referring to memory or data its lifetime is managed automatically.
56 |
57 | **Marshal** – The process of converting data when transitioning between **managed** and **native** code.
58 |
59 | **Native** – Code running where memory is managed manually by the developer (for example, C or C++). When referring to memory or data its lifetime is managed manually.
60 |
61 | [**Pinning**][doc_pinning] – Mechanism used to indicate to the Garbage Collector not to move the **managed** data.
62 |
63 | **Reverse IL Stub** – A function generated by the .NET runtime at run time to facilitate a **native** to **managed** transition.
64 |
65 | **Unmanaged** – See **native**. The ['unmanaged'][doc_unmanaged_types] term is also used with the C# language and has special meaning for .NET interoperability with C#.
66 |
67 | ### Transitioning between Managed and Native
68 |
69 | Declaring the previous C function to be callable in C# is accomplished via [`DllImportAttribute`][api_dllimportattr] – also called a [P/Invoke][doc_pinvoke]. Assuming the C function is contained within a C library named `NativeLib.dll`, the following C# would declare the native function. Other mechanisms for defining callable native functions in IL are discussed in subsequent sections.
70 |
71 | ```csharp
72 | [DllImport("NativeLib.dll")]
73 | static extern int sum_ints(int length, int[] integers);
74 | ```
75 |
76 | This P/Invoke declaration enables the C function to be called in C# but this call is not as simple as it may appear. Given this P/Invoke declaration an implicit function will also need to be constructed.
77 |
78 | The implicit function is what is called an IL Stub. This IL Stub is a stream of IL instructions computed at run time by the interop system to marshal the function arguments from managed to native. There are a number of considerations that must be accounted for by the IL Stub but for the purposes of this example the IL Stub's responsibility is to ensure the two arguments are safely passed to the native function and the native function's return value is returned to the C# caller. An annotated and slightly modified example of an IL Stub is below. It is similar to what would be generated by the .NET runtime.
79 |
80 | ```
81 | int32 ILStub_sum_ints(int32,int32[])
82 | .locals (int64,native int,object pinned)
83 | ldarg.0
84 | conv.i8
85 | stloc.0 // Store input length as 64-bit number.
86 | ldc.i4.0
87 | conv.i
88 | stloc.1 // Initialize pointer local to null.
89 | ldarg.1 // Load and check if array is null.
90 | brfalse NO_ARRAY
91 | ldarg.1
92 | stloc.2 // Pin the array so GC won't move it.
93 | ldloc.2
94 | conv.i
95 | ldc.i4.s 0x10
96 | add
97 | stloc.1 // Store pointer to managed array data.
98 | NO_ARRAY :ldloc.0 // Load input length.
99 | ldloc.1 // Load pointer to managed array data.
100 | call native int GetStubContext()
101 | ldc.i4.s 0x20
102 | add
103 | ldind.i
104 | ldind.i // Load pointer to native function.
105 | calli unmanaged stdcall int32(int64,native int)
106 | ret // Return value on stack.
107 | ```
108 |
109 | The precise details of the IL Stub aren't necessary to understand but the general flow is described to help illustrate its purpose. With the above IL Stub, a C# application can now call the `sum_ints()` function written in C.
110 |
111 | ```csharp
112 | int[] arr = new[] { 1, 2, 3 };
113 | int len = arr.Length;
114 |
115 | int sum = sum_ints(len, arr);
116 |
117 | // During the call to the P/Invoke the call stack
118 | // will be the following:
119 | // - ILStub_sum_ints(len, arr) // C#
120 | // Locals:
121 | // int64 l0 = len
122 | // int* l1 = pointer into arr's data
123 | // object l2 = pinned arr
124 | // - sum_ints(l0, l1) // C
125 | ```
126 |
127 | During a transition from native to managed a Reverse IL Stub is generated. This stub performs similar duties to the one defined above but in the opposite direction – marshals data from native to managed.
128 |
129 | This represents a conceptual introduction into how the .NET runtime handles interop.
130 |
131 | ## Concepts
132 |
133 | There are some fundamental concepts in .NET interop that provide a core understanding to how the system works and what is and isn't possible. Some of these concepts are only exposed in IL or some times limited to a single .NET language (for example, C# with [`unmanaged` function pointers](https://docs.microsoft.com/dotnet/csharp/language-reference/proposals/csharp-9.0/function-pointers)). The concepts below are continually used when designing a solution involving interop. None of them are exhaustively explained here, rather the below represents the salient points on the concepts as they relate to interop.
134 |
135 | ### Garbage Collection
136 |
137 | The entire .NET system is based around Garbage Collection. For interop, Garbage collection will be mentioned as the answer to the "why" in many cases. Be aware that Garbage collection is a vast topic and only a narrow aspect of it will be discussed here. For a deeper understanding of the .NET Garbage Collector (GC) the [Book of the Runtime][doc_botr] (BOTR) should be used or the excellent [mem-doc][repo_mem_doc] maintained by the GC architect.
138 |
139 | What should be known about the GC is that memory allocated by the GC (that is, managed memory) that is directly exposed to native code requires coordination with the GC. This coordination means providing a way to let the GC know that native code may be using managed memory when attempting to collect "garbage" – one of its responsibilities. Another GC responsibility is to make efficient use of the memory it owns. This may entail compacting managed memory as holes are created between allocations during collection. These two GC responsibilities create two concerns when using managed memory in native code:
140 |
141 | 1) The GC collects the memory since there is no other managed code using it.
142 | 1) The GC moves the memory to a different address.
143 |
144 | One coordination mechanism was demonstrated in the [Introduction](#intro). Recall that the `int[]` passed to the P/Invoke was "pinned" prior to passing the `int*` to native code. Pinning lets the GC know the `int[]` shouldn't be moved from its current location in memory. It was still referenced in the method calling the native function so issue (1) above wasn't a concern. The example in the Introduction was in IL, but pinning can be accomplished in C# using the [`fixed`](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/fixed-statement) statement.
145 |
146 | Another mechanism is to use a [`GCHandle`][api_gchandle]. This provides a level of indirection to managed memory which means the `GCHandle` can be passed around instead of the managed memory itself. The `GCHandle` helps with (1) since it extends the managed memory's lifetime. The `GCHandle` API is powerful and has additional options that also enable it to handle (2) – see [`GCHandleType.Pinned`][api_gchandletype] – and even permit some insight into if the managed memory was collected – see [`GCHandleType.WeakTrackResurrection`][api_gchandletype].
147 |
148 | These two mechanisms are incredibly powerful and provide the needed coordination with the GC to make .NET interoperability with native code possible.
149 |
150 | ### Referencing Memory
151 |
152 | Indirectly referencing memory is an important feature of any programming model. Levels of indirection present challenges for interop in languages like IL and C# that permit levels of indirection to both managed and native memory at the same time. Below is an example of some C# and equivalent IL, in comments, that illustrate levels of indirection.
153 |
154 | ```csharp
155 | void Method(
156 | in Class ic, /* [in] class Class& ic */
157 | ref Class rc, /* class Class& rc */
158 | out Class oc, /* [out] class Class& oc */
159 | in Struct iv, /* [in] valuetype Struct& iv */
160 | ref Struct rv, /* valuetype Struct& rv */
161 | out Struct ov, /* [out] valuetype Struct& ov */
162 | Struct* pv, /* valuetype Struct* pv */
163 | in int ii, /* [in] int32& ii */
164 | ref int ri, /* int32& ri */
165 | out int oi, /* [out] int32& oi */
166 | int* pi) /* int32* pi */
167 | { ... }
168 | ```
169 |
170 | The above may appear to be a significant number of options but in reality, at the IL level, there are only two – the `&` and the `*`. The `[in]` and `[out]` represent metadata that carries semantic importance but doesn't impact the level of indirection. The `&` represents a reference to managed memory. This should not be confused with the C++ use of `&` – although they do share similar semantics. Note that the C# keywords `in`, `ref`, and `out` all reduce to managed references. The other, `*`, represents a native level of indirection or pointer to memory. This one is identical to the `*` found in C or C++. Note here that an example of a native pointer to a `Struct` and `int` are illustrated, but not to `Class`. When and how these indicators are applied to memory tells the interop system a lot about how the referenced memory should be treated during a transition between managed and native code.
171 |
172 | The use of native indirection (`*`, pointer) is more restrictive in practice and often limited to interop scenarios. Since .NET language compilers tend to help out and inform when it is and isn't possible to get a pointer to memory it is often acquired correctly; however once it is acquired, misuse can cause catastrophic issues. The pointer has all the same implications and assumptions inherent in a native environment.
173 |
174 | - The memory pointed at will not move.
175 | - The memory will exist until some explicit 'release'/'free'/'delete' action is performed.
176 |
177 | In order to get a pointer to managed memory on the .NET platform some coordination with the GC must be performed – see [Garbage Collection](#gc) for options. What types of memory a pointer can point at are also limited. For the C# example above notice the `Class` type does not have a pointer example. This is because it is not an [unmanaged type][doc_unmanaged_types]. Only unmanaged types may be what is called the "referent" of a pointer.
178 |
179 | Getting a pointer to native memory is as simple as having it be returned/passed from a native function or calling a managed API that is documented to return native memory (for example, [`Marshal.AllocCoTaskMem()`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshal.alloccotaskmem)).
180 |
181 | Referencing managed memory with `&` has much broader implications because it indicates the memory being referenced is also tracked by the GC. This level of indirection is not limited in C# to unmanaged types because the reference is only usable when in a managed environment. It is possible to convert a `&` to a `*`, but the referent must be an unmanaged type. For example the `ref Struct rv` argument above could be converted to a pointer in C# as follows:
182 |
183 | ```csharp
184 | fixed (Struct* prv = &rv)
185 | {
186 | ...
187 | }
188 | ```
189 |
190 | Observe though that in order to convert to a pointer coordination with the GC was performed. The `fixed` statement indicated that regardless of where this `Struct` is actually located, it mustn't be moved from that location.
191 |
192 | ### Blittable vs Unmanaged types
193 |
194 | These two concepts are important in interop – especially when involving the C# language. The two are closely related but are not equivalent and can cause confusion when thinking about interop. Both are defined in the [Terminology](#term) section but examples and additional clarity are warranted.
195 |
196 | [Blittable types][doc_blittable] are those types that have the same layout in memory in both managed and native environments. Since blittable isn't defined in the [ECMA-335][spec_ecma335] specification, runtime implementations are free to optimize cases where types aren't technically blittable but can be treated as such under certain circumstances.
197 |
198 | [Unmanaged types][doc_unmanaged_types] are defined in the C# specification and are not related to blittability at all.
199 |
200 | Consider the following .NET types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double`. Every one of these types has a representation that is the same in both a managed and native environment - so they are blittable. All of them are also defined as C# unmanaged types.
201 |
202 | There are however unmanaged types that are not blittable. The .NET types `bool`, `char`, and enumerations are unmanaged types, but are not considered blittable. The reason for this is partially historical and partially based on ECMA-335. The `bool` is specified as a single byte in .NET, but historically is marshalled as an `int` to align with the Windows' [`BOOL`][doc_windatatypes] type. The `char` is specified to be a two-byte 16-bit value, but the native mapping for a character in C/C++ could be either [`char`][cppdoc_char] or [`wchar_t`][cppdoc_wchar_t], which are 1 byte and 2 bytes respectively on the Windows platform. The `wchar_t` is particularly difficult given it is 4-bytes on some non-Windows platforms. Finally, the ECMA-335 specification in II.14.3 defines the underlying type of an enumeration may be any numeric type or a `bool` or `char`. This means that an enumeration can't always be considered blittable.
203 |
204 | The opposite case is also possible – blittable but not unmanaged. Single dimension arrays of blittable primitive types are considered blittable by the interop system but are not unmanaged types. Considering single dimension arrays of blittable primitive types as blittable is an example of a runtime implementation optimization.
205 |
206 | ### IL Stubs and Reverse IL Stubs
207 |
208 | The transition between a managed and native environment during functions calls may require help for various reasons. The most obvious cases involve marshalling types that aren't blittable as was [previously illustrated](#transition), but other cases occur when the semantics of the call are altered to improve the interop experience. For example, consider the [`PreserveSig`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.dllimportattribute.preservesig) field on [`DllImportAttribute`][api_dllimportattr]. This field indicates if APIs that return a Windows' [`HRESULT`][wiki_hresult] error code should convert that error code into a .NET [`Exception`][api_exception] or retain the original function signature and return an `int`. This translation is handled in an ad-hoc basis based on the function declaration and is performed by the runtime generated IL Stub.
209 |
210 | Before discussing what these stubs do, let's define what they don't do.
211 | - The stubs themselves do not perform any GC mode transitions (that is, [switch from Preemptive to Cooperative mode][doc_botr]). That transition logic is performed around the stubs but added by the JIT during JIT compilation.
212 | - The stubs are not responsible for handling native calling conventions. Prior to .NET 6, the generated IL Stubs were involved in calling conventions under certain circumstances when calling a COM or C++ member function on Windows. A consequence of having the IL Stubs handle aspects of calling conventions meant that anyone wanting to auto-generate interop code had to handle these same aspects.
213 | - As of .NET 5, IL Stubs don't do anything that can't be done in C#. Prior to C# function pointers, the IL Stubs used IL instructions that were not representable in C# and therefore were difficult to be source-generated.
214 |
215 | Let's enumerate what IL Stubs do and what influences them.
216 |
217 | - Respond to the fields on the [`DllImportAttribute`][api_dllimportattr]. Each of the available fields influences either the logic in the IL Stub or the intended native target.
218 | - Respond to interop-related attributes that control transitioning or marshalling behavior. For example, see [`PreserveSigAttribute`][api_preservesigattr] and [`UnmanagedFunctionPointerAttribute`][api_unmanagedfunctionpointerattr], and [`MarshalAsAttribute`][api_marshalasattr].
219 | - Marshal each argument into the appropriate form for the target. For example, if a .NET `string` is being passed to a native function, then the stub will convert it to a `wchar_t const *` by default on Windows. Conversely, if a `wchar_t const *` is passed to a Reverse IL Stub from native then that stub is responsible for converting it to a .NET `string`.
220 | - Marshal out all non-[`in`](
221 | https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/in-parameter-modifier) by-ref arguments and the return type to the caller. The [`OutAttribute`][api_outattr] is a special case that indicates out semantics should be followed and can be applied to an argument that is passed by-value instead of by-ref.
222 | - The [`MarshalAsAttribute`][api_marshalasattr] deserves special mention as it can heavily influence how any argument or return value is marshalled.
223 | - Ensure the unmanaged/managed boundary is seamless as it relates to memory leaks or corruption. For example, if passing a [`SafeHandle`][api_safehandle] derived type to a native function, a leak of that handle shouldn't be possible.
224 |
225 | An IL Stub or Reverse IL Stub will be generated in several circumstances. The following examples represent symmetrical operations for a function that takes a sequence of characters (that is, a string) and returns a new string that has been converted into lowercase letters. The logic of that operation is defined within the function itself but in order to correctly pass the arguments and return the value to the other environment, a stub is used.
226 |
227 | **IL Stub**
228 |
229 | The generated stub will marshal the .NET `string` `"ABCDEFG"` to the native function `to_lower()`.
230 |
231 | ```csharp
232 | [DllImport("NativeLib.dll", EntryPoint = "to_lower")]
233 | static extern string ToLower(string str);
234 |
235 | string lowered = ToLower("ABCDEFG");
236 | ```
237 |
238 | Another way to call the native `to_lower()` function is using a `Delegate` type – which will also require an IL Stub.
239 |
240 | ```csharp
241 | [UnmanagedFunctionPointer(CallingConvention.Winapi)]
242 | delegate string ToLowerDelegate(string str);
243 |
244 | IntPtr mod = NativeLibrary.Load("NativeLib.dll");
245 | IntPtr fptr = NativeLibrary.GetExport(mod, "to_lower");
246 |
247 | string toLower = Marshal.GetDelegateForFunctionPointer(fptr);
248 | string lowered = toLower("ABCDEFG");
249 | ```
250 |
251 | The generated stub in the above examples would convert the supplied .NET `string` to the appropriate native representation. This conversion carries some implications that are worth considering. The .NET platform internally stores `string` values as UTF-16 encoded values. This means that if the native environment were expecting a UTF-8 or ANSI code page encoding, that would need to be handled by some logic – the IL Stub in this case. The `to_lower()` function also returns a string, which means the reverse operation (converting the native representation to a .NET `string`, including encoding considerations), must be taken into account. An additional consideration is that the GC is free to move the .NET `string` during the native dispatch call. The generated stub must also account for this possible GC interaction and employ pinning of managed memory when appropriate.
252 |
253 | **Reverse IL Stub**
254 |
255 | A Reverse IL Stub will be created so the native caller can provide a native string and have it marshalled to a .NET `string`.
256 |
257 | ```csharp
258 | delegate string ToLowerDelegate(string str);
259 |
260 | static string ToLowerManaged(string str)
261 | {
262 | return str.ToLower();
263 | }
264 |
265 | [DllImport("NativeLib.dll", EntryPoint = "call_fptr")]
266 | static extern void CallFptr(IntPtr fptr);
267 |
268 | var fptr = Marshal.GetFunctionPointerForDelegate(ToLowerManaged);
269 | CallFptr(fptr);
270 |
271 | // Native code - for the Windows platform.
272 | // typedef const wchar_t* (*to_lower_t)(const wchar_t* str);
273 | // void call_fptr(void* fptr)
274 | // {
275 | // const wchar_t* lowered = ((to_lower_t)fptr)(L"ABCDEFG");
276 | // }
277 | ```
278 |
279 | ## C++/CLI
280 |
281 | Support for writing C++ to run in a .NET runtime is accomplished using a language called C++/CLI as specified in [ECMA-372][spec_ecma372]. At present, the [C++/CLI language][doc_cppcli] is only supported by the VC++ compiler and limited exclusively to the Windows platform. One of the goals of C++/CLI was to make interoperability between native and managed code seamless and sort of magic – that is why it is commonly referred to as IJW or "It-Just-Works". C++/CLI's goal of making interop simpler is laudable, but given its design, it comes with non-trivial costs in terms of implementation and requires a deeper understanding of interop as opposed performing it from within a language like C#.
282 |
283 | Comprehensive official documentation for C++/CLI can be found [here][doc_cppcli].
284 |
285 | There are 4 modes of operation for a C++/CLI compiled assembly. The documentation for these modes is relatively vague so additional details are added here. The VC++ compiler will compile a C++/CLI assembly when passed the [`/clr`](https://docs.microsoft.com/cpp/build/reference/clr-common-language-runtime-compilation) flag.
286 |
287 | |Mode|Operation|
288 | |-|-|
289 | |_none_| The default mode. The resulting assembly may contain both native and managed code. The assembly is linked against `mscoree.lib` and only runs on .NET Framework. |
290 | |`pure`| **Deprecated**. The resulting assembly contains only managed code. All native data types are converted to pure managed types. The assembly is linked against `mscoree.lib` and only runs on .NET Framework. |
291 | |`safe`| **Deprecated**. The resulting assembly may contain both native and managed code, but no native memory dereferences are permitted in the native code. The intent of this build is to produce verifiable code (that is, no `unsafe` code). The assembly is linked against `mscoree.lib` and only runs on .NET Framework.|
292 | |`NetCore`| This mode is identical to the default but links against `ijwhost.lib` and only runs on .NET Core 3.1 or .NET 5+. |
293 |
294 | If either of the two deprecated modes are desired, it is strongly recommended to write in or port code to a pure .NET language (for example, C#).
295 |
296 | ### C++ language extensions
297 |
298 | The C++/CLI language is an extension of C++ that attempts to express .NET interop [Concepts](#concepts) in C++ – the most important being memory ownership and levels of indirection.
299 |
300 | **`&`** – A native reference to native memory. This is the [C++ construct][cppdoc_reference] and has the same semantics. This should not be confused with the `&` defined above when talking about managed references in IL – a completely different language.
301 |
302 | **`*`** – A native pointer to native memory. This is the [C++ construct][cppdoc_pointer] and has the same semantics.
303 |
304 | **`%`** – A managed reference, also called a ["tracking reference"](https://docs.microsoft.com/cpp/dotnet/how-to-use-tracking-references-in-cpp-cli), to managed memory. This is a new construct and provides symmetry with the native `&` construct but for managed.
305 |
306 | **`^`** – A managed pointer to managed memory. This is a new construct and provides symmetry with the native `*` construct but for managed. The managed pointer is also used for all managed type instances that derive from [`System.Object`][api_object] (that is, [reference types][wiki_valuereftypes]). For example, `Object^ o = gcnew Object;`.
307 |
308 | Examples of these constructs in C++/CLI can be found in the [official documentation][doc_cppcli].
309 |
310 | Regardless of how these constructs are used in practice, their function should be clear – coordination with the [GC](#gc) to help share memory between native and managed environments.
311 |
312 | ### Activation of the .NET runtime
313 |
314 | Introducing .NET into an existing native application is a common scenario where C++/CLI is employed. Imagine a large native application that would like to leverage an existing .NET library. Since the applicaion is native, this would imply that a .NET runtime instance is not already running in the process and the main entry point is native (for example, [`int main()`][cppdoc_mainfunction]). When a C++/CLI assembly is loaded into the process, the .NET runtime must be initialized and prepared to operate in this process prior to any managed code running.
315 |
316 | There are are two types of .NET runtime activation for C++/CLI – .NET Framework and .NET Core 3.1/.NET 5+. These two types are mechanically similar once the .NET runtime is loaded and initialized but prior to that the details are very different.
317 |
318 | **.NET Framework** – The C++/CLI assembly is implicitly linked against `mscoree.lib`. The linking indicates the assembly has a dependency on the native `mscoree.dll` binary and must be loaded prior to the assembly being executed. The `mscoree.dll` binary is located globally (for example, `%SystemRoot%\System32\mscoree.dll`) on the Windows platform and facilitates loading the .NET runtime. The version of the runtime (that is, `2.0`, `3.5`, or `4.5`+) that is loaded is influenced by additional flags passed during the compilation, an `app.config`, or global settings.
319 |
320 | **.NET Core 3.1** and **.NET 5+** – The C++/CLI assembly is implicitly linked against `ijwhost.lib` and thus requires `ijwhost.dll` during load. The `ijwhost.dll` binary is not necessarily globally installed and available. This makes it a dependency that must be provided in another manner (for example, update `PATH` environment variable). Once the `ijwhost.dll` is loaded, it reads an associated `.runtimeconfig.json` file to determine which .NET runtime is needed by the assembly. If an existing .NET runtime is already loaded, `ijwhost.dll` will verify that the loaded runtime supports the needs of the C++/CLI assembly. This includes validation that all requested frameworks exist and match the requested versions. Framework version reconciliation is non-trivial and has many options that are described in detail [here][design_framework_resolution]. The design document for this process can be found in the [`dotnet/runtime`][design_ijw_activation] repository.
321 |
322 | Once the specific native dependency (that is, `mscoree.dll`/`ijwhost.dll`) is loaded, all managed exports exposed to native code are "thunked". This thunk provides a level of indirection between the actual managed function and the calling native code in order to load the .NET runtime on demand. The first time a managed function is called by native code, the thunk is executed and a .NET runtime loaded or the existing one confirmed to be compatible and adopted. All native exports are then populated with the appropriate managed function. Only one managed call must pay this initialization price since all thunks for an assembly will be updated when it is loaded into a .NET runtime. After a .NET runtime has loaded the assembly and the managed function thunks have been updated, the interop experience follows all the rules and principles described in the [Concepts](#concepts) section.
323 |
324 | Aside from the `mscoree.dll`/`ijwhost.dll` differences, there are other functional discrepancies between .NET Framework and .NET Core 3.1/.NET 5+. These are captured in official documentation for transitioning C++/CLI from [.NET Framework to a newer .NET version](https://docs.microsoft.com/dotnet/core/porting/cpp-cli).
325 |
326 | ## COM and `IUnknown`
327 |
328 | The [Component Object Model (COM)][doc_com] was first presented in 1993 and has been a integral part of the Windows platform since then. The history of .NET can't be written without COM—its influence and impact can be seen in many aspects of .NET, but none as prominently as interoperability. This section is not intended to fully describe all COM concerns in .NET. It will instead give a basic overview of COM concepts that are important to .NET interoperability and then focus on the nuts and bolts of how COM support is implemented and considerations when engaging with COM from .NET.
329 |
330 | ### `IUnknown` – the base of COM
331 |
332 | The base of all COM types is the [`IUnknown` interface][api_iunknown]. This simple interface, consisting of 3 functions, is designed to handle lifetime management and conversion from one interface to another.
333 |
334 | Lifetime management is handled by a [reference counting][wiki_reference_counting] method. Reference counting provides accurate lifetime management but has two major drawbacks: circular references between instances are cumbersome and it has much higher overhead than other garbage collection techniques. The [`AddRef()`](https://docs.microsoft.com/windows/win32/api/unknwn/nf-unknwn-iunknown-addref) and [`Release()`](https://docs.microsoft.com/windows/win32/api/unknwn/nf-unknwn-iunknown-release) functions handle this lifetime management. Implementing these functions is straightforward, even in a thread-safe manner. The basic rule is: when the reference count reaches 0, the memory for the instance can safely be freed.
335 |
336 | Moving between interfaces is accomplished via the [`QueryInterface()`](https://docs.microsoft.com/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(refiid_void)) function. Conceptually, `QueryInterface()` is similar to the [C# `as` cast](https://docs.microsoft.com/dotnet/csharp/language-reference/operators/type-testing-and-cast#as-operator)—attempt to cast to a type and get the desired type or return `null`. Unlike the straightforward lifetime management functions, properly implementing `QueryInterface()` requires some care to follow [specific rules](https://docs.microsoft.com/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(refiid_void)#remarks). These rules will not be enumerated here as the official documentation is clear, but they are fundamental to maintaining the integrity of lifetime management and the COM type system.
337 |
338 | The last feature of the `IUnknown` interface is its name or rather ID. In COM, all types have an ID defined as a 128-bit [`UUID`][wiki_uuid], commonly called a `GUID` in COM and .NET. The `UUID`, as an ID, is used for all COM types—for example, interfaces (`IID`), coclasses (`CLSID`), records, etc. In COM, this means the name isn't actually interesting and the `UUID` is what matters—this will become important in .NET. Note that the `UUID`s on Windows have a [mixed endian encoding](https://wikipedia.org/wiki/Universally_unique_identifier#Encoding) that can cause confusion.
339 |
340 | #### COM interface ABI
341 |
342 | All interfaces that inherit from `IUnknown` are defined in memory in a way that permits interoperability with the C language—a goal of COM. An example will help to illustrate how all `IUnknown` interfaces are stored in memory. Imagine a COM type, `CClass`, that implements two COM interfaces, `IInterface1` and `IInterface2`. Beyond inheriting from `IUnknown`, the `IInterface1` and `IInterface2` interfaces each define a single function, `DoWork1()` and `DoWork2()` respectively. Through some COM function an instance of `CClass` is allocated at memory address `0x10000` and the initial interface is `IInterface1`. COM dictates that at that address the first pointer-sized value points to the [virtual function table][wiki_vtable] (vtable), `0x20000` in the example. Within each vtable, the interface member functions are laid out sequentially.
343 |
344 | ```
345 | 0x10000 0x20000
346 | 0x10008 0x30000
347 | 0x10010
348 | ...
349 | 0x20000
350 | 0x20008
351 | 0x20010
352 | 0x20018
353 | ...
354 | 0x30000
355 | 0x30008
356 | 0x30010
357 | 0x30018
358 | ```
359 |
360 | ### COM interfaces and classes
361 |
362 | The COM system is represented by interfaces and classes. The interfaces define the API contract and the classes define an implementation of that contract. For example, let us define an interface for parsing a string and returning it as an integer. COM types are typically defined using the [Microsoft Interface Definition Language (MIDL)][doc_midl], which we use in the following example.
363 |
364 | ```idl
365 | [
366 | object,
367 | uuid(4298318b-5934-476d-b65a-58cdce3071ba)
368 | ]
369 | interface IParseNumber : IUnknown
370 | {
371 | HRESULT Parse(
372 | [in, string] const wchar_t* str,
373 | [out] int* num);
374 | }
375 | ```
376 |
377 | The semantics of the `Parse()` function on the `IParseNumber` interface should be clear—given a string, `str`, and a pointer to an integer, `num`, parse the string and return the value to the caller via the `int*`. The [`HRESULT`][wiki_hresult] return type, a typdef'd C `unsigned long`, represents an "error code" pattern that is used throughout COM – exceptions are not permitted to cross a COM method boundary. Now that we have defined the interface API, let us examine an implementation definition.
378 |
379 | ```idl
380 | [
381 | uuid(2d65f911-b8b6-4f46-b8f4-b5389ab8c082),
382 | version(1.0),
383 | ]
384 | library NumberParsing
385 | {
386 | [uuid(6f560103-971c-4092-9f18-888f06ac2d26)]
387 | coclass CParseAsDecimal
388 | {
389 | [default] interface IParseNumber;
390 | };
391 |
392 | [uuid(361a4edf-d6b4-4c08-a2fe-2a8c21ed377c)]
393 | coclass CParseAsHexadecimal
394 | {
395 | [default] interface IParseNumber;
396 | };
397 | };
398 | ```
399 |
400 | The above defines two implementations of the `IParseNumber` interface. The first, `CParseAsDecimal`, implies that it will parse the supplied string as a decimal value and the second, `CParseAsHexadecimal`, implies parsing as a hexadecimal value. The salient point in the implementation snippet is that each `coclass`, read "COM class", has its own `UUID`. One could then imagine, based on the previous discussion of `UUID`, that in order to instantiate a specific implementation, the associated `UUID` should be passed to some function and the desired implementation would be returned. How one implements a COM class or registers it for activation is beyond the scope of this document. The key take-away here is an interface defines an API and a class provides an implementation of that API.
401 |
402 | ### Additional COM features
403 |
404 | This list represents some of the more impactful features of COM as it relates to interoperability with .NET.
405 |
406 | - [Apartments][doc_com_apartments]: Organizational structure for threading in COM.
407 | - [Security](https://docs.microsoft.com/windows/win32/com/security-in-com): COM provides a rich security model for class activation and proxied permission.
408 | - Remote Procedural Calls (RPC): COM can perform RPC with another process locally or across a network.
409 |
410 | ### COM and .NET
411 |
412 | Interoperability between COM and .NET has existed since the beginning of the runtime and always been a first-class scenario. A conceptual overview of the wrappers and how the built-in system works can be found [here][doc_wrappers_builtin]. The below provides some of the low-level details about COM and COM interop that could enable someone to create their own COM interop solution. For .NET 5 and later versions, an alternative to the built-in COM interop system is available through the [`ComWrappers` API][api_comwrappers] – a [`ComWrappers` tutorial][doc_wrappers_api_tutorial] provides some of the following content.
413 |
414 | With the introduction of the `ComWrappers` API in .NET 5, terminology for COM interop needed to be slightly revised to help disambiguate between built-in COM interop wrappers and wrappers provided by a `ComWrappers` sub-class. The Runtime Callable Wrapper (RCW) and COM Callable Wrapper (CCW) are terms used to describe wrappers with the built-in COM interop, whereas Native Object Wrapper (NOW) and Managed Object Wrapper (MOW) are used for wrappers created by a `ComWrappers` sub-class. It is still common to refer to a NOW as an RCW or MOW as a CCW if there is no ambiguity in the current context with built-in COM interop wrappers.
415 |
416 | **Runtime Callable Wrapper (RCW) / Native Object Wrapper (NOW)**
417 |
418 | The RCW/NOW is designed to wrap native `IUnknown` instances and project a native implementation of a COM interface to a managed API surface. This is similar to how a P/Invoke is designed to project a C-style function in managed code using `DllImport`. There are two primary functional concerns for an RCW/NOW implementation.
419 |
420 | 1) Maintain correct lifetime semantics for the instance via the COM lifetime APIs `AddRef()` and `Release()`.
421 | 1) Limit costly `QueryInterface()` calls to detect "is supported" during wrapper creation. The "is supported" question should be "pay for play" since it is not possible to `QueryInterface()` for every possible COM interface.
422 |
423 | A functionally optional, but performance critical, secondary concern is avoiding the allocation of multiple managed wrappers for the same native instance. This implies a mapping between native instance and managed wrapper and thus a caching mechanism that is GC-aware, which has broad implications. Normal COM objects have thread affinity that requires all function calls to be performed on a specific group of threads or sometimes even a specific thread—see [COM Apartments][doc_com_apartments]. Requiring operations to be executed on specific threads pushes behavioral requirements onto the GC, which typically is unaware of any interop scenario. The runtime must provide a mechanism to efficiently solve this caching issue.
424 |
425 | To maintain lifetime semantics, consumers must be able to call `AddRef()` or `Release()` at the correct time. This requires being able to dispatch calls on a native instance. If we understand the [COM interface ABI](#com_interface_abi), using C# function pointers makes this straightforward. Given a native instance as a `void*` or `IntPtr` in C#, dispatching to `AddRef()` (second slot), or `Release()` (third slot) is ugly but efficient:
426 |
427 | ```csharp
428 | IntPtr nativeInstance = ...;
429 |
430 | // AddRef()
431 | int currCount = ((delegate* unmanaged)(*(*(void***)nativeInstance + 1 /* second slot */)))(nativeInstance);
432 |
433 | // Release()
434 | int currCount = ((delegate* unmanaged)(*(*(void***)nativeInstance + 2 /* third slot */)))(nativeInstance);
435 | ```
436 |
437 | When the native instance first enters managed code, the `AddRef()` should either be taken or been performed if the native instance was an out argument from an API call. The RCW/NOW is now the "owner" and must call `Release()` when the managed object is no longer needed. Knowing when the wrapper is no longer needed is a hard problem that the GC-aware cache helps solve. With the built-in COM interop, in the case where it is certain the wrapper no longer has any purpose, the system provides the [`Marshal.ReleaseComObject`][api_releasecomobject] or [`Marshal.FinalReleaseComObject`][api_finalreleasecomobject] API. With `ComWrappers`, the `ComWrappers` implementer must handle this themselves since the design of the wrapper is up to them. In most cases however, the wrapper consumer can't be certain where else the wrapper is being consumed and must rely upon the GC-aware cache to handle this case correctly.
438 |
439 | The GC-aware cache provides hooks that permit safe inspection of a wrapper in the runtime as it goes through its life cycle. The steps below are marked with the GC mode under which they run—see [GC, Preemptive, and Cooperative modes][doc_botr].
440 |
441 | 1) [**Cooperative**] The wrapper is created and an entry is inserted into the cache that maps from `IUnknown*` to wrapper.
442 | 1) [**GC**] The wrapper is determined to be collectible by the GC, put on the Finalizer queue, and marked "detached" from the cache.
443 | * The detached state means the cache no longer has a mapping from an `IUnknown*` value to a wrapper.
444 | 1) [**Cooperative**] The wrapper's finalizer is executed clearing any wrapper state.
445 | * [**Preemptive**] The Finalizer thread switches GC modes to call the native object's `Release()`.
446 | 1) [**GC**] The wrapper mapping is removed from the cache.
447 | 1) [**Preemptive**] After the Finalizer thread has cleared its queue, the wrapper's runtime context memory will be freed.
448 |
449 | Enabling lazy `QueryInterface()` calls with static code is possible in .NET 5 and later versions using the [`IDynamicInterfaceCastable`][api_idynamicinterfacecastable] interface. Without `IDynamicInterfaceCastable`, support is limited to either leveraging built-in COM interop features or dynamic code-generation using [`Reflection.Emit`][doc_refemit]. The idea is to permit the determination of what interfaces a specific native COM instance supports to occur as late as possible, ideally only when requested via some mechanism like a type cast in C#.
450 |
451 | All of the above is further complicated when thread affinitized COM objects are introduced—see [COM Apartments][doc_com_apartments]. Ensuring that RCW/NOW instances are only used in the correct context can be done using one of two mechansims:
452 |
453 | * [`RoGetAgileReference()`][api_rogetagilereference]: Permits the creation of an "agile reference" to a native instance that can be used to access that instance from any thread.
454 | * [Global Interface Table (GIT)][doc_globalinterfacetable]: Prior to Windows 8, a low level API that enables access to thread affinitized native objects from different threads.
455 |
456 | **COM Callable Wrapper (CCW) / Managed Object Wrapper (MOW)**
457 |
458 | The CCW/MOW is designed to project COM interfaces that are implemented in managed into a native environment. Compared to the RCW/NOW, the concerns are narrower and easier to reason about since the .NET environment is more forgiving than the native COM environment—.NET doesn't impose thread affinity on objects. There are two concerns for a CCW/MOW implementation:
459 |
460 | 1) Extend the lifetime of a managed object via the COM lifetime APIs `AddRef()` and `Release()`.
461 | 1) Avoid unnecessary wrapper creation.
462 |
463 | Extending the lifetime of the managed object requires allocating a small block of memory and then applying knowledge of the [COM ABI](#com_interface_abi) to invoke the appropriate function. Let's consider one example of how the memory could be laid out for a managed object exposing the `IUnknown` interface. All internal .NET COM interop solutions use a similar methodology, but are optimized for process bitness and alignment within the allocated memory.
464 |
465 | ```
466 | 0x10000
467 | 0x10008
468 | 0x1000c
469 | 0x10010 0x10000
470 | 0x10018 0x20000
471 | ...
472 | 0x20000
473 | 0x20008
474 | 0x20010
475 | ```
476 |
477 | Given the above memory layout we can walkthrough a native scenario calling into the managed implementation of `Release()`.
478 |
479 | ```cpp
480 | // Get a managed implementation of IUnknown.
481 | IUnknown* pUnk = ...; // pUnk = 0x10018
482 |
483 | // Access the vtable at 0x10018
484 | // 0x10018 -> 0x20000
485 | // Access the third slot
486 | // 0x20000[2] ->
487 | pUnk->Release();
488 | ```
489 |
490 | Calling the third slot, `Release()`, above enters the managed implementation in .NET 5 and later versions. Prior to .NET 5, a `Delegate` would be used instead.
491 |
492 | Using the built-in COM interop or `ComWrappers` API, the implementation of `IUnknown` is provided by the runtime; the `ReleaseImpl()` below is used for illustrative purposes and doesn't represent any actual implementation. The `ComWrappers` API requires implementers to provide the vtables but abstracts away the masking and pointer dispatch through helper APIs—see [`ComWrappers` tutorial][doc_wrappers_api_tutorial].
493 |
494 | ```csharp
495 | struct WrapperHeader
496 | {
497 | public IntPtr Handle;
498 | public uint RefCount;
499 | }
500 |
501 | [UnmanagedCallersOnly]
502 | static unsafe int ReleaseImpl(UIntPtr _this)
503 | {
504 | // 0x10010 = 0x10018 & 0xffffffffffff0
505 | // Masking is needed to get the address of the wrapper itself.
506 | // This is based on an optimization in the built-in system as well as ComWrappers for memory efficiency.
507 | UIntPtr wrapperPtr = (nuint)_this & ~(nuint)0xf;
508 |
509 | // 0x10000 = *0x10010
510 | WrapperHeader* header = *(WrapperHeader**)wrapperPtr;
511 |
512 | // Decrement the reference count for the wrapper
513 | uint refCount = Interlocked.Decrement(ref header->RefCount);
514 | if (refCount == 0)
515 | {
516 | // Convert the handle pointer to a GCHandle.
517 | // From the GCHandle, the GCHandle.Target property could be used
518 | // to access the managed object.
519 | GCHandle instanceHandle = GCHandle.FromIntPtr(header->Handle);
520 |
521 | // Free the GCHandle so it will stop extending the managed
522 | // object's lifetime.
523 | instanceHandle.Free();
524 | header->Handle = IntPtr.Zero;
525 | }
526 |
527 | return refCount;
528 | }
529 | ```
530 |
531 | Avoiding creation of multiple wrappers is possible through a global concurrent dictionary, reader/writer lock around a normal dictionary, or another mechanism such as a `ConditionalWeakTable`; the choice would depend on the desired lifetime semantics and performance requirements. Another option is to associate the wrapper with the managed object—the approach taken by the runtime. The runtime's association process is done in the lowest levels and makes for fast access and the lowest possible overhead. Without the built-in COM interop or `ComWrappers` API an affordance can be provided by the user if COM interop is a high priority for user types. A field for the wrapper could be defined in the managed definition and the manual COM interop could be enlightened with this knowledge.
532 |
533 | ### WinRT
534 |
535 | **TODO**
536 |
537 | ## Diagnostics
538 |
539 | The ability to observe what the runtime is doing during an interop scenario is very important. Multiple tools and Runtime onfiguration options exist to help understand the interop scenario while it is underway.
540 |
541 | **Runtime tracing**
542 |
543 | The CLR has extensive tracing probes built-in. Historically, this information is emitted via an ETW provider on Windows but has been extended to work on all platforms using [`EventPipe`][doc_eventpipe]. The CLR emits this data using the [`Microsoft-Windows-DotNETRuntime` provider](https://docs.microsoft.com/dotnet/core/diagnostics/well-known-event-providers#microsoft-windows-dotnetruntime-provider) (ID: `{e13c0d23-ccbc-4e12-931b-d9cc2eee27e4}`). The [documentation for runtime events](https://docs.microsoft.com/dotnet/fundamentals/diagnostics/runtime-events) describes all available events. Specific [interop details](https://docs.microsoft.com/dotnet/fundamentals/diagnostics/runtime-interop-events) can be requested using the `InteropKeyword` (ID: `0x2000`). The data can be collected and/or analyzed using a number of tools and libraries (for example, [`TraceEvent`][nuget_traceevent]). For a UI scenario, [PerfView][repo_perfview] is recommended or for a CLI scenario, one can use [`dotnet trace`][doc_dotnet_trace].
544 |
545 | In .NET Core 3.1/.NET 5+ the interop events contain extensive IL Stub details such as the generated IL instructions. Below is an abridged example for the `sum_ints()` P/Invoke captured and formatted using the PerfView tool. Observe the signatures in the event payload—this is useful when validating the declared P/Invoke signature expresses what is desired.
546 |
547 | ```xml
548 |
612 | ```
613 |
614 | **Debugger**
615 |
616 | The CoreCLR runtime has a rich diagnostic story for inspecting internal data structures. A common way to investigate interop issues is by using the [SOS][doc_sos] debugger extension. SOS can be loaded into either [LLDB](https://lldb.llvm.org/) or [WinDBG](https://docs.microsoft.com/windows-hardware/drivers/debugger/debugger-download-tools). The [SOS debugging extension documentation](https://docs.microsoft.com/dotnet/core/diagnostics/sos-debugging-extension) describes all available commands. The most common commands to aid in investigating interop related issues are:
617 |
618 | - `DumpObj` – Dump details of a managed object.
619 | - `GCInfo` – Display GC sensitive locations for a `MethodDesc` (that is, a managed method).
620 | - `IP2MD` – Map an instruction pointer value to a `MethodDesc`.
621 | - `U` – Use with the `-gcinfo` flag to dissassembly a managed method and interleave GC information.
622 |
623 | Only on Windows and not documented.
624 | - `DumpCCW` – Show details of the COM Callable Wrapper.
625 | - `DumpRCW` – Show details of the Runtime Callable Wrapper.
626 |
627 |
632 |
633 | ## Tooling
634 |
635 | Multiple tools exist for building interop solutions in .NET interop. Below is a list of tools that may be useful for .NET applications with interop requirements.
636 |
637 | [SharpGenTools][repo_sharpgentools] – Compile time generation of interop marshalling code for P/Invokes and the `IUnknown` ABI.
638 |
639 | [C#/WinRT][repo_cswinrt] – Compile time generation of WinRT type projections into .NET.
640 |
641 | [DNNE][repo_dnne] – Export C functions from a managed assembly.
642 |
643 | [SharpLab](https://sharplab.io/) – Website used to view compiled C# as IL.
644 |
645 | ## FAQs
646 |
647 | ## Additional Resources
648 |
649 | [Investigating GC managed memory][repo_mem_doc].
650 |
651 | [Book of the Runtime][doc_botr].
652 |
653 |
654 |
655 | [api_comwrappers]:https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.comwrappers
656 | [api_dllimportattr]:https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.dllimportattribute
657 | [api_exception]:https://docs.microsoft.com/dotnet/api/system.exception
658 | [api_releasecomobject]:https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshal.releasecomobject
659 | [api_finalreleasecomobject]:https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshal.finalreleasecomobject
660 | [api_gchandle]:https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.gchandle
661 | [api_gchandletype]:https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.gchandletype
662 | [api_iunknown]:https://docs.microsoft.com/windows/win32/api/unknwn/nn-unknwn-iunknown
663 | [api_idynamicinterfacecastable]:https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.idynamicinterfacecastable
664 | [api_marshalasattr]:https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshalasattribute
665 | [api_object]:https://docs.microsoft.com/dotnet/api/system.object
666 | [api_outattr]:https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.outattribute
667 | [api_preservesigattr]:https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.preservesigattribute
668 | [api_rogetagilereference]:https://docs.microsoft.com/windows/win32/api/combaseapi/nf-combaseapi-rogetagilereference
669 | [api_safehandle]:https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.safehandle
670 | [api_unmanagedfunctionpointerattr]:https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.unmanagedfunctionpointerattribute
671 |
672 |
673 | [cppdoc_char]:https://en.cppreference.com/w/cpp/keyword/char
674 | [cppdoc_mainfunction]:https://en.cppreference.com/w/cpp/language/main_function
675 | [cppdoc_pointer]:https://en.cppreference.com/w/cpp/language/pointer
676 | [cppdoc_reference]:https://en.cppreference.com/w/cpp/language/reference
677 | [cppdoc_wchar_t]:https://en.cppreference.com/w/cpp/keyword/wchar_t
678 |
679 | [design_framework_resolution]:https://github.com/dotnet/runtime/blob/main/docs/design/features/framework-version-resolution.md
680 | [design_ijw_activation]:https://github.com/dotnet/runtime/blob/main/docs/design/features/IJW-activation.md
681 |
682 | [doc_blittable]:https://docs.microsoft.com/dotnet/framework/interop/blittable-and-non-blittable-types
683 | [doc_botr]:https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/botr/README.md
684 | [doc_com]:https://docs.microsoft.com/windows/win32/com/the-component-object-model
685 | [doc_com_apartments]:https://docs.microsoft.com/windows/win32/com/processes--threads--and-apartments
686 | [doc_cppcli]:https://docs.microsoft.com/cpp/dotnet/dotnet-programming-with-cpp-cli-visual-cpp
687 | [doc_dotnet_trace]:https://docs.microsoft.com/dotnet/core/diagnostics/dotnet-trace
688 | [doc_eventpipe]:https://docs.microsoft.com/dotnet/core/diagnostics/eventpipe
689 | [doc_globalinterfacetable]:https://docs.microsoft.com/windows/win32/com/accessing-interfaces-across-apartments
690 | [doc_midl]:https://docs.microsoft.com/windows/win32/midl/midl-start-page
691 | [doc_pinning]:https://docs.microsoft.com/dotnet/framework/interop/copying-and-pinning
692 | [doc_pinvoke]:https://docs.microsoft.com/dotnet/standard/native-interop/pinvoke
693 | [doc_refemit]:https://docs.microsoft.com/dotnet/framework/reflection-and-codedom/emitting-dynamic-methods-and-assemblies
694 | [doc_sos]:https://docs.microsoft.com/dotnet/core/diagnostics/dotnet-sos
695 | [doc_unmanaged_types]:https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/unmanaged-types
696 | [doc_windatatypes]:https://docs.microsoft.com/windows/win32/winprog/windows-data-types
697 | [doc_wrappers_builtin]:https://docs.microsoft.com/dotnet/standard/native-interop/com-wrappers
698 | [doc_wrappers_api_tutorial]:https://docs.microsoft.com/dotnet/standard/native-interop/tutorial-comwrappers
699 |
700 | [nuget_traceevent]:https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent/
701 |
702 | [spec_ecma335]:https://www.ecma-international.org/publications-and-standards/standards/ecma-335/
703 | [spec_ecma372]:https://www.ecma-international.org/publications-and-standards/standards/ecma-372/
704 |
705 | [repo_cswinrt]:https://github.com/microsoft/CsWinRT
706 | [repo_dnne]:https://github.com/AaronRobinsonMSFT/DNNE
707 | [repo_mem_doc]:https://github.com/Maoni0/mem-doc
708 | [repo_perfview]:https://github.com/Microsoft/perfview
709 | [repo_sharpgentools]:https://github.com/SharpGenTools/SharpGenTools
710 |
711 | [wiki_com]:https://en.wikipedia.org/wiki/Component_Object_Model
712 | [wiki_hresult]:https://wikipedia.org/wiki/HRESULT
713 | [wiki_reference_counting]:https://wikipedia.org/wiki/Reference_counting
714 | [wiki_uuid]:https://wikipedia.org/wiki/Universally_unique_identifier
715 | [wiki_valuereftypes]:https://wikipedia.org/wiki/Value_type_and_reference_type
716 | [wiki_vtable]:https://wikipedia.org/wiki/Virtual_method_table
717 | [wiki_x86callconv]:https://wikipedia.org/wiki/X86_calling_conventions
718 |
--------------------------------------------------------------------------------