diff --git a/MetadataProcessor.Shared/SkeletonGenerator/AssemblyClass.cs b/MetadataProcessor.Shared/SkeletonGenerator/AssemblyClass.cs index c4897705..697d33f5 100644 --- a/MetadataProcessor.Shared/SkeletonGenerator/AssemblyClass.cs +++ b/MetadataProcessor.Shared/SkeletonGenerator/AssemblyClass.cs @@ -35,6 +35,7 @@ public class StaticField { public string Name; public int ReferenceIndex; + public string FieldWarning; } public class InstanceField diff --git a/MetadataProcessor.Shared/SkeletonGenerator/SkeletonTemplates.cs b/MetadataProcessor.Shared/SkeletonGenerator/SkeletonTemplates.cs index 26a50a55..9d696257 100644 --- a/MetadataProcessor.Shared/SkeletonGenerator/SkeletonTemplates.cs +++ b/MetadataProcessor.Shared/SkeletonGenerator/SkeletonTemplates.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) .NET Foundation and Contributors // See LICENSE file in the project root for full license information. // @@ -62,6 +62,9 @@ struct Library_{{AssemblyName}}_{{Name}}{{#newline}} {{{#newline}} {{#each StaticFields}} +{{#if FieldWarning}} + {{FieldWarning}}{{#newline}} +{{/if}} static const int FIELD_STATIC__{{Name}} = {{ReferenceIndex}};{{#newline}} {{/each}} {{#if StaticFields}}{{#newline}}{{/if}} diff --git a/MetadataProcessor.Shared/nanoSkeletonGenerator.cs b/MetadataProcessor.Shared/nanoSkeletonGenerator.cs index 4457bb33..31966289 100644 --- a/MetadataProcessor.Shared/nanoSkeletonGenerator.cs +++ b/MetadataProcessor.Shared/nanoSkeletonGenerator.cs @@ -465,14 +465,17 @@ private void GenerateAssemblyHeader() // static fields int fieldCount = 0; - var staticFields = c.Fields.Where(f => f.IsStatic && !f.IsLiteral); + IEnumerable staticFields = c.Fields.Where(f => f.IsStatic && !f.IsLiteral); - foreach (var f in staticFields) + foreach (FieldDefinition f in staticFields) { + FixFieldName(f, out string fixedFieldName, out string fieldWarning); + classData.StaticFields.Add(new StaticField() { - Name = f.Name, - ReferenceIndex = staticFieldCount + fieldCount++ + Name = string.IsNullOrEmpty(fixedFieldName) ? f.Name : fixedFieldName, + ReferenceIndex = staticFieldCount + fieldCount++, + FieldWarning = fieldWarning }); } @@ -488,15 +491,7 @@ private void GenerateAssemblyHeader() fieldCount = 0; foreach (var f in c.Fields.Where(f => !f.IsStatic && !f.IsLiteral)) { - // rename auto-properties backing field to a valid C++ identifier - string fixedFieldName = string.Empty; - string fieldWarning = string.Empty; - - if (Regex.IsMatch(f.Name, @"<\w+>k__BackingField")) - { - fixedFieldName = $"{f.Name.Replace("<", "").Replace(">", "_")}"; - fieldWarning = $"// auto-property backing field renamed to '{fixedFieldName}'"; - } + FixFieldName(f, out string fixedFieldName, out string fieldWarning); if (_tablesContext.FieldsTable.TryGetFieldReferenceId(f, false, out ushort fieldRefId)) { @@ -566,6 +561,27 @@ private void GenerateAssemblyHeader() } } + /// + /// Fix field name to a valid C++ identifier. + /// + /// The field definition to work on. + /// The fixed field name, or an string if no fix is needed. + /// The warning message to be added to the field declaration, or empty if no warning is needed. + private static void FixFieldName( + FieldDefinition field, + out string fixedFieldName, + out string fieldWarning) + { + fixedFieldName = string.Empty; + fieldWarning = string.Empty; + + if (Regex.IsMatch(field.Name, @"<\w+>k__BackingField")) + { + fixedFieldName = $"{field.Name.Replace("<", "").Replace(">k__BackingField", "")}"; + fieldWarning = $"// renamed backing field '{field.Name}'"; + } + } + private int GetInstanceFieldsOffset(TypeDefinition c) { // check if this type has a base type different from System.Object diff --git a/MetadataProcessor.Tests/Core/StubsGenerationTests.cs b/MetadataProcessor.Tests/Core/StubsGenerationTests.cs index 6782bfc1..777c6db0 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.Text.RegularExpressions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Mono.Cecil; using nanoFramework.Tools.MetadataProcessor.Core; @@ -58,7 +59,7 @@ public class StubsGenerationTests private const string NativeHeaderMethodGenerationDeclaration = "static void NativeMethodWithReferenceParameters( uint8_t& param0, uint16_t& param1, HRESULT &hr );"; - private string stubPath; + private string _stubsPath; [TestMethod] public void GeneratingStubsFromNFAppTest() @@ -66,7 +67,7 @@ public void GeneratingStubsFromNFAppTest() // read generated stub file and look for the function declaration var generatedFile = File.ReadAllText( - $"{stubPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.cpp"); + $"{_stubsPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.cpp"); Assert.IsTrue(generatedFile.Contains(NativeMethodGenerationDeclaration)); } @@ -76,7 +77,7 @@ public void GeneratingMarshallingStubsFromNFAppTest() { var generatedFile = File.ReadAllText( - $"{stubPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration_mshl.cpp"); + $"{_stubsPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration_mshl.cpp"); Assert.IsTrue(generatedFile.Contains(NativeMarshallingMethodGenerationDeclaration)); } @@ -86,7 +87,7 @@ public void GeneratingHeaderStubsFromNFAppTest() { var generatedFile = File.ReadAllText( - $"{stubPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.h"); + $"{_stubsPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.h"); Assert.IsTrue(generatedFile.Contains(NativeHeaderMethodGenerationDeclaration)); } @@ -127,15 +128,15 @@ public void GeneratingStaticMethodWithoutParams() { var generatedHeaderFile = File.ReadAllText( - $"{stubPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.h"); + $"{_stubsPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.h"); var generatedMarshallFile = File.ReadAllText( - $"{stubPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration_mshl.cpp"); + $"{_stubsPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration_mshl.cpp"); var generatedImplementationFile = File.ReadAllText( - $"{stubPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.cpp"); + $"{_stubsPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.cpp"); Assert.IsTrue(generatedHeaderFile.Contains(StaticMethodWithoutParameterHeaderGeneration)); Assert.IsTrue(generatedMarshallFile.Contains(StaticMethodWithoutParameterMarshallGeneration)); @@ -182,27 +183,41 @@ public void GeneratingStaticMethod() { var generatedHeaderFile = File.ReadAllText( - $"{stubPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.h"); + $"{_stubsPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.h"); var generatedMarshallFile = File.ReadAllText( - $"{stubPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration_mshl.cpp"); + $"{_stubsPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration_mshl.cpp"); var generatedImplementationFile = File.ReadAllText( - $"{stubPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.cpp"); + $"{_stubsPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.cpp"); Assert.IsTrue(generatedHeaderFile.Contains(StaticMethodHeaderGeneration)); Assert.IsTrue(generatedMarshallFile.Contains(StaticMethodMarshallGeneration)); Assert.IsTrue(generatedImplementationFile.Contains(StaticMethodImplementationGeneration)); } + [TestMethod] + public void BackingFieldsAbsentTests() + { + string generatedAssemblyHeaderFile = + File.ReadAllText( + $"{_stubsPath}\\TestNFClassLibrary.h"); + + // check for property with backing field patter in the name + Assert.IsFalse(generatedAssemblyHeaderFile.Contains("k__BackingField ="), "Found a name with BackingField pattern, when it shouldn't"); + + // deep check for backing field name pattern (except for entry patter in comments) + Assert.IsFalse(Regex.IsMatch(generatedAssemblyHeaderFile, @"(?k__BackingField(?!')"), "Found a name with BackingField pattern, when it shouldn't"); + } + [TestInitialize] public void GenerateStubs() { var loadHints = new Dictionary(StringComparer.Ordinal) { - ["mscorlib"] = Path.Combine(Directory.GetParent(TestObjectHelper.GenerationNFAppFullPath).FullName, + ["mscorlib"] = Path.Combine(Directory.GetParent(TestObjectHelper.StubsGenerationNFAppFullPath).FullName, "mscorlib.dll") }; @@ -212,51 +227,92 @@ public void GenerateStubs() "THIS_NAME_DOES_NOT_EXIST_IN_THE_PROJECT" }; - var fileToParse = TestObjectHelper.GenerationNFAppFullPath; - var fileToCompile = Path.ChangeExtension(fileToParse, "pe"); + // Conpile StubsGenerationNFApp + string stubsGenerationFileToParse = TestObjectHelper.StubsGenerationNFAppFullPath; + string stubsGenerationFileToCompile = Path.ChangeExtension(stubsGenerationFileToParse, "pe"); // get path where stubs will be generated - stubPath = Path.Combine( + _stubsPath = Path.Combine( TestObjectHelper.TestExecutionLocation, "Stubs"); - var assemblyDefinition = AssemblyDefinition.ReadAssembly( - fileToParse, + AssemblyDefinition assemblyDefinition = AssemblyDefinition.ReadAssembly( + stubsGenerationFileToParse, new ReaderParameters { AssemblyResolver = new LoadHintsAssemblyResolver(loadHints) }); - var assemblyBuilder = new nanoAssemblyBuilder(assemblyDefinition, classNamesToExclude, false); + nanoAssemblyBuilder assemblyBuilder = new nanoAssemblyBuilder(assemblyDefinition, classNamesToExclude, false); - using (var stream = File.Open( - Path.ChangeExtension(fileToCompile, "tmp"), + using (FileStream stream = File.Open( + Path.ChangeExtension(stubsGenerationFileToCompile, "tmp"), FileMode.Create, FileAccess.ReadWrite)) - using (var writer = new BinaryWriter(stream)) + using (BinaryWriter writer = new BinaryWriter(stream)) { assemblyBuilder.Write(GetBinaryWriter(writer)); } // OK to delete tmp PE file - File.Delete(Path.ChangeExtension(fileToCompile, "tmp")); + File.Delete(Path.ChangeExtension(stubsGenerationFileToCompile, "tmp")); assemblyBuilder.Minimize(); - var tablesContext = assemblyBuilder.TablesContext; + nanoTablesContext tablesContext = assemblyBuilder.TablesContext; var skeletonGenerator = new nanoSkeletonGenerator( tablesContext, - stubPath, + _stubsPath, "testStubs", "StubsGenerationTestNFApp", false, false); skeletonGenerator.GenerateSkeleton(); + + // Compile the TestNFClassLibrary + string nfLibFileToParse = TestObjectHelper.TestNFClassLibFullPath; + string nfLibFileToCompile = Path.ChangeExtension(nfLibFileToParse, "pe"); + + assemblyDefinition = AssemblyDefinition.ReadAssembly( + nfLibFileToParse, + new ReaderParameters { AssemblyResolver = new LoadHintsAssemblyResolver(loadHints) }); + + assemblyBuilder = new nanoAssemblyBuilder( + assemblyDefinition, + new List(), + false); + + using (FileStream stream = File.Open( + Path.ChangeExtension(nfLibFileToCompile, "tmp"), + FileMode.Create, + FileAccess.ReadWrite)) + + using (BinaryWriter writer = new BinaryWriter(stream)) + { + assemblyBuilder.Write(GetBinaryWriter(writer)); + } + + // OK to delete tmp PE file + File.Delete(Path.ChangeExtension(nfLibFileToCompile, "tmp")); + + assemblyBuilder.Minimize(); + + tablesContext = assemblyBuilder.TablesContext; + + skeletonGenerator = new nanoSkeletonGenerator( + tablesContext, + _stubsPath, + "testStubs", + "TestNFClassLibrary", + true, + true); + + skeletonGenerator.GenerateSkeleton(); } [TestCleanup] public void DeleteStubs() { - Directory.Delete(stubPath, true); + Directory.Delete(_stubsPath, true); } private nanoBinaryWriter GetBinaryWriter( @@ -265,4 +321,4 @@ private nanoBinaryWriter GetBinaryWriter( return nanoBinaryWriter.CreateLittleEndianBinaryWriter(writer); } } -} \ No newline at end of file +} diff --git a/MetadataProcessor.Tests/Core/Utility/DumperTests.cs b/MetadataProcessor.Tests/Core/Utility/DumperTests.cs index bd1c623b..03c9120a 100644 --- a/MetadataProcessor.Tests/Core/Utility/DumperTests.cs +++ b/MetadataProcessor.Tests/Core/Utility/DumperTests.cs @@ -43,9 +43,9 @@ public void DumpAssemblyTest() Assert.IsTrue(dumpFileContent.Contains("TypeRefProps [01000001]: Scope: 23000001 'System.Diagnostics.DebuggableAttribute'"), "Wrong entry for System.Diagnostics.DebuggableAttribute in type ref"); Assert.IsTrue(dumpFileContent.Contains(": Scope: 23000002 'TestNFClassLibrary.ClassOnAnotherAssembly'"), "Wrong entry for TestNFClassLibrary.ClassOnAnotherAssembly in type ref"); - Assert.IsTrue(dumpFileContent.Contains(": Flags: 00001001 Extends: 0100000f Enclosed: 02000000 'TestNFApp.DummyCustomAttribute1'"), "Wrong entry for TestNFApp.DummyCustomAttribute1 in type ref"); + Assert.IsTrue(dumpFileContent.Contains(": Flags: 00001001 Extends: 01000010 Enclosed: 02000000 'TestNFApp.DummyCustomAttribute1'"), "Wrong entry for TestNFApp.DummyCustomAttribute1 in type ref"); - Assert.IsTrue(dumpFileContent.Contains(": Flags: 00001001 Extends: 0100000f Enclosed: 02000000 'TestNFApp.DummyCustomAttribute2'"), "Wrong entry for TestNFApp.DummyCustomAttribute2 in type ref"); + Assert.IsTrue(dumpFileContent.Contains(": Flags: 00001001 Extends: 01000010 Enclosed: 02000000 'TestNFApp.DummyCustomAttribute2'"), "Wrong entry for TestNFApp.DummyCustomAttribute2 in type ref"); Assert.IsTrue(dumpFileContent.Contains(": Flags: 00001061 Extends: 01000000 Enclosed: 02000000 'TestNFApp.IOneClassOverAll'"), "Wrong entry for TestNFApp.IOneClassOverAll in type ref"); Assert.IsTrue(dumpFileContent.Contains(": Flags: 000007c6 Impl: 00000000 RVA: 00000000 'get_DummyProperty' [I4( )]"), "Wrong entry for get_DummyProperty in type ref"); diff --git a/MetadataProcessor.Tests/MetadataProcessor.Tests.csproj b/MetadataProcessor.Tests/MetadataProcessor.Tests.csproj index b713510a..5320fe42 100644 --- a/MetadataProcessor.Tests/MetadataProcessor.Tests.csproj +++ b/MetadataProcessor.Tests/MetadataProcessor.Tests.csproj @@ -129,9 +129,11 @@ "$(MSBuildBinPath)\msbuild" "$(ProjectDir)TestNFApp\TestNFApp.nfproj" -t:Build -nr:False -p:Configuration=$(Configuration) -p:NF_MDP_MSBUILDTASK_PATH="$(ProjectDir)..\MetadataProcessor.MsBuildTask\bin\$(Configuration)\net472" "$(MSBuildBinPath)\msbuild" "$(ProjectDir)StubsGenerationTestNFApp\StubsGenerationTestNFApp.nfproj" -t:Build -nr:False -p:Configuration=$(Configuration) -p:NF_MDP_MSBUILDTASK_PATH="$(ProjectDir)..\MetadataProcessor.MsBuildTask\bin\$(Configuration)\net472" mkdir "$(TargetDir)\TestNFApp" + mkdir "$(TargetDir)\TestNFClassLibrary" mkdir "$(TargetDir)\StubsGenerationTestNFApp" copy /y "$(ProjectDir)TestNFApp\$(OutDir)\*" "$(TargetDir)\TestNFApp" + copy /y "$(ProjectDir)TestNFApp\$(OutDir)\TestNFClassLibrary.*" "$(TargetDir)\TestNFClassLibrary" copy /y "$(ProjectDir)StubsGenerationTestNFApp\$(OutDir)\*" "$(TargetDir)\StubsGenerationTestNFApp" - \ No newline at end of file + diff --git a/MetadataProcessor.Tests/TestNFApp/Properties/AssemblyInfo.cs b/MetadataProcessor.Tests/TestNFApp/Properties/AssemblyInfo.cs index 262254df..0a617ec9 100644 --- a/MetadataProcessor.Tests/TestNFApp/Properties/AssemblyInfo.cs +++ b/MetadataProcessor.Tests/TestNFApp/Properties/AssemblyInfo.cs @@ -31,3 +31,9 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] + +///////////////////////////////////////////////////////////////// +// This attribute is mandatory when building Interop libraries // +// update this whenever the native assembly signature changes // +[assembly: AssemblyNativeVersion("0.0.0.0")] +///////////////////////////////////////////////////////////////// diff --git a/MetadataProcessor.Tests/TestNFClassLibrary/TestNFClassLibrary/ClassWithNativeImplementation.cs b/MetadataProcessor.Tests/TestNFClassLibrary/TestNFClassLibrary/ClassWithNativeImplementation.cs new file mode 100644 index 00000000..5a0311de --- /dev/null +++ b/MetadataProcessor.Tests/TestNFClassLibrary/TestNFClassLibrary/ClassWithNativeImplementation.cs @@ -0,0 +1,35 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +using System.Runtime.CompilerServices; + +namespace TestNFClassLibrary +{ + public class ClassWithNativeImplementation + { + private static int _staticField; + private int _field; + + public static int StaticProperty1 { get; set; } + + public int Property1 { get; set; } + + public void ManagedMethod1() + { + NativeMethod1(); + } + + public void ManagedMethod2() + { + NativeMethod2(); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern void NativeMethod1(); + + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern void NativeMethod2(); + } +} diff --git a/MetadataProcessor.Tests/TestNFClassLibrary/TestNFClassLibrary/TestNFClassLibrary.nfproj b/MetadataProcessor.Tests/TestNFClassLibrary/TestNFClassLibrary/TestNFClassLibrary.nfproj index 6cc26bf4..7ddd2bd5 100644 --- a/MetadataProcessor.Tests/TestNFClassLibrary/TestNFClassLibrary/TestNFClassLibrary.nfproj +++ b/MetadataProcessor.Tests/TestNFClassLibrary/TestNFClassLibrary/TestNFClassLibrary.nfproj @@ -20,6 +20,7 @@ + @@ -39,4 +40,4 @@ - + \ No newline at end of file diff --git a/MetadataProcessor.Tests/TestObjectHelper.cs b/MetadataProcessor.Tests/TestObjectHelper.cs index 3e351664..e2301fd6 100644 --- a/MetadataProcessor.Tests/TestObjectHelper.cs +++ b/MetadataProcessor.Tests/TestObjectHelper.cs @@ -173,7 +173,7 @@ public static string NanoClrLocation } } - public static string GenerationNFAppFullPath + public static string StubsGenerationNFAppFullPath { get {