diff --git a/MetadataProcessor.Shared/nanoSkeletonGenerator.cs b/MetadataProcessor.Shared/nanoSkeletonGenerator.cs index 31966289..093bbad0 100644 --- a/MetadataProcessor.Shared/nanoSkeletonGenerator.cs +++ b/MetadataProcessor.Shared/nanoSkeletonGenerator.cs @@ -30,6 +30,8 @@ public sealed class nanoSkeletonGenerator private string _safeProjectName => _project.Replace('.', '_'); + public string SafeProjectName => _safeProjectName; + public nanoSkeletonGenerator( nanoTablesContext tablesContext, string path, @@ -87,11 +89,9 @@ private void GenerateStubs() IsInterop = !_withoutInteropCode }; - foreach (var c in _tablesContext.TypeDefinitionTable.TypeDefinitions) + foreach (TypeDefinition c in _tablesContext.TypeDefinitionTable.TypeDefinitions) { - if (c.IncludeInStub() && - !c.IsToExclude() && - !nanoTablesContext.IgnoringAttributes.Contains(c.FullName)) + if (ShouldIncludeType(c)) { var className = NativeMethodsCrc.GetClassName(c); @@ -370,12 +370,12 @@ private void GenerateAssemblyLookup() }; - foreach (var c in _tablesContext.TypeDefinitionTable.Items) + foreach (TypeDefinition c in _tablesContext.TypeDefinitionTable.Items) { // only care about types that have methods if (c.HasMethods) { - if (c.IncludeInStub()) + if (ShouldIncludeType(c)) { var className = NativeMethodsCrc.GetClassName(c); @@ -399,8 +399,11 @@ private void GenerateAssemblyLookup() // need to add a NULL entry for it assemblyLookup.LookupTable.Add(new MethodStub() { +#if DEBUG + Declaration = $"NULL, // <<<<< Library_{_safeProjectName}_{NativeMethodsCrc.GetClassName(c)}::{NativeMethodsCrc.GetMethodName(m)}", +#else Declaration = "NULL" - //Declaration = $"**Library_{_safeProjectName}_{NativeMethodsCrc.GetClassName(c)}::{NativeMethodsCrc.GetMethodName(m)}" +#endif }); } } @@ -413,8 +416,11 @@ private void GenerateAssemblyLookup() { assemblyLookup.LookupTable.Add(new MethodStub() { +#if DEBUG + Declaration = $"NULL, // <<<<< Library_{_safeProjectName}_{NativeMethodsCrc.GetClassName(c)}::{NativeMethodsCrc.GetMethodName(m)}", +#else Declaration = "NULL" - //Declaration = $"**Library_{_safeProjectName}_{NativeMethodsCrc.GetClassName(c)}::{NativeMethodsCrc.GetMethodName(m)}" +#endif }); } } @@ -443,10 +449,9 @@ private void GenerateAssemblyHeader() IsCoreLib = _isCoreLib }; - foreach (var c in _tablesContext.TypeDefinitionTable.Items) + foreach (TypeDefinition c in _tablesContext.TypeDefinitionTable.Items) { - if (c.IncludeInStub() && - !c.IsToExclude()) + if (ShouldIncludeType(c)) { var classData = new Class() { @@ -619,5 +624,12 @@ private int GetNestedFieldsCount(TypeDefinition c) return c.Fields.Count(f => !f.IsStatic && !f.IsLiteral); } } + + internal static bool ShouldIncludeType(TypeDefinition type) + { + return type.IncludeInStub() + && !type.IsToExclude() + && !nanoTablesContext.IgnoringAttributes.Contains(type.FullName); + } } } diff --git a/MetadataProcessor.Tests/Core/StubsGenerationTests.cs b/MetadataProcessor.Tests/Core/StubsGenerationTests.cs index 777c6db0..57ca9b20 100644 --- a/MetadataProcessor.Tests/Core/StubsGenerationTests.cs +++ b/MetadataProcessor.Tests/Core/StubsGenerationTests.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text.RegularExpressions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Mono.Cecil; @@ -60,6 +61,8 @@ public class StubsGenerationTests "static void NativeMethodWithReferenceParameters( uint8_t& param0, uint16_t& param1, HRESULT &hr );"; private string _stubsPath; + private List _nfTestLibTypeToIncludeInLookupTable = new List(); + private List _nfTestLibTypeToIncludeInHeader = new List(); [TestMethod] public void GeneratingStubsFromNFAppTest() @@ -212,6 +215,49 @@ public void BackingFieldsAbsentTests() Assert.IsFalse(Regex.IsMatch(generatedAssemblyHeaderFile, @"(?k__BackingField(?!')"), "Found a name with BackingField pattern, when it shouldn't"); } + [TestMethod] + public void StubsAndDeclarationMatchTests() + { + string generatedAssemblyHeaderFile = File.ReadAllText($"{_stubsPath}\\TestNFClassLibrary.h"); + string generatedAssemblyLookupFile = File.ReadAllText($"{_stubsPath}\\TestNFClassLibrary.cpp"); + + // extract all type definitions from the header file + MatchCollection typeDefinitionsInHeader = Regex.Matches(generatedAssemblyHeaderFile, @"struct\s{1}(\w+_\w+_\w+_\w+)\b", RegexOptions.IgnoreCase | RegexOptions.Multiline); + + // extract all type definitions from the lookup file + List typeDefinitionsInLookupTable = Regex.Matches(generatedAssemblyLookupFile, @"^\s{4}(\w+_\w+_\w+_\w+)::", RegexOptions.IgnoreCase | RegexOptions.Multiline) + .Cast() + .GroupBy(m => m.Groups[1].Value) + .Select(g => g.First()) + .ToList(); + + // check if all entries in lookup table are present in the header + foreach (Match typeDefinition in typeDefinitionsInLookupTable) + { + string typeName = typeDefinition.Groups[1].Value; + bool found = typeDefinitionsInHeader.Cast().Any(md => md.Groups[1].Value == typeName); + Assert.IsTrue(found, $"Type definition {typeName} not found in header file"); + } + + // check if all expected types are present in the lookup table + Assert.AreEqual(_nfTestLibTypeToIncludeInLookupTable.Count, typeDefinitionsInLookupTable.Count, "Number of type definitions don't match"); + + foreach (string typeName in _nfTestLibTypeToIncludeInLookupTable) + { + bool found = typeDefinitionsInLookupTable.Any(md => md.Groups[1].Value == typeName); + Assert.IsTrue(found, $"Type definition {typeName} not found in lookup table"); + } + + // check if all expected types are present in the header file + Assert.AreEqual(_nfTestLibTypeToIncludeInHeader.Count, typeDefinitionsInHeader.Count, "Number of type definitions don't match"); + + foreach (string typeName in _nfTestLibTypeToIncludeInHeader) + { + bool found = typeDefinitionsInHeader.Cast().Any(md => md.Groups[1].Value == typeName); + Assert.IsTrue(found, $"Type definition {typeName} not found in header file"); + } + } + [TestInitialize] public void GenerateStubs() { @@ -256,6 +302,16 @@ public void GenerateStubs() assemblyBuilder.Minimize(); + // recompile + using (FileStream stream = File.Open( + Path.ChangeExtension(stubsGenerationFileToCompile, "tmp"), + FileMode.Create, + FileAccess.ReadWrite)) + using (BinaryWriter writer = new BinaryWriter(stream)) + { + assemblyBuilder.Write(GetBinaryWriter(writer)); + } + nanoTablesContext tablesContext = assemblyBuilder.TablesContext; var skeletonGenerator = new nanoSkeletonGenerator( @@ -296,6 +352,17 @@ public void GenerateStubs() assemblyBuilder.Minimize(); + // recompile + using (FileStream stream = File.Open( + Path.ChangeExtension(nfLibFileToCompile, "tmp"), + FileMode.Create, + FileAccess.ReadWrite)) + + using (BinaryWriter writer = new BinaryWriter(stream)) + { + assemblyBuilder.Write(GetBinaryWriter(writer)); + } + tablesContext = assemblyBuilder.TablesContext; skeletonGenerator = new nanoSkeletonGenerator( @@ -307,6 +374,39 @@ public void GenerateStubs() true); skeletonGenerator.GenerateSkeleton(); + + // save types that are to be included from assembly lookup declaration + foreach (TypeDefinition c in tablesContext.TypeDefinitionTable.Items) + { + if (c.HasMethods && nanoSkeletonGenerator.ShouldIncludeType(c)) + { + foreach (MethodDefinition m in nanoTablesContext.GetOrderedMethods(c.Methods)) + { + ushort rva = tablesContext.ByteCodeTable.GetMethodRva(m); + + // check method inclusion + // method is not a native implementation (RVA 0xFFFF) and is not abstract + if (rva == 0xFFFF && !m.IsAbstract) + { + _nfTestLibTypeToIncludeInLookupTable.Add($"Library_{skeletonGenerator.SafeProjectName}_{NativeMethodsCrc.GetClassName(c)}"); + + // only need to add the type once + break; + } + } + } + } + + // save types that are to be included in assembly header + foreach (TypeDefinition c in tablesContext.TypeDefinitionTable.Items) + { + if (nanoSkeletonGenerator.ShouldIncludeType(c) + && c.HasMethods + && c.HasFields) + { + _nfTestLibTypeToIncludeInHeader.Add($"Library_{skeletonGenerator.SafeProjectName}_{NativeMethodsCrc.GetClassName(c)}"); + } + } } [TestCleanup]