├── .editorconfig
├── .gitattributes
├── .gitignore
├── LICENSE
├── PortableMetadata.Benchmarks
├── PortableMetadata.Benchmarks.csproj
└── Program.cs
├── PortableMetadata.Dnlib
├── PortableMetadata.Dnlib.csproj
├── PortableMetadataReader.cs
└── PortableMetadataWriter.cs
├── PortableMetadata.Sample.ApplyAPatch
├── PortableMetadata.Sample.ApplyAPatch.csproj
└── ServerAndClient.cs
├── PortableMetadata.Samples
├── NewtonsoftJson.cs
├── PortableMetadata.Samples.csproj
├── Program.cs
├── Sample_ApplyAPatch.cs
├── Sample_CustomOptions.cs
├── Sample_ExportOneMethod.cs
├── Sample_ImportOneMethod.cs
├── Sample_WholeAssemblyExportImport.cs
└── SystemTextJson.cs
├── PortableMetadata.sln
├── PortableMetadata
├── Attributes.cs
├── PortableCommon.cs
├── PortableField.cs
├── PortableMetadata.cs
├── PortableMetadata.csproj
├── PortableMetadataEqualityComparer.cs
├── PortableMethod.cs
└── PortableType.cs
└── README.md
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = crlf
7 | indent_size = 4
8 | indent_style = tab
9 | insert_final_newline = true
10 | tab_width = 4
11 | trim_trailing_whitespace = true
12 |
13 | # C# files
14 | [*.cs]
15 |
16 | #### .NET Coding Conventions ####
17 |
18 | # Organize usings
19 | dotnet_separate_import_directive_groups = false
20 | dotnet_sort_system_directives_first = true
21 |
22 | # this. and Me. preferences
23 | dotnet_style_qualification_for_event = false:suggestion
24 | dotnet_style_qualification_for_field = false:suggestion
25 | dotnet_style_qualification_for_method = false:suggestion
26 | dotnet_style_qualification_for_property = false:suggestion
27 |
28 | # Language keywords vs BCL types preferences
29 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
30 | dotnet_style_predefined_type_for_member_access = true:suggestion
31 |
32 | # Parentheses preferences
33 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
34 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
35 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
36 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
37 |
38 | # Modifier preferences
39 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
40 |
41 | # Expression-level preferences
42 | dotnet_style_coalesce_expression = true:suggestion
43 | dotnet_style_collection_initializer = true:suggestion
44 | dotnet_style_explicit_tuple_names = true:suggestion
45 | dotnet_style_namespace_match_folder = true:suggestion
46 | dotnet_style_null_propagation = true:suggestion
47 | dotnet_style_object_initializer = true:suggestion
48 | dotnet_style_operator_placement_when_wrapping = end_of_line
49 | dotnet_style_prefer_auto_properties = false:suggestion
50 | dotnet_style_prefer_compound_assignment = true:suggestion
51 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent
52 | dotnet_style_prefer_conditional_expression_over_return = true:silent
53 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
54 | dotnet_style_prefer_inferred_tuple_names = true:suggestion
55 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
56 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
57 | dotnet_style_prefer_simplified_interpolation = true:suggestion
58 |
59 | # Field preferences
60 | dotnet_style_readonly_field = true:suggestion
61 |
62 | # Parameter preferences
63 | dotnet_code_quality_unused_parameters = all:suggestion
64 |
65 | # Suppression preferences
66 | dotnet_remove_unnecessary_suppression_exclusions = none
67 |
68 | # New line preferences
69 | dotnet_style_allow_multiple_blank_lines_experimental = true:silent
70 | dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
71 |
72 | #### C# Coding Conventions ####
73 |
74 | # var preferences
75 | csharp_style_var_elsewhere = true:suggestion
76 | csharp_style_var_for_built_in_types = false:silent
77 | csharp_style_var_when_type_is_apparent = true:suggestion
78 |
79 | # Expression-bodied members
80 | csharp_style_expression_bodied_accessors = true:suggestion
81 | csharp_style_expression_bodied_constructors = false:suggestion
82 | csharp_style_expression_bodied_indexers = true:suggestion
83 | csharp_style_expression_bodied_lambdas = true:suggestion
84 | csharp_style_expression_bodied_local_functions = false:suggestion
85 | csharp_style_expression_bodied_methods = false:suggestion
86 | csharp_style_expression_bodied_operators = false:suggestion
87 | csharp_style_expression_bodied_properties = true:suggestion
88 |
89 | # Pattern matching preferences
90 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
91 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
92 | csharp_style_prefer_not_pattern = true:suggestion
93 | csharp_style_prefer_pattern_matching = true:silent
94 | csharp_style_prefer_switch_expression = false:suggestion
95 |
96 | # Null-checking preferences
97 | csharp_style_conditional_delegate_call = true:suggestion
98 |
99 | # Modifier preferences
100 | csharp_prefer_static_local_function = true:suggestion
101 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
102 |
103 | # Code-block preferences
104 | csharp_prefer_braces = false:silent
105 | csharp_prefer_simple_using_statement = true:suggestion
106 | csharp_style_namespace_declarations = file_scoped:suggestion
107 |
108 | # Expression-level preferences
109 | csharp_prefer_simple_default_expression = true:suggestion
110 | csharp_style_deconstructed_variable_declaration = true:suggestion
111 | csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
112 | csharp_style_inlined_variable_declaration = true:suggestion
113 | csharp_style_pattern_local_over_anonymous_function = true:suggestion
114 | csharp_style_prefer_index_operator = true:suggestion
115 | csharp_style_prefer_null_check_over_type_check = true:suggestion
116 | csharp_style_prefer_range_operator = true:suggestion
117 | csharp_style_throw_expression = true:suggestion
118 | csharp_style_unused_value_assignment_preference = unused_local_variable:silent
119 | csharp_style_unused_value_expression_statement_preference = unused_local_variable:silent
120 |
121 | # 'using' directive preferences
122 | csharp_using_directive_placement = outside_namespace:suggestion
123 |
124 | # New line preferences
125 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
126 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
127 | csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
128 |
129 | #### C# Formatting Rules ####
130 |
131 | # New line preferences
132 | csharp_new_line_before_catch = true
133 | csharp_new_line_before_else = true
134 | csharp_new_line_before_finally = true
135 | csharp_new_line_before_members_in_anonymous_types = true
136 | csharp_new_line_before_members_in_object_initializers = true
137 | csharp_new_line_before_open_brace = none
138 | csharp_new_line_between_query_expression_clauses = false
139 |
140 | # Indentation preferences
141 | csharp_indent_block_contents = true
142 | csharp_indent_braces = false
143 | csharp_indent_case_contents = true
144 | csharp_indent_case_contents_when_block = false
145 | csharp_indent_labels = one_less_than_current
146 | csharp_indent_switch_labels = false
147 |
148 | # Space preferences
149 | csharp_space_after_cast = false
150 | csharp_space_after_colon_in_inheritance_clause = true
151 | csharp_space_after_comma = true
152 | csharp_space_after_dot = false
153 | csharp_space_after_keywords_in_control_flow_statements = true
154 | csharp_space_after_semicolon_in_for_statement = true
155 | csharp_space_around_binary_operators = before_and_after
156 | csharp_space_around_declaration_statements = false
157 | csharp_space_before_colon_in_inheritance_clause = true
158 | csharp_space_before_comma = false
159 | csharp_space_before_dot = false
160 | csharp_space_before_open_square_brackets = false
161 | csharp_space_before_semicolon_in_for_statement = false
162 | csharp_space_between_empty_square_brackets = false
163 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
164 | csharp_space_between_method_call_name_and_opening_parenthesis = false
165 | csharp_space_between_method_call_parameter_list_parentheses = false
166 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
167 | csharp_space_between_method_declaration_name_and_open_parenthesis = false
168 | csharp_space_between_method_declaration_parameter_list_parentheses = false
169 | csharp_space_between_parentheses = false
170 | csharp_space_between_square_brackets = false
171 |
172 | # Wrapping preferences
173 | csharp_preserve_single_line_blocks = true
174 | csharp_preserve_single_line_statements = true
175 |
176 | #### Naming styles ####
177 |
178 | # Naming rules
179 |
180 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
181 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
182 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
183 |
184 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
185 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types
186 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
187 |
188 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
189 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
190 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
191 |
192 | # Symbol specifications
193 |
194 | dotnet_naming_symbols.interface.applicable_kinds = interface
195 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
196 | dotnet_naming_symbols.interface.required_modifiers =
197 |
198 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
199 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
200 | dotnet_naming_symbols.types.required_modifiers =
201 |
202 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
203 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
204 | dotnet_naming_symbols.non_field_members.required_modifiers =
205 |
206 | # Naming styles
207 |
208 | dotnet_naming_style.pascal_case.required_prefix =
209 | dotnet_naming_style.pascal_case.required_suffix =
210 | dotnet_naming_style.pascal_case.word_separator =
211 | dotnet_naming_style.pascal_case.capitalization = pascal_case
212 |
213 | dotnet_naming_style.begins_with_i.required_prefix = I
214 | dotnet_naming_style.begins_with_i.required_suffix =
215 | dotnet_naming_style.begins_with_i.word_separator =
216 | dotnet_naming_style.begins_with_i.capitalization = pascal_case
217 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Oo]ut/
33 | [Ll]og/
34 | [Ll]ogs/
35 |
36 | # Visual Studio 2015/2017 cache/options directory
37 | .vs/
38 | # Uncomment if you have tasks that create the project's static files in wwwroot
39 | #wwwroot/
40 |
41 | # Visual Studio 2017 auto generated files
42 | Generated\ Files/
43 |
44 | # MSTest test Results
45 | [Tt]est[Rr]esult*/
46 | [Bb]uild[Ll]og.*
47 |
48 | # NUnit
49 | *.VisualState.xml
50 | TestResult.xml
51 | nunit-*.xml
52 |
53 | # Build Results of an ATL Project
54 | [Dd]ebugPS/
55 | [Rr]eleasePS/
56 | dlldata.c
57 |
58 | # Benchmark Results
59 | BenchmarkDotNet.Artifacts/
60 |
61 | # .NET Core
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | # ASP.NET Scaffolding
67 | ScaffoldingReadMe.txt
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_h.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *_wpftmp.csproj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio LightSwitch build output
298 | **/*.HTMLClient/GeneratedArtifacts
299 | **/*.DesktopClient/GeneratedArtifacts
300 | **/*.DesktopClient/ModelManifest.xml
301 | **/*.Server/GeneratedArtifacts
302 | **/*.Server/ModelManifest.xml
303 | _Pvt_Extensions
304 |
305 | # Paket dependency manager
306 | .paket/paket.exe
307 | paket-files/
308 |
309 | # FAKE - F# Make
310 | .fake/
311 |
312 | # CodeRush personal settings
313 | .cr/personal
314 |
315 | # Python Tools for Visual Studio (PTVS)
316 | __pycache__/
317 | *.pyc
318 |
319 | # Cake - Uncomment if you are using it
320 | # tools/**
321 | # !tools/packages.config
322 |
323 | # Tabs Studio
324 | *.tss
325 |
326 | # Telerik's JustMock configuration file
327 | *.jmconfig
328 |
329 | # BizTalk build output
330 | *.btp.cs
331 | *.btm.cs
332 | *.odx.cs
333 | *.xsd.cs
334 |
335 | # OpenCover UI analysis results
336 | OpenCover/
337 |
338 | # Azure Stream Analytics local run output
339 | ASALocalRun/
340 |
341 | # MSBuild Binary and Structured Log
342 | *.binlog
343 |
344 | # NVidia Nsight GPU debugger configuration file
345 | *.nvuser
346 |
347 | # MFractors (Xamarin productivity tool) working folder
348 | .mfractor/
349 |
350 | # Local History for Visual Studio
351 | .localhistory/
352 |
353 | # BeatPulse healthcheck temp database
354 | healthchecksdb
355 |
356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
357 | MigrationBackup/
358 |
359 | # Ionide (cross platform F# VS Code tools) working folder
360 | .ionide/
361 |
362 | # Fody - auto-generated XML schema
363 | FodyWeavers.xsd
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 wwh1004
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/PortableMetadata.Benchmarks/PortableMetadata.Benchmarks.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/PortableMetadata.Benchmarks/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Runtime.CompilerServices;
3 | using System.Text.Encodings.Web;
4 | using System.Text.Json.Serialization;
5 | using System.Text.Json.Serialization.Metadata;
6 | using BenchmarkDotNet.Attributes;
7 | using BenchmarkDotNet.Running;
8 | using dnlib.DotNet;
9 | using MetadataSerialization;
10 | using MetadataSerialization.Dnlib;
11 | using Newtonsoft.Json;
12 |
13 | [MemoryDiagnoser]
14 | [GcServer(true), GcForce]
15 | public partial class Program {
16 | [JsonSourceGenerationOptions(DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, WriteIndented = true)]
17 | [JsonSerializable(typeof(PortableMetadataFacade))]
18 | [JsonSerializable(typeof(bool)), JsonSerializable(typeof(char)), JsonSerializable(typeof(sbyte)), JsonSerializable(typeof(byte)), JsonSerializable(typeof(short)),
19 | JsonSerializable(typeof(ushort)), JsonSerializable(typeof(uint)), JsonSerializable(typeof(ulong)), JsonSerializable(typeof(float)), JsonSerializable(typeof(double))]
20 | partial class JsonSourceGenerationContext : JsonSerializerContext { }
21 |
22 | readonly ModuleDef module;
23 | readonly PortableMetadataOptions options;
24 | readonly PortableMetadata metadata;
25 |
26 | public Program() {
27 | module = ModuleDefMD.Load(File.ReadAllBytes("dnlib.dll"));
28 | options = /*PortableMetadataOptions.UseNamedToken | */PortableMetadataOptions.UseAssemblyFullName | PortableMetadataOptions.IncludeMethodBodies | PortableMetadataOptions.IncludeCustomAttributes;
29 | var reader = new PortableMetadataReader(module, options);
30 | metadata = reader.Metadata;
31 | foreach (var type in module.Types)
32 | reader.AddType(type, PortableMetadataLevel.DefinitionWithChildren);
33 | }
34 |
35 | static void Main() {
36 | BenchmarkRunner.Run();
37 | }
38 |
39 | [Benchmark]
40 | public void PortableMetadataReader() {
41 | var reader = new PortableMetadataReader(module, options);
42 | foreach (var type in module.Types)
43 | reader.AddType(type, PortableMetadataLevel.DefinitionWithChildren);
44 | }
45 |
46 | [Benchmark]
47 | public void PortableMetadataWriter() {
48 | var module = new ModuleDefUser();
49 | var writer = new PortableMetadataWriter(module, metadata);
50 | foreach (var type in metadata.Types.Values) {
51 | if (type is PortableTypeDef && type.Assembly is null)
52 | writer.AddType(type, PortableMetadataLevel.DefinitionWithChildren);
53 | }
54 | }
55 |
56 | [Benchmark]
57 | public void NewtonsoftJson() {
58 | var settings = new JsonSerializerSettings {
59 | NullValueHandling = NullValueHandling.Ignore
60 | };
61 | var json = JsonConvert.SerializeObject(new PortableMetadataFacade(metadata), settings);
62 | LogJson(json);
63 | var obj = JsonConvert.DeserializeObject(json, settings)!.ToMetadata();
64 | Compare(obj, metadata);
65 | }
66 |
67 | [Benchmark]
68 | public void NewtonsoftJsonWithConverts() {
69 | var settings = new JsonSerializerSettings {
70 | Converters = [new NJPortableTokenConverter(), new NJPortableComplexTypeConverter()],
71 | NullValueHandling = NullValueHandling.Ignore,
72 | ContractResolver = new NJPortableMetadataObjectPropertyRemover()
73 | };
74 | var json = JsonConvert.SerializeObject(new PortableMetadataFacade(metadata), settings);
75 | LogJson(json);
76 | var obj = JsonConvert.DeserializeObject(json, settings)!.ToMetadata();
77 | Compare(obj, metadata);
78 | }
79 |
80 | [Benchmark]
81 | public void SystemTextJson() {
82 | var options = new System.Text.Json.JsonSerializerOptions {
83 | Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
84 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
85 | NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals // Without converters, serializing NaN will throw an exception.
86 | };
87 | var json = System.Text.Json.JsonSerializer.Serialize(new PortableMetadataFacade(metadata), options);
88 | LogJson(json);
89 | var obj = System.Text.Json.JsonSerializer.Deserialize(json, options)!.ToMetadata();
90 | Compare(obj, metadata);
91 | }
92 |
93 | [Benchmark]
94 | public void SystemTextJsonWithConverts() {
95 | var options = new System.Text.Json.JsonSerializerOptions {
96 | Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
97 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
98 | TypeInfoResolver = new DefaultJsonTypeInfoResolver().WithAddedModifier(STJPortableMetadataObjectPropertyRemover.RemoveObjectProperties)
99 | };
100 | options.Converters.Add(new STJPortableTokenConverter());
101 | options.Converters.Add(new STJPortableComplexTypeConverter());
102 | var json = System.Text.Json.JsonSerializer.Serialize(new PortableMetadataFacade(metadata), options);
103 | LogJson(json);
104 | var obj = System.Text.Json.JsonSerializer.Deserialize(json, options)!.ToMetadata();
105 | Compare(obj, metadata);
106 | }
107 |
108 | [Benchmark]
109 | public void SystemTextJsonSourceGenerator() {
110 | var options = new System.Text.Json.JsonSerializerOptions {
111 | Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
112 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
113 | NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals, // Without converters, serializing NaN will throw an exception.
114 | TypeInfoResolver = new JsonSourceGenerationContext()
115 | };
116 | var json = System.Text.Json.JsonSerializer.Serialize(new PortableMetadataFacade(metadata), options);
117 | LogJson(json);
118 | var obj = System.Text.Json.JsonSerializer.Deserialize(json, options)!.ToMetadata();
119 | Compare(obj, metadata);
120 | }
121 |
122 | [Benchmark]
123 | public void SystemTextJsonSourceGeneratorWithConverts() {
124 | var options = new System.Text.Json.JsonSerializerOptions {
125 | Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
126 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
127 | TypeInfoResolver = new JsonSourceGenerationContext().WithAddedModifier(STJPortableMetadataObjectPropertyRemover.RemoveObjectProperties)
128 | };
129 | options.Converters.Add(new STJPortableTokenConverter());
130 | options.Converters.Add(new STJPortableComplexTypeConverter());
131 | var json = System.Text.Json.JsonSerializer.Serialize(new PortableMetadataFacade(metadata), options);
132 | LogJson(json);
133 | var obj = System.Text.Json.JsonSerializer.Deserialize(json, options)!.ToMetadata();
134 | Compare(obj, metadata);
135 | }
136 |
137 | static void LogJson(string json, [CallerMemberName] string name = "") {
138 | #if DEBUG
139 | Console.WriteLine(name + ": " + json.Length);
140 | File.WriteAllText(name + ".json", json);
141 | #endif
142 | }
143 |
144 | static void Compare(PortableMetadata x, PortableMetadata y) {
145 | Debug.Assert(x.Options == y.Options);
146 | Debug.Assert(x.Types.Count == y.Types.Count);
147 | var xt = x.Types.ToArray();
148 | var yt = y.Types.ToArray();
149 | for (int i = 0; i < xt.Length; i++) {
150 | Debug.Assert(xt[i].Key == yt[i].Key);
151 | Debug.Assert(PortableMetadataEqualityComparer.FullComparer.Equals(xt[i].Value, yt[i].Value));
152 | }
153 | Debug.Assert(x.Fields.Count == y.Fields.Count);
154 | var xf = x.Fields.ToArray();
155 | var yf = y.Fields.ToArray();
156 | for (int i = 0; i < xf.Length; i++) {
157 | Debug.Assert(xf[i].Key == yf[i].Key);
158 | Debug.Assert(PortableMetadataEqualityComparer.FullComparer.Equals(xf[i].Value, yf[i].Value));
159 | }
160 | Debug.Assert(x.Methods.Count == y.Methods.Count);
161 | var xm = x.Methods.ToArray();
162 | var ym = y.Methods.ToArray();
163 | for (int i = 0; i < xm.Length; i++) {
164 | Debug.Assert(xm[i].Key == ym[i].Key);
165 | Debug.Assert(PortableMetadataEqualityComparer.FullComparer.Equals(xm[i].Value, ym[i].Value));
166 | }
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/PortableMetadata.Dnlib/PortableMetadata.Dnlib.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net35;net40;net45;netstandard2.0;net6.0;net8.0
4 | 12.0
5 | enable
6 | true
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/PortableMetadata.Dnlib/PortableMetadataReader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using dnlib.DotNet;
5 | using dnlib.DotNet.Emit;
6 | using dnlib.DotNet.Writer;
7 |
8 | namespace MetadataSerialization.Dnlib;
9 |
10 | ///
11 | /// The metadata reader that reads the metadata from the to the .
12 | ///
13 | public sealed class PortableMetadataReader : ICustomAttributeWriterHelper {
14 | readonly ModuleDef module;
15 | readonly PortableMetadata metadata;
16 | readonly PortableMetadataUpdater updater;
17 |
18 | ///
19 | /// Gets the associated with the .
20 | ///
21 | public ModuleDef Module => module;
22 |
23 | ///
24 | /// Gets the associated with the .
25 | ///
26 | public PortableMetadata Metadata => metadata;
27 |
28 | bool UseAssemblyFullName => (metadata.Options & PortableMetadataOptions.UseAssemblyFullName) != 0;
29 |
30 | bool IncludeMethodBodies => (metadata.Options & PortableMetadataOptions.IncludeMethodBodies) != 0;
31 |
32 | bool IncludeCustomAttributes => (metadata.Options & PortableMetadataOptions.IncludeCustomAttributes) != 0;
33 |
34 | bool KeepOldMaxStack => (metadata.Options & PortableMetadataOptions.KeepOldMaxStack) != 0;
35 |
36 | ///
37 | /// Constructor
38 | ///
39 | ///
40 | ///
41 | public PortableMetadataReader(ModuleDef module, PortableMetadataOptions options = PortableMetadata.DefaultOptions) {
42 | this.module = module;
43 | metadata = new PortableMetadata(options);
44 | updater = new PortableMetadataUpdater(metadata);
45 | }
46 |
47 | ///
48 | /// Add a type to .
49 | ///
50 | ///
51 | ///
52 | ///
53 | public PortableToken AddType(TypeDef type, PortableMetadataLevel level) {
54 | if (type is null)
55 | throw new ArgumentNullException(nameof(type));
56 | if (level < PortableMetadataLevel.Reference || level > PortableMetadataLevel.DefinitionWithChildren)
57 | throw new ArgumentOutOfRangeException(nameof(level));
58 | if (type.Module != module)
59 | throw new ArgumentException("Type is not in the same module.", nameof(type));
60 |
61 | // 1. Write the type reference
62 | var enclosingNames = type.DeclaringType is not null ? new List() : null;
63 | var enclosingType = type;
64 | while (enclosingType.DeclaringType is not null) {
65 | enclosingType = enclosingType.DeclaringType;
66 | enclosingNames!.Add(enclosingType.Name);
67 | }
68 | var typeRef = new PortableType(type.Name, enclosingType.Namespace, null, enclosingNames);
69 | var token = updater.Update(typeRef, PortableMetadataLevel.Reference, out var oldLevel);
70 | if (level <= oldLevel)
71 | return token;
72 |
73 | // 2. Write the type definition
74 | if (oldLevel < PortableMetadataLevel.Definition) {
75 | var baseType = type.BaseType is ITypeDefOrRef bt ? AddType(bt) : default(PortableComplexType?);
76 | var interfaces = AddInterfaces(type.Interfaces);
77 | var classLayout = AddClassLayout(type.ClassLayout);
78 | var genericParameters = AddGenericParameters(type.GenericParameters);
79 | var customAttributes = AddCustomAttributes(type.CustomAttributes);
80 | var typeDef = new PortableTypeDef(type.Name, enclosingType.Namespace, null, enclosingNames,
81 | (int)type.Attributes, baseType, interfaces, classLayout, genericParameters, customAttributes);
82 | var t = updater.Update(typeDef, PortableMetadataLevel.Definition, out _);
83 | Debug.Assert(t == token);
84 | }
85 | if (level <= PortableMetadataLevel.Definition)
86 | return token;
87 |
88 | // 3. Write the children
89 | if (oldLevel < PortableMetadataLevel.DefinitionWithChildren) {
90 | var typeDef = (PortableTypeDef)metadata.Types[token];
91 | typeDef.NestedTypes = AddTypes(type.NestedTypes, level);
92 | typeDef.Fields = AddFields(type.Fields, PortableMetadataLevel.Definition);
93 | typeDef.Methods = AddMethods(type.Methods, PortableMetadataLevel.Definition);
94 | typeDef.Properties = AddProperties(type.Properties);
95 | typeDef.Events = AddEvents(type.Events);
96 | var t = updater.Update(typeDef, PortableMetadataLevel.DefinitionWithChildren, out _);
97 | Debug.Assert(t == token);
98 | }
99 | if (level <= PortableMetadataLevel.DefinitionWithChildren)
100 | return token;
101 |
102 | throw new InvalidOperationException();
103 | }
104 |
105 | ///
106 | /// Add a type to .
107 | ///
108 | ///
109 | ///
110 | ///
111 | public PortableComplexType AddType(TypeSpec type) {
112 | if (type is null)
113 | throw new ArgumentNullException(nameof(type));
114 |
115 | return AddTypeSig(type.TypeSig);
116 | }
117 |
118 | ///
119 | /// Add a field to .
120 | ///
121 | ///
122 | ///
123 | ///
124 | public PortableToken AddField(FieldDef field, PortableMetadataLevel level) {
125 | if (field is null)
126 | throw new ArgumentNullException(nameof(field));
127 | if (level < PortableMetadataLevel.Reference || level > PortableMetadataLevel.Definition)
128 | throw new ArgumentOutOfRangeException(nameof(level));
129 | if (field.Module != module)
130 | throw new ArgumentException("Field is not in the same module.", nameof(field));
131 |
132 | // 1. Write the field reference
133 | var type = AddType(field.DeclaringType);
134 | var signature = AddCallingConventionSig(field.Signature);
135 | var fieldRef = new PortableField(field.Name, type, signature);
136 | var token = updater.Update(fieldRef, PortableMetadataLevel.Reference, out var oldLevel);
137 | if (level <= oldLevel)
138 | return token;
139 |
140 | // 2. Write the field definition
141 | if (oldLevel < PortableMetadataLevel.Definition) {
142 | var constant = AddConstant(field.Constant);
143 | var customAttributes = AddCustomAttributes(field.CustomAttributes);
144 | var fieldDef = new PortableFieldDef(field.Name, type, signature, (ushort)field.Attributes, field.InitialValue,
145 | constant, customAttributes);
146 | var t = updater.Update(fieldDef, PortableMetadataLevel.Definition, out _);
147 | Debug.Assert(t == token);
148 | }
149 | if (level <= PortableMetadataLevel.Definition)
150 | return token;
151 |
152 | throw new InvalidOperationException();
153 | }
154 |
155 | ///
156 | /// Add a method to .
157 | ///
158 | ///
159 | ///
160 | ///
161 | public PortableToken AddMethod(MethodDef method, PortableMetadataLevel level) {
162 | if (method is null)
163 | throw new ArgumentNullException(nameof(method));
164 | if (level < PortableMetadataLevel.Reference || level > PortableMetadataLevel.Definition)
165 | throw new ArgumentOutOfRangeException(nameof(level));
166 | if (method.Module != module)
167 | throw new ArgumentException("Method is not in the same module.", nameof(method));
168 |
169 | // 1. Write the method reference
170 | var type = AddType(method.DeclaringType);
171 | var signature = AddCallingConventionSig(method.Signature);
172 | var methodRef = new PortableMethod(method.Name, type, signature);
173 | var token = updater.Update(methodRef, PortableMetadataLevel.Reference, out var oldLevel);
174 | if (level <= oldLevel)
175 | return token;
176 |
177 | // 2. Write the method definition
178 | if (oldLevel < PortableMetadataLevel.Definition) {
179 | var parameters = AddParameters(method.ParamDefs);
180 | var body = IncludeMethodBodies ? AddMethodBody(method.Body) : null;
181 | var overrides = AddMethodOverrides(method.Overrides);
182 | var implMap = AddImplMap(method.ImplMap);
183 | var genericParameters = AddGenericParameters(method.GenericParameters);
184 | var customAttributes = AddCustomAttributes(method.CustomAttributes);
185 | var methodDef = new PortableMethodDef(method.Name, type, signature, (ushort)method.Attributes, (ushort)method.ImplAttributes, parameters, body,
186 | overrides, implMap, genericParameters, customAttributes);
187 | var t = updater.Update(methodDef, PortableMetadataLevel.Definition, out _);
188 | Debug.Assert(t == token);
189 | }
190 | if (level <= PortableMetadataLevel.Definition)
191 | return token;
192 |
193 | throw new InvalidOperationException();
194 | }
195 |
196 | #region Wrappers
197 | PortableToken AddType(TypeRef type) {
198 | if (type is null)
199 | throw new ArgumentNullException(nameof(type));
200 | if (type.ResolutionScope is ModuleRef)
201 | throw new NotSupportedException("Doesn't support the reference to the types of another module.");
202 |
203 | var assembly = UseAssemblyFullName ? type.DefinitionAssembly.FullName : (string)type.DefinitionAssembly.Name;
204 | var enclosingNames = type.DeclaringType is not null ? new List() : null;
205 | var enclosingType = type;
206 | while (enclosingType.DeclaringType is not null) {
207 | enclosingType = enclosingType.DeclaringType;
208 | enclosingNames!.Add(enclosingType.Name);
209 | }
210 | var typeRef = new PortableType(type.Name, enclosingType.Namespace, assembly, enclosingNames);
211 | return updater.Update(typeRef, PortableMetadataLevel.Reference, out _);
212 | }
213 |
214 | PortableComplexType AddType(ITypeDefOrRef type, bool allowTypeSpec = true) {
215 | if (type is null)
216 | throw new ArgumentNullException(nameof(type));
217 |
218 | if (type is TypeDef td)
219 | return PortableComplexType.CreateToken(AddType(td, PortableMetadataLevel.Reference));
220 | else if (type is TypeRef tr)
221 | return PortableComplexType.CreateToken(AddType(tr));
222 | else if (allowTypeSpec && type is TypeSpec ts)
223 | return AddType(ts);
224 | else
225 | throw new NotSupportedException();
226 | }
227 |
228 | PortableToken AddField(MemberRef field) {
229 | if (field is null)
230 | throw new ArgumentNullException(nameof(field));
231 | if (!field.IsFieldRef)
232 | throw new ArgumentException("MemberRef is not a field reference", nameof(field));
233 | if (field.Class is ModuleRef)
234 | throw new NotSupportedException("Doesn't support the reference to the members of another module's .");
235 |
236 | var type = AddType(field.DeclaringType);
237 | var signature = AddCallingConventionSig(field.Signature);
238 | var fieldRef = new PortableField(field.Name, type, signature);
239 | return updater.Update(fieldRef, PortableMetadataLevel.Reference, out _);
240 | }
241 |
242 | PortableToken AddField(IField field) {
243 | if (field is null)
244 | throw new ArgumentNullException(nameof(field));
245 |
246 | if (field is FieldDef fd)
247 | return AddField(fd, PortableMetadataLevel.Reference);
248 | else if (field is MemberRef fr)
249 | return AddField(fr);
250 | else
251 | throw new NotSupportedException();
252 | }
253 |
254 | PortableToken AddMethod(MemberRef method) {
255 | if (method is null)
256 | throw new ArgumentNullException(nameof(method));
257 | if (!method.IsMethodRef)
258 | throw new ArgumentException("MemberRef is not a method reference", nameof(method));
259 | if (method.Class is MethodDef)
260 | throw new NotSupportedException("Doesn't support the varargs method reference.");
261 | if (method.Class is ModuleRef)
262 | throw new NotSupportedException("Doesn't support the reference to the members of another module's .");
263 |
264 | var type = AddType(method.DeclaringType);
265 | var signature = AddCallingConventionSig(method.Signature);
266 | var methodRef = new PortableMethod(method.Name, type, signature);
267 | return updater.Update(methodRef, PortableMetadataLevel.Reference, out _);
268 | }
269 |
270 | PortableToken AddMethod(IMethodDefOrRef method) {
271 | if (method is null)
272 | throw new ArgumentNullException(nameof(method));
273 |
274 | if (method is MethodDef md)
275 | return AddMethod(md, PortableMetadataLevel.Reference);
276 | else if (method is MemberRef mr)
277 | return AddMethod(mr);
278 | else
279 | throw new NotSupportedException();
280 | }
281 |
282 | ///
283 | /// Add the types to .
284 | ///
285 | ///
286 | ///
287 | ///
288 | public List AddTypes(IEnumerable types, PortableMetadataLevel level) {
289 | if (types is null)
290 | throw new ArgumentNullException(nameof(types));
291 |
292 | if (Enumerable2.NewList(types, out List list))
293 | return list;
294 | foreach (var t in types) {
295 | var type = AddType(t, level);
296 | list.Add(type);
297 | }
298 | return list;
299 | }
300 |
301 | ///
302 | /// Add the fields to .
303 | ///
304 | ///
305 | ///
306 | ///
307 | public List AddFields(IEnumerable fields, PortableMetadataLevel level) {
308 | if (fields is null)
309 | throw new ArgumentNullException(nameof(fields));
310 |
311 | if (Enumerable2.NewList(fields, out List list))
312 | return list;
313 | foreach (var f in fields) {
314 | var field = AddField(f, level);
315 | list.Add(field);
316 | }
317 | return list;
318 | }
319 |
320 | ///
321 | /// Add the methods to .
322 | ///
323 | ///
324 | ///
325 | ///
326 | public List AddMethods(IEnumerable methods, PortableMetadataLevel level) {
327 | if (methods is null)
328 | throw new ArgumentNullException(nameof(methods));
329 |
330 | if (Enumerable2.NewList(methods, out List list))
331 | return list;
332 | foreach (var m in methods) {
333 | var method = AddMethod(m, level);
334 | list.Add(method);
335 | }
336 | return list;
337 | }
338 | #endregion
339 |
340 | #region Private
341 | List? AddCustomAttributes(CustomAttributeCollection customAttributes) {
342 | if (!IncludeCustomAttributes || customAttributes.Count == 0)
343 | return null;
344 | var list = new List(customAttributes.Count);
345 | foreach (var ca in customAttributes) {
346 | var ctor = AddMethod((IMethodDefOrRef)ca.Constructor);
347 | var data = CustomAttributeWriter.Write(this, ca);
348 | list.Add(new PortableCustomAttribute(ctor, data));
349 | }
350 | return list;
351 | }
352 |
353 | List? AddGenericParameters(IList genericParameters) {
354 | if (genericParameters.Count == 0)
355 | return null;
356 | var list = new List(genericParameters.Count);
357 | foreach (var gp in genericParameters) {
358 | var gpcs = gp.GenericParamConstraints;
359 | List? constraints = null;
360 | if (gpcs.Count != 0) {
361 | constraints = new List(gpcs.Count);
362 | foreach (var gpc in gpcs) {
363 | var type = AddType(gpc.Constraint);
364 | constraints!.Add(type);
365 | }
366 | }
367 | list.Add(new PortableGenericParameter(gp.Name, (ushort)gp.Flags, gp.Number, constraints));
368 | }
369 | return list;
370 | }
371 |
372 | static PortableConstant? AddConstant(Constant? constant) {
373 | if (constant is null)
374 | return null;
375 | return new PortableConstant((int)constant.Type, constant.Value);
376 | }
377 |
378 | List? AddInterfaces(IList interfaces) {
379 | if (interfaces.Count == 0)
380 | return null;
381 | var list = new List(interfaces.Count);
382 | foreach (var i in interfaces) {
383 | var type = AddType(i.Interface);
384 | list.Add(type);
385 | }
386 | return list;
387 | }
388 |
389 | static PortableClassLayout? AddClassLayout(ClassLayout? classLayout) {
390 | if (classLayout is null)
391 | return null;
392 | return new PortableClassLayout(classLayout.PackingSize, (int)classLayout.ClassSize);
393 | }
394 |
395 | List AddParameters(IList parameters) {
396 | var list = new List(parameters.Count);
397 | foreach (var p in parameters) {
398 | var constant = AddConstant(p.Constant);
399 | var customAttributes = AddCustomAttributes(p.CustomAttributes);
400 | list.Add(new PortableParameter(p.Name, p.Sequence, (ushort)p.Attributes, constant, customAttributes));
401 | }
402 | return list;
403 | }
404 |
405 | List? AddMethodOverrides(IList overrides) {
406 | if (overrides.Count == 0)
407 | return null;
408 | var list = new List(overrides.Count);
409 | foreach (var o in overrides) {
410 | var method = AddMethod(o.MethodDeclaration);
411 | list.Add(method);
412 | }
413 | return list;
414 | }
415 |
416 | static PortableImplMap? AddImplMap(ImplMap? implMap) {
417 | if (implMap is null)
418 | return null;
419 | return new PortableImplMap(implMap.Name, implMap.Module.Name, (ushort)implMap.Attributes);
420 | }
421 |
422 | List AddProperties(IList properties) {
423 | var list = new List(properties.Count);
424 | foreach (var p in properties) {
425 | var signature = AddCallingConventionSig(p.PropertySig);
426 | var getMethod = p.GetMethod is MethodDef get ? AddMethod(get) : default(PortableToken?);
427 | var setMethod = p.SetMethod is MethodDef set ? AddMethod(set) : default(PortableToken?);
428 | var customAttributes = AddCustomAttributes(p.CustomAttributes);
429 | list.Add(new PortableProperty(p.Name, signature, (int)p.Attributes, getMethod, setMethod, customAttributes));
430 | }
431 | return list;
432 | }
433 |
434 | List AddEvents(IList events) {
435 | var list = new List(events.Count);
436 | foreach (var e in events) {
437 | var type = AddType(e.EventType);
438 | var addMethod = e.AddMethod is MethodDef add ? AddMethod(add) : default(PortableToken?);
439 | var removeMethod = e.RemoveMethod is MethodDef remove ? AddMethod(remove) : default(PortableToken?);
440 | var invokeMethod = e.InvokeMethod is MethodDef invoke ? AddMethod(invoke) : default(PortableToken?);
441 | var customAttributes = AddCustomAttributes(e.CustomAttributes);
442 | list.Add(new PortableEvent(e.Name, type, (int)e.Attributes, addMethod, removeMethod, invokeMethod, customAttributes));
443 | }
444 | return list;
445 | }
446 |
447 | bool IFullNameFactoryHelper.MustUseAssemblyName(IType type) {
448 | return FullNameFactory.MustUseAssemblyName(module, type, true);
449 | }
450 |
451 | void IWriterError.Error(string message) {
452 | // TODO: implement this method.
453 | }
454 | #endregion
455 |
456 | #region Method Body
457 | PortableMethodBody? AddMethodBody(CilBody? body) {
458 | if (body is null)
459 | return null;
460 |
461 | var indexes = new Dictionary(body.Instructions.Count);
462 | int index = 0;
463 | foreach (var instr in body.Instructions)
464 | indexes.Add(instr, index++);
465 |
466 | var instructions = new List(body.Instructions.Count);
467 | foreach (var instr in body.Instructions) {
468 | object? operand;
469 | switch (instr.OpCode.OperandType) {
470 | case OperandType.InlineBrTarget:
471 | case OperandType.ShortInlineBrTarget:
472 | operand = indexes[(Instruction)instr.Operand];
473 | break;
474 |
475 | case OperandType.InlineField:
476 | case OperandType.InlineMethod:
477 | case OperandType.InlineSig:
478 | case OperandType.InlineTok:
479 | case OperandType.InlineType:
480 | operand = AddToken(instr.Operand);
481 | break;
482 |
483 | case OperandType.InlineI:
484 | case OperandType.InlineI8:
485 | case OperandType.InlineNone:
486 | case OperandType.InlinePhi:
487 | case OperandType.InlineR:
488 | case OperandType.InlineString:
489 | case OperandType.ShortInlineR:
490 | operand = instr.Operand;
491 | break;
492 |
493 | case OperandType.InlineSwitch: {
494 | var targets = (IList)instr.Operand;
495 | var newTargets = new int[targets.Count];
496 | operand = newTargets;
497 | for (int i = 0; i < targets.Count; i++)
498 | newTargets[i] = indexes[targets[i]];
499 | break;
500 | }
501 |
502 | case OperandType.InlineVar:
503 | case OperandType.ShortInlineVar:
504 | operand = ((IVariable)instr.Operand).Index;
505 | break;
506 |
507 | case OperandType.ShortInlineI:
508 | operand = instr.Operand is sbyte sb ? sb : (int)(byte)instr.Operand;
509 | break;
510 |
511 | default:
512 | throw new NotSupportedException();
513 | }
514 |
515 | instructions.Add(new PortableInstruction(instr.OpCode.Name, operand));
516 | }
517 |
518 | var exceptionHandlers = new List(body.ExceptionHandlers.Count);
519 | foreach (var eh in body.ExceptionHandlers) {
520 | int tryStart = indexes[eh.TryStart];
521 | int tryEnd = eh.TryEnd is not null ? indexes[eh.TryEnd] : -1;
522 | int filterStart = eh.FilterStart is not null ? indexes[eh.FilterStart] : -1;
523 | int handlerStart = indexes[eh.HandlerStart];
524 | int handlerEnd = eh.HandlerEnd is not null ? indexes[eh.HandlerEnd] : -1;
525 | var catchType = eh.CatchType is not null ? AddType(eh.CatchType) : default(PortableComplexType?);
526 | int handlerType = (int)eh.HandlerType;
527 | exceptionHandlers.Add(new PortableExceptionHandler(tryStart, tryEnd, filterStart, handlerStart, handlerEnd, catchType, handlerType));
528 | }
529 |
530 | var variables = new List(body.Variables.Count);
531 | foreach (var v in body.Variables)
532 | variables.Add(AddTypeSig(v.Type));
533 |
534 | int maxStack = KeepOldMaxStack ? body.MaxStack : (int)MaxStackCalculator.GetMaxStack(body.Instructions, body.ExceptionHandlers);
535 | return new PortableMethodBody(instructions, exceptionHandlers, variables, maxStack, body.InitLocals);
536 | }
537 |
538 | PortableComplexType AddToken(object o) {
539 | if (o is CallingConventionSig sig) {
540 | Debug.Assert(sig is MethodSig || sig is LocalSig);
541 | return AddCallingConventionSig(sig);
542 | }
543 |
544 | PortableComplexTypeKind kind;
545 | PortableToken? token = null;
546 | PortableComplexType? type = null;
547 | switch (o) {
548 | case TypeDef td:
549 | kind = PortableComplexTypeKind.InlineType;
550 | token = AddType(td, PortableMetadataLevel.Reference);
551 | break;
552 | case TypeRef tr:
553 | kind = PortableComplexTypeKind.InlineType;
554 | token = AddType(tr);
555 | break;
556 | case TypeSpec ts:
557 | kind = PortableComplexTypeKind.InlineType;
558 | type = AddType(ts);
559 | break;
560 |
561 | case FieldDef fd:
562 | kind = PortableComplexTypeKind.InlineField;
563 | token = AddField(fd, PortableMetadataLevel.Reference);
564 | break;
565 | case MemberRef { IsFieldRef: true } fr:
566 | kind = PortableComplexTypeKind.InlineField;
567 | token = AddField(fr);
568 | break;
569 |
570 | case MethodDef md:
571 | kind = PortableComplexTypeKind.InlineMethod;
572 | token = AddMethod(md, PortableMetadataLevel.Reference);
573 | break;
574 | case MemberRef { IsMethodRef: true } mr:
575 | kind = PortableComplexTypeKind.InlineMethod;
576 | token = AddMethod(mr);
577 | break;
578 | case MethodSpec ms:
579 | kind = PortableComplexTypeKind.InlineMethod;
580 | token = AddMethod(ms.Method);
581 | type = AddCallingConventionSig(ms.Instantiation);
582 | break;
583 |
584 | default:
585 | throw new NotSupportedException();
586 | }
587 |
588 | if (token is PortableToken token2) {
589 | if (type is PortableComplexType type2)
590 | type = PortableComplexType.CreateMethodSpec(PortableComplexType.CreateToken(token2), type2);
591 | else
592 | type = PortableComplexType.CreateToken(token2);
593 | }
594 | return PortableComplexType.CreateInlineTokenOperand(kind, type!.Value);
595 | }
596 | #endregion
597 |
598 | #region Signature
599 | PortableComplexType AddTypeSig(TypeSig typeSig) {
600 | byte elementType = (byte)typeSig.ElementType;
601 | switch ((ElementType)elementType) {
602 | case ElementType.End:
603 | case ElementType.Void:
604 | case ElementType.Boolean:
605 | case ElementType.Char:
606 | case ElementType.I1:
607 | case ElementType.U1:
608 | case ElementType.I2:
609 | case ElementType.U2:
610 | case ElementType.I4:
611 | case ElementType.U4:
612 | case ElementType.I8:
613 | case ElementType.U8:
614 | case ElementType.R4:
615 | case ElementType.R8:
616 | case ElementType.String:
617 | case ElementType.TypedByRef:
618 | case ElementType.I:
619 | case ElementType.U:
620 | case ElementType.R:
621 | case ElementType.Object:
622 | case ElementType.Sentinel:
623 | // et
624 | return PortableComplexType.CreateTypeSig(elementType, null);
625 |
626 | case ElementType.Ptr:
627 | case ElementType.ByRef:
628 | case ElementType.FnPtr:
629 | case ElementType.SZArray:
630 | case ElementType.Pinned:
631 | // et(next)
632 | return PortableComplexType.CreateTypeSig(elementType, [AddTypeSig(typeSig.Next)]);
633 |
634 | case ElementType.ValueType:
635 | case ElementType.Class:
636 | // et(next)
637 | return PortableComplexType.CreateTypeSig(elementType, [AddType(((ClassOrValueTypeSig)typeSig).TypeDefOrRef, false)]);
638 |
639 | case ElementType.Var:
640 | case ElementType.MVar:
641 | // et(index)
642 | return PortableComplexType.CreateTypeSig(elementType, [PortableComplexType.CreateInt32((int)((GenericSig)typeSig).Number)]);
643 |
644 | case ElementType.Array: {
645 | // et(next, rank, numSizes, .. sizes, numLowerBounds, .. lowerBounds)
646 | var arraySig = (ArraySig)typeSig;
647 | var sizes = arraySig.Sizes;
648 | var lowerBounds = arraySig.LowerBounds;
649 | var arguments = new List(4 + sizes.Count + lowerBounds.Count) {
650 | AddTypeSig(arraySig.Next),
651 | PortableComplexType.CreateInt32((int)arraySig.Rank),
652 | PortableComplexType.CreateInt32(sizes.Count)
653 | };
654 | foreach (uint size in sizes)
655 | arguments.Add(PortableComplexType.CreateInt32((int)size));
656 | arguments.Add(PortableComplexType.CreateInt32(lowerBounds.Count));
657 | foreach (int lowerBound in lowerBounds)
658 | arguments.Add(PortableComplexType.CreateInt32(lowerBound));
659 | return PortableComplexType.CreateTypeSig(elementType, arguments);
660 | }
661 |
662 | case ElementType.GenericInst: {
663 | // et(next, num, .. args)
664 | var instSig = (GenericInstSig)typeSig;
665 | var arguments = new List(2 + instSig.GenericArguments.Count) {
666 | AddTypeSig(instSig.GenericType),
667 | PortableComplexType.CreateInt32(instSig.GenericArguments.Count)
668 | };
669 | foreach (var arg in instSig.GenericArguments)
670 | arguments.Add(AddTypeSig(arg));
671 | return PortableComplexType.CreateTypeSig(elementType, arguments);
672 | }
673 |
674 | case ElementType.ValueArray:
675 | // et(next, size)
676 | return PortableComplexType.CreateTypeSig(elementType, [AddTypeSig(((ValueArraySig)typeSig).Next), PortableComplexType.CreateInt32((int)((ValueArraySig)typeSig).Size)]);
677 |
678 | case ElementType.CModReqd:
679 | case ElementType.CModOpt:
680 | // et(modifier, next)
681 | return PortableComplexType.CreateTypeSig(elementType, [AddType(((ModifierSig)typeSig).Modifier), AddTypeSig(((ModifierSig)typeSig).Next)]);
682 |
683 | case ElementType.Module:
684 | // et(index, next)
685 | return PortableComplexType.CreateTypeSig(elementType, [PortableComplexType.CreateInt32((int)((ModuleSig)typeSig).Index), AddTypeSig(((ModuleSig)typeSig).Next)]);
686 |
687 | default:
688 | throw new InvalidOperationException();
689 | }
690 | }
691 |
692 | PortableComplexType AddCallingConventionSig(CallingConventionSig callingConventionSig) {
693 | byte callingConvention = (byte)(callingConventionSig.GetCallingConvention() & CallingConvention.Mask);
694 | int flags = (byte)(callingConventionSig.GetCallingConvention() & ~CallingConvention.Mask);
695 | List arguments = [PortableComplexType.CreateInt32(flags)];
696 | switch ((CallingConvention)callingConvention) {
697 | case CallingConvention.Default:
698 | case CallingConvention.C:
699 | case CallingConvention.StdCall:
700 | case CallingConvention.ThisCall:
701 | case CallingConvention.FastCall:
702 | case CallingConvention.VarArg:
703 | case CallingConvention.Property:
704 | case CallingConvention.Unmanaged:
705 | case CallingConvention.NativeVarArg: {
706 | // cc([numGPs], numParams, retType, .. params)
707 | var methodSig = (MethodBaseSig)callingConventionSig;
708 | if (methodSig.Generic)
709 | arguments.Add(PortableComplexType.CreateInt32((int)methodSig.GenParamCount));
710 | arguments.Add(PortableComplexType.CreateInt32(methodSig.Params.Count));
711 | arguments.Add(AddTypeSig(methodSig.RetType));
712 | foreach (var param in methodSig.Params)
713 | arguments.Add(AddTypeSig(param));
714 | if (methodSig.ParamsAfterSentinel is not null && methodSig.ParamsAfterSentinel.Count > 0) {
715 | arguments.Add(PortableComplexType.CreateTypeSig((byte)ElementType.Sentinel, null));
716 | foreach (var param in methodSig.ParamsAfterSentinel)
717 | arguments.Add(AddTypeSig(param));
718 | }
719 | break;
720 | }
721 |
722 | case CallingConvention.Field:
723 | // cc(fieldType)
724 | arguments.Add(AddTypeSig(((FieldSig)callingConventionSig).Type));
725 | break;
726 |
727 | case CallingConvention.LocalSig: {
728 | // cc(numLocals, .. locals)
729 | var locals = ((LocalSig)callingConventionSig).Locals;
730 | arguments.Add(PortableComplexType.CreateInt32(locals.Count));
731 | foreach (var local in locals)
732 | arguments.Add(AddTypeSig(local));
733 | break;
734 | }
735 |
736 | case CallingConvention.GenericInst: {
737 | // cc(numArgs, .. args)
738 | var instMethodSig = (GenericInstMethodSig)callingConventionSig;
739 | arguments.Add(PortableComplexType.CreateInt32(instMethodSig.GenericArguments.Count));
740 | foreach (var arg in instMethodSig.GenericArguments)
741 | arguments.Add(AddTypeSig(arg));
742 | break;
743 | }
744 |
745 | default:
746 | throw new InvalidOperationException();
747 | }
748 | return PortableComplexType.CreateCallingConventionSig(callingConvention, arguments);
749 | }
750 | #endregion
751 | }
752 |
753 | static class Enumerable2 {
754 | public static bool NewList(IEnumerable a, out List b) {
755 | if (a is IList l) {
756 | if (l.Count == 0) {
757 | b = [];
758 | return true;
759 | }
760 | b = new List(l.Count);
761 | }
762 | else
763 | b = [];
764 | return false;
765 | }
766 | }
767 |
--------------------------------------------------------------------------------
/PortableMetadata.Sample.ApplyAPatch/PortableMetadata.Sample.ApplyAPatch.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0
4 | enable
5 | enable
6 |
7 |
8 |
--------------------------------------------------------------------------------
/PortableMetadata.Sample.ApplyAPatch/ServerAndClient.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Sockets;
2 | using System.Net;
3 | using System.Text;
4 |
5 | public sealed class MyServer {
6 | readonly TcpListener listener = new(IPAddress.Loopback, 0);
7 |
8 | public int Start() {
9 | listener.Start();
10 | listener.AcceptTcpClientAsync().ContinueWith(async t => {
11 | using var stream = t.Result.GetStream();
12 | while (true) {
13 | var buffer = new byte[4];
14 | await stream.ReadExactlyAsync(buffer);
15 | int length = BitConverter.ToInt32(buffer, 0);
16 | buffer = new byte[length];
17 | await stream.ReadExactlyAsync(buffer);
18 | var s = Encoding.UTF8.GetString(buffer);
19 | Console.WriteLine($"Data from client: {s}");
20 | }
21 | }, TaskContinuationOptions.OnlyOnRanToCompletion);
22 | return ((IPEndPoint)listener.LocalEndpoint).Port;
23 | }
24 | }
25 |
26 | public sealed class MyClient {
27 | public string GetUserName() {
28 | return Environment.UserName;
29 | }
30 |
31 | public void Start(int port) {
32 | var client = new TcpClient();
33 | client.ConnectAsync(IPAddress.Loopback, port).ContinueWith(async _ => {
34 | using var stream = client.GetStream();
35 | for (int i = 0; i < 5; i++) {
36 | var name = GetUserName();
37 | int length = Encoding.UTF8.GetByteCount(name);
38 | var buffer = BitConverter.GetBytes(length);
39 | await stream.WriteAsync(buffer);
40 | buffer = Encoding.UTF8.GetBytes(name);
41 | await stream.WriteAsync(buffer);
42 | await Task.Delay(1000);
43 | }
44 | }, TaskContinuationOptions.OnlyOnRanToCompletion);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/PortableMetadata.Samples/NewtonsoftJson.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using Newtonsoft.Json;
3 | using Newtonsoft.Json.Serialization;
4 | using MetadataSerialization;
5 |
6 | sealed class NJPortableTokenConverter : JsonConverter {
7 | public override PortableToken? ReadJson(JsonReader reader, Type objectType, PortableToken? existingValue, bool hasExistingValue, JsonSerializer serializer) {
8 | if (reader.Value is long index)
9 | return (int)index;
10 | else if (reader.Value is string name)
11 | return name;
12 | else
13 | return null;
14 | }
15 |
16 | public override void WriteJson(JsonWriter writer, PortableToken? value, JsonSerializer serializer) {
17 | if (value is not PortableToken token)
18 | writer.WriteNull();
19 | else if (token.Name is string name)
20 | writer.WriteValue(name);
21 | else
22 | writer.WriteValue(token.Index);
23 | }
24 | }
25 |
26 | sealed class NJPortableComplexTypeConverter : JsonConverter {
27 | public override PortableComplexType? ReadJson(JsonReader reader, Type objectType, PortableComplexType? existingValue, bool hasExistingValue, JsonSerializer serializer) {
28 | return reader.Value is string type ? PortableComplexType.Parse(type) : null;
29 | }
30 |
31 | public override void WriteJson(JsonWriter writer, PortableComplexType? value, JsonSerializer serializer) {
32 | if (value is PortableComplexType type)
33 | writer.WriteValue(type.ToString());
34 | else
35 | writer.WriteNull();
36 | }
37 | }
38 |
39 | sealed class NJPortableMetadataObjectPropertyRemover : DefaultContractResolver {
40 | protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) {
41 | var property = base.CreateProperty(member, memberSerialization);
42 | if (property.PropertyType == typeof(object))
43 | property.Ignored = true;
44 | return property;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/PortableMetadata.Samples/PortableMetadata.Samples.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
26 |
27 |
--------------------------------------------------------------------------------
/PortableMetadata.Samples/Program.cs:
--------------------------------------------------------------------------------
1 | Sample_ExportOneMethod.Run();
2 | Sample_ImportOneMethod.Run();
3 | Sample_CustomOptions.Run();
4 | Sample_ApplyAPatch.Run();
5 | Sample_WholeAssemblyExportImport.Run();
6 | Console.ReadKey(true);
7 |
--------------------------------------------------------------------------------
/PortableMetadata.Samples/Sample_ApplyAPatch.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Reflection;
3 | using System.Text.Json;
4 | using System.Text.Json.Serialization;
5 | using System.Text.Json.Serialization.Metadata;
6 | using dnlib.DotNet;
7 | using dnlib.DotNet.Emit;
8 | using MetadataSerialization;
9 | using MetadataSerialization.Dnlib;
10 |
11 | static class MyClientPatch {
12 | public record struct MyUserName(string Value);
13 |
14 | public static string MyGetUserName() {
15 | var data = ReadFile("myuser.json");
16 | MyUserName obj;
17 | try {
18 | obj = JsonSerializer.Deserialize(data);
19 | }
20 | catch {
21 | return "??";
22 | }
23 | return obj.Value;
24 | }
25 |
26 | static string ReadFile(string path) {
27 | while (true) {
28 | try {
29 | if (!File.Exists(path))
30 | goto next;
31 | return File.ReadAllText(path);
32 | }
33 | catch {
34 | goto next;
35 | }
36 | next:
37 | Thread.Sleep(100);
38 | }
39 | }
40 | }
41 |
42 | static class Sample_ApplyAPatch {
43 | public static void Run() {
44 | // 1. Get the patch info of MyClient class
45 | var patch = GetPatch();
46 | var patchMetadata = JsonSerializer.Deserialize(patch, CreateJsonSerializerOptions())!.ToMetadata();
47 |
48 | // 2. Apply the patch
49 | using var module = ModuleDefMD.Load(File.ReadAllBytes("PortableMetadata.Sample.ApplyAPatch.dll"));
50 | var writer = new PortableMetadataWriter(module, patchMetadata);
51 | var myCientPatch = writer.AddType(patchMetadata.Types.Values.First(t => t.Name == "MyClientPatch"), PortableMetadataLevel.DefinitionWithChildren);
52 | var getUserName = module.FindNormalThrow("MyClient").FindMethod("GetUserName");
53 | getUserName.FreeMethodBody();
54 | var body = getUserName.Body = new CilBody();
55 | body.Instructions.Add(OpCodes.Call.ToInstruction(myCientPatch.FindMethod("MyGetUserName")));
56 | body.Instructions.Add(OpCodes.Ret.ToInstruction());
57 | module.Name = module.Assembly.Name = Guid.NewGuid().ToString();
58 | var path = Path.GetFullPath("PortableMetadata.Sample.ApplyAPatch.Patched.dll");
59 | module.Write(path);
60 |
61 | // 3. Test the patched assembly
62 | File.Delete("myuser.json");
63 | var assembly = Assembly.LoadFrom(path);
64 | dynamic server = Activator.CreateInstance(assembly.GetType("MyServer")!)!;
65 | int port = server.Start();
66 | dynamic client = Activator.CreateInstance(assembly.GetType("MyClient")!)!;
67 | client.Start(port);
68 | var names = new List { "Alice", "Bob", "Charlie", "David", "Eve" };
69 | for (int i = 0; i < 5; i++) {
70 | WriteFile("myuser.json", JsonSerializer.Serialize(new(names[i])));
71 | Thread.Sleep(1000);
72 | }
73 | Thread.Sleep(500);
74 | }
75 |
76 | /*
77 | Output:
78 | Data from client: Alice
79 | Data from client: Bob
80 | Data from client: Charlie
81 | Data from client: David
82 | Data from client: Eve
83 | */
84 |
85 | static string GetPatch() {
86 | // 1. Load the module using dnlib
87 | using var module = ModuleDefMD.Load(typeof(MyClientPatch).Module);
88 |
89 | // 2. Create the portable metadata
90 | var reader = new PortableMetadataReader(module, PortableMetadata.DefaultOptions | PortableMetadataOptions.UseNamedToken);
91 | reader.AddType(module.FindNormalThrow("MyClientPatch"), PortableMetadataLevel.DefinitionWithChildren);
92 | var metadata = reader.Metadata;
93 |
94 | // 4. Export the patch
95 | var json = JsonSerializer.Serialize(new PortableMetadataFacade(metadata), CreateJsonSerializerOptions());
96 | var path = Path.GetFullPath("patch.json");
97 | File.WriteAllText(path, json);
98 | try {
99 | Process.Start("notepad", path)?.Dispose();
100 | }
101 | catch {
102 | }
103 | return json;
104 | }
105 |
106 | static JsonSerializerOptions CreateJsonSerializerOptions() {
107 | var options = new JsonSerializerOptions {
108 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
109 | TypeInfoResolver = new DefaultJsonTypeInfoResolver().WithAddedModifier(STJPortableMetadataObjectPropertyRemover.RemoveObjectProperties),
110 | Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
111 | WriteIndented = true
112 | };
113 | options.Converters.Add(new STJPortableTokenConverter());
114 | options.Converters.Add(new STJPortableComplexTypeConverter());
115 | return options;
116 | }
117 |
118 | static void WriteFile(string path, string content) {
119 | while (true) {
120 | try {
121 | File.WriteAllText(path, content);
122 | return;
123 | }
124 | catch {
125 | }
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/PortableMetadata.Samples/Sample_CustomOptions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using System.Text.Json;
3 | using System.Text.Json.Serialization;
4 | using System.Text.Json.Serialization.Metadata;
5 | using dnlib.DotNet;
6 | using MetadataSerialization;
7 | using MetadataSerialization.Dnlib;
8 |
9 | static class Sample_CustomOptions {
10 | [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
11 | sealed class MyAttribute : Attribute { }
12 |
13 | [My]
14 | public static void DemoMethod([CallerMemberName] string arg = "") {
15 | Console.WriteLine($"Hello World: {arg}");
16 | }
17 |
18 | public static void Run() {
19 | // 1. Load the module using dnlib
20 | using var module = ModuleDefMD.Load(typeof(Sample_CustomOptions).Module);
21 |
22 | // 2. Create the portable metadata with custom options (use named token and exclude custom attributes)
23 | var reader = new PortableMetadataReader(module, PortableMetadataOptions.UseNamedToken | PortableMetadataOptions.IncludeMethodBodies);
24 | reader.AddMethod(module.FindNormalThrow("Sample_CustomOptions").FindMethod("DemoMethod"), PortableMetadataLevel.Definition);
25 | var metadata = reader.Metadata;
26 |
27 | // 3. Export the metadata
28 | var options = new JsonSerializerOptions {
29 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
30 | TypeInfoResolver = new DefaultJsonTypeInfoResolver().WithAddedModifier(STJPortableMetadataObjectPropertyRemover.RemoveObjectProperties),
31 | Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
32 | WriteIndented = true
33 | };
34 | options.Converters.Add(new STJPortableTokenConverter());
35 | options.Converters.Add(new STJPortableComplexTypeConverter());
36 | var json = JsonSerializer.Serialize(new PortableMetadataFacade(metadata), options);
37 | Console.WriteLine(json);
38 | }
39 |
40 | /*
41 | Output:
42 | {
43 | "Options": 5,
44 | "Types": {
45 | "References": {
46 | "Sample_CustomOptions": {
47 | "Name": "Sample_CustomOptions",
48 | "Namespace": ""
49 | },
50 | "String": {
51 | "Name": "String",
52 | "Namespace": "System",
53 | "Assembly": "System.Runtime"
54 | },
55 | "Console": {
56 | "Name": "Console",
57 | "Namespace": "System",
58 | "Assembly": "System.Console"
59 | }
60 | },
61 | "Definitions": {},
62 | "Orders": []
63 | },
64 | "Fields": {
65 | "References": {},
66 | "Definitions": {},
67 | "Orders": []
68 | },
69 | "Methods": {
70 | "References": {
71 | "String::Concat": {
72 | "Name": "Concat",
73 | "Type": "'String'",
74 | "Signature": "Default(Int32(0),Int32(2),String,String,String)"
75 | },
76 | "Console::WriteLine": {
77 | "Name": "WriteLine",
78 | "Type": "'Console'",
79 | "Signature": "Default(Int32(0),Int32(1),Void,String)"
80 | }
81 | },
82 | "Definitions": {
83 | "Sample_CustomOptions::DemoMethod": {
84 | "Attributes": 150,
85 | "ImplAttributes": 0,
86 | "Parameters": [
87 | {
88 | "Name": "arg",
89 | "Sequence": 1,
90 | "Attributes": 4112,
91 | "Constant": {
92 | "Type": 14,
93 | "StringValue": ""
94 | }
95 | }
96 | ],
97 | "Body": {
98 | "Instructions": [
99 | {
100 | "OpCode": "nop"
101 | },
102 | {
103 | "OpCode": "ldstr",
104 | "StringValue": "Hello World: "
105 | },
106 | {
107 | "OpCode": "ldarg.0"
108 | },
109 | {
110 | "OpCode": "call",
111 | "TypeValue": "InlineMethod('String::Concat')"
112 | },
113 | {
114 | "OpCode": "call",
115 | "TypeValue": "InlineMethod('Console::WriteLine')"
116 | },
117 | {
118 | "OpCode": "nop"
119 | },
120 | {
121 | "OpCode": "ret"
122 | }
123 | ],
124 | "ExceptionHandlers": [],
125 | "Variables": []
126 | },
127 | "Name": "DemoMethod",
128 | "Type": "'Sample_CustomOptions'",
129 | "Signature": "Default(Int32(0),Int32(1),Void,String)"
130 | }
131 | },
132 | "Orders": [
133 | 0,
134 | 1
135 | ]
136 | }
137 | }
138 | */
139 | }
140 |
--------------------------------------------------------------------------------
/PortableMetadata.Samples/Sample_ExportOneMethod.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using System.Text.Json.Serialization;
3 | using System.Text.Json.Serialization.Metadata;
4 | using dnlib.DotNet;
5 | using MetadataSerialization;
6 | using MetadataSerialization.Dnlib;
7 |
8 | static class Sample_ExportOneMethod {
9 | public static void DemoMethod() {
10 | Console.WriteLine("Hello World!");
11 | }
12 |
13 | public static void Run() {
14 | // 1. Load the module using dnlib
15 | using var module = ModuleDefMD.Load(typeof(Sample_ExportOneMethod).Module);
16 |
17 | // 2. Create the portable metadata
18 | var reader = new PortableMetadataReader(module);
19 | reader.AddMethod(module.FindNormalThrow("Sample_ExportOneMethod").FindMethod("DemoMethod"), PortableMetadataLevel.Definition);
20 | var metadata = reader.Metadata;
21 |
22 | // 3. Export the metadata (without any converters)
23 | var json = JsonSerializer.Serialize(new PortableMetadataFacade(metadata), new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
24 | Console.WriteLine(json);
25 |
26 | // 4. Export the metadata (with converters)
27 | var options = new JsonSerializerOptions {
28 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
29 | TypeInfoResolver = new DefaultJsonTypeInfoResolver().WithAddedModifier(STJPortableMetadataObjectPropertyRemover.RemoveObjectProperties),
30 | WriteIndented = true
31 | };
32 | options.Converters.Add(new STJPortableTokenConverter());
33 | options.Converters.Add(new STJPortableComplexTypeConverter());
34 | json = JsonSerializer.Serialize(new PortableMetadataFacade(metadata), options);
35 | Console.WriteLine(json);
36 | }
37 |
38 | /*
39 | Output:
40 | {"Options":14,"Types":{"References":{"0":{"Name":"Sample_ExportOneMethod","Namespace":""},"1":{"Name":"Console","Namespace":"System","Assembly":"System.Console, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"}},"Definitions":{},"Orders":[]},"Fields":{"References":{},"Definitions":{},"Orders":[]},"Methods":{"References":{"1":{"Name":"WriteLine","Type":{"Kind":0,"Token":{"Index":1},"Type":0},"Signature":{"Kind":2,"Token":{"Index":0},"Type":0,"Arguments":[{"Kind":3,"Token":{"Index":0},"Type":0},{"Kind":3,"Token":{"Index":1},"Type":0},{"Kind":1,"Token":{"Index":0},"Type":1},{"Kind":1,"Token":{"Index":0},"Type":14}]}}},"Definitions":{"0":{"Attributes":150,"ImplAttributes":0,"Parameters":[],"Body":{"Instructions":[{"OpCode":"nop"},{"OpCode":"ldstr","Operand":"Hello World!","StringValue":"Hello World!"},{"OpCode":"call","Operand":{"Kind":7,"Token":{"Index":0},"Type":0,"Arguments":[{"Kind":0,"Token":{"Index":1},"Type":0}]},"TypeValue":{"Kind":7,"Token":{"Index":0},"Type":0,"Arguments":[{"Kind":0,"Token":{"Index":1},"Type":0}]}},{"OpCode":"nop"},{"OpCode":"ret"}],"ExceptionHandlers":[],"Variables":[]},"Name":"DemoMethod","Type":{"Kind":0,"Token":{"Index":0},"Type":0},"Signature":{"Kind":2,"Token":{"Index":0},"Type":0,"Arguments":[{"Kind":3,"Token":{"Index":0},"Type":0},{"Kind":3,"Token":{"Index":0},"Type":0},{"Kind":1,"Token":{"Index":0},"Type":1}]}}},"Orders":[]}}
41 | {
42 | "Options": 14,
43 | "Types": {
44 | "References": {
45 | "0": {
46 | "Name": "Sample_ExportOneMethod",
47 | "Namespace": ""
48 | },
49 | "1": {
50 | "Name": "Console",
51 | "Namespace": "System",
52 | "Assembly": "System.Console, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
53 | }
54 | },
55 | "Definitions": {},
56 | "Orders": []
57 | },
58 | "Fields": {
59 | "References": {},
60 | "Definitions": {},
61 | "Orders": []
62 | },
63 | "Methods": {
64 | "References": {
65 | "1": {
66 | "Name": "WriteLine",
67 | "Type": "1",
68 | "Signature": "Default(Int32(0),Int32(1),Void,String)"
69 | }
70 | },
71 | "Definitions": {
72 | "0": {
73 | "Attributes": 150,
74 | "ImplAttributes": 0,
75 | "Parameters": [],
76 | "Body": {
77 | "Instructions": [
78 | {
79 | "OpCode": "nop"
80 | },
81 | {
82 | "OpCode": "ldstr",
83 | "StringValue": "Hello World!"
84 | },
85 | {
86 | "OpCode": "call",
87 | "TypeValue": "InlineMethod(1)"
88 | },
89 | {
90 | "OpCode": "nop"
91 | },
92 | {
93 | "OpCode": "ret"
94 | }
95 | ],
96 | "ExceptionHandlers": [],
97 | "Variables": []
98 | },
99 | "Name": "DemoMethod",
100 | "Type": "0",
101 | "Signature": "Default(Int32(0),Int32(0),Void)"
102 | }
103 | },
104 | "Orders": []
105 | }
106 | }
107 | */
108 | }
109 |
--------------------------------------------------------------------------------
/PortableMetadata.Samples/Sample_ImportOneMethod.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Reflection;
3 | using System.Text.Json;
4 | using System.Text.Json.Serialization;
5 | using System.Text.Json.Serialization.Metadata;
6 | using dnlib.DotNet;
7 | using MetadataSerialization;
8 | using MetadataSerialization.Dnlib;
9 |
10 | static class Sample_ImportOneMethod {
11 | public static void DemoMethod() {
12 | //var list = new List {
13 | // "Hello",
14 | // "World"
15 | //};
16 | //foreach (var item in list)
17 | // Console.WriteLine(item);
18 | throw new NotImplementedException();
19 | }
20 |
21 | public static void Run() {
22 | // 1. Load the module using dnlib
23 | using var module = ModuleDefMD.Load(typeof(Sample_ImportOneMethod).Module);
24 |
25 | // 2. Deserialize the metadata
26 | var options = new JsonSerializerOptions {
27 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
28 | TypeInfoResolver = new DefaultJsonTypeInfoResolver().WithAddedModifier(STJPortableMetadataObjectPropertyRemover.RemoveObjectProperties),
29 | WriteIndented = true
30 | };
31 | options.Converters.Add(new STJPortableTokenConverter());
32 | options.Converters.Add(new STJPortableComplexTypeConverter());
33 | var metadata = JsonSerializer.Deserialize(DemoMethodJson, options)!.ToMetadata();
34 | var demoMethod = metadata.Methods[0];
35 | Debug.Assert(demoMethod is PortableMethodDef);
36 |
37 | // 3. Import the method
38 | var writer = new PortableMetadataWriter(module, metadata);
39 | writer.AddMethod(demoMethod, PortableMetadataLevel.Definition);
40 | var path = Path.GetFullPath("Sample_ImportOneMethod.dll");
41 | module.Assembly.Name = module.Name = Guid.NewGuid().ToString();
42 | module.Write(path);
43 | Assembly.LoadFrom(path).GetType("Sample_ImportOneMethod")!.GetMethod("DemoMethod")!.Invoke(null, null);
44 | }
45 |
46 | const string DemoMethodJson =
47 | """
48 | {
49 | "Options": 14,
50 | "Types": {
51 | "References": {
52 | "0": {
53 | "Name": "Sample_ImportOneMethod",
54 | "Namespace": ""
55 | },
56 | "1": {
57 | "Name": "List\u00601",
58 | "Namespace": "System.Collections.Generic",
59 | "Assembly": "System.Collections, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
60 | },
61 | "2": {
62 | "Name": "Enumerator",
63 | "Namespace": "System.Collections.Generic",
64 | "Assembly": "System.Collections, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
65 | "EnclosingNames": [
66 | "List\u00601"
67 | ]
68 | },
69 | "3": {
70 | "Name": "Console",
71 | "Namespace": "System",
72 | "Assembly": "System.Console, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
73 | },
74 | "4": {
75 | "Name": "IDisposable",
76 | "Namespace": "System",
77 | "Assembly": "System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
78 | }
79 | },
80 | "Definitions": {},
81 | "Orders": []
82 | },
83 | "Fields": {
84 | "References": {},
85 | "Definitions": {},
86 | "Orders": []
87 | },
88 | "Methods": {
89 | "References": {
90 | "1": {
91 | "Name": ".ctor",
92 | "Type": "GenericInst(Class(1),Int32(1),String)",
93 | "Signature": "Default(Int32(32),Int32(0),Void)"
94 | },
95 | "2": {
96 | "Name": "Add",
97 | "Type": "GenericInst(Class(1),Int32(1),String)",
98 | "Signature": "Default(Int32(32),Int32(1),Void,Var(Int32(0)))"
99 | },
100 | "3": {
101 | "Name": "GetEnumerator",
102 | "Type": "GenericInst(Class(1),Int32(1),String)",
103 | "Signature": "Default(Int32(32),Int32(0),GenericInst(ValueType(2),Int32(1),Var(Int32(0))))"
104 | },
105 | "4": {
106 | "Name": "get_Current",
107 | "Type": "GenericInst(ValueType(2),Int32(1),String)",
108 | "Signature": "Default(Int32(32),Int32(0),Var(Int32(0)))"
109 | },
110 | "5": {
111 | "Name": "WriteLine",
112 | "Type": "3",
113 | "Signature": "Default(Int32(0),Int32(1),Void,String)"
114 | },
115 | "6": {
116 | "Name": "MoveNext",
117 | "Type": "GenericInst(ValueType(2),Int32(1),String)",
118 | "Signature": "Default(Int32(32),Int32(0),Boolean)"
119 | },
120 | "7": {
121 | "Name": "Dispose",
122 | "Type": "4",
123 | "Signature": "Default(Int32(32),Int32(0),Void)"
124 | }
125 | },
126 | "Definitions": {
127 | "0": {
128 | "Attributes": 150,
129 | "ImplAttributes": 0,
130 | "Parameters": [],
131 | "Body": {
132 | "Instructions": [
133 | {
134 | "OpCode": "nop"
135 | },
136 | {
137 | "OpCode": "newobj",
138 | "TypeValue": "InlineMethod(1)"
139 | },
140 | {
141 | "OpCode": "dup"
142 | },
143 | {
144 | "OpCode": "ldstr",
145 | "StringValue": "Hello"
146 | },
147 | {
148 | "OpCode": "callvirt",
149 | "TypeValue": "InlineMethod(2)"
150 | },
151 | {
152 | "OpCode": "nop"
153 | },
154 | {
155 | "OpCode": "dup"
156 | },
157 | {
158 | "OpCode": "ldstr",
159 | "StringValue": "World"
160 | },
161 | {
162 | "OpCode": "callvirt",
163 | "TypeValue": "InlineMethod(2)"
164 | },
165 | {
166 | "OpCode": "nop"
167 | },
168 | {
169 | "OpCode": "stloc.0"
170 | },
171 | {
172 | "OpCode": "nop"
173 | },
174 | {
175 | "OpCode": "ldloc.0"
176 | },
177 | {
178 | "OpCode": "callvirt",
179 | "TypeValue": "InlineMethod(3)"
180 | },
181 | {
182 | "OpCode": "stloc.1"
183 | },
184 | {
185 | "OpCode": "br.s",
186 | "PrimitiveValue": 22
187 | },
188 | {
189 | "OpCode": "ldloca.s",
190 | "PrimitiveValue": 1
191 | },
192 | {
193 | "OpCode": "call",
194 | "TypeValue": "InlineMethod(4)"
195 | },
196 | {
197 | "OpCode": "stloc.2"
198 | },
199 | {
200 | "OpCode": "ldloc.2"
201 | },
202 | {
203 | "OpCode": "call",
204 | "TypeValue": "InlineMethod(5)"
205 | },
206 | {
207 | "OpCode": "nop"
208 | },
209 | {
210 | "OpCode": "ldloca.s",
211 | "PrimitiveValue": 1
212 | },
213 | {
214 | "OpCode": "call",
215 | "TypeValue": "InlineMethod(6)"
216 | },
217 | {
218 | "OpCode": "brtrue.s",
219 | "PrimitiveValue": 16
220 | },
221 | {
222 | "OpCode": "leave.s",
223 | "PrimitiveValue": 31
224 | },
225 | {
226 | "OpCode": "ldloca.s",
227 | "PrimitiveValue": 1
228 | },
229 | {
230 | "OpCode": "constrained.",
231 | "TypeValue": "InlineType(GenericInst(ValueType(2),Int32(1),String))"
232 | },
233 | {
234 | "OpCode": "callvirt",
235 | "TypeValue": "InlineMethod(7)"
236 | },
237 | {
238 | "OpCode": "nop"
239 | },
240 | {
241 | "OpCode": "endfinally"
242 | },
243 | {
244 | "OpCode": "ret"
245 | }
246 | ],
247 | "ExceptionHandlers": [
248 | {
249 | "TryStart": 15,
250 | "TryEnd": 26,
251 | "FilterStart": -1,
252 | "HandlerStart": 26,
253 | "HandlerEnd": 31,
254 | "HandlerType": 2
255 | }
256 | ],
257 | "Variables": [
258 | "GenericInst(Class(1),Int32(1),String)",
259 | "GenericInst(ValueType(2),Int32(1),String)",
260 | "String"
261 | ]
262 | },
263 | "Name": "DemoMethod",
264 | "Type": "0",
265 | "Signature": "Default(Int32(0),Int32(0),Void)"
266 | }
267 | },
268 | "Orders": []
269 | }
270 | }
271 | """;
272 | }
273 |
--------------------------------------------------------------------------------
/PortableMetadata.Samples/Sample_WholeAssemblyExportImport.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Reflection;
3 | using System.Text.Json;
4 | using System.Text.Json.Serialization;
5 | using System.Text.Json.Serialization.Metadata;
6 | using dnlib.DotNet;
7 | using dnlib.DotNet.MD;
8 | using MetadataSerialization;
9 | using MetadataSerialization.Dnlib;
10 |
11 | static class Sample_WholeAssemblyExportImport {
12 | public static void DemoMethod() {
13 | var assembly = Assembly.GetExecutingAssembly();
14 | Console.WriteLine(assembly.FullName);
15 | Console.WriteLine(assembly.Location);
16 | }
17 |
18 | public static void Run() {
19 | // Test dnlib.dll
20 | Run(typeof(ModuleDef).Module, "dnlib2");
21 |
22 | // Test current assembly
23 | var path = Run(typeof(Sample_WholeAssemblyExportImport).Module, "Sample_WholeAssemblyExportImport");
24 | Assembly.LoadFrom(path).GetType("Sample_WholeAssemblyExportImport")!.GetMethod("DemoMethod")!.Invoke(null, null);
25 | }
26 |
27 | /*
28 | Output:
29 | 431e8643-0ed9-49b9-97af-c020c0dacd62, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
30 | X:\bin\Debug\net8.0\Sample_WholeAssemblyExportImport.dll
31 | */
32 |
33 | static string Run(Module reflModule, string name) {
34 | // 1. Load the module using dnlib
35 | using var module = ModuleDefMD.Load(reflModule);
36 |
37 | // 2. Import all the typedefs
38 | var reader = new PortableMetadataReader(module);
39 | foreach (var type in module.Types)
40 | reader.AddType(type, PortableMetadataLevel.DefinitionWithChildren);
41 | var metadata = reader.Metadata;
42 |
43 | // 3. Serialize the metadata
44 | var options = new JsonSerializerOptions {
45 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
46 | TypeInfoResolver = new DefaultJsonTypeInfoResolver().WithAddedModifier(STJPortableMetadataObjectPropertyRemover.RemoveObjectProperties)
47 | };
48 | options.Converters.Add(new STJPortableTokenConverter());
49 | options.Converters.Add(new STJPortableComplexTypeConverter());
50 | var json = JsonSerializer.Serialize(new PortableMetadataFacade(metadata), options);
51 | File.WriteAllText($"{name}.json", json);
52 |
53 | // 4. Deserialize the metadata
54 | var metadata2 = JsonSerializer.Deserialize(json, options)!.ToMetadata();
55 | Compare(metadata, metadata2);
56 |
57 | // 5. Create the new module
58 | var module2 = new ModuleDefUser(Guid.NewGuid().ToString()) { RuntimeVersion = MDHeaderRuntimeVersion.MS_CLR_40 };
59 | new AssemblyDefUser(module2.Name).Modules.Add(module2);
60 | var writer = new PortableMetadataWriter(module2, metadata2);
61 | foreach (var type in metadata2.Types.Values.OfType())
62 | writer.AddType(type, PortableMetadataLevel.DefinitionWithChildren);
63 | var path = Path.GetFullPath($"{name}.dll");
64 | module2.Write(path);
65 | return path;
66 | }
67 |
68 | static void Compare(PortableMetadata x, PortableMetadata y) {
69 | Debug.Assert(x.Options == y.Options);
70 | Debug.Assert(x.Types.Count == y.Types.Count);
71 | var xt = x.Types.ToArray();
72 | var yt = y.Types.ToArray();
73 | for (int i = 0; i < xt.Length; i++) {
74 | Debug.Assert(xt[i].Key == yt[i].Key);
75 | Debug.Assert(PortableMetadataEqualityComparer.FullComparer.Equals(xt[i].Value, yt[i].Value));
76 | }
77 | Debug.Assert(x.Fields.Count == y.Fields.Count);
78 | var xf = x.Fields.ToArray();
79 | var yf = y.Fields.ToArray();
80 | for (int i = 0; i < xf.Length; i++) {
81 | Debug.Assert(xf[i].Key == yf[i].Key);
82 | Debug.Assert(PortableMetadataEqualityComparer.FullComparer.Equals(xf[i].Value, yf[i].Value));
83 | }
84 | Debug.Assert(x.Methods.Count == y.Methods.Count);
85 | var xm = x.Methods.ToArray();
86 | var ym = y.Methods.ToArray();
87 | for (int i = 0; i < xm.Length; i++) {
88 | Debug.Assert(xm[i].Key == ym[i].Key);
89 | Debug.Assert(PortableMetadataEqualityComparer.FullComparer.Equals(xm[i].Value, ym[i].Value));
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/PortableMetadata.Samples/SystemTextJson.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using System.Text.Json.Serialization;
3 | using System.Text.Json.Serialization.Metadata;
4 | using MetadataSerialization;
5 |
6 | sealed class STJPortableTokenConverter : JsonConverter {
7 | public override PortableToken Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
8 | if (reader.TokenType == JsonTokenType.Number && reader.TryGetInt32(out int index))
9 | return (PortableToken)index;
10 | else if (reader.TokenType == JsonTokenType.String)
11 | return (PortableToken)reader.GetString()!;
12 | else
13 | throw new InvalidDataException();
14 | }
15 |
16 | public override void Write(Utf8JsonWriter writer, PortableToken value, JsonSerializerOptions options) {
17 | if (value.Name is string name)
18 | writer.WriteStringValue(name);
19 | else
20 | writer.WriteNumberValue(value.Index);
21 | }
22 | }
23 |
24 | sealed class STJPortableComplexTypeConverter : JsonConverter {
25 | public override PortableComplexType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
26 | return reader.GetString() is string s ? PortableComplexType.Parse(s) : throw new InvalidDataException();
27 | }
28 |
29 | public override void Write(Utf8JsonWriter writer, PortableComplexType value, JsonSerializerOptions options) {
30 | writer.WriteStringValue(value.ToString());
31 | }
32 | }
33 |
34 | static class STJPortableMetadataObjectPropertyRemover {
35 | public static void RemoveObjectProperties(JsonTypeInfo typeInfo) {
36 | if (typeInfo.Kind != JsonTypeInfoKind.Object)
37 | return;
38 | // Don't use Properties.RemoteAt, which breaks the source generator mode.
39 | foreach (var property in typeInfo.Properties) {
40 | if (property.PropertyType == typeof(object)) {
41 | property.Get = null;
42 | property.Set = null;
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/PortableMetadata.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.10.35027.167
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PortableMetadata", "PortableMetadata\PortableMetadata.csproj", "{449DDA11-5956-48FB-A220-63E219E3C43A}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PortableMetadata.Benchmarks", "PortableMetadata.Benchmarks\PortableMetadata.Benchmarks.csproj", "{90494317-C1E5-461B-8A20-F223AC8FBA7A}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PortableMetadata.Samples", "PortableMetadata.Samples\PortableMetadata.Samples.csproj", "{6BDFFE09-162E-4A54-A8A3-C790465EFB8E}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PortableMetadata.Sample.ApplyAPatch", "PortableMetadata.Sample.ApplyAPatch\PortableMetadata.Sample.ApplyAPatch.csproj", "{C70261EF-3F5B-4F02-A69C-5129716A06F5}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PortableMetadata.Dnlib", "PortableMetadata.Dnlib\PortableMetadata.Dnlib.csproj", "{0A4B416A-7272-488E-9034-A1B7D06F4FC7}"
15 | EndProject
16 | Global
17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
18 | Debug|Any CPU = Debug|Any CPU
19 | Release|Any CPU = Release|Any CPU
20 | EndGlobalSection
21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 | {449DDA11-5956-48FB-A220-63E219E3C43A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {449DDA11-5956-48FB-A220-63E219E3C43A}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {449DDA11-5956-48FB-A220-63E219E3C43A}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {449DDA11-5956-48FB-A220-63E219E3C43A}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {90494317-C1E5-461B-8A20-F223AC8FBA7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {90494317-C1E5-461B-8A20-F223AC8FBA7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {90494317-C1E5-461B-8A20-F223AC8FBA7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {90494317-C1E5-461B-8A20-F223AC8FBA7A}.Release|Any CPU.Build.0 = Release|Any CPU
30 | {6BDFFE09-162E-4A54-A8A3-C790465EFB8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {6BDFFE09-162E-4A54-A8A3-C790465EFB8E}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {6BDFFE09-162E-4A54-A8A3-C790465EFB8E}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {6BDFFE09-162E-4A54-A8A3-C790465EFB8E}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {C70261EF-3F5B-4F02-A69C-5129716A06F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {C70261EF-3F5B-4F02-A69C-5129716A06F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {C70261EF-3F5B-4F02-A69C-5129716A06F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {C70261EF-3F5B-4F02-A69C-5129716A06F5}.Release|Any CPU.Build.0 = Release|Any CPU
38 | {0A4B416A-7272-488E-9034-A1B7D06F4FC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 | {0A4B416A-7272-488E-9034-A1B7D06F4FC7}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 | {0A4B416A-7272-488E-9034-A1B7D06F4FC7}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {0A4B416A-7272-488E-9034-A1B7D06F4FC7}.Release|Any CPU.Build.0 = Release|Any CPU
42 | EndGlobalSection
43 | GlobalSection(SolutionProperties) = preSolution
44 | HideSolutionNode = FALSE
45 | EndGlobalSection
46 | GlobalSection(ExtensibilityGlobals) = postSolution
47 | SolutionGuid = {9D20BEA2-0224-4200-8A1B-F19A380389D0}
48 | EndGlobalSection
49 | EndGlobal
50 |
--------------------------------------------------------------------------------
/PortableMetadata/Attributes.cs:
--------------------------------------------------------------------------------
1 | #if !NET
2 | using System.ComponentModel;
3 |
4 | namespace System.Diagnostics.CodeAnalysis {
5 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
6 | sealed class MaybeNullWhenAttribute(bool returnValue) : Attribute {
7 | public bool ReturnValue { get; } = returnValue;
8 | }
9 | }
10 |
11 | namespace System.Runtime.CompilerServices {
12 | [EditorBrowsable(EditorBrowsableState.Never)]
13 | static class IsExternalInit { }
14 | }
15 | #endif
16 |
17 | #if NETFRAMEWORK && !NET35_OR_GREATER
18 | namespace System {
19 | delegate TResult Func(T arg);
20 | }
21 | #endif
22 |
--------------------------------------------------------------------------------
/PortableMetadata/PortableCommon.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.IO;
5 | using System.Text;
6 | using ElementType2 = MetadataSerialization.PortableComplexTypeFormatter.ElementType;
7 |
8 | namespace MetadataSerialization;
9 |
10 | ///
11 | /// Represents a metadata token.
12 | ///
13 | public readonly struct PortableToken : IEquatable, IComparable {
14 | ///
15 | /// Invalid characters for a token name.
16 | ///
17 | public static readonly char[] InvalidNameChars = ['(', ')', ',', '@'];
18 |
19 | ///
20 | /// Token index.
21 | ///
22 | public int Index { get; init; }
23 |
24 | ///
25 | /// Token name.
26 | ///
27 | /// Only valid when is enabled.
28 | public string? Name { get; init; }
29 | ///
30 | /// Initializes a new instance of the class with the specified index.
31 | ///
32 | /// The index of the token.
33 | public PortableToken(int index) {
34 | Index = index;
35 | Name = null;
36 | }
37 |
38 | ///
39 | /// Initializes a new instance of the class with the specified name.
40 | ///
41 | /// The name of the token.
42 | /// Thrown when the name is null or empty.
43 | public PortableToken(string name) {
44 | if (string.IsNullOrEmpty(name))
45 | throw new ArgumentException($"'{nameof(name)}' cannot be null or empty.", nameof(name));
46 |
47 | Debug.Assert(name.IndexOfAny(InvalidNameChars) == -1, "Contains invalid characters.");
48 | Index = 0;
49 | Name = name;
50 | }
51 |
52 | ///
53 | /// Determines whether the specified is equal to the current .
54 | ///
55 | /// The to compare with the current .
56 | /// true if the specified is equal to the current ; otherwise, false.
57 | public bool Equals(PortableToken other) {
58 | return Index == other.Index && Name == other.Name;
59 | }
60 |
61 | ///
62 | /// Determines whether the specified object is equal to the current .
63 | ///
64 | /// The object to compare with the current .
65 | /// true if the specified object is equal to the current ; otherwise, false.
66 | public override bool Equals(object? obj) {
67 | return obj is PortableToken other && Equals(other);
68 | }
69 |
70 | ///
71 | /// Compares the current with another .
72 | ///
73 | /// The to compare with the current .
74 | /// A value that indicates the relative order of the objects being compared.
75 | public int CompareTo(PortableToken other) {
76 | if (Name is not null) {
77 | // Named tokens are always lesser than unnamed tokens
78 | if (other.Name is null)
79 | return -1;
80 | return Name.CompareTo(other.Name);
81 | }
82 | else {
83 | // Unnamed tokens
84 | if (other.Name is not null)
85 | return 1;
86 | return Index.CompareTo(other.Index);
87 | }
88 | }
89 |
90 | ///
91 | /// Returns the hash code for the current .
92 | ///
93 | /// A 32-bit signed integer hash code.
94 | public override int GetHashCode() {
95 | return Name is not null ? Name.GetHashCode() : Index;
96 | }
97 |
98 | ///
99 | /// Returns a string that represents the current .
100 | ///
101 | /// A string that represents the current .
102 | public override string ToString() {
103 | return Name is not null ? Name : Index.ToString();
104 | }
105 |
106 | #region Operators
107 | ///
108 | public static implicit operator PortableToken(int index) { return new PortableToken(index); }
109 | ///
110 | public static implicit operator PortableToken(string name) { return new PortableToken(name); }
111 | ///
112 | public static explicit operator int(PortableToken token) { return token.Index; }
113 | ///
114 | public static explicit operator string(PortableToken token) { return token.Name ?? throw new InvalidCastException("Token is not named."); }
115 | ///
116 | public static bool operator ==(PortableToken x, PortableToken y) { return x.Equals(y); }
117 | ///
118 | public static bool operator !=(PortableToken x, PortableToken y) { return !x.Equals(y); }
119 | ///
120 | public static bool operator <(PortableToken x, PortableToken y) { return x.CompareTo(y) < 0; }
121 | ///
122 | public static bool operator >(PortableToken x, PortableToken y) { return x.CompareTo(y) > 0; }
123 | ///
124 | public static bool operator <=(PortableToken x, PortableToken y) { return x.CompareTo(y) <= 0; }
125 | ///
126 | public static bool operator >=(PortableToken x, PortableToken y) { return x.CompareTo(y) >= 0; }
127 | #endregion
128 | }
129 |
130 | ///
131 | /// Represents the kind of .
132 | ///
133 | public enum PortableComplexTypeKind : byte {
134 | ///
135 | /// A .
136 | ///
137 | Token,
138 |
139 | ///
140 | /// A type signature.
141 | ///
142 | TypeSig,
143 |
144 | ///
145 | /// A calling convention signature.
146 | ///
147 | CallingConventionSig,
148 |
149 | ///
150 | /// An integer value.
151 | ///
152 | Int32,
153 |
154 | ///
155 | /// A method specification. (An instantiated generic method)
156 | ///
157 | MethodSpec,
158 |
159 | ///
160 | /// Inline type/token operand wrapper.
161 | ///
162 | InlineType,
163 |
164 | ///
165 | /// Inline field/token operand wrapper.
166 | ///
167 | InlineField,
168 |
169 | ///
170 | /// Inline method/token operand wrapper.
171 | ///
172 | InlineMethod
173 | }
174 |
175 | ///
176 | /// Represents a portable complex type.
177 | ///
178 | ///
179 | /// TypeDef/TypeRef/TypeSpec/TypeDefOrRef
180 | /// FieldDef/MemberRef
181 | /// MethodDef/MemberRef/MethodSpec/MethodDefOrRef
182 | /// TypeSig/CallingConventionSig
183 | ///
184 | public struct PortableComplexType {
185 | ///
186 | /// Gets or sets the kind of .
187 | ///
188 | public PortableComplexTypeKind Kind { get; set; }
189 |
190 | ///
191 | /// Gets or sets the token.
192 | ///
193 | /// See
194 | public PortableToken Token { get; set; }
195 |
196 | ///
197 | /// Gets or sets the element type or calling convention.
198 | ///
199 | /// See and
200 | public byte Type { get; set; }
201 |
202 | ///
203 | /// Gets or sets the arguments.
204 | ///
205 | public IList? Arguments { get; set; }
206 |
207 | ///
208 | /// Gets the integer value stored in the .
209 | ///
210 | ///
211 | /// See
212 | public readonly int GetInt32() {
213 | return Kind == PortableComplexTypeKind.Int32 ? Token.Index : throw new InvalidOperationException();
214 | }
215 |
216 | private PortableComplexType(PortableComplexTypeKind kind, PortableToken token, byte type, IList? arguments) {
217 | Kind = kind;
218 | Token = token;
219 | Type = type;
220 | Arguments = arguments;
221 | }
222 |
223 | ///
224 | /// Creates a complex type with the specified token.
225 | ///
226 | /// The token of the complex type.
227 | /// A complex type with the specified token.
228 | public static PortableComplexType CreateToken(PortableToken token) {
229 | return new PortableComplexType(PortableComplexTypeKind.Token, token, 0, null);
230 | }
231 |
232 | ///
233 | /// Creates a complex type with the specified element type and arguments.
234 | ///
235 | /// The element type of the complex type.
236 | /// The arguments of the complex type.
237 | /// A complex type with the specified element type and arguments.
238 | public static PortableComplexType CreateTypeSig(byte elementType, IList? arguments) {
239 | return new PortableComplexType(PortableComplexTypeKind.TypeSig, default, elementType, arguments);
240 | }
241 |
242 | ///
243 | /// Creates a complex type with the specified calling convention and arguments.
244 | ///
245 | /// The calling convention of the complex type.
246 | /// The arguments of the complex type.
247 | /// A complex type with the specified calling convention and arguments.
248 | public static PortableComplexType CreateCallingConventionSig(byte callingConvention, IList? arguments) {
249 | return new PortableComplexType(PortableComplexTypeKind.CallingConventionSig, default, callingConvention, arguments);
250 | }
251 |
252 | ///
253 | /// Creates a complex type with the specified integer value.
254 | ///
255 | /// The integer value of the complex type.
256 | /// A complex type with the specified integer value.
257 | public static PortableComplexType CreateInt32(int value) {
258 | return new PortableComplexType(PortableComplexTypeKind.Int32, value, 0, null);
259 | }
260 |
261 | ///
262 | /// Creates a complex type with the specified method and instantiation.
263 | ///
264 | /// The method of the complex type.
265 | /// The instantiation of the complex type.
266 | /// A complex type with the specified method and instantiation.
267 | public static PortableComplexType CreateMethodSpec(PortableComplexType method, PortableComplexType instantiation) {
268 | return new PortableComplexType(PortableComplexTypeKind.MethodSpec, default, 0, [method, instantiation]);
269 | }
270 |
271 | ///
272 | /// Creates a complex type with the specified kind and type.
273 | ///
274 | /// The kind of the complex type.
275 | /// The type of the complex type.
276 | /// A complex type with the specified kind and type.
277 | public static PortableComplexType CreateInlineTokenOperand(PortableComplexTypeKind kind, PortableComplexType type) {
278 | if (kind < PortableComplexTypeKind.InlineType || kind > PortableComplexTypeKind.InlineMethod)
279 | throw new ArgumentOutOfRangeException(nameof(kind));
280 | return new PortableComplexType(kind, default, 0, [type]);
281 | }
282 |
283 | ///
284 | /// Parses the specified string and returns a complex type.
285 | ///
286 | /// The string to parse.
287 | /// A complex type parsed from the specified string.
288 | public static PortableComplexType Parse(string s) {
289 | if (string.IsNullOrEmpty(s))
290 | throw new ArgumentException($"'{nameof(s)}' cannot be null or empty.", nameof(s));
291 |
292 | return PortableComplexTypeFormatter.Parse(s);
293 | }
294 |
295 | ///
296 | /// Returns a string representation of the complex type.
297 | ///
298 | /// A string representation of the complex type.
299 | public override readonly string ToString() {
300 | return PortableComplexTypeFormatter.ToString(this);
301 | }
302 | }
303 |
304 | ///
305 | /// Represents a portable constant.
306 | ///
307 | ///
308 | ///
309 | public struct PortableConstant(int type, object? value) {
310 | ///
311 | /// Gets or sets the type of the constant.
312 | ///
313 | public int Type { get; set; } = type;
314 |
315 | ///
316 | /// Gets or sets the value of the constant.
317 | ///
318 | ///
319 | /// The value can be of type , , , ,
320 | /// , , , , ,
321 | /// , , , or .
322 | ///
323 | public object? Value { get; set; } = value;
324 |
325 | ///
326 | [Obsolete("Reserved for deserialization.")]
327 | public long? PrimitiveValue {
328 | readonly get => PrimitivesHelper.ToSlot(Value);
329 | set {
330 | if (PrimitivesHelper.FromSlot(value, Type) is object slot)
331 | Value = slot;
332 | }
333 | }
334 |
335 | ///
336 | [Obsolete("Reserved for deserialization.")]
337 | public string? StringValue {
338 | readonly get => Value as string;
339 | set {
340 | if (Type == 0x0E && value is string s)
341 | Value = s;
342 | }
343 | }
344 |
345 | ///
346 | /// Returns the string of the constant value.
347 | ///
348 | ///
349 | public override readonly string? ToString() {
350 | return Value?.ToString();
351 | }
352 | }
353 |
354 | ///
355 | /// Represents a generic parameter.
356 | ///
357 | /// The name of the generic parameter.
358 | /// The attributes of the generic parameter.
359 | /// The number of the generic parameter.
360 | /// The constraints of the generic parameter.
361 | public struct PortableGenericParameter(string name, int attributes, int number, IList? constraints) {
362 | ///
363 | /// Gets or sets the name of the generic parameter.
364 | ///
365 | public string Name { get; set; } = name;
366 |
367 | ///
368 | /// Gets or sets the attributes of the generic parameter.
369 | ///
370 | public int Attributes { get; set; } = attributes;
371 |
372 | ///
373 | /// Gets or sets the number of the generic parameter.
374 | ///
375 | public int Number { get; set; } = number;
376 |
377 | ///
378 | /// Gets or sets the constraints of the generic parameter.
379 | ///
380 | /// TypeDefOrRef
381 | public IList? Constraints { get; set; } = constraints;
382 |
383 | ///
384 | /// Returns the string representation of the generic parameter.
385 | ///
386 | /// The name of the generic parameter.
387 | public override readonly string ToString() {
388 | return Name;
389 | }
390 | }
391 |
392 | ///
393 | /// Represents a custom attribute.
394 | ///
395 | public struct PortableCustomAttribute(PortableToken constructor, byte[] rawData) {
396 | ///
397 | /// Gets or sets the constructor of the custom attribute.
398 | ///
399 | /// MethodDefOrRef
400 | public PortableToken Constructor { get; set; } = constructor;
401 |
402 | ///
403 | /// Gets or sets the raw data of the custom attribute.
404 | ///
405 | public byte[] RawData { get; set; } = rawData ?? throw new ArgumentNullException(nameof(rawData));
406 | }
407 |
408 | static class PortableComplexTypeFormatter {
409 | const char TokenNameQuote = '\'';
410 |
411 | #region Parse
412 | public static PortableComplexType Parse(string s) {
413 | if (string.IsNullOrEmpty(s))
414 | throw new ArgumentException($"'{nameof(s)}' cannot be null or empty.", nameof(s));
415 |
416 | int index = 0;
417 | return ReadType(s, ref index);
418 | }
419 |
420 | static PortableComplexType ReadType(string s, ref int index) {
421 | var next = ReadNext(s, ref index);
422 | if (TryParseToken(next, out var token))
423 | return PortableComplexType.CreateToken(token);
424 | if (EnumTryParse(next, out var et))
425 | return PortableComplexType.CreateTypeSig((byte)et, ReadElementTypeArgs(s, ref index, et));
426 | if (EnumTryParse(next, out var cc))
427 | return PortableComplexType.CreateCallingConventionSig((byte)cc, ReadCallingConventionArgs(s, ref index, cc));
428 | if (EnumTryParse(next, out var st))
429 | return ReadSpecialType(s, ref index, st);
430 | throw new InvalidDataException($"Invalid complex type beginning: {next}.");
431 |
432 | #if NETFRAMEWORK && !NET40_OR_GREATER
433 | static bool EnumTryParse(string value, out TEnum result) where TEnum : struct {
434 | if (Enum.IsDefined(typeof(TEnum), value)) {
435 | result = (TEnum)Enum.Parse(typeof(TEnum), value);
436 | return true;
437 | }
438 | else {
439 | result = default;
440 | return false;
441 | }
442 | }
443 | #else
444 | static bool EnumTryParse(string value, out TEnum result) where TEnum : struct {
445 | return Enum.TryParse(value, out result);
446 | }
447 | #endif
448 | }
449 |
450 | static bool TryParseToken(string s, out PortableToken token) {
451 | if (s.Length > 2 && s[0] == TokenNameQuote && s[s.Length - 1] == TokenNameQuote) {
452 | token = s.Substring(1, s.Length - 2);
453 | return true;
454 | }
455 | if (int.TryParse(s, out int index)) {
456 | token = index;
457 | return true;
458 | }
459 | token = default;
460 | return false;
461 | }
462 |
463 | static List? ReadElementTypeArgs(string s, ref int index, ElementType2 elementType) {
464 | switch (elementType) {
465 | case ElementType2.End:
466 | case ElementType2.Void:
467 | case ElementType2.Boolean:
468 | case ElementType2.Char:
469 | case ElementType2.I1:
470 | case ElementType2.U1:
471 | case ElementType2.I2:
472 | case ElementType2.U2:
473 | case ElementType2.I4:
474 | case ElementType2.U4:
475 | case ElementType2.I8:
476 | case ElementType2.U8:
477 | case ElementType2.R4:
478 | case ElementType2.R8:
479 | case ElementType2.String:
480 | case ElementType2.TypedByRef:
481 | case ElementType2.I:
482 | case ElementType2.U:
483 | case ElementType2.R:
484 | case ElementType2.Object:
485 | case ElementType2.Sentinel:
486 | return null;
487 | }
488 |
489 | if (!ReadUntil(s, ref index, '('))
490 | throw new InvalidDataException("Can't find '('.");
491 |
492 | List arguments;
493 | switch (elementType) {
494 | case ElementType2.Ptr:
495 | case ElementType2.ByRef:
496 | case ElementType2.FnPtr:
497 | case ElementType2.SZArray:
498 | case ElementType2.Pinned:
499 | // et(next)
500 | arguments = [ReadType(s, ref index)];
501 | break;
502 |
503 | case ElementType2.ValueType:
504 | case ElementType2.Class:
505 | // et(next)
506 | arguments = [ReadType(s, ref index)];
507 | break;
508 |
509 | case ElementType2.Var:
510 | case ElementType2.MVar:
511 | // et(index)
512 | arguments = [ReadInt32(s, ref index)];
513 | break;
514 |
515 | case ElementType2.Array: {
516 | // et(next, rank, numSizes, .. sizes, numLowerBounds, .. lowerBounds)
517 | arguments = [];
518 | var nextType = ReadType(s, ref index);
519 | arguments.Add(nextType);
520 | arguments.Add(ReadInt32(s, ref index, out int rank));
521 | arguments.Add(ReadInt32(s, ref index, out int numSizes));
522 | for (int i = 0; i < numSizes; i++)
523 | arguments.Add(ReadInt32(s, ref index));
524 | arguments.Add(ReadInt32(s, ref index, out int numLowerBounds));
525 | for (int i = 0; i < numLowerBounds; i++)
526 | arguments.Add(ReadInt32(s, ref index));
527 | break;
528 | }
529 |
530 | case ElementType2.GenericInst: {
531 | // et(next, num, .. args)
532 | arguments = [];
533 | var nextType = ReadType(s, ref index);
534 | arguments.Add(nextType);
535 | arguments.Add(ReadInt32(s, ref index, out int num));
536 | for (int i = 0; i < num; i++)
537 | arguments.Add(ReadType(s, ref index));
538 | break;
539 | }
540 |
541 | case ElementType2.ValueArray:
542 | // et(next, size)
543 | arguments = [ReadType(s, ref index), ReadInt32(s, ref index)];
544 | break;
545 |
546 | case ElementType2.CModReqd:
547 | case ElementType2.CModOpt:
548 | // et(modifier, next)
549 | arguments = [ReadType(s, ref index), ReadType(s, ref index)];
550 | break;
551 |
552 | case ElementType2.Module:
553 | // et(index, next)
554 | arguments = [ReadInt32(s, ref index), ReadType(s, ref index)];
555 | break;
556 |
557 | default:
558 | throw new InvalidOperationException();
559 | }
560 |
561 | if (!ReadUntil(s, ref index, ')'))
562 | throw new InvalidDataException("Can't find ')'.");
563 |
564 | return arguments;
565 | }
566 |
567 | static List ReadCallingConventionArgs(string s, ref int index, CallingConvention callingConvention) {
568 | if (!ReadUntil(s, ref index, '('))
569 | throw new InvalidDataException("Can't find '('.");
570 |
571 | List arguments = [ReadInt32(s, ref index, out int flags)];
572 | switch (callingConvention) {
573 | case CallingConvention.Default:
574 | case CallingConvention.C:
575 | case CallingConvention.StdCall:
576 | case CallingConvention.ThisCall:
577 | case CallingConvention.FastCall:
578 | case CallingConvention.VarArg:
579 | case CallingConvention.Property:
580 | case CallingConvention.Unmanaged:
581 | case CallingConvention.NativeVarArg:
582 | // cc([numGPs], numParams, retType, .. params)
583 | bool generic = (flags & 0x10) != 0;
584 | if (generic) // GenericParam count
585 | arguments.Add(ReadInt32(s, ref index));
586 | arguments.Add(ReadInt32(s, ref index, out int parameterCount));
587 | var returnType = ReadType(s, ref index);
588 | arguments.Add(returnType);
589 | for (int i = 0; i < parameterCount; i++) {
590 | var sig = ReadType(s, ref index);
591 | if (sig.Type == (byte)ElementType2.Sentinel) {
592 | i--;
593 | continue;
594 | }
595 | arguments.Add(sig);
596 | }
597 | break;
598 |
599 | case CallingConvention.Field:
600 | // cc(fieldType)
601 | arguments.Add(ReadType(s, ref index));
602 | break;
603 |
604 | case CallingConvention.LocalSig:
605 | // cc(numLocals, .. locals)
606 | arguments.Add(ReadInt32(s, ref index, out int numLocals));
607 | for (int i = 0; i < numLocals; i++)
608 | arguments.Add(ReadType(s, ref index));
609 | break;
610 |
611 | case CallingConvention.GenericInstCC:
612 | // cc(numArgs, .. args)
613 | arguments.Add(ReadInt32(s, ref index, out int numArgs));
614 | for (int i = 0; i < numArgs; i++)
615 | arguments.Add(ReadType(s, ref index));
616 | break;
617 |
618 | default:
619 | throw new InvalidOperationException();
620 | }
621 |
622 | if (!ReadUntil(s, ref index, ')'))
623 | throw new InvalidDataException("Can't find ')'.");
624 |
625 | return arguments;
626 | }
627 |
628 | static PortableComplexType ReadSpecialType(string s, ref int index, SpecialType specialType) {
629 | if (!ReadUntil(s, ref index, '('))
630 | throw new InvalidDataException("Can't find '('.");
631 |
632 | PortableComplexType type;
633 | switch (specialType) {
634 | case SpecialType.Int32:
635 | type = PortableComplexType.CreateInt32(int.Parse(ReadNext(s, ref index)));
636 | break;
637 | case SpecialType.MethodSpec:
638 | type = PortableComplexType.CreateMethodSpec(ReadType(s, ref index), ReadType(s, ref index));
639 | break;
640 | case SpecialType.InlineType:
641 | case SpecialType.InlineField:
642 | case SpecialType.InlineMethod:
643 | type = PortableComplexType.CreateInlineTokenOperand(PortableComplexTypeKind.Int32 + (byte)specialType, ReadType(s, ref index));
644 | break;
645 | default:
646 | throw new InvalidOperationException();
647 | }
648 |
649 | if (!ReadUntil(s, ref index, ')'))
650 | throw new InvalidDataException("Can't find ')'.");
651 |
652 | return type;
653 | }
654 |
655 | static PortableComplexType ReadInt32(string s, ref int index) {
656 | return ReadInt32(s, ref index, out _);
657 | }
658 |
659 | static PortableComplexType ReadInt32(string s, ref int index, out int value) {
660 | var type = ReadType(s, ref index);
661 | value = type.GetInt32();
662 | return type;
663 | }
664 |
665 | static string ReadNext(string s, ref int index) {
666 | var sb = new StringBuilder();
667 | while (index < s.Length) {
668 | var c = s[index++];
669 | if (c == ',') {
670 | if (sb.Length == 0)
671 | continue; // Skip leading commas
672 | break;
673 | }
674 | if (c == '(' || c == ')') {
675 | index--;
676 | break;
677 | }
678 | sb.Append(c);
679 | }
680 | Debug.Assert(sb.Length != 0);
681 | var result = sb.ToString();
682 | return result;
683 | }
684 |
685 | static bool ReadUntil(string s, ref int index, char c) {
686 | while (index < s.Length) {
687 | if (s[index++] == c)
688 | return true;
689 | }
690 | return false;
691 | }
692 | #endregion
693 |
694 | #region ToString
695 | public static string ToString(PortableComplexType type) {
696 | var sb = new StringBuilder();
697 | WriteType(type, sb);
698 | return sb.ToString();
699 | }
700 |
701 | static void WriteType(PortableComplexType type, StringBuilder sb) {
702 | switch (type.Kind) {
703 | case PortableComplexTypeKind.Token:
704 | if (type.Token.Name is string name)
705 | sb.Append(TokenNameQuote).Append(name).Append(TokenNameQuote);
706 | else
707 | sb.Append(type.Token.Index);
708 | return;
709 | case PortableComplexTypeKind.TypeSig:
710 | var elementType = (ElementType2)type.Type;
711 | if (!Enum.IsDefined(typeof(ElementType2), elementType))
712 | throw new InvalidDataException($"Invalid element type: {type.Type}.");
713 | sb.Append(elementType.ToString());
714 | WriteArgs(type.Arguments, sb);
715 | return;
716 | case PortableComplexTypeKind.CallingConventionSig:
717 | var callingConvention = (CallingConvention)type.Type;
718 | if (!Enum.IsDefined(typeof(CallingConvention), callingConvention))
719 | throw new InvalidDataException($"Invalid calling convention: {type.Type}.");
720 | sb.Append(callingConvention.ToString());
721 | WriteArgs(type.Arguments, sb);
722 | return;
723 | default:
724 | var specialType = (SpecialType)(type.Kind - PortableComplexTypeKind.Int32);
725 | if (!Enum.IsDefined(typeof(SpecialType), specialType))
726 | throw new InvalidDataException($"Invalid complex type kind: {type.Kind}.");
727 | sb.Append(specialType);
728 | if (specialType == SpecialType.Int32)
729 | sb.Append('(').Append(type.GetInt32()).Append(')');
730 | else
731 | WriteArgs(type.Arguments, sb);
732 | return;
733 | }
734 | }
735 |
736 | static void WriteArgs(IList? arguments, StringBuilder sb) {
737 | Debug.Assert(arguments is null || arguments.Count != 0, "Arguments should be null if empty.");
738 | if (arguments is null || arguments.Count == 0)
739 | return;
740 | sb.Append('(');
741 | foreach (var arg in arguments) {
742 | WriteType(arg, sb);
743 | sb.Append(',');
744 | }
745 | sb.Remove(sb.Length - 1, 1);
746 | sb.Append(')');
747 | }
748 | #endregion
749 |
750 | internal static PortableToken? GetScopeType(PortableComplexType type) {
751 | if (type.Kind != PortableComplexTypeKind.TypeSig)
752 | return null;
753 |
754 | var args = type.Arguments;
755 | switch ((ElementType2)type.Type) {
756 | case ElementType2.Ptr:
757 | case ElementType2.ByRef:
758 | case ElementType2.Array:
759 | case ElementType2.GenericInst:
760 | case ElementType2.ValueArray:
761 | case ElementType2.SZArray:
762 | case ElementType2.Pinned:
763 | return args is not null ? GetScopeType(args[0]) : null;
764 |
765 | case ElementType2.ValueType:
766 | case ElementType2.Class:
767 | return args is not null && args[0].Kind == PortableComplexTypeKind.Token ? args[0].Token : default(PortableToken?);
768 |
769 | case ElementType2.CModReqd:
770 | case ElementType2.CModOpt:
771 | return args is not null ? GetScopeType(args[1]) : null;
772 |
773 | default:
774 | return null;
775 | }
776 | }
777 |
778 | internal enum ElementType : byte {
779 | End = 0x00,
780 | Void = 0x01,
781 | Boolean = 0x02,
782 | Char = 0x03,
783 | I1 = 0x04,
784 | U1 = 0x05,
785 | I2 = 0x06,
786 | U2 = 0x07,
787 | I4 = 0x08,
788 | U4 = 0x09,
789 | I8 = 0x0A,
790 | U8 = 0x0B,
791 | R4 = 0x0C,
792 | R8 = 0x0D,
793 | String = 0x0E,
794 | Ptr = 0x0F,
795 | ByRef = 0x10,
796 | ValueType = 0x11,
797 | Class = 0x12,
798 | Var = 0x13,
799 | Array = 0x14,
800 | GenericInst = 0x15,
801 | TypedByRef = 0x16,
802 | ValueArray = 0x17,
803 | I = 0x18,
804 | U = 0x19,
805 | R = 0x1A,
806 | FnPtr = 0x1B,
807 | Object = 0x1C,
808 | SZArray = 0x1D,
809 | MVar = 0x1E,
810 | CModReqd = 0x1F,
811 | CModOpt = 0x20,
812 | Module = 0x3F,
813 | Sentinel = 0x41,
814 | Pinned = 0x45
815 | }
816 |
817 | enum CallingConvention : byte {
818 | Default = 0x0,
819 | C = 0x1,
820 | StdCall = 0x2,
821 | ThisCall = 0x3,
822 | FastCall = 0x4,
823 | VarArg = 0x5,
824 | Field = 0x6,
825 | LocalSig = 0x7,
826 | Property = 0x8,
827 | Unmanaged = 0x9,
828 | GenericInstCC = 0xA,
829 | NativeVarArg = 0xB
830 | }
831 |
832 | enum SpecialType : byte {
833 | Int32,
834 | MethodSpec,
835 | InlineType,
836 | InlineField,
837 | InlineMethod
838 | }
839 | }
840 |
841 | ///
842 | /// A helper to convert primitive values.
843 | ///
844 | public static class PrimitivesHelper {
845 | ///
846 | /// Converts the specified value to a slot representation.
847 | ///
848 | /// The value to convert.
849 | /// The slot representation of the value.
850 | public static long? ToSlot(object? value) {
851 | switch (value) {
852 | case null:
853 | default:
854 | return null;
855 | case bool b:
856 | return b ? 1 : 0;
857 | case char c:
858 | return c;
859 | case sbyte i1:
860 | return i1;
861 | case byte u1:
862 | return u1;
863 | case short i2:
864 | return i2;
865 | case ushort u2:
866 | return u2;
867 | case int i4:
868 | return i4;
869 | case uint u4:
870 | return u4;
871 | case long i8:
872 | return i8;
873 | case ulong u8:
874 | return (long)u8;
875 | case float r4:
876 | long t = BitConverter.DoubleToInt64Bits(r4);
877 | Debug.Assert(!float.IsNaN(r4) || (ulong)t == 0xFFF8000000000000, "Casting a NaN float to double will lost information.");
878 | return t;
879 | case double r8:
880 | return BitConverter.DoubleToInt64Bits(r8);
881 | }
882 | }
883 |
884 | ///
885 | /// Converts the specified slot representation to a value of the specified element type.
886 | ///
887 | /// The slot representation to convert.
888 | /// The element type of the value.
889 | /// The value of the specified element type.
890 | public static object? FromSlot(long? value, int elementType) {
891 | if (value is not long slot)
892 | return null;
893 | var elementType2 = (ElementType2)elementType;
894 | switch (elementType2) {
895 | case ElementType2.Boolean:
896 | return slot != 0;
897 | case ElementType2.Char:
898 | return (char)slot;
899 | case ElementType2.I1:
900 | return (sbyte)slot;
901 | case ElementType2.U1:
902 | return (byte)slot;
903 | case ElementType2.I2:
904 | return (short)slot;
905 | case ElementType2.U2:
906 | return (ushort)slot;
907 | case ElementType2.I4:
908 | return (int)slot;
909 | case ElementType2.U4:
910 | return (uint)slot;
911 | case ElementType2.I8:
912 | return slot;
913 | case ElementType2.U8:
914 | return (ulong)slot;
915 | case ElementType2.R4:
916 | double t = BitConverter.Int64BitsToDouble(slot);
917 | Debug.Assert(!double.IsNaN(t) || t == 0xFFF8000000000000, "Casting a NaN double to float will lost information.");
918 | return (float)t;
919 | case ElementType2.R8:
920 | return BitConverter.Int64BitsToDouble(slot);
921 | default:
922 | Debug.Assert(Enum.IsDefined(typeof(ElementType2), elementType2), "Invalid element type.");
923 | return null;
924 | }
925 | }
926 | }
927 |
--------------------------------------------------------------------------------
/PortableMetadata/PortableField.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace MetadataSerialization;
4 |
5 | ///
6 | /// Represents a portable field.
7 | ///
8 | /// The name of the field.
9 | /// The declaring type of the field.
10 | /// The signature of the field.
11 | public class PortableField(string name, PortableComplexType type, PortableComplexType signature) {
12 | ///
13 | /// Gets or sets the name of the field.
14 | ///
15 | public string Name { get; set; } = name;
16 |
17 | ///
18 | /// Gets or sets the declaring type of the field.
19 | ///
20 | /// TypeDefOrRef
21 | public PortableComplexType Type { get; set; } = type;
22 |
23 | ///
24 | /// Gets or sets the signature of the field.
25 | ///
26 | /// FieldSig
27 | public PortableComplexType Signature { get; set; } = signature;
28 |
29 | ///
30 | /// Returns the name of the field.
31 | ///
32 | /// The name of the field.
33 | public override string ToString() {
34 | return Name;
35 | }
36 | }
37 |
38 | ///
39 | /// Represents a portable field definition.
40 | ///
41 | /// The name of the field.
42 | /// The declaring type of the field.
43 | /// The signature of the field.
44 | /// The attributes of the field.
45 | /// The initial value of the field.
46 | /// The constant value of the field.
47 | /// The custom attributes of the field.
48 | public sealed class PortableFieldDef(string name, PortableComplexType type, PortableComplexType signature, int attributes,
49 | byte[]? initialValue, PortableConstant? constant, IList? customAttributes)
50 | : PortableField(name, type, signature) {
51 | ///
52 | /// Gets or sets the attributes of the field.
53 | ///
54 | public int Attributes { get; set; } = attributes;
55 |
56 | ///
57 | /// Gets or sets the initial value of the field.
58 | ///
59 | public byte[]? InitialValue { get; set; } = initialValue;
60 |
61 | ///
62 | /// Gets or sets the constant value of the field.
63 | ///
64 | public PortableConstant? Constant { get; set; } = constant;
65 |
66 | ///
67 | /// Gets or sets the custom attributes of the field.
68 | ///
69 | public IList? CustomAttributes { get; set; } = customAttributes;
70 | }
71 |
--------------------------------------------------------------------------------
/PortableMetadata/PortableMetadata.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Diagnostics.CodeAnalysis;
6 | using System.Text;
7 |
8 | namespace MetadataSerialization;
9 |
10 | ///
11 | /// The options to create the portable metadata.
12 | ///
13 | [Flags]
14 | public enum PortableMetadataOptions {
15 | ///
16 | /// None
17 | ///
18 | None = 0,
19 |
20 | ///
21 | /// Use type/member name as token instead of index. This option will make the output more readable but significantly increase the size.
22 | ///
23 | UseNamedToken = 1,
24 |
25 | ///
26 | /// Use assembly full name not the display name.
27 | ///
28 | UseAssemblyFullName = 2,
29 |
30 | ///
31 | /// Include method bodies.
32 | ///
33 | IncludeMethodBodies = 4,
34 |
35 | ///
36 | /// Include custom attributes.
37 | ///
38 | IncludeCustomAttributes = 8,
39 |
40 | ///
41 | /// The original method body's max stack field should be used and a new one should not be calculated.
42 | ///
43 | KeepOldMaxStack = 16
44 | }
45 |
46 | ///
47 | /// Portable metadata
48 | ///
49 | /// The options of the portable metadata.
50 | public sealed class PortableMetadata(PortableMetadataOptions options) {
51 | ///
52 | /// The default options of the portable metadata.
53 | ///
54 | public const PortableMetadataOptions DefaultOptions = PortableMetadataOptions.UseAssemblyFullName | PortableMetadataOptions.IncludeMethodBodies | PortableMetadataOptions.IncludeCustomAttributes;
55 |
56 | ///
57 | /// Gets the options of the portable metadata.
58 | ///
59 | public PortableMetadataOptions Options { get; } = options;
60 |
61 | ///
62 | /// Gets the dictionary of portable types.
63 | ///
64 | public IDictionary Types { get; } = CreateDictionary((options & PortableMetadataOptions.UseNamedToken) != 0);
65 |
66 | ///
67 | /// Gets the dictionary of portable fields.
68 | ///
69 | public IDictionary Fields { get; } = CreateDictionary((options & PortableMetadataOptions.UseNamedToken) != 0);
70 |
71 | ///
72 | /// Gets the dictionary of portable methods.
73 | ///
74 | public IDictionary Methods { get; } = CreateDictionary((options & PortableMetadataOptions.UseNamedToken) != 0);
75 |
76 | static IDictionary CreateDictionary(bool useNamedToken) where TValue : class {
77 | return useNamedToken ? new Dictionary() : new ListToDictionaryAdapter();
78 | }
79 |
80 | [DebuggerDisplay("Count = {Count}")]
81 | [DebuggerTypeProxy(typeof(ListToDictionaryAdapter_CollectionDebugView<>))]
82 | sealed class ListToDictionaryAdapter : IDictionary, IDictionary where TValue : class {
83 | readonly List values = [];
84 | readonly List keys = [];
85 |
86 | public TValue this[PortableToken key] {
87 | get => values[GetIndex(key)];
88 | set => Add(key, value, true);
89 | }
90 |
91 | public ICollection Keys => keys.AsReadOnly();
92 |
93 | public ICollection Values => values.AsReadOnly();
94 |
95 | public int Count => values.Count;
96 |
97 | public bool IsReadOnly => false;
98 |
99 | public void Add(PortableToken key, TValue value) {
100 | Add(key, value, false);
101 | }
102 |
103 | void Add(PortableToken key, TValue value, bool inserting) {
104 | if (value is null)
105 | throw new ArgumentNullException(nameof(value));
106 |
107 | int index = GetIndex(key);
108 | if (index > values.Count)
109 | throw new ArgumentException("Key is out of range", nameof(key));
110 |
111 | if (index == values.Count) {
112 | values.Add(value);
113 | keys.Add(index);
114 | return;
115 | }
116 |
117 | if (!inserting)
118 | throw new ArgumentException("Key already exists", nameof(key));
119 | values[index] = value;
120 | }
121 |
122 | void ICollection>.Add(KeyValuePair item) {
123 | Add(item.Key, item.Value);
124 | }
125 |
126 | public void Clear() {
127 | values.Clear();
128 | keys.Clear();
129 | }
130 |
131 | bool ICollection>.Contains(KeyValuePair item) {
132 | return ContainsKey(item.Key) && EqualityComparer.Default.Equals(values[GetIndex(item.Key)], item.Value);
133 | }
134 |
135 | public bool ContainsKey(PortableToken key) {
136 | return GetIndex(key) < values.Count;
137 | }
138 |
139 | void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) {
140 | if (array is null)
141 | throw new ArgumentNullException(nameof(array));
142 | if (arrayIndex < 0 || arrayIndex >= array.Length)
143 | throw new ArgumentOutOfRangeException(nameof(arrayIndex));
144 | if (array.Length - arrayIndex < values.Count)
145 | throw new ArgumentException("Insufficient space in destination array", nameof(array));
146 |
147 | for (int i = 0; i < values.Count; i++)
148 | array[arrayIndex + i] = new KeyValuePair(keys[i], values[i]);
149 | }
150 |
151 | public IEnumerator> GetEnumerator() {
152 | int oldCount = values.Count;
153 | for (int i = 0; i < oldCount; i++) {
154 | if (values.Count != oldCount)
155 | throw new InvalidOperationException("Collection was modified");
156 | yield return new KeyValuePair(keys[i], values[i]);
157 | }
158 | }
159 |
160 | public bool Remove(PortableToken key) {
161 | throw new NotSupportedException();
162 | }
163 |
164 | bool ICollection>.Remove(KeyValuePair item) {
165 | throw new NotImplementedException();
166 | }
167 |
168 | #pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes).
169 | public bool TryGetValue(PortableToken key, [MaybeNullWhen(false)] out TValue value) {
170 | #pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes).
171 | int index = GetIndex(key);
172 | if (index >= values.Count) {
173 | value = default;
174 | return false;
175 | }
176 | value = values[index];
177 | return true;
178 | }
179 |
180 | IEnumerator IEnumerable.GetEnumerator() {
181 | return GetEnumerator();
182 | }
183 |
184 | static int GetIndex(PortableToken token) {
185 | if (token.Name is not null)
186 | throw new ArgumentException("Token is named.", nameof(token));
187 | int index = token.Index;
188 | if (index < 0)
189 | throw new ArgumentOutOfRangeException(nameof(token));
190 | return index;
191 | }
192 |
193 | #region IDictionary
194 | ICollection IDictionary.Keys => (ICollection)Keys;
195 | ICollection IDictionary.Values => (ICollection)Values;
196 | bool IDictionary.IsFixedSize => false;
197 | object ICollection.SyncRoot => values;
198 | bool ICollection.IsSynchronized => false;
199 | object? IDictionary.this[object key] { get => this[(PortableToken)key]; set => this[(PortableToken)key] = (TValue)value!; }
200 | bool IDictionary.Contains(object key) { return ContainsKey((PortableToken)key); }
201 | void IDictionary.Add(object key, object? value) { Add((PortableToken)key, (TValue)value!); }
202 | IDictionaryEnumerator IDictionary.GetEnumerator() { return new DictionaryEnumerator(GetEnumerator()); }
203 | void IDictionary.Remove(object key) { Remove((PortableToken)key); }
204 | void ICollection.CopyTo(Array array, int index) { ((ICollection>)this).CopyTo((KeyValuePair[])array, index); }
205 | sealed class DictionaryEnumerator(IEnumerator> enumerator) : IDictionaryEnumerator {
206 | public object Current => Entry;
207 | public object Key => enumerator.Current.Key;
208 | public object Value => enumerator.Current.Value;
209 | public DictionaryEntry Entry => new(enumerator.Current.Key, enumerator.Current.Value);
210 | public bool MoveNext() { return enumerator.MoveNext(); }
211 | public void Reset() { enumerator.Reset(); }
212 | }
213 | #endregion
214 | }
215 |
216 | sealed class ListToDictionaryAdapter_CollectionDebugView(ListToDictionaryAdapter dictionary) where TValue : class {
217 | [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
218 | public TValue[] Items => [.. dictionary.Values];
219 | }
220 | }
221 |
222 | ///
223 | /// Represents a facade for portable metadata.
224 | ///
225 | ///
226 | public sealed class PortableMetadataFacade(PortableMetadata? metadata) {
227 | ///
228 | /// Represents a facade for accessing portable metadata.
229 | ///
230 | /// The type of the portable entity.
231 | /// The type of the portable entity definition.
232 | public struct EntityFacade where T : class where TDef : T {
233 | ///
234 | /// Gets or sets the dictionary of portable entity references.
235 | ///
236 | public IDictionary References { get; set; }
237 |
238 | ///
239 | /// Gets or sets the dictionary of portable entity definitions.
240 | ///
241 | public IDictionary Definitions { get; set; }
242 |
243 | ///
244 | /// Gets or sets the list of orders for portable entities.
245 | ///
246 | public IList Orders { get; set; }
247 | }
248 |
249 | ///
250 | /// Gets or sets the options of the portable metadata.
251 | ///
252 | public PortableMetadataOptions Options { get; set; } = metadata?.Options ?? 0;
253 |
254 | ///
255 | /// Gets or sets the entity facade for portable types.
256 | ///
257 | public EntityFacade Types { get; set; } = Copy(metadata?.Types, metadata?.Options ?? 0);
258 |
259 | ///
260 | /// Gets or sets the entity facade for portable fields.
261 | ///
262 | public EntityFacade Fields { get; set; } = Copy(metadata?.Fields, metadata?.Options ?? 0);
263 |
264 | ///
265 | /// Gets or sets the entity facade for portable methods.
266 | ///
267 | public EntityFacade Methods { get; set; } = Copy(metadata?.Methods, metadata?.Options ?? 0);
268 |
269 | ///
270 | /// The constructor for deserialization.
271 | ///
272 | public PortableMetadataFacade() : this(null) { }
273 |
274 | static EntityFacade Copy(IDictionary? source, PortableMetadataOptions useNamedToken) where T : class where TDef : T {
275 | var result = new EntityFacade {
276 | References = new Dictionary(),
277 | Definitions = new Dictionary(),
278 | Orders = []
279 | };
280 | if (source is null)
281 | return result;
282 | bool useNamedToken2 = (useNamedToken & PortableMetadataOptions.UseNamedToken) != 0;
283 | bool lastIsDef = false;
284 | int i = 0;
285 | foreach (var kvp in source) {
286 | var key = kvp.Key.ToString();
287 | var value = kvp.Value;
288 | bool addOrder;
289 | if (value is TDef t) {
290 | result.Definitions.Add(key, t);
291 | addOrder = !lastIsDef;
292 | lastIsDef = true;
293 | }
294 | else {
295 | result.References.Add(key, value);
296 | addOrder = lastIsDef;
297 | lastIsDef = false;
298 | }
299 | if (addOrder && useNamedToken2)
300 | result.Orders.Add(i);
301 | i++;
302 | }
303 | return result;
304 | }
305 |
306 | ///
307 | /// Converts the to .
308 | ///
309 | /// The converted .
310 | public PortableMetadata ToMetadata() {
311 | var metadata = new PortableMetadata(Options);
312 | bool useNamedToken = (Options & PortableMetadataOptions.UseNamedToken) != 0;
313 | Copy(Types, metadata.Types, useNamedToken);
314 | Copy(Fields, metadata.Fields, useNamedToken);
315 | Copy(Methods, metadata.Methods, useNamedToken);
316 | return metadata;
317 | }
318 |
319 | static void Copy(EntityFacade source, IDictionary destination, bool useNamedToken) where T : class where TDef : T {
320 | int count = source.References.Count + source.Definitions.Count;
321 | if (useNamedToken) {
322 | using var orders = source.Orders.GetEnumerator();
323 | using var refs = source.References.GetEnumerator();
324 | using var defs = source.Definitions.GetEnumerator();
325 | orders.MoveNext();
326 | bool lastIsDef = false;
327 | for (int i = 0; i < count; i++) {
328 | if (i == orders.Current) {
329 | lastIsDef = !lastIsDef;
330 | orders.MoveNext();
331 | }
332 | string key;
333 | T value;
334 | if (lastIsDef) {
335 | defs.MoveNext();
336 | key = defs.Current.Key;
337 | value = defs.Current.Value;
338 | }
339 | else {
340 | refs.MoveNext();
341 | key = refs.Current.Key;
342 | value = refs.Current.Value;
343 | }
344 | destination.Add(key, value);
345 | }
346 | }
347 | else {
348 | for (int i = 0; i < count; i++) {
349 | var key = i.ToString();
350 | var value = source.References.TryGetValue(key, out var t) ? t : source.Definitions[key];
351 | destination.Add(i, value);
352 | }
353 | }
354 | }
355 | }
356 |
357 | ///
358 | /// Represents the serialization level of the portable entity.
359 | ///
360 | public enum PortableMetadataLevel {
361 | ///
362 | /// Represents the reference level of the portable entity.
363 | ///
364 | Reference,
365 |
366 | ///
367 | /// Represents the definition level of the portable entity.
368 | ///
369 | Definition,
370 |
371 | ///
372 | /// Represents the definition with children level of the portable entity.
373 | ///
374 | DefinitionWithChildren
375 | }
376 |
377 | ///
378 | /// A helper type to create or update the portable metadata.
379 | ///
380 | /// The portable metadata.
381 | public sealed class PortableMetadataUpdater(PortableMetadata metadata) {
382 | sealed class TokenWithLevel {
383 | public PortableToken Token;
384 | public PortableMetadataLevel Level;
385 | }
386 |
387 | readonly Dictionary typeTokens = CreateTokenMap(metadata.Types);
388 | readonly Dictionary fieldTokens = CreateTokenMap(metadata.Fields);
389 | readonly Dictionary methodTokens = CreateTokenMap(metadata.Methods);
390 |
391 | bool UseNamedToken => (metadata.Options & PortableMetadataOptions.UseNamedToken) != 0;
392 |
393 | static Dictionary CreateTokenMap(IDictionary source) where T : class {
394 | var map = new Dictionary((IEqualityComparer)(object)PortableMetadataEqualityComparer.ReferenceComparer);
395 | foreach (var kvp in source) {
396 | var level = kvp.Value.GetType() == typeof(T) ? PortableMetadataLevel.Reference : PortableMetadataLevel.Definition;
397 | map.Add(kvp.Value, new TokenWithLevel {
398 | Token = kvp.Key,
399 | Level = level
400 | });
401 | }
402 | return map;
403 | }
404 |
405 | ///
406 | /// Updates the portable type in the portable metadata.
407 | ///
408 | /// The portable type to update.
409 | /// The serialization level of the portable type.
410 | /// The current serialization level of the portable type.
411 | /// The updated portable type token.
412 | public PortableToken Update(PortableType type, PortableMetadataLevel level, out PortableMetadataLevel currentLevel) {
413 | if (!typeTokens.TryGetValue(type, out var tl)) {
414 | tl = new TokenWithLevel {
415 | Token = UseNamedToken ? GetUniqueName(type.Name, s => metadata.Types.ContainsKey(s)) : metadata.Types.Count,
416 | Level = currentLevel = level
417 | };
418 | typeTokens.Add(type, tl);
419 | metadata.Types.Add(tl.Token, type);
420 | }
421 | else if (level > tl.Level) {
422 | tl.Level = currentLevel = level;
423 | metadata.Types[tl.Token] = type;
424 | }
425 | else
426 | currentLevel = tl.Level;
427 |
428 | return tl.Token;
429 | }
430 |
431 | ///
432 | /// Updates the portable field in the portable metadata.
433 | ///
434 | /// The portable field to update.
435 | /// The serialization level of the portable field.
436 | /// The current serialization level of the portable field.
437 | /// The updated portable field token.
438 | public PortableToken Update(PortableField field, PortableMetadataLevel level, out PortableMetadataLevel currentLevel) {
439 | if (!fieldTokens.TryGetValue(field, out var tl)) {
440 | tl = new TokenWithLevel {
441 | Token = UseNamedToken ? GetUniqueName(field.Type, field.Name, s => metadata.Fields.ContainsKey(s)) : metadata.Fields.Count,
442 | Level = currentLevel = level
443 | };
444 | fieldTokens.Add(field, tl);
445 | metadata.Fields.Add(tl.Token, field);
446 | }
447 | else if (level > tl.Level) {
448 | tl.Level = currentLevel = level;
449 | metadata.Fields[tl.Token] = field;
450 | }
451 | else
452 | currentLevel = tl.Level;
453 |
454 | return tl.Token;
455 | }
456 |
457 | ///
458 | /// Updates the portable method in the portable metadata.
459 | ///
460 | /// The portable method to update.
461 | /// The serialization level of the portable method.
462 | /// The current serialization level of the portable method.
463 | /// The updated portable method token.
464 | public PortableToken Update(PortableMethod method, PortableMetadataLevel level, out PortableMetadataLevel currentLevel) {
465 | if (!methodTokens.TryGetValue(method, out var tl)) {
466 | tl = new TokenWithLevel {
467 | Token = UseNamedToken ? GetUniqueName(method.Type, method.Name, s => metadata.Methods.ContainsKey(s)) : metadata.Methods.Count,
468 | Level = currentLevel = level
469 | };
470 | methodTokens.Add(method, tl);
471 | metadata.Methods.Add(tl.Token, method);
472 | }
473 | else if (level > tl.Level) {
474 | tl.Level = currentLevel = level;
475 | metadata.Methods[tl.Token] = method;
476 | }
477 | else
478 | currentLevel = tl.Level;
479 |
480 | return tl.Token;
481 | }
482 |
483 | static string GetUniqueName(PortableComplexType type, string baseName, Func hasName) {
484 | var typeName = type.Kind == PortableComplexTypeKind.Token ? type.Token.Name : PortableComplexTypeFormatter.GetScopeType(type)!.Value + ">";
485 | return GetUniqueName(typeName + "::" + baseName, hasName);
486 | }
487 |
488 | static string GetUniqueName(string baseName, Func hasName) {
489 | var sb = new StringBuilder(baseName);
490 | for (int i = 0; i < sb.Length; i++) {
491 | bool flag = false;
492 | foreach (char c in PortableToken.InvalidNameChars) {
493 | if (sb[i] == c) {
494 | flag = true;
495 | break;
496 | }
497 | }
498 | if (flag)
499 | sb[i] = '_';
500 | }
501 | baseName = sb.ToString();
502 |
503 | if (!hasName(baseName))
504 | return baseName;
505 |
506 | for (int n = 2; n < int.MaxValue; n++) {
507 | var name = $"{baseName}_{n}";
508 | if (!hasName(name))
509 | return name;
510 | }
511 |
512 | throw new InvalidOperationException();
513 | }
514 | }
515 |
--------------------------------------------------------------------------------
/PortableMetadata/PortableMetadata.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net20;net40;netstandard2.0;net6.0;net8.0
4 | 12.0
5 | enable
6 | true
7 |
8 |
9 |
--------------------------------------------------------------------------------
/PortableMetadata/PortableMetadataEqualityComparer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace MetadataSerialization;
5 |
6 | ///
7 | /// The equality comparer for , , , and .
8 | ///
9 | public sealed class PortableMetadataEqualityComparer : IEqualityComparer, IEqualityComparer, IEqualityComparer, IEqualityComparer {
10 | readonly bool onlyReference;
11 | readonly bool nullEqualsEmpty;
12 |
13 | ///
14 | /// Gets the default equality comparer that compares only the reference.
15 | ///
16 | public static readonly PortableMetadataEqualityComparer ReferenceComparer = new(true, true);
17 |
18 | ///
19 | /// Gets the default equality comparer that compares all properties.
20 | ///
21 | public static readonly PortableMetadataEqualityComparer FullComparer = new(false, false);
22 |
23 | PortableMetadataEqualityComparer(bool onlyReference, bool nullEqualsEmpty) {
24 | this.onlyReference = onlyReference;
25 | this.nullEqualsEmpty = nullEqualsEmpty;
26 | }
27 |
28 | ///
29 | public bool Equals(PortableType? x, PortableType? y) {
30 | if (ReferenceEquals(x, y))
31 | return true;
32 | if (x is null || y is null)
33 | return false;
34 | if (!onlyReference && x.GetType() != y.GetType())
35 | return false;
36 | if (x.Name != y.Name || x.Namespace != y.Namespace || x.Assembly != y.Assembly || !Equals_NameList(x.EnclosingNames, y.EnclosingNames))
37 | return false;
38 | if (onlyReference || (x is not PortableTypeDef && y is not PortableTypeDef))
39 | return true;
40 |
41 | if (x is not PortableTypeDef xd || y is not PortableTypeDef yd)
42 | return false;
43 | if (xd.Attributes != yd.Attributes)
44 | return false;
45 | if (!Equals_ComplexType(xd.BaseType, yd.BaseType))
46 | return false;
47 |
48 | if (!Equals_CustomAttributeList(xd.CustomAttributes, yd.CustomAttributes))
49 | return false;
50 | if (!Equals_GenericParamList(xd.GenericParameters, yd.GenericParameters))
51 | return false;
52 | if (!Equals_ComplexTypeList(xd.Interfaces, yd.Interfaces))
53 | return false;
54 | if (!Equals_ClassLayout(xd.ClassLayout, yd.ClassLayout))
55 | return false;
56 |
57 | if (!Equals_TokenList(xd.NestedTypes, yd.NestedTypes))
58 | return false;
59 | if (!Equals_TokenList(xd.Fields, yd.Fields))
60 | return false;
61 | if (!Equals_TokenList(xd.Methods, yd.Methods))
62 | return false;
63 | if (!Equals_PropertyList(xd.Properties, yd.Properties))
64 | return false;
65 | if (!Equals_EventList(xd.Events, yd.Events))
66 | return false;
67 | return true;
68 | }
69 |
70 | ///
71 | public int GetHashCode(PortableType obj) {
72 | if (obj is null)
73 | return 0;
74 | return (((((obj.Name.GetHashCode() * -1521134295) + obj.Namespace.GetHashCode()) * -1521134295) + (obj.Assembly?.GetHashCode() ?? 0)) * -1521134295) + GetHashCode_NameList(obj.EnclosingNames);
75 | }
76 |
77 | ///
78 | public bool Equals(PortableField? x, PortableField? y) {
79 | if (ReferenceEquals(x, y))
80 | return true;
81 | if (x is null || y is null)
82 | return false;
83 | if (!onlyReference && x.GetType() != y.GetType())
84 | return false;
85 | if (x.Name != y.Name || !Equals(x.Type, y.Type) || !Equals(x.Signature, y.Signature))
86 | return false;
87 | if (onlyReference || (x is not PortableFieldDef && y is not PortableFieldDef))
88 | return true;
89 |
90 | if (x is not PortableFieldDef xd || y is not PortableFieldDef yd)
91 | return false;
92 | if (xd.Attributes != yd.Attributes)
93 | return false;
94 | if (!Equals_ByteArray(xd.InitialValue, yd.InitialValue))
95 | return false;
96 |
97 | if (!Equals_CustomAttributeList(xd.CustomAttributes, yd.CustomAttributes))
98 | return false;
99 | if (!Equals_Constant(xd.Constant, yd.Constant))
100 | return false;
101 | return true;
102 | }
103 |
104 | ///
105 | public int GetHashCode(PortableField obj) {
106 | if (obj is null)
107 | return 0;
108 | return (((obj.Name.GetHashCode() * -1521134295) + GetHashCode(obj.Type)) * -1521134295) + GetHashCode(obj.Signature);
109 | }
110 |
111 | ///
112 | public bool Equals(PortableMethod? x, PortableMethod? y) {
113 | if (ReferenceEquals(x, y))
114 | return true;
115 | if (x is null || y is null)
116 | return false;
117 | if (!onlyReference && x.GetType() != y.GetType())
118 | return false;
119 | if (x.Name != y.Name || !Equals(x.Type, y.Type) || !Equals(x.Signature, y.Signature))
120 | return false;
121 | if (onlyReference || (x is not PortableMethodDef && y is not PortableMethodDef))
122 | return true;
123 |
124 | if (x is not PortableMethodDef xd || y is not PortableMethodDef yd)
125 | return false;
126 | if (xd.Attributes != yd.Attributes || xd.ImplAttributes != yd.ImplAttributes)
127 | return false;
128 | if (!Equals_ParamList(xd.Parameters, yd.Parameters))
129 | return false;
130 | if (!Equals_MethodBody(xd.Body, yd.Body))
131 | return false;
132 |
133 | if (!Equals_CustomAttributeList(xd.CustomAttributes, yd.CustomAttributes))
134 | return false;
135 | if (!Equals_GenericParamList(xd.GenericParameters, yd.GenericParameters))
136 | return false;
137 | if (!Equals_TokenList(xd.Overrides, yd.Overrides))
138 | return false;
139 | if (!Equals_ImplMap(xd.ImplMap, yd.ImplMap))
140 | return false;
141 | return true;
142 | }
143 |
144 | ///
145 | public int GetHashCode(PortableMethod obj) {
146 | if (obj is null)
147 | return 0;
148 | return (((obj.Name.GetHashCode() * -1521134295) + GetHashCode(obj.Type)) * -1521134295) + GetHashCode(obj.Signature);
149 | }
150 |
151 | ///
152 | public bool Equals(PortableComplexType x, PortableComplexType y) {
153 | return x.Kind == y.Kind && x.Token == y.Token && x.Type == y.Type && Equals_ComplexTypeList(x.Arguments, y.Arguments);
154 | }
155 |
156 | ///
157 | public int GetHashCode(PortableComplexType obj) {
158 | int hash = obj.Kind.GetHashCode();
159 | hash = (hash * -1521134295) + obj.Token.GetHashCode();
160 | hash = (hash * -1521134295) + obj.Type.GetHashCode();
161 | var args = obj.Arguments;
162 | if (args is null)
163 | return hash;
164 | foreach (var arg in args)
165 | hash = (hash * -1521134295) + GetHashCode(arg);
166 | return hash;
167 | }
168 |
169 | bool Equals_ComplexTypeList(IList? x, IList? y) {
170 | if (x == y)
171 | return true;
172 | if (x is null || y is null)
173 | return HandleNullableListCount(x, y);
174 | if (x.Count != y.Count)
175 | return false;
176 | for (int i = 0; i < x.Count; i++) {
177 | if (!Equals(x[i], y[i]))
178 | return false;
179 | }
180 | return true;
181 | }
182 |
183 | bool Equals_NameList(IList? x, IList? y) {
184 | if (x == y)
185 | return true;
186 | if (x is null || y is null)
187 | return HandleNullableListCount(x, y);
188 | if (x.Count != y.Count)
189 | return false;
190 | for (int i = 0; i < x.Count; i++) {
191 | if (x[i] != y[i])
192 | return false;
193 | }
194 | return true;
195 | }
196 |
197 | static int GetHashCode_NameList(IList? obj) {
198 | if (obj is null)
199 | return 0;
200 | int hash = 0;
201 | foreach (var name in obj)
202 | hash = (hash * -1521134295) + name.GetHashCode();
203 | return hash;
204 | }
205 |
206 | bool HandleNullableListCount(IList? x, IList? y) {
207 | if (nullEqualsEmpty) {
208 | if (x is null)
209 | return y!.Count == 0;
210 | if (y is null)
211 | return x!.Count == 0;
212 | }
213 | return false;
214 | }
215 |
216 | #region Definition Comparer
217 | bool Equals_ComplexType(PortableComplexType? x, PortableComplexType? y) {
218 | if (x is PortableComplexType x1)
219 | return y is PortableComplexType y1 && Equals(x1, y1);
220 | else
221 | return y is null;
222 | }
223 |
224 | static bool Equals_ClassLayout(PortableClassLayout? x, PortableClassLayout? y) {
225 | if (x is PortableClassLayout x1) {
226 | if (y is not PortableClassLayout y1)
227 | return false;
228 | return x1.PackingSize == y1.PackingSize && x1.ClassSize == y1.ClassSize;
229 | }
230 | else
231 | return y is null;
232 | }
233 |
234 | static bool Equals_ImplMap(PortableImplMap? x, PortableImplMap? y) {
235 | if (x is PortableImplMap x1) {
236 | if (y is not PortableImplMap y1)
237 | return false;
238 | return x1.Name == y1.Name && x1.Module == y1.Module && x1.Attributes == y1.Attributes;
239 | }
240 | else
241 | return y is null;
242 | }
243 |
244 | bool Equals_MethodBody(PortableMethodBody? x, PortableMethodBody? y) {
245 | if (x is PortableMethodBody x1) {
246 | if (y is not PortableMethodBody y1)
247 | return false;
248 | if (!Equals_InstructionList(x1.Instructions, y1.Instructions))
249 | return false;
250 | if (!Equals_ExceptionHandlerList(x1.ExceptionHandlers, y1.ExceptionHandlers))
251 | return false;
252 | if (!Equals_ComplexTypeList(x1.Variables, y1.Variables))
253 | return false;
254 | return true;
255 | }
256 | else
257 | return y is null;
258 | }
259 |
260 | static bool Equals_Constant(PortableConstant? x, PortableConstant? y) {
261 | if (x is PortableConstant x1) {
262 | if (y is not PortableConstant y1)
263 | return false;
264 | if (x1.Type != y1.Type)
265 | return false;
266 | if (x1.Value is object xv)
267 | return y1.Value is object yv && xv.Equals(yv);
268 | else
269 | return y1.Value is null;
270 | }
271 | else
272 | return y is null;
273 | }
274 |
275 | bool Equals_TokenList(IList? x, IList? y) {
276 | if (x == y)
277 | return true;
278 | if (x is null || y is null)
279 | return HandleNullableListCount(x, y);
280 | if (x.Count != y.Count)
281 | return false;
282 | for (int i = 0; i < x.Count; i++) {
283 | if (x[i] != y[i])
284 | return false;
285 | }
286 | return true;
287 | }
288 |
289 | bool Equals_CustomAttributeList(IList? x, IList? y) {
290 | if (x == y)
291 | return true;
292 | if (x is null || y is null)
293 | return HandleNullableListCount(x, y);
294 | if (x.Count != y.Count)
295 | return false;
296 | for (int i = 0; i < x.Count; i++) {
297 | if (x[i].Constructor != y[i].Constructor)
298 | return false;
299 | if (!Equals_ByteArray(x[i].RawData, y[i].RawData))
300 | return false;
301 | }
302 | return true;
303 | }
304 |
305 | bool Equals_GenericParamList(IList? x, IList? y) {
306 | if (x == y)
307 | return true;
308 | if (x is null || y is null)
309 | return HandleNullableListCount(x, y);
310 | if (x.Count != y.Count)
311 | return false;
312 | for (int i = 0; i < x.Count; i++) {
313 | if (x[i].Name != y[i].Name || x[i].Attributes != y[i].Attributes || x[i].Number != y[i].Number || !Equals_ComplexTypeList(x[i].Constraints, y[i].Constraints))
314 | return false;
315 | }
316 | return true;
317 | }
318 |
319 | bool Equals_PropertyList(IList? x, IList? y) {
320 | if (x == y)
321 | return true;
322 | if (x is null || y is null)
323 | return HandleNullableListCount(x, y);
324 | if (x.Count != y.Count)
325 | return false;
326 | for (int i = 0; i < x.Count; i++) {
327 | if (x[i].Name != y[i].Name || !Equals(x[i].Signature, y[i].Signature) || x[i].Attributes != y[i].Attributes
328 | || x[i].GetMethod != y[i].GetMethod || x[i].SetMethod != y[i].SetMethod || !Equals_CustomAttributeList(x[i].CustomAttributes, y[i].CustomAttributes))
329 | return false;
330 | }
331 | return true;
332 | }
333 |
334 | bool Equals_EventList(IList? x, IList? y) {
335 | if (x == y)
336 | return true;
337 | if (x is null || y is null)
338 | return HandleNullableListCount(x, y);
339 | if (x.Count != y.Count)
340 | return false;
341 | for (int i = 0; i < x.Count; i++) {
342 | if (x[i].Name != y[i].Name || !Equals(x[i].Type, y[i].Type) || x[i].Attributes != y[i].Attributes || x[i].AddMethod != y[i].AddMethod
343 | || x[i].RemoveMethod != y[i].RemoveMethod || x[i].InvokeMethod != y[i].InvokeMethod || !Equals_CustomAttributeList(x[i].CustomAttributes, y[i].CustomAttributes))
344 | return false;
345 | }
346 | return true;
347 | }
348 |
349 | bool Equals_ParamList(IList? x, IList? y) {
350 | if (x == y)
351 | return true;
352 | if (x is null || y is null)
353 | return HandleNullableListCount(x, y);
354 | if (x.Count != y.Count)
355 | return false;
356 | for (int i = 0; i < x.Count; i++) {
357 | if (x[i].Name != y[i].Name || x[i].Sequence != y[i].Sequence || x[i].Attributes != y[i].Attributes
358 | || !Equals_CustomAttributeList(x[i].CustomAttributes, y[i].CustomAttributes) || !Equals_Constant(x[i].Constant, y[i].Constant))
359 | return false;
360 | }
361 | return true;
362 | }
363 |
364 | bool Equals_ExceptionHandlerList(IList? x, IList? y) {
365 | if (x == y)
366 | return true;
367 | if (x is null || y is null)
368 | return HandleNullableListCount(x, y);
369 | if (x.Count != y.Count)
370 | return false;
371 | for (int i = 0; i < x.Count; i++) {
372 | if (x[i].TryStart != y[i].TryStart || x[i].TryEnd != y[i].TryEnd || x[i].FilterStart != y[i].FilterStart
373 | || x[i].HandlerStart != y[i].HandlerStart || x[i].HandlerType != y[i].HandlerType || !Equals_ComplexType(x[i].CatchType, y[i].CatchType))
374 | return false;
375 | }
376 | return true;
377 | }
378 |
379 | bool Equals_InstructionList(IList? x, IList? y) {
380 | if (x == y)
381 | return true;
382 | if (x is null || y is null)
383 | return HandleNullableListCount(x, y);
384 | if (x.Count != y.Count)
385 | return false;
386 | for (int i = 0; i < x.Count; i++) {
387 | if (x[i].OpCode != y[i].OpCode)
388 | return false;
389 | switch (x[i].Operand) {
390 | case null:
391 | if (y[i].Operand is not null)
392 | return false;
393 | break;
394 | case int xi:
395 | if (y[i].Operand is not int yi || xi != yi)
396 | return false;
397 | break;
398 | case long xl:
399 | if (y[i].Operand is not long yl || xl != yl)
400 | return false;
401 | break;
402 | case float xf:
403 | if (y[i].Operand is not float yf || BitConverter.DoubleToInt64Bits(xf) != BitConverter.DoubleToInt64Bits(yf))
404 | return false;
405 | break;
406 | case double xd:
407 | if (y[i].Operand is not double yd || BitConverter.DoubleToInt64Bits(xd) != BitConverter.DoubleToInt64Bits(yd))
408 | return false;
409 | break;
410 | case string xs:
411 | if (y[i].Operand is not string ys || xs != ys)
412 | return false;
413 | break;
414 | case int[] xia:
415 | if (y[i].Operand is not int[] yia || !Equals_Int32Array(xia, yia))
416 | return false;
417 | break;
418 | case PortableComplexType xt:
419 | if (y[i].Operand is not PortableComplexType yt || !Equals(xt, yt))
420 | return false;
421 | break;
422 | default:
423 | throw new NotSupportedException();
424 | }
425 | }
426 | return true;
427 | }
428 |
429 | bool Equals_Int32Array(int[]? x, int[]? y) {
430 | if (x == y)
431 | return true;
432 | if (x is null || y is null)
433 | return HandleNullableListCount(x, y);
434 | if (x.Length != y.Length)
435 | return false;
436 | for (int i = 0; i < x.Length; i++) {
437 | if (x[i] != y[i])
438 | return false;
439 | }
440 | return true;
441 | }
442 |
443 | bool Equals_ByteArray(byte[]? x, byte[]? y) {
444 | if (x == y)
445 | return true;
446 | if (x is null || y is null)
447 | return HandleNullableListCount(x, y);
448 | if (x.Length != y.Length)
449 | return false;
450 | for (int i = 0; i < x.Length; i++) {
451 | if (x[i] != y[i])
452 | return false;
453 | }
454 | return true;
455 | }
456 | #endregion
457 | }
458 |
--------------------------------------------------------------------------------
/PortableMetadata/PortableMethod.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Diagnostics;
3 | using System;
4 | using ElementType2 = MetadataSerialization.PortableComplexTypeFormatter.ElementType;
5 |
6 | namespace MetadataSerialization;
7 |
8 | ///
9 | /// Represents a portable method.
10 | ///
11 | /// The name of the method.
12 | /// The declaring type of the method.
13 | /// The signature of the method.
14 | public class PortableMethod(string name, PortableComplexType type, PortableComplexType signature) {
15 | ///
16 | /// Gets or sets the name of the method.
17 | ///
18 | public string Name { get; set; } = name ?? throw new ArgumentNullException(nameof(name));
19 |
20 | ///
21 | /// Gets or sets the declaring type of the method.
22 | ///
23 | /// TypeDefOrRef
24 | public PortableComplexType Type { get; set; } = type;
25 |
26 | ///
27 | /// Gets or sets the signature of the method.
28 | ///
29 | /// MethodSig
30 | public PortableComplexType Signature { get; set; } = signature;
31 |
32 | ///
33 | /// Returns the name of the method.
34 | ///
35 | /// The name of the method.
36 | public override string ToString() {
37 | return Name;
38 | }
39 | }
40 |
41 | ///
42 | /// Represents a portable parameter.
43 | ///
44 | /// The name of the parameter.
45 | /// The sequence of the parameter.
46 | /// The attributes of the parameter.
47 | /// The constant value of the parameter.
48 | /// The custom attributes of the parameter.
49 | public struct PortableParameter(string name, int sequence, int attributes, PortableConstant? constant, IList? customAttributes) {
50 | ///
51 | /// Gets or sets the name of the parameter.
52 | ///
53 | public string Name { get; set; } = name ?? throw new ArgumentNullException(nameof(name));
54 |
55 | ///
56 | /// Gets or sets the sequence of the parameter.
57 | ///
58 | public int Sequence { get; set; } = sequence;
59 |
60 | ///
61 | /// Gets or sets the attributes of the parameter.
62 | ///
63 | public int Attributes { get; set; } = attributes;
64 |
65 | ///
66 | /// Gets or sets the constant value of the parameter.
67 | ///
68 | public PortableConstant? Constant { get; set; } = constant;
69 |
70 | ///
71 | /// Gets or sets the custom attributes of the parameter.
72 | ///
73 | public IList? CustomAttributes { get; set; } = customAttributes;
74 | }
75 |
76 | ///
77 | /// Represents a portable instruction.
78 | ///
79 | /// The opcode of the instruction.
80 | /// The operand of the instruction.
81 | public struct PortableInstruction(string opCode, object? operand) {
82 | ///
83 | /// Gets or sets the opcode of the instruction.
84 | ///
85 | public string OpCode { get; set; } = opCode ?? throw new ArgumentNullException(nameof(opCode));
86 |
87 | ///
88 | /// Gets or sets the operand of the instruction.
89 | ///
90 | ///
91 | /// The operand can be of type , , , , , , or .
92 | /// For , , , , , types, they should be stored as .
93 | ///
94 | public object? Operand { get; set; } = operand;
95 |
96 | ///
97 | [Obsolete("Reserved for deserialization.")]
98 | public long? PrimitiveValue {
99 | readonly get => PrimitivesHelper.ToSlot(Operand);
100 | set {
101 | Debug.Assert(OpCode is not null, "We assume OpCode is set before setting Operand.");
102 | if (value is null)
103 | return;
104 | int type;
105 | switch (OpCode) {
106 | case "ldc.i8":
107 | type = (int)ElementType2.I8;
108 | break;
109 | case "ldc.r4":
110 | type = (int)ElementType2.R4;
111 | break;
112 | case "ldc.r8":
113 | type = (int)ElementType2.R8;
114 | break;
115 | default:
116 | type = (int)ElementType2.I4;
117 | break;
118 | }
119 | if (PrimitivesHelper.FromSlot(value, type) is object slot)
120 | Operand = slot;
121 | }
122 | }
123 |
124 | ///
125 | [Obsolete("Reserved for deserialization.")]
126 | public string? StringValue {
127 | readonly get => Operand as string;
128 | set {
129 | Debug.Assert(OpCode is not null, "We assume OpCode is set before setting Operand.");
130 | if (OpCode == "ldstr" && value is string s)
131 | Operand = s;
132 | }
133 | }
134 |
135 | // Newtonsoft.Json's default behavior of deserializing List is appending to the existing list instead of replacing it. Use fixed-size array can avoid some potential issues.
136 | ///
137 | [Obsolete("Reserved for deserialization.")]
138 | public int[]? Int32ArrayValue {
139 | readonly get => Operand as int[];
140 | set {
141 | Debug.Assert(OpCode is not null, "We assume OpCode is set before setting Operand.");
142 | if (OpCode == "switch" && value is int[] v)
143 | Operand = v;
144 | }
145 | }
146 |
147 | ///
148 | [Obsolete("Reserved for deserialization.")]
149 | public PortableComplexType? TypeValue {
150 | readonly get => Operand is PortableComplexType type ? type : default(PortableComplexType?);
151 | set {
152 | Debug.Assert(OpCode is not null, "We assume OpCode is set before setting Operand.");
153 | if (value is PortableComplexType type)
154 | Operand = type;
155 | }
156 | }
157 | }
158 |
159 | ///
160 | /// Represents a portable method.
161 | ///
162 | /// The start index of the try block.
163 | /// The end index of the try block.
164 | /// The start index of the filter block.
165 | /// The start index of the handler block.
166 | /// The end index of the handler block.
167 | /// The catch type of the exception handler.
168 | /// The type of the exception handler.
169 | public struct PortableExceptionHandler(int tryStart, int tryEnd, int filterStart, int handlerStart, int handlerEnd, PortableComplexType? catchType, int handlerType) {
170 | ///
171 | /// Gets or sets the start index of the try block.
172 | ///
173 | public int TryStart { get; set; } = tryStart;
174 |
175 | ///
176 | /// Gets or sets the end index of the try block.
177 | ///
178 | public int TryEnd { get; set; } = tryEnd;
179 |
180 | ///
181 | /// Gets or sets the start index of the filter block.
182 | ///
183 | public int FilterStart { get; set; } = filterStart;
184 |
185 | ///
186 | /// Gets or sets the start index of the handler block.
187 | ///
188 | public int HandlerStart { get; set; } = handlerStart;
189 |
190 | ///
191 | /// Gets or sets the end index of the handler block.
192 | ///
193 | public int HandlerEnd { get; set; } = handlerEnd;
194 |
195 | ///
196 | /// Gets or sets the catch type of the exception handler.
197 | ///
198 | /// TypeDefOrRef
199 | public PortableComplexType? CatchType { get; set; } = catchType;
200 |
201 | ///
202 | /// Gets or sets the type of the exception handler.
203 | ///
204 | public int HandlerType { get; set; } = handlerType;
205 | }
206 |
207 | ///
208 | /// Represents a portable method body.
209 | ///
210 | /// The list of instructions in the method body.
211 | /// The list of exception handlers in the method body.
212 | /// The list of variables in the method body.
213 | /// The max stack value of the method body.
214 | /// The init locals flag.
215 | public struct PortableMethodBody(IList instructions, IList exceptionHandlers, IList variables, int maxStack, bool initLocals) {
216 | ///
217 | /// Gets or sets the list of instructions in the method body.
218 | ///
219 | public IList Instructions { get; set; } = instructions;
220 |
221 | ///
222 | /// Gets or sets the list of exception handlers in the method body.
223 | ///
224 | public IList ExceptionHandlers { get; set; } = exceptionHandlers;
225 |
226 | ///
227 | /// Gets or sets the list of variables in the method body.
228 | ///
229 | public IList Variables { get; set; } = variables;
230 |
231 | ///
232 | /// Gets or sets the max stack value of the method body.
233 | ///
234 | public int MaxStack { get; set; } = maxStack;
235 |
236 | ///
237 | /// Gets or sets the init locals flag.
238 | ///
239 | public bool InitLocals { get; set; } = initLocals;
240 | }
241 |
242 | ///
243 | /// Represents a portable PInvoke mapping.
244 | ///
245 | /// The name of the PInvoke method.
246 | /// The module name of the PInvoke method.
247 | /// The attributes of the PInvoke method.
248 | public struct PortableImplMap(string name, string module, int attributes) {
249 | ///
250 | /// Gets or sets the name of the PInvoke method.
251 | ///
252 | public string Name { get; set; } = name ?? throw new ArgumentNullException(nameof(name));
253 |
254 | ///
255 | /// Gets or sets the module name of the PInvoke method.
256 | ///
257 | public string Module { get; set; } = module ?? throw new ArgumentNullException(nameof(module));
258 |
259 | ///
260 | /// Gets or sets the attributes of the PInvoke method.
261 | ///
262 | public int Attributes { get; set; } = attributes;
263 |
264 | ///
265 | /// Returns the name of the PInvoke method.
266 | ///
267 | /// The name of the PInvoke method.
268 | public override readonly string ToString() {
269 | return Name;
270 | }
271 | }
272 |
273 | ///
274 | /// Represents a portable method definition.
275 | ///
276 | /// The name of the method.
277 | /// The declaring type of the method.
278 | /// The signature of the method.
279 | /// The attributes of the method.
280 | /// The implementation attributes of the method.
281 | /// The parameters of the method.
282 | /// The method body.
283 | /// The overridden methods.
284 | /// The PInvoke mapping information.
285 | /// The generic parameters of the method.
286 | /// The custom attributes of the method.
287 | public class PortableMethodDef(string name, PortableComplexType type, PortableComplexType signature, int attributes, int implAttributes,
288 | IList parameters, PortableMethodBody? body, IList? overrides, PortableImplMap? implMap,
289 | IList? genericParameters, IList? customAttributes)
290 | : PortableMethod(name, type, signature) {
291 | ///
292 | /// Gets or sets the attributes of the method.
293 | ///
294 | public int Attributes { get; set; } = attributes;
295 |
296 | ///
297 | /// Gets or sets the implementation attributes of the method.
298 | ///
299 | public int ImplAttributes { get; set; } = implAttributes;
300 |
301 | ///
302 | /// Gets or sets the parameters of the method.
303 | ///
304 | public IList Parameters { get; set; } = parameters;
305 |
306 | ///
307 | /// Gets or sets the method body.
308 | ///
309 | public PortableMethodBody? Body { get; set; } = body;
310 |
311 | ///
312 | /// Gets or sets the overridden methods.
313 | ///
314 | /// MethodDefOrRef
315 | public IList? Overrides { get; set; } = overrides;
316 |
317 | ///
318 | /// Gets or sets the PInvoke mapping information.
319 | ///
320 | public PortableImplMap? ImplMap { get; set; } = implMap;
321 |
322 | ///
323 | /// Gets or sets the generic parameters of the method.
324 | ///
325 | public IList? GenericParameters { get; set; } = genericParameters;
326 |
327 | ///
328 | /// Gets or sets the custom attributes of the method.
329 | ///
330 | public IList? CustomAttributes { get; set; } = customAttributes;
331 | }
332 |
--------------------------------------------------------------------------------
/PortableMetadata/PortableType.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace MetadataSerialization;
5 |
6 | ///
7 | /// Represents a portable type.
8 | ///
9 | /// The name of the type.
10 | /// The namespace of the type.
11 | /// The assembly of the type.
12 | /// The enclosing names of the type.
13 | public class PortableType(string name, string @namespace, string? assembly, IList? enclosingNames) {
14 | ///
15 | /// Gets or sets the name of the type.
16 | ///
17 | public string Name { get; set; } = name ?? throw new ArgumentNullException(nameof(name));
18 |
19 | ///
20 | /// Gets or sets the namespace of the type.
21 | ///
22 | public string Namespace { get; set; } = @namespace ?? throw new ArgumentNullException(nameof(@namespace));
23 |
24 | ///
25 | /// Gets or sets the assembly of the type.
26 | ///
27 | public string? Assembly { get; set; } = assembly;
28 |
29 | ///
30 | /// Gets or sets the enclosing names of the type.
31 | ///
32 | public IList? EnclosingNames { get; set; } = enclosingNames;
33 |
34 | ///
35 | /// Returns the name of the type.
36 | ///
37 | /// The name of the type.
38 | public override string ToString() {
39 | return Name;
40 | }
41 | }
42 |
43 | ///
44 | /// Represents the layout of a type.
45 | ///
46 | /// The packing size of the type.
47 | /// The size of the type.
48 | public struct PortableClassLayout(int packingSize, int classSize) {
49 | ///
50 | /// Gets or sets the packing size of the type.
51 | ///
52 | public int PackingSize { get; set; } = packingSize;
53 |
54 | ///
55 | /// Gets or sets the size of the type.
56 | ///
57 | public int ClassSize { get; set; } = classSize;
58 | }
59 |
60 | ///
61 | /// Represents a portable property.
62 | ///
63 | /// The name of the property.
64 | /// The signature of the property.
65 | /// The attributes of the property.
66 | /// The get method of the property.
67 | /// The set method of the property.
68 | /// The custom attributes of the property.
69 | public struct PortableProperty(string name, PortableComplexType signature, int attributes, PortableToken? getMethod, PortableToken? setMethod, IList? customAttributes) {
70 | ///
71 | /// Gets or sets the name of the property.
72 | ///
73 | public string Name { get; set; } = name ?? throw new ArgumentNullException(nameof(name));
74 |
75 | ///
76 | /// Gets or sets the signature of the property.
77 | ///
78 | /// PropertySig
79 | public PortableComplexType Signature { get; set; } = signature;
80 |
81 | ///
82 | /// Gets or sets the attributes of the property.
83 | ///
84 | public int Attributes { get; set; } = attributes;
85 |
86 | ///
87 | /// Gets or sets the get method of the property.
88 | ///
89 | public PortableToken? GetMethod { get; set; } = getMethod;
90 |
91 | ///
92 | /// Gets or sets the set method of the property.
93 | ///
94 | public PortableToken? SetMethod { get; set; } = setMethod;
95 |
96 | ///
97 | /// Gets or sets the custom attributes of the property.
98 | ///
99 | public IList? CustomAttributes { get; set; } = customAttributes;
100 |
101 | ///
102 | /// Returns the name of the property.
103 | ///
104 | /// The name of the property.
105 | public override readonly string ToString() {
106 | return Name;
107 | }
108 | }
109 |
110 | ///
111 | /// Represents a portable event.
112 | ///
113 | /// The name of the event.
114 | /// The type of the event.
115 | /// The attributes of the event.
116 | /// The add method of the event.
117 | /// The remove method of the event.
118 | /// The invoke method of the event.
119 | /// The custom attributes of the event.
120 | public struct PortableEvent(string name, PortableComplexType type, int attributes, PortableToken? addMethod, PortableToken? removeMethod, PortableToken? invokeMethod, IList? customAttributes) {
121 | ///
122 | /// Gets or sets the name of the event.
123 | ///
124 | public string Name { get; set; } = name ?? throw new ArgumentNullException(nameof(name));
125 |
126 | ///
127 | /// Gets or sets the type of the event.
128 | ///
129 | /// TypeDefOrRef
130 | public PortableComplexType Type { get; set; } = type;
131 |
132 | ///
133 | /// Gets or sets the attributes of the event.
134 | ///
135 | public int Attributes { get; set; } = attributes;
136 |
137 | ///
138 | /// Gets or sets the add method of the event.
139 | ///
140 | public PortableToken? AddMethod { get; set; } = addMethod;
141 |
142 | ///
143 | /// Gets or sets the remove method of the event.
144 | ///
145 | public PortableToken? RemoveMethod { get; set; } = removeMethod;
146 |
147 | ///
148 | /// Gets or sets the invoke method of the event.
149 | ///
150 | public PortableToken? InvokeMethod { get; set; } = invokeMethod;
151 |
152 | ///
153 | /// Gets or sets the custom attributes of the event.
154 | ///
155 | public IList? CustomAttributes { get; set; } = customAttributes;
156 |
157 | ///
158 | /// Returns the name of the event.
159 | ///
160 | /// The name of the event.
161 | public override readonly string ToString() {
162 | return Name;
163 | }
164 | }
165 |
166 | ///
167 | /// Represents a portable type definition.
168 | ///
169 | /// The name of the type.
170 | /// The namespace of the type.
171 | /// The assembly of the type.
172 | /// The enclosing names of the type.
173 | /// The attributes of the type.
174 | /// The base type of the type.
175 | /// The interfaces implemented by the type.
176 | /// The class layout of the type.
177 | /// The generic parameters of the type.
178 | /// The custom attributes of the type.
179 | public sealed class PortableTypeDef(string name, string @namespace, string? assembly, IList? enclosingNames, int attributes,
180 | PortableComplexType? baseType, IList? interfaces, PortableClassLayout? classLayout,
181 | IList? genericParameters, IList? customAttributes)
182 | : PortableType(name, @namespace, assembly, enclosingNames) {
183 | ///
184 | /// Gets or sets the attributes of the type.
185 | ///
186 | public int Attributes { get; set; } = attributes;
187 |
188 | ///
189 | /// Gets or sets the base type of the type.
190 | ///
191 | /// TypeDefOrRef
192 | public PortableComplexType? BaseType { get; set; } = baseType;
193 |
194 | ///
195 | /// Gets or sets the interfaces implemented by the type.
196 | ///
197 | /// TypeDefOrRef
198 | public IList? Interfaces { get; set; } = interfaces;
199 |
200 | ///
201 | /// Gets or sets the class layout of the type.
202 | ///
203 | public PortableClassLayout? ClassLayout { get; set; } = classLayout;
204 |
205 | ///
206 | /// Gets or sets the generic parameters of the type.
207 | ///
208 | public IList? GenericParameters { get; set; } = genericParameters;
209 |
210 | ///
211 | /// Gets or sets the custom attributes of the type.
212 | ///
213 | public IList? CustomAttributes { get; set; } = customAttributes;
214 |
215 | ///
216 | /// Gets or sets the nested types of the type.
217 | ///
218 | public IList? NestedTypes { get; set; }
219 |
220 | ///
221 | /// Gets or sets the fields of the type.
222 | ///
223 | public IList? Fields { get; set; }
224 |
225 | ///
226 | /// Gets or sets the methods of the type.
227 | ///
228 | public IList? Methods { get; set; }
229 |
230 | ///
231 | /// Gets or sets the properties of the type.
232 | ///
233 | public IList? Properties { get; set; }
234 |
235 | ///
236 | /// Gets or sets the events of the type.
237 | ///
238 | public IList? Events { get; set; }
239 | }
240 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PortableMetadata
2 |
3 | .NET metadata serialization library. Defines a human-readable and serializable metadata data structure. The PortableMetadata class is the center of the entire library.
4 |
5 | ## Features
6 |
7 | 1. Support saving the entire assembly.
8 | 1. Support saving only a single or specific types/fields/methods.
9 | 1. PortableMetadata is serialization friendly. It can be serialized directly to json by Json.NET or System.Text.Json and does not require any custom converters. Using the custom converts can achieve better display results, and the relevant code is in the samples.
10 | 1. PortableMetadata only requires a binary size of around 50kb and does not require any external dependencies. Compatible with .NET Framework 2.0 to .NET 8.0+.
11 |
12 | ## Scenarios
13 |
14 | 1. Dump part of .NET metadata to view.
15 | 1. Transfer processed .NET metadata between processes.
16 | 1. Exchange data directly between different .NET metadata libraries.
17 | 1. Create a plaintext patch and apply it to an existing assembly.
18 | 1. And more...
19 |
20 | ## Limits
21 |
22 | 1. Do NOT support the varargs method.
23 | 1. Do NOT support the multiple module assemblies.
24 |
25 | ## Samples
26 |
27 | ### Basics
28 |
29 | Currently, the built-in reader/writer that uses dnlib are provided. Assume the corresponding namespaces are imported.
30 |
31 | ```cs
32 | using dnlib.DotNet;
33 | using dnlib.DotNet.MD;
34 | using MetadataSerialization;
35 | using MetadataSerialization.Dnlib;
36 | ```
37 |
38 | Read the PortableMetadata from the ModuleDef in dnlib:
39 |
40 | ```cs
41 | // 1. Load the module using dnlib
42 | using ModuleDefMD module = ModuleDefMD.Load(typeof(YourType).Module);
43 |
44 | // 2. Create the portable metadata
45 | var reader = new PortableMetadataReader(module);
46 | reader.AddType(module.FindNormalThrow("YourType"), PortableMetadataLevel.DefinitionWithChildren);
47 |
48 | // 3. Get the PortableMetadata instance
49 | PortableMetadata metadata = reader.Metadata;
50 | ```
51 |
52 | Write the data from the PortableMetadata to the ModuleDef:
53 |
54 | ```cs
55 | // 1. Specify the data source and module to write to
56 | ModuleDef module = ...;
57 | PortableMetadata metadata = ...;
58 | PortableType type = metadata.Types.Values.First(t => ...);
59 |
60 | // 2. Write the metadata from PortableMetadata to ModuleDef
61 | var writer = new PortableMetadataWriter(module, metadata);
62 | writer.AddType(type, PortableMetadataLevel.Definition.DefinitionWithChildren);
63 | ```
64 |
65 | ### ExportOneMethod
66 |
67 | This example demonstrates how to export a method.
68 |
69 | Code is available in 'PortableMetadata.Samples\Sample_ExportOneMethod.cs'.
70 |
71 | ```cs
72 | static class Sample_ExportOneMethod {
73 | public static void DemoMethod() {
74 | Console.WriteLine("Hello World!");
75 | }
76 |
77 | public static void Run() {
78 | // 1. Load the module using dnlib
79 | using var module = ModuleDefMD.Load(typeof(Sample_ExportOneMethod).Module);
80 |
81 | // 2. Create the portable metadata
82 | var reader = new PortableMetadataReader(module);
83 | reader.AddMethod(module.FindNormalThrow("Sample_ExportOneMethod").FindMethod("DemoMethod"), PortableMetadataLevel.Definition);
84 | var metadata = reader.Metadata;
85 |
86 | // 3. Export the metadata (without any converters)
87 | var json = JsonSerializer.Serialize(new PortableMetadataFacade(metadata), new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
88 | // Console.WriteLine(json);
89 |
90 | // 4. Export the metadata (with converters)
91 | var options = new JsonSerializerOptions {
92 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
93 | TypeInfoResolver = new DefaultJsonTypeInfoResolver().WithAddedModifier(STJPortableMetadataObjectPropertyRemover.RemoveObjectProperties),
94 | WriteIndented = true
95 | };
96 | options.Converters.Add(new STJPortableTokenConverter());
97 | options.Converters.Add(new STJPortableComplexTypeConverter());
98 | json = JsonSerializer.Serialize(new PortableMetadataFacade(metadata), options);
99 | Console.WriteLine(json);
100 | }
101 | }
102 | ```
103 |
104 | Output:
105 |
106 | ```json
107 | {
108 | "Options": 14,
109 | "Types": {
110 | "References": {
111 | "0": {
112 | "Name": "Sample_ExportOneMethod",
113 | "Namespace": ""
114 | },
115 | "1": {
116 | "Name": "Console",
117 | "Namespace": "System",
118 | "Assembly": "System.Console, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
119 | }
120 | },
121 | "Definitions": {},
122 | "Orders": []
123 | },
124 | "Fields": {
125 | "References": {},
126 | "Definitions": {},
127 | "Orders": []
128 | },
129 | "Methods": {
130 | "References": {
131 | "1": {
132 | "Name": "WriteLine",
133 | "Type": "1",
134 | "Signature": "Default(Int32(0),Int32(1),Void,String)"
135 | }
136 | },
137 | "Definitions": {
138 | "0": {
139 | "Attributes": 150,
140 | "ImplAttributes": 0,
141 | "Parameters": [],
142 | "Body": {
143 | "Instructions": [
144 | {
145 | "OpCode": "nop"
146 | },
147 | {
148 | "OpCode": "ldstr",
149 | "StringValue": "Hello World!"
150 | },
151 | {
152 | "OpCode": "call",
153 | "TypeValue": "InlineMethod(1)"
154 | },
155 | {
156 | "OpCode": "nop"
157 | },
158 | {
159 | "OpCode": "ret"
160 | }
161 | ],
162 | "ExceptionHandlers": [],
163 | "Variables": []
164 | },
165 | "Name": "DemoMethod",
166 | "Type": "0",
167 | "Signature": "Default(Int32(0),Int32(0),Void)"
168 | }
169 | },
170 | "Orders": []
171 | }
172 | }
173 | ```
174 |
175 | ### ImportOneMethod
176 |
177 | This example demonstrates how to import a method.
178 |
179 | Code is available in 'PortableMetadata.Samples\Sample_ImportOneMethod.cs'.
180 |
181 | ```cs
182 | static class Sample_ImportOneMethod {
183 | public static void DemoMethod() {
184 | //var list = new List {
185 | // "Hello",
186 | // "World"
187 | //};
188 | //foreach (var item in list)
189 | // Console.WriteLine(item);
190 | throw new NotImplementedException();
191 | }
192 |
193 | public static void Run() {
194 | // 1. Load the module using dnlib
195 | using var module = ModuleDefMD.Load(typeof(Sample_ImportOneMethod).Module);
196 |
197 | // 2. Deserialize the metadata
198 | var options = new JsonSerializerOptions {
199 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
200 | TypeInfoResolver = new DefaultJsonTypeInfoResolver().WithAddedModifier(STJPortableMetadataObjectPropertyRemover.RemoveObjectProperties),
201 | WriteIndented = true
202 | };
203 | options.Converters.Add(new STJPortableTokenConverter());
204 | options.Converters.Add(new STJPortableComplexTypeConverter());
205 | var metadata = JsonSerializer.Deserialize(DemoMethodJson, options)!.ToMetadata();
206 | var demoMethod = metadata.Methods[0];
207 | Debug.Assert(demoMethod is PortableMethodDef);
208 |
209 | // 3. Import the method
210 | var writer = new PortableMetadataWriter(module, metadata);
211 | writer.AddMethod(demoMethod, PortableMetadataLevel.Definition);
212 | var path = Path.GetFullPath("Sample_ImportOneMethod.dll");
213 | module.Assembly.Name = module.Name = Guid.NewGuid().ToString();
214 | module.Write(path);
215 | Assembly.LoadFrom(path).GetType("Sample_ImportOneMethod")!.GetMethod("DemoMethod")!.Invoke(null, null);
216 | }
217 |
218 | const string DemoMethodJson =
219 | """
220 | Omitted here, please go to the corresponding file to view the complete content
221 | """;
222 | }
223 | ```
224 |
225 | ### Others
226 |
227 | See PortableMetadata.Samples\PortableMetadata.Samples.csproj.
228 |
--------------------------------------------------------------------------------