[dotnet] preliminary cut for class-redirector (#17951)

This is the preliminary version of class-redirector addressing issue
#16671
This commit is contained in:
Steve Hawley 2023-04-03 12:37:26 -04:00 коммит произвёл GitHub
Родитель 098d9c5b9f
Коммит bef5d47ef4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 707 добавлений и 0 удалений

Просмотреть файл

@ -201,6 +201,15 @@ namespace ObjCRuntime {
internal static unsafe InitializationOptions* options; internal static unsafe InitializationOptions* options;
#if NET
public static class ClassHandles
{
internal static unsafe void InitializeClassHandles (MTClassMap* map)
{
}
}
#endif
#if NET #if NET
[BindingImpl (BindingImplOptions.Optimizable)] [BindingImpl (BindingImplOptions.Optimizable)]
internal unsafe static bool IsCoreCLR { internal unsafe static bool IsCoreCLR {

Просмотреть файл

@ -0,0 +1,2 @@
global using NUnit.Framework;

Просмотреть файл

@ -0,0 +1,93 @@
using ClassRedirector;
using System.Xml.Linq;
namespace ClassRedirectorTests;
public class XmlTests {
[Test]
public void WritesCorrectXml ()
{
var map = new CSToObjCMap () {
["Foo"] = new ObjCNameIndex ("Bar", 2),
["Baz"] = new ObjCNameIndex ("Zed", 3),
};
var doc = CSToObjCMap.ToXDocument (map);
var elem = doc.Elements ().Where (el => el.Name == "CSToObjCMap").FirstOrDefault ();
Assert.IsNotNull (elem, "no map");
Assert.That (elem.Elements ().Count (), Is.EqualTo (2), "Incorrect number of children");
var cselem = XElementWithAttribute (elem, "CSName", "Foo");
Assert.IsNotNull (cselem, "missing Foo elem");
var nameElem = NameElem (cselem);
Assert.IsNotNull (nameElem, "no name elem1");
Assert.That (nameElem.Value, Is.EqualTo ("Bar"));
var indexElem = IndexElem (cselem);
Assert.IsNotNull (indexElem, "no value elem1");
Assert.That ((int) indexElem, Is.EqualTo (2));
cselem = XElementWithAttribute (elem, "CSName", "Baz");
Assert.IsNotNull (cselem, "missing Baz elem");
nameElem = NameElem (cselem);
Assert.IsNotNull (nameElem, "no name elem2");
indexElem = IndexElem (cselem);
Assert.That (nameElem.Value, Is.EqualTo ("Zed"));
Assert.IsNotNull (indexElem, "no value elem2");
Assert.That ((int) indexElem, Is.EqualTo (3));
}
[Test]
public void ReadsCorrectXml ()
{
var text = @"<?xml version=""1.0"" encoding=""utf-8""?>
<CSToObjCMap>
<Element CSName=""Foo"">
<ObjNameIndex>
<Name>Bar</Name>
<Index>2</Index>
</ObjNameIndex>
</Element>
<Element CSName=""Baz"">
<ObjNameIndex>
<Name>Zed</Name>
<Index>3</Index>
</ObjNameIndex>
</Element>
</CSToObjCMap>";
using var reader = new StringReader (text);
var doc = XDocument.Load (reader);
var map = CSToObjCMap.FromXDocument (doc);
Assert.IsNotNull (map, "no map");
Assert.That (map.Count (), Is.EqualTo (2));
Assert.True (map.TryGetValue ("Foo", out var nameIndex), "no nameIndex");
Assert.That (nameIndex.ObjCName, Is.EqualTo ("Bar"), "no bar name");
Assert.That (nameIndex.MapIndex, Is.EqualTo (2), "no bar index");
Assert.True (map.TryGetValue ("Baz", out var nameIndex1));
Assert.That (nameIndex1.ObjCName, Is.EqualTo ("Zed"), "no bar name");
Assert.That (nameIndex1.MapIndex, Is.EqualTo (3), "no bar index");
}
static XElement? XElementWithAttribute (XElement el, string attrName, string attrValue)
{
return el.Elements ().Where (e => {
var attr = e.Attribute (attrName);
return attr is not null && attr.Value == attrValue;
}).FirstOrDefault ();
}
static XElement? NameElem (XElement el) => FirstElementNamed (el, "Name");
static XElement? IndexElem (XElement el) => FirstElementNamed (el, "Index");
static XElement? FirstElementNamed (XElement el, string name)
{
return el.Descendants ().Where (e => e.Name == name).FirstOrDefault ();
}
}

Просмотреть файл

@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>class_redirector_tests</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="NUnit.Analyzers" Version="3.3.0" />
<PackageReference Include="coverlet.collector" Version="3.1.2" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\common\CSToObjCMap.cs">
<Link>CSToObjCMap.cs</Link>
</Compile>
<Compile Include="..\..\common\ObjCNameIndex.cs">
<Link>ObjCNameIndex.cs</Link>
</Compile>
<Compile Include="..\..\common\StaticRegistrarFile.cs">
<Link>StaticRegistrarFile.cs</Link>
</Compile>
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,59 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 25.0.1705.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "class-redirector", "class-redirector\class-redirector.csproj", "{0723580E-2C56-4460-BDDD-DEF38E0F543F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "class-redirector-tests", "class-redirector-tests\class-redirector-tests.csproj", "{16FD4B5E-5537-46A2-B573-0424A51472AA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0723580E-2C56-4460-BDDD-DEF38E0F543F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0723580E-2C56-4460-BDDD-DEF38E0F543F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0723580E-2C56-4460-BDDD-DEF38E0F543F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0723580E-2C56-4460-BDDD-DEF38E0F543F}.Release|Any CPU.Build.0 = Release|Any CPU
{16FD4B5E-5537-46A2-B573-0424A51472AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{16FD4B5E-5537-46A2-B573-0424A51472AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{16FD4B5E-5537-46A2-B573-0424A51472AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{16FD4B5E-5537-46A2-B573-0424A51472AA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {ECD11F77-045E-4B88-BF92-28ED04BBD631}
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
Policies = $0
$0.TextStylePolicy = $1
$1.FileWidth = 80
$1.TabWidth = 8
$1.IndentWidth = 8
$1.scope = text/x-csharp
$0.CSharpFormattingPolicy = $2
$2.IndentSwitchSection = False
$2.NewLinesForBracesInTypes = False
$2.NewLinesForBracesInProperties = False
$2.NewLinesForBracesInAccessors = False
$2.NewLinesForBracesInAnonymousMethods = False
$2.NewLinesForBracesInControlBlocks = False
$2.NewLinesForBracesInAnonymousTypes = False
$2.NewLinesForBracesInObjectCollectionArrayInitializers = False
$2.NewLinesForBracesInLambdaExpressionBody = False
$2.NewLineForElse = False
$2.NewLineForCatch = False
$2.NewLineForFinally = False
$2.NewLineForMembersInObjectInit = False
$2.NewLineForMembersInAnonymousTypes = False
$2.NewLineForClausesInQuery = False
$2.SpacingAfterMethodDeclarationName = True
$2.SpaceAfterMethodCallName = True
$2.SpaceBeforeOpenSquareBracket = True
$2.scope = text/x-csharp
EndGlobalSection
EndGlobal

Просмотреть файл

@ -0,0 +1,106 @@
using System.IO;
using Mono.Options;
using System.Xml.Linq;
#nullable enable
namespace ClassRedirector {
public class Program {
public static int Main (string [] args)
{
try {
return Main2 (args);
} catch (Exception e) {
Console.Error.WriteLine (e);
return 1;
}
}
public static int Main2 (string [] args)
{
var doHelp = false;
string inputDirectory = "";
var options = new OptionSet () {
{ "h|?|help", o => doHelp = true },
{ "i=|input-directory=", d => inputDirectory = d },
};
if (doHelp) {
options.WriteOptionDescriptions (Console.Out);
Console.WriteLine ($"This program takes an input directory and looks for the file '{StaticRegistrarFile.Name}'.");
Console.WriteLine ("Upon finding it, it uses that as a map for finding all C# classes defined in dll's in the");
Console.WriteLine ("directory and rewrites the classes' definition and usage of class_ptr to be more");
Console.WriteLine ("efficient and, if possible, to no longer need a static constructor.");
Console.WriteLine ("This program also requires that the directory contains one of the Microsoft platform dlls.");
Console.WriteLine ("Classes in the directory are modified in place.");
return 0;
}
if (String.IsNullOrEmpty (inputDirectory)) {
throw new Exception ($"input-directory is required");
}
if (!Directory.Exists (inputDirectory)) {
throw new Exception ($"input-directory {inputDirectory} does not exist.");
}
if (!DirectoryIsWritable (inputDirectory)) {
throw new Exception ($"input-directory {inputDirectory} is not writable");
}
var registrarFile = Path.Combine (inputDirectory, StaticRegistrarFile.Name);
if (!File.Exists (registrarFile)) {
throw new Exception ($"map file {registrarFile} does not exist.");
}
var dllsToProcess = CollectDlls (inputDirectory);
var xamarinDll = FindXamarinDll (dllsToProcess);
if (xamarinDll is null)
throw new Exception ($"unable to find platform dll in {inputDirectory}");
var map = ReadRegistrarFile (registrarFile);
var rewriter = new Rewriter (map, xamarinDll, dllsToProcess);
rewriter.Process ();
return 0;
}
static bool DirectoryIsWritable (string path)
{
var info = new DirectoryInfo (path);
return !info.Attributes.HasFlag (FileAttributes.ReadOnly);
}
static string [] CollectDlls (string dir)
{
return Directory.GetFiles (dir, "*.dll"); // GetFiles returns full paths
}
static string [] xamarinDlls = new string [] {
"Microsoft.iOS.dll",
"Microsoft.macOS.dll",
"Microsoft.tvOS.dll",
};
static bool IsXamarinDll (string p)
{
return xamarinDlls.FirstOrDefault (dll => p.EndsWith (dll, StringComparison.Ordinal)) is not null;
}
static string? FindXamarinDll (string [] paths)
{
return paths.FirstOrDefault (IsXamarinDll);
}
static CSToObjCMap ReadRegistrarFile (string path)
{
var doc = XDocument.Load (path);
var map = CSToObjCMap.FromXDocument (doc);
if (map is null)
throw new Exception ($"Unable to read static registrar map file {path}");
return map;
}
}
}

Просмотреть файл

@ -0,0 +1,258 @@
using System;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
namespace ClassRedirector {
public class Rewriter {
const string classHandleName = "ObjRuntime.Runtime.ClassHandles";
const string mtClassMapName = "ObjCRuntime.Runtime.MTClassMap";
const string nativeHandleName = "ObjCRuntime.NativeHandle";
const string initClassHandlesName = "InitializeClassHandles";
const string classPtrName = "class_ptr";
CSToObjCMap map;
string pathToXamarinAssembly;
string [] assembliesToPatch;
SimpleAssemblyResolver resolver;
Dictionary<string, FieldDefinition> csTypeToFieldDef = new Dictionary<string, FieldDefinition> ();
public Rewriter (CSToObjCMap map, string pathToXamarinAssembly, string [] assembliesToPatch)
{
this.map = map;
this.pathToXamarinAssembly = pathToXamarinAssembly;
this.assembliesToPatch = assembliesToPatch;
resolver = new SimpleAssemblyResolver (assembliesToPatch);
}
public void Process ()
{
var classMap = CreateClassHandles ();
PatchClassPtrUsage (classMap);
}
Dictionary<string, FieldDefinition> CreateClassHandles ()
{
var classMap = new Dictionary<string, FieldDefinition> ();
using var module = ModuleDefinition.ReadModule (pathToXamarinAssembly);
var classHandles = LocateClassHandles (module);
if (classHandles is null)
throw new Exception ($"Unable to find {classHandleName} type in {pathToXamarinAssembly}");
var initMethod = classHandles.Methods.FirstOrDefault (m => m.Name == initClassHandlesName);
if (initMethod is null)
throw new Exception ($"Unable to find {initClassHandlesName} method in {classHandles.Name}");
var processor = initMethod.Body.GetILProcessor ();
var mtClassMapDef = LocateMTClassMap (module);
if (mtClassMapDef is null)
throw new Exception ($"Unable to find {mtClassMapName} in {pathToXamarinAssembly}");
var nativeHandle = module.Types.FirstOrDefault (t => t.Name == nativeHandleName);
if (nativeHandle is null)
throw new Exception ($"Unable to find {nativeHandleName} in {pathToXamarinAssembly}");
var nativeHandleOpImplicit = FindOpImplicit (nativeHandle);
if (nativeHandleOpImplicit is null)
throw new Exception ($"Unable to find implicit cast in {nativeHandleName}");
foreach (var (csName, nameIndex) in map) {
var fieldDef = AddPublicStaticField (classHandles, nameIndex.ObjCName, nativeHandle);
AddInitializer (nativeHandleOpImplicit, processor, mtClassMapDef, nameIndex.MapIndex, fieldDef);
classMap [csName] = fieldDef;
}
module.Write ();
return classMap;
}
MethodDefinition? FindOpImplicit (TypeDefinition nativeHandle)
{
return nativeHandle.Methods.FirstOrDefault (m => m.Name == "op_Implicit" && m.ReturnType == nativeHandle &&
m.Parameters.Count == 1 && m.Parameters [0].ParameterType == nativeHandle.Module.TypeSystem.IntPtr);
}
void AddInitializer (MethodReference nativeHandleOpImplicit, ILProcessor il, TypeDefinition mtClassMapDef, int index, FieldDefinition fieldDef)
{
// Assuming that we have a method that looks like this:
// internal static unsafe void InitializeClassHandles (MTClassMap* map)
// {
// }
// We should have a compiled method that looks like this:
// nop
// ret
//
// For each handle that we define, we should add the following instructions:
// ldarg.0
// ldc.i4 index
// conv.i
// sizeof ObjCRuntime.Runtime.MTClassMap
// mul
// add
// ldfld ObjCRuntime.Runtime.MTClassMap.handle
// call ObjCRuntime.NativeHandle ObjCRuntime.NativeHandle::op_Implicit(System.IntPtr)
// stsfld fieldDef
var handleRef = mtClassMapDef.Fields.First (f => f.Name == "handle");
var last = il.Body.Instructions.Last ();
il.InsertBefore (last, Instruction.Create (OpCodes.Ldarg_0));
il.InsertBefore (last, Instruction.Create (OpCodes.Ldc_I4, index));
il.InsertBefore (last, Instruction.Create (OpCodes.Conv_I));
il.InsertBefore (last, Instruction.Create (OpCodes.Sizeof, mtClassMapDef));
il.InsertBefore (last, Instruction.Create (OpCodes.Mul));
il.InsertBefore (last, Instruction.Create (OpCodes.Add));
il.InsertBefore (last, Instruction.Create (OpCodes.Ldfld, handleRef));
il.InsertBefore (last, Instruction.Create (OpCodes.Call, nativeHandleOpImplicit));
il.InsertBefore (last, Instruction.Create (OpCodes.Stsfld, fieldDef));
}
FieldDefinition AddPublicStaticField (TypeDefinition inType, string fieldName, TypeReference fieldType)
{
var fieldDef = new FieldDefinition (fieldName, FieldAttributes.Public | FieldAttributes.Static, fieldType);
inType.Fields.Add (fieldDef);
return fieldDef;
}
TypeDefinition? LocateClassHandles (ModuleDefinition module)
{
return module.GetType (classHandleName);
}
TypeDefinition? LocateMTClassMap (ModuleDefinition module)
{
return module.GetType (mtClassMapName);
}
void PatchClassPtrUsage (Dictionary<string, FieldDefinition> classMap)
{
foreach (var path in assembliesToPatch) {
using var module = ModuleDefinition.ReadModule (path);
PatchClassPtrUsage (classMap, module);
module.Write ();
}
}
void PatchClassPtrUsage (Dictionary<string, FieldDefinition> classMap, ModuleDefinition module)
{
foreach (var cl in module.Types) {
if (classMap.TryGetValue (cl.FullName, out var classPtrField)) {
PatchClassPtrUsage (cl, classPtrField);
}
}
}
void PatchClassPtrUsage (TypeDefinition cl, FieldDefinition classPtrField)
{
var class_ptr = cl.Fields.FirstOrDefault (f => f.Name == classPtrName);
if (class_ptr is null) {
throw new Exception ($"Error processing class {cl.FullName} - no {classPtrName} field.");
}
// step 1: remove the field
cl.Fields.Remove (class_ptr);
// step 2: remove init code from cctor
RemoveCCtorInit (cl, class_ptr);
// step 3: patch every method
PatchMethods (cl, class_ptr, classPtrField);
}
void PatchMethods (TypeDefinition cl, FieldDefinition classPtr, FieldDefinition classPtrField)
{
foreach (var method in cl.Methods) {
PatchMethod (method, classPtr, classPtrField);
}
}
void PatchMethod (MethodDefinition method, FieldDefinition classPtr, FieldDefinition classPtrField)
{
foreach (var instr in method.Body.Instructions) {
if (instr.OpCode == OpCodes.Ldsfld && instr.Operand == classPtr) {
instr.Operand = classPtrField;
}
}
}
void RemoveCCtorInit (TypeDefinition cl, FieldDefinition class_ptr)
{
var cctor = cl.Methods.FirstOrDefault (m => m.Name == ".cctor");
if (cctor is null)
return; // no static init - should never happen, but we can deal.
var il = cctor.Body.GetILProcessor ();
Instruction? stsfld = null;
int i = 0;
for (; i < il.Body.Instructions.Count; i++) {
var instr = il.Body.Instructions [i];
// look for
// stsfld class_ptr
if (instr.OpCode == OpCodes.Stsfld && instr.Operand == class_ptr) {
stsfld = instr;
break;
}
}
if (stsfld is null)
return;
// if we see:
// ldstr "any"
// call ObjCRuntime.GetClassHandle
// stsfld class_ptr
// Then we can remove all of those instructions
var isGetClassHandle = IsGetClassHandle (il, i - 1);
var isLdStr = IsLdStr (il, i - 2);
if (isGetClassHandle && isLdStr) {
il.RemoveAt (i);
il.RemoveAt (i - 1);
il.RemoveAt (i - 2);
} else if (isGetClassHandle) {
// don't know how the string got on the stack, so at least get rid of the
// call to GetClassHandle by nopping it out. This still leaves the string on
// the stack, so pop it.
il.Replace (il.Body.Instructions [i - 1], Instruction.Create (OpCodes.Nop));
// can't remove all three, so just pop the IntPtr.
il.Replace (il.Body.Instructions [i], Instruction.Create (OpCodes.Pop));
} else {
// can't remove all three, so just pop the IntPtr.
il.Replace (il.Body.Instructions [i], Instruction.Create (OpCodes.Pop));
}
// if we're left with exactly 1 instruction and it's a return,
// then we can get rid of the entire method
if (cctor.Body.Instructions.Count == 1) {
if (cctor.Body.Instructions.Last ().OpCode == OpCodes.Ret)
cl.Methods.Remove (cctor);
}
// if we're left with exactly 2 instructions and the first is a no-op
// and the last is a return, then we can get rid of the entire method
if (cctor.Body.Instructions.Count == 2) {
if (cctor.Body.Instructions.Last ().OpCode == OpCodes.Ret &&
cctor.Body.Instructions.First ().OpCode == OpCodes.Nop)
cl.Methods.Remove (cctor);
}
}
bool IsGetClassHandle (ILProcessor il, int index)
{
if (index < 0)
return false;
var instr = il.Body.Instructions [index]!;
var operand = instr.Operand?.ToString () ?? "";
return instr.OpCode == OpCodes.Call && operand.Contains ("Class::GetHandle", StringComparison.Ordinal);
}
bool IsLdStr (ILProcessor il, int index)
{
if (index < 0)
return false;
var instr = il.Body.Instructions [index]!;
return instr.OpCode == OpCodes.Ldstr;
}
}
}

Просмотреть файл

@ -0,0 +1,21 @@
using System.IO;
using Mono.Cecil;
#nullable enable
namespace ClassRedirector {
public class SimpleAssemblyResolver : DefaultAssemblyResolver {
public SimpleAssemblyResolver (params string [] filesOrDirectories)
: base ()
{
foreach (var fileOrDirectory in filesOrDirectories) {
if (File.Exists (fileOrDirectory)) {
AddSearchDirectory (Path.GetDirectoryName (fileOrDirectory));
} else if (Directory.Exists (fileOrDirectory)) {
AddSearchDirectory (fileOrDirectory);
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>class_redirector</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mono.Cecil" Version="0.11.4" />
<PackageReference Include="Mono.Options" Version="6.12.0.148" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\common\CSToObjCMap.cs">
<Link>CSToObjCMap.cs</Link>
</Compile>
<Compile Include="..\..\common\ObjCNameIndex.cs">
<Link>ObjCNameIndex.cs</Link>
</Compile>
<Compile Include="..\..\common\StaticRegistrarFile.cs">
<Link>StaticRegistrarFile.cs</Link>
</Compile>
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
#nullable enable
namespace ClassRedirector {
public class CSToObjCMap : Dictionary<string, ObjCNameIndex> {
const string objMapName = "CSToObjCMap";
const string elementName = "Element";
const string csNameName = "CSName";
public CSToObjCMap () : base ()
{
}
public static XElement ToXElement (CSToObjCMap map)
{
return new XElement (objMapName, Elements (map));
}
static IEnumerable<XElement> Elements (CSToObjCMap map)
{
return map.Select (kvp => new XElement (elementName, new XAttribute (csNameName, kvp.Key), ObjCNameIndex.ToXElement (kvp.Value)));
}
public static CSToObjCMap FromXElement (XElement xmap)
{
var map = new CSToObjCMap ();
var elements = from el in xmap.Descendants (elementName)
select new KeyValuePair<string?, ObjCNameIndex?> (el.Attribute (csNameName)?.Value,
ObjCNameIndex.FromXElement (el.Element (ObjCNameIndex.ObjNameIndexName)));
foreach (var elem in elements) {
if (elem.Key is not null && elem.Value is not null)
map.Add (elem.Key, elem.Value);
}
return map;
}
public static CSToObjCMap? FromXDocument (XDocument doc)
{
var el = doc.Descendants (objMapName).FirstOrDefault ();
return el is null ? null : FromXElement (el);
}
public static XDocument ToXDocument (CSToObjCMap map)
{
return new XDocument (ToXElement (map));
}
}
}

Просмотреть файл

@ -0,0 +1,39 @@
using System;
using System.Linq;
using System.Xml.Linq;
#nullable enable
namespace ClassRedirector {
public class ObjCNameIndex {
public const string ObjNameIndexName = "ObjNameIndex";
const string nameName = "Name";
const string indexName = "Index";
public ObjCNameIndex (string objCName, int mapIndex)
{
ObjCName = objCName;
MapIndex = mapIndex;
}
public string ObjCName { get; private set; }
public int MapIndex { get; private set; }
public static XElement ToXElement (ObjCNameIndex nameIndex)
{
return new XElement (ObjNameIndexName,
new XElement (nameName, nameIndex.ObjCName),
new XElement (indexName, nameIndex.MapIndex));
}
public static ObjCNameIndex? FromXElement (XElement? objNameIndex)
{
if (objNameIndex is null)
return null;
var name = (string?) objNameIndex.Element (nameName);
var index = (int?) objNameIndex.Element (indexName);
if (name is null || index is null)
return null;
return new ObjCNameIndex (name, index.Value);
}
}
}

Просмотреть файл

@ -0,0 +1,12 @@
using System;
using System.Linq;
using System.Xml.Linq;
#nullable enable
namespace ClassRedirector {
public class StaticRegistrarFile {
public const string Name = "static-registrar-map.xml";
}
}