| 1 |
#Region "Microsoft.VisualBasic::fbae67b2a610cf873dc16ac6a28a5169, Microsoft.VisualBasic.Core\ApplicationServices\VBDev\Ngen\Ngen.vb"
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
|
| 17 |
|
| 18 |
|
| 19 |
|
| 20 |
|
| 21 |
|
| 22 |
|
| 23 |
|
| 24 |
|
| 25 |
|
| 26 |
|
| 27 |
|
| 28 |
|
| 29 |
|
| 30 |
|
| 31 |
|
| 32 |
|
| 33 |
|
| 34 |
|
| 35 |
|
| 36 |
|
| 37 |
|
| 38 |
|
| 39 |
|
| 40 |
|
| 41 |
|
| 42 |
|
| 43 |
|
| 44 |
|
| 45 |
|
| 46 |
|
| 47 |
|
| 48 |
|
| 49 |
|
| 50 |
|
| 51 |
|
| 52 |
|
| 53 |
|
| 54 |
|
| 55 |
|
| 56 |
|
| 57 |
|
| 58 |
|
| 59 |
|
| 60 |
|
| 61 |
|
| 62 |
|
| 63 |
|
| 64 |
|
| 65 |
|
| 66 |
|
| 67 |
|
| 68 |
|
| 69 |
|
| 70 |
|
| 71 |
|
| 72 |
|
| 73 |
|
| 74 |
#End Region
|
| 75 |
|
| 76 |
Imports System.ComponentModel
|
| 77 |
Imports System.Runtime.InteropServices
|
| 78 |
Imports System.Text
|
| 79 |
Imports Microsoft.VisualBasic.ApplicationServices
|
| 80 |
Imports Microsoft.VisualBasic.CommandLine
|
| 81 |
Imports Microsoft.VisualBasic.CommandLine.Reflection
|
| 82 |
Imports Microsoft.VisualBasic.Language
|
| 83 |
Imports Microsoft.VisualBasic.Language.UnixBash
|
| 84 |
|
| 85 |
Namespace ApplicationServices
|
| 86 |
|
| 87 |
|
| 88 |
|
| 89 |
|
| 90 |
The Native Image Generator (Ngen.exe) is a tool that improves the performance of managed applications.
|
| 91 |
Ngen.exe creates native images, which are files containing compiled processor-specific machine code,
|
| 92 |
and installs them into the native image cache on the local computer. The runtime can use native images
|
| 93 |
from the cache instead of using the just-in-time (JIT) compiler to compile the original assembly.
|
| 94 |
|
| 95 |
Changes To Ngen.exe In the .NET Framework 4
|
| 96 |
Ngen.exe now compiles assemblies With full trust, And code access security (CAS) policy Is no longer evaluated.
|
| 97 |
Native images that are generated With Ngen.exe can no longer be loaded into applications that are running In Partial trust.
|
| 98 |
|
| 99 |
Changes To Ngen.exe In the .NET Framework version 2.0:
|
| 100 |
Installing an assembly also installs its dependencies, simplifying the syntax Of Ngen.exe.
|
| 101 |
Native images can now be Shared across application domains.
|
| 102 |
A New Action, update, re - creates images that have been invalidated.
|
| 103 |
Actions can be deferred For execution by a service that uses idle time On the computer To generate And install images.
|
| 104 |
Some causes Of image invalidation have been eliminated.
|
| 105 |
</summary>
|
| 106 |
<remarks>
|
| 107 |
1.7 Native代码产生器: NGen.exe
|
| 108 |
|
| 109 |
随.NET Framework发布的NGen.exe工具可以将IL代码编译成native代码, 当应用程序安装在用户的机器上时. 因为代码是在安装的时候编译的, CLR的JIT编译器不需要在运行时刻编译IL代码
|
| 110 |
这能提高应用程序的性能. NGen.exe工具在下面两个场合很有趣:
|
| 111 |
|
| 112 |
提高了应用程序的启动速度 运行NGen.exe能提高启动速度, 因为代码已经编译成native代码, 所以在运行时就不需要编译了
|
| 113 |
减少应用程序的工作集 如果你认为一个程序集会被同时载入到多个进程/ Appdomain, 在这个程序集上运行NGen.exe能减少应用程序的工作集, 其原因是NGen.exe工具将IL编译成native代码,
|
| 114 |
然后将输出保存到单独的文件中, 这个文件能同时被内存映射(memory - mapping)到多个进程地址空间中, 允许代码共享, 每个进程 / AppDomain不必为自己拷贝一份代码
|
| 115 |
|
| 116 |
当一个安装程序调用NGen.exe对一个应用程序或程序集进行编译时, 那个应用程序的所有程序集或者一个特定的程序集会把其IL代码编译成native代码, 一个新的只包含native代码而不含有IL的程序集文件会被NGen.exe创建.
|
| 117 |
这个新的文件被放到名字类似于 C: /Windows/Assembly/NativeImages_v2.0.50727_32的文件夹下面, 这个文件家名字包含了CLR的版本和native代码是否是为x86(32位版本的Windows), x64,
|
| 118 |
或者Itaninum(64位版本的Windows)编译的信息.
|
| 119 |
|
| 120 |
现在, 当CLR载入一个程序集文件时, CLR查看对应的NGen native文件是否存在, 如果没发现native文件, CLR JIT对IL代码像通常那样进行编译.
|
| 121 |
然而, 如果对应的native文件存在, CLR将使用native文件中的编译好的代码, 文件中的函数就不需要在运行时刻编译了.
|
| 122 |
|
| 123 |
在表面上, 这听起来非常好, 听上去就像如果你得到了托管代码的全部优点(垃圾回收, 代码验证, 类型安全, 等等)而不牺牲托管代码的性能(JIT编译),
|
| 124 |
但是实际情况并不总是那么美好, NGen
|
| 125 |
|
| 126 |
没有知识产权保护 很多人以为可以发布NGen文件而不用发布包含原始IL代码的文件, 从而使他们的知识产权更加保密
|
| 127 |
不幸的是, 这并不可行, 在运行时刻, CLR需要访问程序集的metadata(为某些函数, 例如反射和串行化函数), 这需要发布包含IL和metadata的程序集.
|
| 128 |
此外, 如果由于某种原因, CLR不能使用NGen文件(如下面所描述的), 那么CLR会回到JIT编译, 对程序集的IL代码进行编译, 因此IL代码必须存在.
|
| 129 |
|
| 130 |
NGen文件可能会过时 当CLR载入NGen文件时, 它会比较以前编译的代码和当前的执行环境的很多特征, 如果任何特征不匹配, NGen文件就不能被使用, JIT编译器进程就要使用. 这是必须被匹配的部分特征列表.
|
| 131 |
|
| 132 |
程序集模块的版本ID(MVID)
|
| 133 |
被引用的程序集的版本ID
|
| 134 |
处理器类型
|
| 135 |
CLR版本
|
| 136 |
Build类型(release, debug, optimized debug, profiling, 等等)
|
| 137 |
|
| 138 |
所有链接时的安全性要求都必须在运行时刻被满足才能允许载入.
|
| 139 |
|
| 140 |
注意有可能以升级的方式运行NGen.exe, 这告诉工具对以前曾经被执行NGen
|
| 141 |
那么service pack的安装程序将会在更新模式下自动运行NGen.exe, 使得NGen文件保持和CLR的版本一致.
|
| 142 |
|
| 143 |
较差的载入时性能(重定位/绑定): 程序集文件是标准的Windows PE文件, 每个文件包含着一个优先使用的基地址. 很多Windows开发者对围绕基地址和重定位的问题很熟悉,
|
| 144 |
关于这个主题的更多信息, 可以参考我的书 programming Applications for Microsoft Windows, 4th Edition. 当JIT编译代码时, 不必关心这些问题, 因为正确的内存地址引用会在运行时计算出来.
|
| 145 |
|
| 146 |
然而, NGen的程序集文件的一些内存地址引用是静态计算的, 当Windows加载一个NGen文件时, 它检查文件是否被载入到优先的基地址上, 如果文件没有载入到优先的基地址,
|
| 147 |
Windows会重新定位文件, 修改所有内存地址引用. 这是极其耗时的, 因为Windows必须载入整个文件, 并修改文件中的很多字节. 此外, 这个页面文件对应的代码不能跨进程边界共享.
|
| 148 |
|
| 149 |
因此如果你打算NGen程序集文件, 你应该为你的程序集文件选择好的基地址(通过csc.exe的 / baseaddress命令行开关).当你NGen一个程序集文件时, NGen文件将被赋予一个基地址,
|
| 150 |
这需要使用一个基于托管程序集基地址的算法. 不幸的是, 微软从没有一个良好的指导来帮助开发者如何赋予基地址. 在64位版本的Windows上, 这还不太会成为问题, 因为地址空间是很充足的,
|
| 151 |
但是对于一个32位的地址空间, 为每一个程序集选择一个好的基地址几乎是不可能的, 除非你精确地知道什么东西会被载入到进程, 知道那个程序集的大小不会超过后一个版本.
|
| 152 |
|
| 153 |
较差的执行时性能 当编译代码时, NGen对执行环境做出的假设不会比JIT编译器的多, 这会造成NGen.exe产生较差的代码, 例如, NGen不能优化一些CPU指令, 对静态字段的访问需要简介的操作,
|
| 154 |
因为静态字段实际的地址需要在运行时刻才能知道.NGen到处插入代码来调用类的构造函数, 因为它不知道代码执行的次序, 不知道类的构造憾事是否已经被调用了(见第8章, 类的构造函数).
|
| 155 |
一些NGen应用程序会比JIT编译的代码慢大约5%, 因此, 如果你打算使用NGen来提高应用程序的性能, 你应该对比NGen’d和非NGen’d版本的应用程序, 确定NGen’d版本在实际执行时并不慢.
|
| 156 |
对于一些应用程序, 减小的工作集大小会提高性能, 因此NGen总体上还是会取胜.
|
| 157 |
|
| 158 |
因为上面列出的所有问题, 当考虑使用NGen.exe时, 你应该非常小心.对于服务器端的应用程序来说, NGen.exe的用处很小甚至没有意义, 因为只有第一个客户需求经历了性能上的下降,
|
| 159 |
后面的客户需求都是高速运行的.此外, 对于大多数服务器应用程序, 只需要代码的一个实例, 因此没有工作集方面的利益.
|
| 160 |
|
| 161 |
对于客户端应用程序, NGen.exe可能对于提高启动速度或者减小工作集有帮助, 如果程序集被多个应用程序同时使用.甚至没有多个应用程序使用一个程序集, NGen一个程序集也会提高工作集.
|
| 162 |
此外, 如果NGen.exe被用于所有的客户端应用程序的程序集, 那么CLR就根本不需要载入JIT编译器, 从而更进一步地降低了工作集.
|
| 163 |
当然, 如果只有一个程序集不是NGen
|
| 164 |
</remarks>
|
| 165 |
<RunInstaller(True)>
|
| 166 |
<[Namespace]("Ngen")>
|
| 167 |
Public Module NgenInstaller
|
| 168 |
|
| 169 |
#Region "Actions::The following table shows the syntax Of Each action."
|
| 170 |
|
| 171 |
Public Enum Scenarios
|
| 172 |
|
| 173 |
Generate native images that can be used under a debugger.
|
| 174 |
</summary>
|
| 175 |
<Description("/Debug")> Debug
|
| 176 |
|
| 177 |
Generate native images that can be used under a profiler.
|
| 178 |
</summary>
|
| 179 |
<Description("/Profile")> Profile
|
| 180 |
|
| 181 |
Generate the minimum number Of native images required by the specified scenario options.
|
| 182 |
</summary>
|
| 183 |
<Description("/NoDependencies")> NoDependencies
|
| 184 |
End Enum
|
| 185 |
|
| 186 |
Public Enum PriorityLevels
|
| 187 |
null = -1
|
| 188 |
|
| 189 |
|
| 190 |
1 Native images are generated And installed immediately, without waiting For idle time.
|
| 191 |
</summary>
|
| 192 |
Immediately = 1
|
| 193 |
|
| 194 |
2 Native images are generated And installed without waiting For idle time, but after all priority 1 actions (And their dependencies) have completed.
|
| 195 |
</summary>
|
| 196 |
Waiting = 2
|
| 197 |
|
| 198 |
3 Native images are installed When the native image service detects that the computer Is idle. See Native Image Service.
|
| 199 |
</summary>
|
| 200 |
Idle
|
| 201 |
End Enum
|
| 202 |
|
| 203 |
|
| 204 |
Generate native images for an assembly and its dependencies and install the images in the native image cache.
|
| 205 |
</summary>
|
| 206 |
<param name="assemblyName">
|
| 207 |
The full display name of the assembly. For example, "myAssembly, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0038abc9deabfle5".
|
| 208 |
Only one assembly can be specified per Ngen.exe command line.
|
| 209 |
|
| 210 |
* Note You can supply a Partial assembly name, such As myAssembly, For the display And uninstall actions.
|
| 211 |
|
| 212 |
The explicit path of the assembly. You can specify a full or relative path.
|
| 213 |
If you specify a file name without a path, the assembly must be located In the current directory.
|
| 214 |
Only one assembly can be specified per Ngen.exe command line.
|
| 215 |
</param>
|
| 216 |
<param name="scenarios"></param>
|
| 217 |
<param name="ExeConfig">exePath, Use the configuration of the specified executable assembly.
|
| 218 |
Ngen.exe needs to make the same decisions as the loader when binding to dependencies. When a shared component Is loaded at run time,
|
| 219 |
using the Load method, the application
|
| 220 |
for example, the version of a dependency that is loaded. The /ExeConfig switch gives Ngen.exe guidance on which dependencies would be loaded at run time.</param>
|
| 221 |
<param name="AppBase">directoryPath, When locating dependencies, use the specified directory as the application base.</param>
|
| 222 |
<param name="queue">If /queue is specified, the action is queued for the native image service. The default priority is 3. See the Priority Levels table.</param>
|
| 223 |
Public Function Install(assemblyName$,
|
| 224 |
scenarios As NgenInstaller.Scenarios,
|
| 225 |
Optional ExeConfig$ = "",
|
| 226 |
Optional AppBase As Boolean = False,
|
| 227 |
Optional queue As NgenInstaller.PriorityLevels = PriorityLevels.null) As String
|
| 228 |
|
| 229 |
Dim cliBuilder As New StringBuilder("install ", 1024)
|
| 230 |
|
| 231 |
Call cliBuilder.Append(assemblyName.CLIPath & " ")
|
| 232 |
Call cliBuilder.Append(scenarios.Description & " ")
|
| 233 |
|
| 234 |
If Not String.IsNullOrEmpty(ExeConfig) Then
|
| 235 |
Call cliBuilder.Append($"/ExeConfig:{ExeConfig}".CLIPath & " ")
|
| 236 |
End If
|
| 237 |
|
| 238 |
If AppBase Then
|
| 239 |
Call cliBuilder.Append("/AppBase ")
|
| 240 |
End If
|
| 241 |
|
| 242 |
If Not queue = PriorityLevels.null Then
|
| 243 |
Call cliBuilder.Append($"/queue:{CInt(queue)}")
|
| 244 |
End If
|
| 245 |
|
| 246 |
Dim NGen As New IORedirectFile(NgenInstaller.Ngen, cliBuilder.ToString & " /verbose")
|
| 247 |
Call NGen.Run()
|
| 248 |
Return NGen.StandardOutput
|
| 249 |
End Function
|
| 250 |
|
| 251 |
|
| 252 |
Delete the native images of an assembly and its dependencies from the native image cache.
|
| 253 |
To uninstall a single image And its dependencies, use the same command-line arguments that were used to install the image.
|
| 254 |
|
| 255 |
Note In the .NET Framework 4, the action uninstall * Is no longer supported.
|
| 256 |
</summary>
|
| 257 |
<param name="assemblyName">
|
| 258 |
The full display name of the assembly. For example, "myAssembly, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0038abc9deabfle5".
|
| 259 |
Only one assembly can be specified per Ngen.exe command line.
|
| 260 |
|
| 261 |
* Note You can supply a Partial assembly name, such As myAssembly, For the display And uninstall actions.
|
| 262 |
|
| 263 |
The explicit path of the assembly. You can specify a full or relative path.
|
| 264 |
If you specify a file name without a path, the assembly must be located In the current directory.
|
| 265 |
Only one assembly can be specified per Ngen.exe command line.
|
| 266 |
</param>
|
| 267 |
<param name="scenarios"></param>
|
| 268 |
<param name="ExeConfig">exePath, Use the configuration of the specified executable assembly.
|
| 269 |
Ngen.exe needs to make the same decisions as the loader when binding to dependencies. When a shared component Is loaded at run time,
|
| 270 |
using the Load method, the application
|
| 271 |
for example, the version of a dependency that is loaded. The /ExeConfig switch gives Ngen.exe guidance on which dependencies would be loaded at run time.</param>
|
| 272 |
<param name="AppBase">directoryPath, When locating dependencies, use the specified directory as the application base.</param>
|
| 273 |
Public Function Uninstall(assemblyName As String,
|
| 274 |
scenarios As NgenInstaller.Scenarios,
|
| 275 |
Optional ExeConfig As String = "",
|
| 276 |
Optional AppBase As Boolean = False) As String
|
| 277 |
|
| 278 |
Dim cliBuilder As StringBuilder = New StringBuilder("uninstall ", 1024)
|
| 279 |
Call cliBuilder.Append(assemblyName.CLIPath & " ")
|
| 280 |
Call cliBuilder.Append(scenarios.Description & " ")
|
| 281 |
|
| 282 |
If Not String.IsNullOrEmpty(ExeConfig) Then
|
| 283 |
Call cliBuilder.Append($"/ExeConfig:{ExeConfig}".CLIPath & " ")
|
| 284 |
End If
|
| 285 |
|
| 286 |
If AppBase Then
|
| 287 |
Call cliBuilder.Append("/AppBase")
|
| 288 |
End If
|
| 289 |
|
| 290 |
Dim NGen = New IORedirectFile(NgenInstaller.Ngen, cliBuilder.ToString & " /verbose")
|
| 291 |
Call NGen.Run()
|
| 292 |
Return NGen.StandardOutput
|
| 293 |
End Function
|
| 294 |
|
| 295 |
|
| 296 |
Update native images that have become invalid.
|
| 297 |
If /queue Is specified, the updates are queued For the native image service. Updates are always scheduled at priority 3, so they run When the computer Is idle.
|
| 298 |
</summary>
|
| 299 |
<param name="queue"></param>
|
| 300 |
Public Function Update(Optional queue As Boolean = False) As String
|
| 301 |
Dim cliBuilder As StringBuilder = New StringBuilder("update ")
|
| 302 |
If queue Then
|
| 303 |
Call cliBuilder.Append("/queue")
|
| 304 |
End If
|
| 305 |
|
| 306 |
Dim NGen = New CommandLine.IORedirectFile(NgenInstaller.Ngen, cliBuilder.ToString & " /verbose")
|
| 307 |
Call NGen.Run()
|
| 308 |
Return NGen.StandardOutput
|
| 309 |
End Function
|
| 310 |
|
| 311 |
|
| 312 |
Display the state of the native images for an assembly and its dependencies.
|
| 313 |
If no argument Is supplied, everything In the native image cache Is displayed.
|
| 314 |
</summary>
|
| 315 |
<param name="assemblyName"></param>
|
| 316 |
Public Function Display(assemblyName As String) As String
|
| 317 |
Dim NGen = New CommandLine.IORedirectFile(NgenInstaller.Ngen, "display " & assemblyName.CLIPath & " /verbose")
|
| 318 |
Call NGen.Run()
|
| 319 |
Return NGen.StandardOutput
|
| 320 |
End Function
|
| 321 |
|
| 322 |
|
| 323 |
Execute queued compilation jobs.
|
| 324 |
If a priority Is specified, compilation jobs With greater Or equal priority are executed.
|
| 325 |
If no priority Is specified, all queued compilation jobs are executed.
|
| 326 |
</summary>
|
| 327 |
<param name="queue"></param>
|
| 328 |
Public Function ExecuteQueuedItems(Optional queue As NgenInstaller.PriorityLevels = PriorityLevels.null) As String
|
| 329 |
Dim cli As String = "eqi "
|
| 330 |
|
| 331 |
If queue <> PriorityLevels.null Then
|
| 332 |
cli = cli & CStr(CInt(queue))
|
| 333 |
End If
|
| 334 |
|
| 335 |
Dim NGen = New CommandLine.IORedirectFile(NgenInstaller.Ngen, cli & " /verbose")
|
| 336 |
Call NGen.Run()
|
| 337 |
Return NGen.StandardOutput
|
| 338 |
End Function
|
| 339 |
|
| 340 |
|
| 341 |
Pause the native image service, allow the paused service to continue, or query the status of the service.
|
| 342 |
</summary>
|
| 343 |
Public Function Queue(action As QueueActions) As String
|
| 344 |
Dim cli As String = "queue " & action.Description
|
| 345 |
Dim NGen = New CommandLine.IORedirectFile(NgenInstaller.Ngen, cli & " /verbose")
|
| 346 |
Call NGen.Run()
|
| 347 |
Return NGen.StandardOutput
|
| 348 |
End Function
|
| 349 |
|
| 350 |
Public Enum QueueActions
|
| 351 |
|
| 352 |
Pause the native image service
|
| 353 |
</summary>
|
| 354 |
<Description("pause")> pause
|
| 355 |
|
| 356 |
allow the paused service to continue
|
| 357 |
</summary>
|
| 358 |
<Description("continue")> [Continue]
|
| 359 |
|
| 360 |
query the status of the service.
|
| 361 |
</summary>
|
| 362 |
<Description("status")> status
|
| 363 |
End Enum
|
| 364 |
#End Region
|
| 365 |
|
| 366 |
|
| 367 |
将当前目录下的所有的.NET程序都进行安装
|
| 368 |
</summary>
|
| 369 |
<ExportAPI("--install", Info:="Install all of the .NET program in the current directory.")>
|
| 370 |
Public Function Install(Optional PATH$ = "./", Optional installExe As Boolean = False) As String()
|
| 371 |
Dim files As IEnumerable(Of String)
|
| 372 |
|
| 373 |
If installExe Then
|
| 374 |
files = ls - l - r - {"*.exe", "*.dll"} <= PATH$
|
| 375 |
Else
|
| 376 |
files = ls - l - r - {"*.dll"} <= PATH$
|
| 377 |
End If
|
| 378 |
|
| 379 |
Dim runInstall = LinqAPI.Exec(Of String) _
|
| 380 |
_
|
| 381 |
() <= From assembly As String
|
| 382 |
In files
|
| 383 |
Let std_out = NgenInstaller.Install(assemblyName:=assembly, scenarios:=Scenarios.Profile)
|
| 384 |
Select std_out
|
| 385 |
|
| 386 |
Return runInstall
|
| 387 |
End Function
|
| 388 |
|
| 389 |
Public Sub Uninstall(savedState As IDictionary)
|
| 390 |
NgenFile(InstallTypes.Uninstall)
|
| 391 |
End Sub
|
| 392 |
|
| 393 |
Private Enum InstallTypes
|
| 394 |
Install
|
| 395 |
Uninstall
|
| 396 |
End Enum
|
| 397 |
|
| 398 |
Public ReadOnly Property Ngen As String
|
| 399 |
Get
|
| 400 |
Dim envDir As String = RuntimeEnvironment.GetRuntimeDirectory()
|
| 401 |
Dim ngenPath As String = IO.Path.Combine(envDir, "ngen.exe")
|
| 402 |
Return ngenPath
|
| 403 |
End Get
|
| 404 |
End Property
|
| 405 |
|
| 406 |
Private Sub NgenFile(options As InstallTypes)
|
| 407 |
|
| 408 |
'Dim exePath As String = Context.Parameters("assemblypath")
|
| 409 |
|
| 410 |
|
| 411 |
|
| 412 |
|
| 413 |
|
| 414 |
' Dim fileKey As String = "ngen" & i
|
| 415 |
|
| 416 |
|
| 417 |
' Dim ngenFileName As String = Context.Parameters("ngen" & i)
|
| 418 |
|
| 419 |
' Dim argument As String = (If(options = InstallTypes.Install, "install", "uninstall")) & " """ & fileFullName & """"
|
| 420 |
|
| 421 |
|
| 422 |
|
| 423 |
|
| 424 |
|
| 425 |
|
| 426 |
|
| 427 |
|
| 428 |
|
| 429 |
|
| 430 |
|
| 431 |
|
| 432 |
|
| 433 |
|
| 434 |
End Sub
|
| 435 |
End Module
|
| 436 |
End Namespace
|