Utrwalone zestawy dynamiczne na platformie .NET
Ten artykuł zawiera dodatkowe uwagi dotyczące dokumentacji referencyjnej dla tego interfejsu API.
Interfejs AssemblyBuilder.Save API nie został pierwotnie portowany do platformy .NET (Core), ponieważ implementacja była w dużym stopniu zależna od kodu natywnego specyficznego dla systemu Windows, który również nie został portowany. Nowość na platformie .NET 9 klasa PersistedAssemblyBuilder dodaje w pełni zarządzaną Reflection.Emit
implementację, która obsługuje zapisywanie. Ta implementacja nie jest zależna od istniejącej implementacji specyficznej dla Reflection.Emit
środowiska uruchomieniowego. Oznacza to, że teraz istnieją dwie różne implementacje na platformie .NET, możliwe do uruchomienia i utrwalone. Aby uruchomić utrwalone zestawy, najpierw zapisz go w strumieniu pamięci lub pliku, a następnie załaduj go z powrotem.
Przed PersistedAssemblyBuilder
poleceniem można było uruchomić tylko wygenerowany zestaw i nie zapisać go. Ponieważ zestaw był tylko w pamięci, trudno było debugować. Zalety zapisywania zestawu dynamicznego w pliku to:
- Możesz zweryfikować wygenerowany zestaw za pomocą narzędzi, takich jak ILVerify, lub dekompilować go ręcznie, za pomocą narzędzi takich jak ILSpy.
- Zapisany zestaw można załadować bezpośrednio, nie trzeba ponownie kompilować, co może zmniejszyć czas uruchamiania aplikacji.
Aby utworzyć PersistedAssemblyBuilder
wystąpienie, użyj konstruktora PersistedAssemblyBuilder(AssemblyName, Assembly, IEnumerable<CustomAttributeBuilder>) . Parametr służy do rozpoznawania coreAssembly
podstawowych typów środowiska uruchomieniowego i może służyć do rozpoznawania wersji zestawu referencyjnego:
Jeśli
Reflection.Emit
jest używany do generowania zestawu, który będzie wykonywany tylko w tej samej wersji środowiska uruchomieniowego co wersja środowiska uruchomieniowego, na którym jest uruchomiony kompilator (zazwyczaj w proc), podstawowy zestaw może być po prostutypeof(object).Assembly
. W poniższym przykładzie pokazano, jak utworzyć i zapisać zestaw w strumieniu i uruchomić go przy użyciu bieżącego zestawu środowiska uruchomieniowego:public static void CreateSaveAndRunAssembly() { PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly); ModuleBuilder mob = ab.DefineDynamicModule("MyModule"); TypeBuilder tb = mob.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); MethodBuilder meb = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), new Type[] { typeof(int), typeof(int) }); ILGenerator il = meb.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Add); il.Emit(OpCodes.Ret); tb.CreateType(); using var stream = new MemoryStream(); ab.Save(stream); // or pass filename to save into a file stream.Seek(0, SeekOrigin.Begin); Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(stream); MethodInfo method = assembly.GetType("MyType").GetMethod("SumMethod"); Console.WriteLine(method.Invoke(null, new object[] { 5, 10 })); }
Jeśli
Reflection.Emit
jest używany do generowania zestawu przeznaczonego dla określonego serwera TFM, otwórz zestawy referencyjne dla danego serwera TFM przy użyciuMetadataLoadContext
i użyj wartości właściwości MetadataLoadContext.CoreAssembly dla elementucoreAssembly
. Ta wartość umożliwia uruchamianie generatora w jednej wersji środowiska uruchomieniowego platformy .NET i określanie innej wersji środowiska uruchomieniowego platformy .NET. Podczas odwoływania się do typów podstawowych należy używać typów zwracanych przezMetadataLoadContext
wystąpienie. Na przykład zamiasttypeof(int)
znajdźSystem.Int32
typ wedługMetadataLoadContext.CoreAssembly
nazwy:public static void CreatePersistedAssemblyBuilderCoreAssemblyWithMetadataLoadContext(string refAssembliesPath) { PathAssemblyResolver resolver = new PathAssemblyResolver(Directory.GetFiles(refAssembliesPath, "*.dll")); using MetadataLoadContext context = new MetadataLoadContext(resolver); Assembly coreAssembly = context.CoreAssembly; PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyDynamicAssembly"), coreAssembly); TypeBuilder typeBuilder = ab.DefineDynamicModule("MyModule").DefineType("Test", TypeAttributes.Public); MethodBuilder methodBuilder = typeBuilder.DefineMethod("Method", MethodAttributes.Public, coreAssembly.GetType(typeof(int).FullName), Type.EmptyTypes); // .. add members and save the assembly }
Ustawianie punktu wejścia dla pliku wykonywalnego
Aby ustawić punkt wejścia pliku wykonywalnego lub ustawić inne opcje dla pliku zestawu, możesz wywołać public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData)
metodę i użyć wypełnionych metadanych, aby wygenerować zestaw z żądanymi opcjami, na przykład:
public static void SetEntryPoint()
{
PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly);
TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
// ...
MethodBuilder entryPoint = tb.DefineMethod("Main", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static);
ILGenerator il2 = entryPoint.GetILGenerator();
// ...
il2.Emit(OpCodes.Ret);
tb.CreateType();
MetadataBuilder metadataBuilder = ab.GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder fieldData);
PEHeaderBuilder peHeaderBuilder = new PEHeaderBuilder(imageCharacteristics: Characteristics.ExecutableImage);
ManagedPEBuilder peBuilder = new ManagedPEBuilder(
header: peHeaderBuilder,
metadataRootBuilder: new MetadataRootBuilder(metadataBuilder),
ilStream: ilStream,
mappedFieldData: fieldData,
entryPoint: MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken));
BlobBuilder peBlob = new BlobBuilder();
peBuilder.Serialize(peBlob);
// in case saving to a file:
using var fileStream = new FileStream("MyAssembly.exe", FileMode.Create, FileAccess.Write);
peBlob.WriteContentTo(fileStream);
}
Emituj symbole i generuj plik PDB
Metadane symboli są wypełniane w parametrze pdbBuilder
out podczas wywoływania GenerateMetadata(BlobBuilder, BlobBuilder) metody w wystąpieniu PersistedAssemblyBuilder
. Aby utworzyć zestaw z przenośnym plikiem PDB:
- Utwórz ISymbolDocumentWriter wystąpienia za pomocą ModuleBuilder.DefineDocument(String, Guid, Guid, Guid) metody . Emitując il metody, emitują również odpowiednie informacje o symbolu.
- PortablePdbBuilder Utwórz wystąpienie przy użyciu
pdbBuilder
wystąpienia wygenerowanego przez metodę GenerateMetadata(BlobBuilder, BlobBuilder) . - Serializuj
PortablePdbBuilder
element w obiekcie Blobi zapisz elementBlob
w strumieniu plików PDB (tylko w przypadku generowania autonomicznego pliku PDB). - DebugDirectoryBuilder Utwórz wystąpienie i dodaj DebugDirectoryBuilder.AddCodeViewEntry element (autonomiczny plik PDB) lub DebugDirectoryBuilder.AddEmbeddedPortablePdbEntry.
- Ustaw opcjonalny
debugDirectoryBuilder
argument podczas tworzenia PEBuilder wystąpienia.
W poniższym przykładzie pokazano, jak emitować informacje o symbolach i generować plik PDB.
static void GenerateAssemblyWithPdb()
{
PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly);
ModuleBuilder mb = ab.DefineDynamicModule("MyModule");
TypeBuilder tb = mb.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
MethodBuilder mb1 = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), [typeof(int), typeof(int)]);
ISymbolDocumentWriter srcDoc = mb.DefineDocument("MySourceFile.cs", SymLanguageType.CSharp);
ILGenerator il = mb1.GetILGenerator();
LocalBuilder local = il.DeclareLocal(typeof(int));
local.SetLocalSymInfo("myLocal");
il.MarkSequencePoint(srcDoc, 7, 0, 7, 11);
...
il.Emit(OpCodes.Ret);
MethodBuilder entryPoint = tb.DefineMethod("Main", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static);
ILGenerator il2 = entryPoint.GetILGenerator();
il2.BeginScope();
...
il2.EndScope();
...
tb.CreateType();
MetadataBuilder metadataBuilder = ab.GenerateMetadata(out BlobBuilder ilStream, out _, out MetadataBuilder pdbBuilder);
MethodDefinitionHandle entryPointHandle = MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken);
DebugDirectoryBuilder debugDirectoryBuilder = GeneratePdb(pdbBuilder, metadataBuilder.GetRowCounts(), entryPointHandle);
ManagedPEBuilder peBuilder = new ManagedPEBuilder(
header: new PEHeaderBuilder(imageCharacteristics: Characteristics.ExecutableImage, subsystem: Subsystem.WindowsCui),
metadataRootBuilder: new MetadataRootBuilder(metadataBuilder),
ilStream: ilStream,
debugDirectoryBuilder: debugDirectoryBuilder,
entryPoint: entryPointHandle);
BlobBuilder peBlob = new BlobBuilder();
peBuilder.Serialize(peBlob);
using var fileStream = new FileStream("MyAssembly.exe", FileMode.Create, FileAccess.Write);
peBlob.WriteContentTo(fileStream);
}
static DebugDirectoryBuilder GeneratePdb(MetadataBuilder pdbBuilder, ImmutableArray<int> rowCounts, MethodDefinitionHandle entryPointHandle)
{
BlobBuilder portablePdbBlob = new BlobBuilder();
PortablePdbBuilder portablePdbBuilder = new PortablePdbBuilder(pdbBuilder, rowCounts, entryPointHandle);
BlobContentId pdbContentId = portablePdbBuilder.Serialize(portablePdbBlob);
// In case saving PDB to a file
using FileStream fileStream = new FileStream("MyAssemblyEmbeddedSource.pdb", FileMode.Create, FileAccess.Write);
portablePdbBlob.WriteContentTo(fileStream);
DebugDirectoryBuilder debugDirectoryBuilder = new DebugDirectoryBuilder();
debugDirectoryBuilder.AddCodeViewEntry("MyAssemblyEmbeddedSource.pdb", pdbContentId, portablePdbBuilder.FormatVersion);
// In case embedded in PE:
// debugDirectoryBuilder.AddEmbeddedPortablePdbEntry(portablePdbBlob, portablePdbBuilder.FormatVersion);
return debugDirectoryBuilder;
}
Ponadto możesz dodać CustomDebugInformation metodę , wywołując metodę MetadataBuilder.AddCustomDebugInformation(EntityHandle, GuidHandle, BlobHandle) z wystąpienia w celu dodania osadzania źródłowego pdbBuilder
i indeksowania źródła zaawansowanych informacji pdB.
private static void EmbedSource(MetadataBuilder pdbBuilder)
{
byte[] sourceBytes = File.ReadAllBytes("MySourceFile2.cs");
BlobBuilder sourceBlob = new BlobBuilder();
sourceBlob.WriteBytes(sourceBytes);
pdbBuilder.AddCustomDebugInformation(MetadataTokens.DocumentHandle(1),
pdbBuilder.GetOrAddGuid(new Guid("0E8A571B-6926-466E-B4AD-8AB04611F5FE")), pdbBuilder.GetOrAddBlob(sourceBlob));
}
Dodawanie zasobów za pomocą elementu PersistedAssemblyBuilder
Możesz wywołać metodę MetadataBuilder.AddManifestResource(ManifestResourceAttributes, StringHandle, EntityHandle, UInt32) , aby dodać dowolną liczbę zasobów zgodnie z potrzebami. Strumienie należy połączyć w taki, BlobBuilder który zostanie przekazany do argumentuManagedPEBuilder. W poniższym przykładzie pokazano, jak utworzyć zasoby i dołączyć je do utworzonego zestawu.
public static void SetResource()
{
PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly);
ab.DefineDynamicModule("MyModule");
MetadataBuilder metadata = ab.GenerateMetadata(out BlobBuilder ilStream, out _);
using MemoryStream stream = new MemoryStream();
ResourceWriter myResourceWriter = new ResourceWriter(stream);
myResourceWriter.AddResource("AddResource 1", "First added resource");
myResourceWriter.AddResource("AddResource 2", "Second added resource");
myResourceWriter.AddResource("AddResource 3", "Third added resource");
myResourceWriter.Close();
BlobBuilder resourceBlob = new BlobBuilder();
resourceBlob.WriteBytes(stream.ToArray());
metadata.AddManifestResource(ManifestResourceAttributes.Public, metadata.GetOrAddString("MyResource"), default, (uint)resourceBlob.Count);
ManagedPEBuilder peBuilder = new ManagedPEBuilder(
header: new PEHeaderBuilder(imageCharacteristics: Characteristics.ExecutableImage | Characteristics.Dll),
metadataRootBuilder: new MetadataRootBuilder(metadata),
ilStream: ilStream,
managedResources: resourceBlob);
BlobBuilder blob = new BlobBuilder();
peBuilder.Serialize(blob);
using var fileStream = new FileStream("MyAssemblyWithResource.dll", FileMode.Create, FileAccess.Write);
blob.WriteContentTo(fileStream);
}
Uwaga
Tokeny metadanych dla wszystkich elementów członkowskich są wypełniane w Save operacji. Nie używaj tokenów wygenerowanego typu i jego składowych przed zapisaniem, ponieważ będą miały wartości domyślne lub zgłaszają wyjątki. Bezpieczne jest używanie tokenów dla typów, do których odwołuje się odwołanie, a nie generowanych.
Niektóre interfejsy API, które nie są ważne do emitowania zestawu, nie są implementowane; na przykład GetCustomAttributes()
nie jest zaimplementowany. Implementacja środowiska uruchomieniowego umożliwiała korzystanie z tych interfejsów API po utworzeniu typu. W przypadku utrwalonego AssemblyBuilder
obiektu zgłaszają wartość NotSupportedException
lub NotImplementedException
. Jeśli masz scenariusz, który wymaga tych interfejsów API, zgłoś problem w repozytorium dotnet/runtime.
Aby uzyskać alternatywny sposób generowania plików zestawów, zobacz MetadataBuilder.
Opinia
https://aka.ms/ContentUserFeedback.
Dostępne już wkrótce: W 2024 r. będziemy stopniowo wycofywać zgłoszenia z serwisu GitHub jako mechanizm przesyłania opinii na temat zawartości i zastępować go nowym systemem opinii. Aby uzyskać więcej informacji, sprawdź:Prześlij i wyświetl opinię dla