Skip to content

Commit d2c51f2

Browse files
Add remarks and example for MetadataBuilder (#5412)
1 parent d7da7e7 commit d2c51f2

File tree

4 files changed

+252
-1
lines changed

4 files changed

+252
-1
lines changed
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
using System;
2+
using System.IO;
3+
using System.Collections.Generic;
4+
using System.Reflection.Metadata;
5+
using System.Reflection.PortableExecutable;
6+
using System.Reflection.Metadata.Ecma335;
7+
using System.Reflection;
8+
9+
namespace MetadataBuilderSnippets
10+
{
11+
static class MetadataBuilderSnippets
12+
{
13+
//<SnippetEmitConsoleApp>
14+
private static readonly Guid s_guid = new Guid("87D4DBE1-1143-4FAD-AAB3-1001F92068E6");
15+
private static readonly BlobContentId s_contentId = new BlobContentId(s_guid, 0x04030201);
16+
17+
private static MethodDefinitionHandle EmitHelloWorld(MetadataBuilder metadata, BlobBuilder ilBuilder)
18+
{
19+
// Create module and assembly for a console application.
20+
metadata.AddModule(
21+
0,
22+
metadata.GetOrAddString("ConsoleApplication.exe"),
23+
metadata.GetOrAddGuid(s_guid),
24+
default(GuidHandle),
25+
default(GuidHandle));
26+
27+
metadata.AddAssembly(
28+
metadata.GetOrAddString("ConsoleApplication"),
29+
version: new Version(1, 0, 0, 0),
30+
culture: default(StringHandle),
31+
publicKey: default(BlobHandle),
32+
flags: 0,
33+
hashAlgorithm: AssemblyHashAlgorithm.None);
34+
35+
// Create references to System.Object and System.Console types.
36+
AssemblyReferenceHandle mscorlibAssemblyRef = metadata.AddAssemblyReference(
37+
name: metadata.GetOrAddString("mscorlib"),
38+
version: new Version(4, 0, 0, 0),
39+
culture: default(StringHandle),
40+
publicKeyOrToken: metadata.GetOrAddBlob(
41+
new byte[] { 0xB7, 0x7A, 0x5C, 0x56, 0x19, 0x34, 0xE0, 0x89 }
42+
),
43+
flags: default(AssemblyFlags),
44+
hashValue: default(BlobHandle));
45+
46+
TypeReferenceHandle systemObjectTypeRef = metadata.AddTypeReference(
47+
mscorlibAssemblyRef,
48+
metadata.GetOrAddString("System"),
49+
metadata.GetOrAddString("Object"));
50+
51+
TypeReferenceHandle systemConsoleTypeRefHandle = metadata.AddTypeReference(
52+
mscorlibAssemblyRef,
53+
metadata.GetOrAddString("System"),
54+
metadata.GetOrAddString("Console"));
55+
56+
// Get reference to Console.WriteLine(string) method.
57+
var consoleWriteLineSignature = new BlobBuilder();
58+
59+
new BlobEncoder(consoleWriteLineSignature).
60+
MethodSignature().
61+
Parameters(1,
62+
returnType => returnType.Void(),
63+
parameters => parameters.AddParameter().Type().String());
64+
65+
MemberReferenceHandle consoleWriteLineMemberRef = metadata.AddMemberReference(
66+
systemConsoleTypeRefHandle,
67+
metadata.GetOrAddString("WriteLine"),
68+
metadata.GetOrAddBlob(consoleWriteLineSignature));
69+
70+
// Get reference to Object's constructor.
71+
var parameterlessCtorSignature = new BlobBuilder();
72+
73+
new BlobEncoder(parameterlessCtorSignature).
74+
MethodSignature(isInstanceMethod: true).
75+
Parameters(0, returnType => returnType.Void(), parameters => { });
76+
77+
BlobHandle parameterlessCtorBlobIndex = metadata.GetOrAddBlob(parameterlessCtorSignature);
78+
79+
MemberReferenceHandle objectCtorMemberRef = metadata.AddMemberReference(
80+
systemObjectTypeRef,
81+
metadata.GetOrAddString(".ctor"),
82+
parameterlessCtorBlobIndex);
83+
84+
// Create signature for "void Main()" method.
85+
var mainSignature = new BlobBuilder();
86+
87+
new BlobEncoder(mainSignature).
88+
MethodSignature().
89+
Parameters(0, returnType => returnType.Void(), parameters => { });
90+
91+
var methodBodyStream = new MethodBodyStreamEncoder(ilBuilder);
92+
93+
var codeBuilder = new BlobBuilder();
94+
InstructionEncoder il;
95+
96+
// Emit IL for Program::.ctor
97+
il = new InstructionEncoder(codeBuilder);
98+
99+
// ldarg.0
100+
il.LoadArgument(0);
101+
102+
// call instance void [mscorlib]System.Object::.ctor()
103+
il.Call(objectCtorMemberRef);
104+
105+
// ret
106+
il.OpCode(ILOpCode.Ret);
107+
108+
int ctorBodyOffset = methodBodyStream.AddMethodBody(il);
109+
codeBuilder.Clear();
110+
111+
// Emit IL for Program::Main
112+
var flowBuilder = new ControlFlowBuilder();
113+
il = new InstructionEncoder(codeBuilder, flowBuilder);
114+
115+
// ldstr "hello"
116+
il.LoadString(metadata.GetOrAddUserString("Hello, world"));
117+
118+
// call void [mscorlib]System.Console::WriteLine(string)
119+
il.Call(consoleWriteLineMemberRef);
120+
121+
// ret
122+
il.OpCode(ILOpCode.Ret);
123+
124+
int mainBodyOffset = methodBodyStream.AddMethodBody(il);
125+
codeBuilder.Clear();
126+
127+
// Create method definition for Program::Main
128+
MethodDefinitionHandle mainMethodDef = metadata.AddMethodDefinition(
129+
MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
130+
MethodImplAttributes.IL,
131+
metadata.GetOrAddString("Main"),
132+
metadata.GetOrAddBlob(mainSignature),
133+
mainBodyOffset,
134+
parameterList: default(ParameterHandle));
135+
136+
// Create method definition for Program::.ctor
137+
MethodDefinitionHandle ctorDef = metadata.AddMethodDefinition(
138+
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
139+
MethodImplAttributes.IL,
140+
metadata.GetOrAddString(".ctor"),
141+
parameterlessCtorBlobIndex,
142+
ctorBodyOffset,
143+
parameterList: default(ParameterHandle));
144+
145+
// Create type definition for the special <Module> type that holds global functions
146+
metadata.AddTypeDefinition(
147+
default(TypeAttributes),
148+
default(StringHandle),
149+
metadata.GetOrAddString("<Module>"),
150+
baseType: default(EntityHandle),
151+
fieldList: MetadataTokens.FieldDefinitionHandle(1),
152+
methodList: mainMethodDef);
153+
154+
// Create type definition for ConsoleApplication.Program
155+
metadata.AddTypeDefinition(
156+
TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.AutoLayout | TypeAttributes.BeforeFieldInit,
157+
metadata.GetOrAddString("ConsoleApplication"),
158+
metadata.GetOrAddString("Program"),
159+
baseType: systemObjectTypeRef,
160+
fieldList: MetadataTokens.FieldDefinitionHandle(1),
161+
methodList: mainMethodDef);
162+
163+
return mainMethodDef;
164+
}
165+
166+
private static void WritePEImage(
167+
Stream peStream,
168+
MetadataBuilder metadataBuilder,
169+
BlobBuilder ilBuilder,
170+
MethodDefinitionHandle entryPointHandle
171+
)
172+
{
173+
// Create executable with the managed metadata from the specified MetadataBuilder.
174+
var peHeaderBuilder = new PEHeaderBuilder(
175+
imageCharacteristics: Characteristics.ExecutableImage
176+
);
177+
178+
var peBuilder = new ManagedPEBuilder(
179+
peHeaderBuilder,
180+
new MetadataRootBuilder(metadataBuilder),
181+
ilBuilder,
182+
entryPoint: entryPointHandle,
183+
flags: CorFlags.ILOnly,
184+
deterministicIdProvider: content => s_contentId);
185+
186+
// Write executable into the specified stream.
187+
var peBlob = new BlobBuilder();
188+
BlobContentId contentId = peBuilder.Serialize(peBlob);
189+
peBlob.WriteContentTo(peStream);
190+
}
191+
192+
public static void BuildHelloWorldApp()
193+
{
194+
using var peStream = new FileStream(
195+
"ConsoleApplication.exe", FileMode.OpenOrCreate, FileAccess.ReadWrite
196+
);
197+
198+
var ilBuilder = new BlobBuilder();
199+
var metadataBuilder = new MetadataBuilder();
200+
201+
MethodDefinitionHandle entryPoint = EmitHelloWorld(metadataBuilder, ilBuilder);
202+
WritePEImage(peStream, metadataBuilder, ilBuilder, entryPoint);
203+
}
204+
//</SnippetEmitConsoleApp>
205+
206+
public static void Run()
207+
{
208+
BuildHelloWorldApp();
209+
}
210+
}
211+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>netcoreapp3.1</TargetFramework>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="System.Reflection.Metadata" Version="5.0.0" />
10+
</ItemGroup>
11+
12+
</Project>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
3+
namespace MetadataBuilderSnippets
4+
{
5+
class Program
6+
{
7+
static void Main(string[] args)
8+
{
9+
MetadataBuilderSnippets.Run();
10+
}
11+
}
12+
}

xml/System.Reflection.Metadata.Ecma335/MetadataBuilder.xml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,23 @@
2121
<Interfaces />
2222
<Docs>
2323
<summary>The MetadataBuilder class reads and writes metadata for an assembly in a highly performant manner. It is designed for use by compilers and other assembly generation tools.</summary>
24-
<remarks>To be added.</remarks>
24+
<remarks>
25+
<format type="text/markdown"><![CDATA[
26+
27+
## Remarks
28+
The <xref:System.Reflection.Metadata.Ecma335.MetadataBuilder> class enables you to programmatically generate assemblies. These assemblies can be saved to a file, unlike dynamic assemblies generated by <xref:System.Reflection.Emit.AssemblyBuilder> class, which does not support saving assemblies to a file on .NET 5 and .NET Core.
29+
30+
The `MetadataBuilder` API operates low-level metadata constructs, such as tables or blobs. For a simpler way to generate assemblies dynamically using C#, see <xref:Microsoft.CodeAnalysis.CSharp.CSharpCompilation> in Roslyn API.
31+
32+
The format of CLI metadata is defined by the ECMA-335 specification. For more information, see [Standard ECMA-335 - Common Language Infrastructure (CLI)](http://www.ecma-international.org/publications/standards/Ecma-335.htm) on the Ecma International Web site.
33+
34+
## Examples
35+
This example shows how to emit a console application assembly using <xref:System.Reflection.Metadata.Ecma335.MetadataBuilder>:
36+
37+
:::code language="csharp" source="~/samples/snippets/csharp/api/system.reflection.metadata.ecma335/metadatabuilder/MetadataBuilderSnippets.cs" id="SnippetEmitConsoleApp":::
38+
39+
]]></format>
40+
</remarks>
2541
</Docs>
2642
<Members>
2743
<Member MemberName=".ctor">

0 commit comments

Comments
 (0)