When you need to understand what an unfamiliar .NET assembly actually does — not to its own data, but to the machine — the native interop is where the answer lives. Managed code that stays inside the CLR is sandboxed by it; the moment it calls into native code via P/Invoke, it can do anything the process can. Fortunately, P/Invoke declarations are among the most faithfully recoverable things in a decompiled assembly, because the information a decompiler needs is sitting in a dedicated metadata table. Reading it well turns "this assembly calls some native stuff" into a precise inventory.
How P/Invoke is stored
A [DllImport] method does not carry a method body — it is an extern declaration. In the assembly it becomes a method flagged pinvokeimpl, with an entry in the ImplMap metadata table that records four things: the module (the native DLL), the entry-point name, the calling convention, and the character set / name-mangling rules. The managed signature (parameter and return types, plus any [MarshalAs]) lives in the normal method signature and parameter metadata.
A decompiler like Glass reads the ImplMap and the signature and reconstructs the original declaration — attribute and all:
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true,
CallingConvention = CallingConvention.Winapi)]
static extern IntPtr CreateFileW(
[MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes,
uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);
Everything in that reconstruction — the DLL name, CharSet, SetLastError, CallingConvention, and the [MarshalAs] on the string — came from metadata, not from guesswork.
.b{fill:none;stroke:#64748b;stroke-width:1.5}.t{fill:currentColor;font-size:12.5px}.s{fill:#64748b;font-size:11px}.hl{stroke:#6366f1;stroke-width:2}.ar{stroke:#64748b;stroke-width:1.5;marker-end:url(#p)}
The ImplMap table is a complete index of every native function the assembly can call.
Building a native-call inventory
Because every P/Invoke is an ImplMap entry, you can enumerate an assembly's entire native surface. In Glass, open the assembly and look at its P/Invoke methods: each one names a DLL and a function. That inventory answers the questions that matter when you are auditing something you did not write:
-
Which native libraries does it touch?
kernel32/user32is routine; a bundled unknown.dll,ws2_32(sockets), oradvapi32crypto/registry calls are worth a closer look. - What can it do to the system? File, process, registry, and network functions are all visible by name.
-
How does data cross the boundary? The
[MarshalAs],[StructLayout], and[FieldOffset]metadata reconstruct the ABI contract, so you can see how a managed struct maps to the native layout a function expects.
This is the same workflow as analyzing a suspicious assembly, with native interop as the lens.
The modern twist: LibraryImport
.NET 7 introduced [LibraryImport], a source-generated alternative to [DllImport]. Instead of the runtime marshalling arguments, a source generator emits the marshalling as C# at compile time: a partial method you declared, plus a generated stub that marshals the arguments and calls a private, DllImport-style native target. Decompiled, a [LibraryImport] therefore shows up as more code than a classic P/Invoke — the generated stub is right there — which is actually a bonus for reading: you see exactly how each argument is converted, rather than trusting the runtime to do it. It reads much like any source-generated code does once lowered.
Doing this with Glass
Glass reconstructs [DllImport] declarations from the ImplMap — DLL, entry point, calling convention, charset, and [MarshalAs] — and shows the generated stubs behind [LibraryImport], so you can read an assembly's complete native-interop surface and the exact marshalling of every call. It is the fastest way to answer "what does this assembly do to the machine?" and it is free.
Point Glass at an assembly and list its P/Invoke methods to see its native reach at a glance.
Top comments (0)