Turning a DLL back into a buildable project with Glass.NET
Reading decompiled C# in a window is one thing; getting a .csproj you can open, grep, refactor and compile is another. Here is how Glass.NET's project export reconstructs a whole assembly — source, references, resources — what it handles cleanly, where compiler-generated code needs a hand, and how to script it from the CLI.
A decompiler that only shows you code in a pane keeps you a tourist. You can look, you can scroll, but you can’t work. The moment you want to grep an assembly for every call to File.Delete, run a static analyzer over it, set a breakpoint in a local reproduction, or refactor a lost library back into shape, you need the thing on disk as a real project — a .csproj your toolchain understands. That jump, from “a window full of C#” to “a solution I can open,” is what Glass.NET’s project export is for.
This post is a working deep-dive on that export: what Glass reconstructs, how to drive it from both the GUI and the CLI, what compiles cleanly, where decompilation’s honest limits show up, and how to close the gap.
From a read-only pane to a project on disk
Open an assembly in Glass and you get decompiled C#, navigation, and an assembly-information view — everything you need to read it. Export turns that reconstruction into files:
A project, not a dump: the .csproj carries the target framework and the assembly references Glass read from the metadata, the namespaces become a sensible file/folder layout, and embedded resources are extracted alongside. Open the folder in any IDE and your normal tools — search, go-to-definition, analyzers, the compiler — are back on the table.
The two ways to export
From the GUI, load the assembly and choose File ▸ Export to Project. Glass writes the .csproj and the reconstructed .cs files (plus extracted resources) to the folder you pick. This is the right path when you’re exploring — you’ve navigated the assembly, you know it’s the one you want, and you’d like it in your editor.
From the CLI, the glass export subcommand does the same thing headlessly, which is what you want in a script, a batch over many files, or an analysis pipeline:
glass export MyLibrary.dll --output ./MyLibrary.Source
That writes a buildable project under ./MyLibrary.Source. Because it’s a plain command, you can fan it out — recover a folder of orphaned assemblies in one pass:
for dll in ./legacy-bin/*.dll; do
name=$(basename "$dll" .dll)
glass export "$dll" --output "./recovered/$name"
done
And because Glass never runs the assembly — it’s strictly static analysis over metadata and IL — exporting an untrusted or suspicious binary is safe. You get the source on disk to grep through without the binary ever executing, which is precisely the situation where you most want it: auditing something you don’t trust enough to run.
What comes back cleanly
For a large class of real assemblies, the export compiles with little or no fuss, because the modern decompiler engine handles the constructs that used to produce garbage:
- Ordinary business logic — classes, interfaces, generics, properties, events, LINQ — reconstructs faithfully.
- Modern C# the engine understands —
async/awaitstate machines, iterator (yield) blocks, pattern matching and switch expressions, records, lambdas and closures — comes back as the high-level construct, not the raw generated plumbing. The engine recognizes the compiler’s lowering and reverses it. - References and framework targeting are carried into the
.csproj, so the project knows what it’s building against. - Embedded resources are written out and wired up, so a library that ships
.resx-backed strings or binary blobs keeps them.
For a small utility library or a single assembly you lost the source to, “export, open, build” frequently just works.
Where decompilation’s honest limits show up
Export is reconstruction, not time travel, and it’s worth being straight about what IL does and doesn’t preserve. The IL encodes behavior exactly; it does not encode every source-level detail, so some things are rebuilt rather than recovered:
- Local variable names are usually gone (IL identifies locals by slot, not name), so you’ll see reconstructed names. Fields, methods, types and parameters keep their names unless the assembly was obfuscated.
- Some compiler-generated constructs reconstruct as valid-but-unlovely C#, and the occasional exotic pattern won’t round-trip to compilable source without a nudge.
unsafecode, P/Invoke signatures, and certain interop may need the project’sAllowUnsafeBlocksturned on or a manual tidy.- Missing references — if the assembly depends on something you don’t have, the export can reconstruct the call sites but can’t conjure the dependency; you supply it before the build goes green.
- Obfuscated input is the hardest case: if names were mangled by an obfuscator, Glass faithfully reproduces the mangled names — it decompiles what’s there, it doesn’t un-obfuscate.
So the realistic expectation for a large or unusual assembly is: Glass gets you a project and the vast majority of the source; closing the last handful of compile errors is a short, targeted manual pass. That’s still a transformation — from “I can only look at this in a viewer” to “I have a project my whole toolchain can work on.”
Why a project beats a pane
Once the assembly is a .csproj, everything you already know how to do comes back:
- Grep and static analysis. Search the reconstructed source for the APIs that matter — every
Process.Start, every reflection call, every network sink — and point Roslyn analyzers or your own rules at it. This is the backbone of auditing a dependency you’re unsure about: export, then systematically read the parts that can do damage. - A local reproduction. Recovered a library whose source was lost? The exported project lets you build it, reference it, and — where the references resolve — debug a reproduction instead of guessing from a stack trace.
- Diffing across versions. Export two builds of the same assembly and diff the trees to see exactly what changed between releases — handy when a dependency bump broke you and the changelog didn’t explain why. (Glass also offers an in-app assembly compare with a line diff for the quick version.)
- Refactoring back to a maintainable state. For genuinely lost code, an exported project is the starting point for renaming the reconstructed locals, re-adding XML docs, and bringing a dead library back to life.
Where it fits
Project export is the step that turns Glass.NET from a viewer into a workbench. Read an assembly with the navigation model, confirm it’s what you want with the assembly-information view, then File ▸ Export to Project — or script glass export MyLibrary.dll --output ./src — to get a .csproj your toolchain understands. Expect a clean build for straightforward assemblies and a short manual pass for large or unusual ones; expect the source on disk either way, without ever running the binary. See the getting-started guide and navigation docs for the reading workflow that leads up to an export, or the Glass.NET product page for the whole toolset. It’s free — no licence, no seats, no sign-up.
Try Nebula.NET
Harden your .NET code in minutes — start with the free edition.