Skip to content

Commit abc813f

Browse files
authored
Support UANodeset export / import under AOT (#3667)
1 parent f6eb72e commit abc813f

13 files changed

Lines changed: 5290 additions & 92 deletions

File tree

Libraries/Opc.Ua.Client/CoreClientUtils.NodeSetExport.cs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929

3030
using System;
3131
using System.Collections.Generic;
32-
using System.Diagnostics.CodeAnalysis;
3332
using System.IO;
3433

3534
namespace Opc.Ua.Client
@@ -97,10 +96,6 @@ public static partial class CoreClientUtils
9796
/// <param name="version">The version to set in the NodeSet2 XML.</param>
9897
/// <param name="lastModified">The last modified date to set in the NodeSet2 XML.</param>
9998
/// <exception cref="ArgumentNullException"><paramref name="nodes"/> is <c>null</c>.</exception>
100-
[RequiresUnreferencedCode(
101-
"Uses XmlSerializer which requires unreferenced code.")]
102-
[RequiresDynamicCode(
103-
"Uses XmlSerializer which requires unreferenced code.")]
10499
public static void ExportNodesToNodeSet2(
105100
ISystemContext context,
106101
IList<INode> nodes,
@@ -127,10 +122,6 @@ public static void ExportNodesToNodeSet2(
127122
/// <param name="version">The version to set in the NodeSet2 XML.</param>
128123
/// <param name="lastModified">The last modified date to set in the NodeSet2 XML.</param>
129124
/// <exception cref="ArgumentNullException"><paramref name="nodes"/> is <c>null</c>.</exception>
130-
[RequiresUnreferencedCode(
131-
"Uses XmlSerializer which requires unreferenced code.")]
132-
[RequiresDynamicCode(
133-
"Uses XmlSerializer which requires unreferenced code.")]
134125
public static void ExportNodesToNodeSet2(
135126
ISystemContext context,
136127
IList<INode> nodes,

Stack/Opc.Ua.Core/Types/Utils/Utils.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,23 +1069,24 @@ public static string ToHexString(byte[] buffer, bool invertEndian = false)
10691069
/// <summary>
10701070
/// Converts a hexadecimal string to an array of bytes.
10711071
/// </summary>
1072+
/// <exception cref="ArgumentNullException">
1073+
/// <paramref name="buffer"/> is <c>null</c>.</exception>
10721074
public static byte[] FromHexString(string buffer)
10731075
{
1074-
#if NET6_0_OR_GREATER
1075-
return Convert.FromHexString(buffer);
1076-
#else
10771076
if (buffer == null)
10781077
{
10791078
return null;
10801079
}
1081-
1080+
#if NET6_0_OR_GREATER
1081+
return Convert.FromHexString(buffer);
1082+
#else
10821083
if (buffer.Length == 0)
10831084
{
10841085
return [];
10851086
}
10861087

1087-
string text = buffer.ToUpperInvariant();
10881088
const string digits = "0123456789ABCDEF";
1089+
buffer = buffer.ToUpperInvariant();
10891090

10901091
byte[] bytes = new byte[(buffer.Length / 2) + (buffer.Length % 2)];
10911092

Stack/Opc.Ua.Types/Schema/UANodeSet.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ public string ModelVersion {
257257

258258
/// <remarks/>
259259
[System.Xml.Serialization.XmlAttributeAttribute()]
260-
[System.ComponentModel.DefaultValueAttribute(typeof(ushort), "0")]
260+
[System.ComponentModel.DefaultValueAttribute((ushort)0)]
261261
public ushort AccessRestrictions {
262262
get {
263263
return this.accessRestrictionsField;
@@ -286,7 +286,7 @@ public RolePermission() {
286286

287287
/// <remarks/>
288288
[System.Xml.Serialization.XmlAttributeAttribute()]
289-
[System.ComponentModel.DefaultValueAttribute(typeof(uint), "0")]
289+
[System.ComponentModel.DefaultValueAttribute((uint)0)]
290290
public uint Permissions {
291291
get {
292292
return this.permissionsField;
@@ -326,7 +326,7 @@ public NodeSetStatus() {
326326

327327
/// <remarks/>
328328
[System.Xml.Serialization.XmlAttributeAttribute()]
329-
[System.ComponentModel.DefaultValueAttribute(typeof(uint), "0")]
329+
[System.ComponentModel.DefaultValueAttribute((uint)0)]
330330
public uint Code {
331331
get {
332332
return this.codeField;
@@ -576,7 +576,7 @@ public string ArrayDimensions {
576576

577577
/// <remarks/>
578578
[System.Xml.Serialization.XmlAttributeAttribute()]
579-
[System.ComponentModel.DefaultValueAttribute(typeof(uint), "0")]
579+
[System.ComponentModel.DefaultValueAttribute((uint)0)]
580580
public uint MaxStringLength {
581581
get {
582582
return this.maxStringLengthField;
@@ -1061,7 +1061,7 @@ public string BrowseName {
10611061

10621062
/// <remarks/>
10631063
[System.Xml.Serialization.XmlAttributeAttribute()]
1064-
[System.ComponentModel.DefaultValueAttribute(typeof(uint), "0")]
1064+
[System.ComponentModel.DefaultValueAttribute((uint)0)]
10651065
public uint WriteMask {
10661066
get {
10671067
return this.writeMaskField;
@@ -1073,7 +1073,7 @@ public uint WriteMask {
10731073

10741074
/// <remarks/>
10751075
[System.Xml.Serialization.XmlAttributeAttribute()]
1076-
[System.ComponentModel.DefaultValueAttribute(typeof(uint), "0")]
1076+
[System.ComponentModel.DefaultValueAttribute((uint)0)]
10771077
public uint UserWriteMask {
10781078
get {
10791079
return this.userWriteMaskField;
@@ -1436,7 +1436,7 @@ public bool ContainsNoLoops {
14361436

14371437
/// <remarks/>
14381438
[System.Xml.Serialization.XmlAttributeAttribute()]
1439-
[System.ComponentModel.DefaultValueAttribute(typeof(byte), "0")]
1439+
[System.ComponentModel.DefaultValueAttribute((byte)0)]
14401440
public byte EventNotifier {
14411441
get {
14421442
return this.eventNotifierField;
@@ -1610,7 +1610,7 @@ public string ArrayDimensions {
16101610

16111611
/// <remarks/>
16121612
[System.Xml.Serialization.XmlAttributeAttribute()]
1613-
[System.ComponentModel.DefaultValueAttribute(typeof(uint), "1")]
1613+
[System.ComponentModel.DefaultValueAttribute((uint)1)]
16141614
public uint AccessLevel {
16151615
get {
16161616
return this.accessLevelField;
@@ -1622,7 +1622,7 @@ public uint AccessLevel {
16221622

16231623
/// <remarks/>
16241624
[System.Xml.Serialization.XmlAttributeAttribute()]
1625-
[System.ComponentModel.DefaultValueAttribute(typeof(uint), "1")]
1625+
[System.ComponentModel.DefaultValueAttribute((uint)1)]
16261626
public uint UserAccessLevel {
16271627
get {
16281628
return this.userAccessLevelField;
@@ -1673,7 +1673,7 @@ public UAObject() {
16731673

16741674
/// <remarks/>
16751675
[System.Xml.Serialization.XmlAttributeAttribute()]
1676-
[System.ComponentModel.DefaultValueAttribute(typeof(byte), "0")]
1676+
[System.ComponentModel.DefaultValueAttribute((byte)0)]
16771677
public byte EventNotifier {
16781678
get {
16791679
return this.eventNotifierField;

Stack/Opc.Ua.Types/Schema/UANodeSetHelpers.cs

Lines changed: 113 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,97 @@ public UANodeSet()
5454
{
5555
}
5656

57+
/// <summary>
58+
/// Gets a cached <see cref="XmlSerializer"/> instance for
59+
/// <see cref="UANodeSet"/>. The serializer is lazily created and
60+
/// reused for all subsequent calls.
61+
/// </summary>
62+
/// <remarks>
63+
/// The suppression is safe because <see cref="UANodeSet"/> and all
64+
/// reachable types in the object graph are annotated with
65+
/// <see cref="XmlRootAttribute"/>, <see cref="XmlTypeAttribute"/>,
66+
/// <see cref="XmlIncludeAttribute"/>, and element/attribute
67+
/// mapping attributes. The NativeAOT linker preserves these types
68+
/// through the static attribute references.
69+
/// </remarks>
70+
internal static XmlSerializer Serializer => s_serializer.Value;
71+
72+
[UnconditionalSuppressMessage("AOT",
73+
"IL3050:RequiresDynamicCode",
74+
Justification = "UANodeSet and all reachable types are fully " +
75+
"annotated with XML serialization attributes.")]
76+
[UnconditionalSuppressMessage("Trimming",
77+
"IL2026:RequiresUnreferencedCode",
78+
Justification = "UANodeSet and all reachable types are fully " +
79+
"annotated with XML serialization attributes.")]
80+
#if NET5_0_OR_GREATER
81+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(UANodeSet))]
82+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(ModelTableEntry))]
83+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NodeIdAlias))]
84+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(UANode))]
85+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(UAType))]
86+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(UAInstance))]
87+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(UAObject))]
88+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(UAVariable))]
89+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(UAMethod))]
90+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(UAView))]
91+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(UAObjectType))]
92+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(UAVariableType))]
93+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(UAReferenceType))]
94+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(UADataType))]
95+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(DataTypeDefinition))]
96+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(DataTypeField))]
97+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Reference))]
98+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(LocalizedText))]
99+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(RolePermission))]
100+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(UAMethodArgument))]
101+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(TranslationType))]
102+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(StructureTranslationType))]
103+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NodeSetStatus))]
104+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NodeToDelete))]
105+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(ReferenceChange))]
106+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(UANodeSetChanges))]
107+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(UANodeSetChangesStatus))]
108+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(ReleaseStatus))]
109+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(DataTypePurpose))]
110+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Xml.XmlElement))]
111+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Xml.XmlDocument))]
112+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Xml.XmlNode))]
113+
#endif
114+
private static XmlSerializer CreateSerializer()
115+
{
116+
return new XmlSerializer(typeof(UANodeSet));
117+
}
118+
119+
private static readonly Lazy<XmlSerializer> s_serializer =
120+
new Lazy<XmlSerializer>(CreateSerializer);
121+
122+
#if NET5_0_OR_GREATER
123+
/// <summary>
124+
/// Serializes a <see cref="UANodeSet"/> to an <see cref="XmlWriter"/>
125+
/// using the pre-generated serializer code. This avoids the
126+
/// reflection-based fallback which fails under NativeAOT.
127+
/// </summary>
128+
private static void SerializePreGen(XmlWriter writer, UANodeSet nodeSet)
129+
{
130+
var serWriter = new UANodeSetXmlSerializerWriter(writer);
131+
serWriter.Write26_UANodeSet(nodeSet);
132+
}
133+
134+
/// <summary>
135+
/// Exposes the protected <see cref="XmlSerializationWriter.Writer"/>
136+
/// property for direct use outside <see cref="XmlSerializer"/>.
137+
/// </summary>
138+
private sealed class UANodeSetXmlSerializerWriter
139+
: Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterUANodeSet
140+
{
141+
public UANodeSetXmlSerializerWriter(XmlWriter w)
142+
{
143+
Writer = w;
144+
}
145+
}
146+
#endif
147+
57148
/// <summary>
58149
/// Validate the nodeset against the schema.
59150
/// </summary>
@@ -120,31 +211,45 @@ void ValidationEventHandler(object sender, ValidationEventArgs e)
120211
/// </summary>
121212
/// <param name="istrm">The input stream.</param>
122213
/// <returns>The set of nodes</returns>
123-
[RequiresUnreferencedCode("Uses XmlSerializer which requires unreferenced code.")]
124-
[RequiresDynamicCode("Uses XmlSerializer which requires unreferenced code.")]
214+
[UnconditionalSuppressMessage("AOT",
215+
"IL3050:RequiresDynamicCode",
216+
Justification = "UANodeSet and all reachable types are fully " +
217+
"annotated with XML serialization attributes.")]
218+
[UnconditionalSuppressMessage("Trimming",
219+
"IL2026:RequiresUnreferencedCode",
220+
Justification = "UANodeSet and all reachable types are fully " +
221+
"annotated with XML serialization attributes.")]
125222
public static UANodeSet Read(Stream istrm)
126223
{
127224
using var reader = new StreamReader(istrm);
128225
using var xmlReader = XmlReader.Create(reader, CoreUtils.DefaultXmlReaderSettings());
129-
var serializer = new XmlSerializer(typeof(UANodeSet));
130-
return serializer.Deserialize(xmlReader) as UANodeSet;
226+
return Serializer.Deserialize(xmlReader) as UANodeSet;
131227
}
132228

133229
/// <summary>
134230
/// Write a nodeset to a stream.
135231
/// </summary>
136232
/// <param name="istrm">The input stream.</param>
137-
[RequiresUnreferencedCode("Uses XmlSerializer which requires unreferenced code.")]
138-
[RequiresDynamicCode("Uses XmlSerializer which requires unreferenced code.")]
233+
[UnconditionalSuppressMessage("AOT",
234+
"IL3050:RequiresDynamicCode",
235+
Justification = "UANodeSet and all reachable types are fully " +
236+
"annotated with XML serialization attributes.")]
237+
[UnconditionalSuppressMessage("Trimming",
238+
"IL2026:RequiresUnreferencedCode",
239+
Justification = "UANodeSet and all reachable types are fully " +
240+
"annotated with XML serialization attributes.")]
139241
public void Write(Stream istrm)
140242
{
141243
XmlWriterSettings setting = CoreUtils.DefaultXmlWriterSettings();
142244
var writer = XmlWriter.Create(istrm, setting);
143245

144246
try
145247
{
146-
var serializer = new XmlSerializer(typeof(UANodeSet));
147-
serializer.Serialize(writer, this, null);
248+
#if NET5_0_OR_GREATER
249+
SerializePreGen(writer, this);
250+
#else
251+
Serializer.Serialize(writer, this, null);
252+
#endif
148253
}
149254
finally
150255
{

0 commit comments

Comments
 (0)