commit 31d81b91b6c31ea7446a4fcdc341bb2226ee27e0 Author: 大师兄法号随缘 <18862253202@qq.com> Date: Tue Aug 26 08:37:44 2025 +0800 初始化上传 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5d80e6d --- /dev/null +++ b/.gitignore @@ -0,0 +1,341 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +.idea/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- Backup*.rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..457b072 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# VS_Tools + +#### 介绍 +小工具 + +#### 软件架构 +软件架构说明 + + +#### 安装教程 + +1. xxxx +2. xxxx +3. xxxx + +#### 使用说明 + +1. xxxx +2. xxxx +3. xxxx + +#### 参与贡献 + +1. Fork 本仓库 +2. 新建 Feat_xxx 分支 +3. 提交代码 +4. 新建 Pull Request + + +#### 码云特技 + +1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md +2. 码云官方博客 [blog.gitee.com](https://blog.gitee.com) +3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解码云上的优秀开源项目 +4. [GVP](https://gitee.com/gvp) 全称是码云最有价值开源项目,是码云综合评定出的优秀开源项目 +5. 码云官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) +6. 码云封面人物是一档用来展示码云会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/常用工具集.sln b/常用工具集.sln new file mode 100644 index 0000000..d835254 --- /dev/null +++ b/常用工具集.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.10.35122.118 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "常用工具集", "常用工具集\常用工具集.csproj", "{8D1F09A1-EF83-FF37-E4DA-6C486B550A71}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8D1F09A1-EF83-FF37-E4DA-6C486B550A71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D1F09A1-EF83-FF37-E4DA-6C486B550A71}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D1F09A1-EF83-FF37-E4DA-6C486B550A71}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D1F09A1-EF83-FF37-E4DA-6C486B550A71}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EE908508-1911-4343-87B2-D66338C656E1} + EndGlobalSection +EndGlobal diff --git a/常用工具集/App.axaml b/常用工具集/App.axaml new file mode 100644 index 0000000..305b9b7 --- /dev/null +++ b/常用工具集/App.axaml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/常用工具集/App.axaml.cs b/常用工具集/App.axaml.cs new file mode 100644 index 0000000..f53eb68 --- /dev/null +++ b/常用工具集/App.axaml.cs @@ -0,0 +1,50 @@ +using System.Linq; +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Data.Core; +using Avalonia.Data.Core.Plugins; +using Avalonia.Markup.Xaml; +using 常用工具集.ViewModels; +using 常用工具集.Views; + +namespace 常用工具集 +{ + public partial class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + this.DataContext = new ApplicationViewModel(); + } + + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + // Avoid duplicate validations from both Avalonia and the CommunityToolkit. + // More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins + DisableAvaloniaDataAnnotationValidation(); + desktop.MainWindow = new MainWindow(); + //desktop.MainWindow = new MainWindow + //{ + // DataContext = new MainWindowViewModel(), + //}; + } + + base.OnFrameworkInitializationCompleted(); + } + + private void DisableAvaloniaDataAnnotationValidation() + { + // Get an array of plugins to remove + var dataValidationPluginsToRemove = BindingPlugins.DataValidators.OfType().ToArray(); + + // remove each entry found + foreach (var plugin in dataValidationPluginsToRemove) + { + BindingPlugins.DataValidators.Remove(plugin); + } + } + } +} \ No newline at end of file diff --git a/常用工具集/Assets/AcrobatXI/amtlib.dll b/常用工具集/Assets/AcrobatXI/amtlib.dll new file mode 100644 index 0000000..b8936f4 Binary files /dev/null and b/常用工具集/Assets/AcrobatXI/amtlib.dll differ diff --git a/常用工具集/Assets/Cat/empty.dll b/常用工具集/Assets/Cat/empty.dll new file mode 100644 index 0000000..0215e0b Binary files /dev/null and b/常用工具集/Assets/Cat/empty.dll differ diff --git a/常用工具集/Assets/Cat/empty.png b/常用工具集/Assets/Cat/empty.png new file mode 100644 index 0000000..0b95e61 Binary files /dev/null and b/常用工具集/Assets/Cat/empty.png differ diff --git a/常用工具集/Assets/Cat/full.dll b/常用工具集/Assets/Cat/full.dll new file mode 100644 index 0000000..b7cb3a5 Binary files /dev/null and b/常用工具集/Assets/Cat/full.dll differ diff --git a/常用工具集/Assets/Cat/full.png b/常用工具集/Assets/Cat/full.png new file mode 100644 index 0000000..9cd1292 Binary files /dev/null and b/常用工具集/Assets/Cat/full.png differ diff --git a/常用工具集/Assets/ColorBag/卡机色.gif b/常用工具集/Assets/ColorBag/卡机色.gif new file mode 100644 index 0000000..ad92c65 Binary files /dev/null and b/常用工具集/Assets/ColorBag/卡机色.gif differ diff --git a/常用工具集/Assets/ColorBag/古代紫.gif b/常用工具集/Assets/ColorBag/古代紫.gif new file mode 100644 index 0000000..ac6c6ed Binary files /dev/null and b/常用工具集/Assets/ColorBag/古代紫.gif differ diff --git a/常用工具集/Assets/ColorBag/地平线.gif b/常用工具集/Assets/ColorBag/地平线.gif new file mode 100644 index 0000000..d9ebdfe Binary files /dev/null and b/常用工具集/Assets/ColorBag/地平线.gif differ diff --git a/常用工具集/Assets/ColorBag/墨绿.gif b/常用工具集/Assets/ColorBag/墨绿.gif new file mode 100644 index 0000000..d91d41e Binary files /dev/null and b/常用工具集/Assets/ColorBag/墨绿.gif differ diff --git a/常用工具集/Assets/ColorBag/太阳橙.gif b/常用工具集/Assets/ColorBag/太阳橙.gif new file mode 100644 index 0000000..bc86db1 Binary files /dev/null and b/常用工具集/Assets/ColorBag/太阳橙.gif differ diff --git a/常用工具集/Assets/ColorBag/孔雀绿.gif b/常用工具集/Assets/ColorBag/孔雀绿.gif new file mode 100644 index 0000000..10a5579 Binary files /dev/null and b/常用工具集/Assets/ColorBag/孔雀绿.gif differ diff --git a/常用工具集/Assets/ColorBag/月亮黄.gif b/常用工具集/Assets/ColorBag/月亮黄.gif new file mode 100644 index 0000000..e98a9d1 Binary files /dev/null and b/常用工具集/Assets/ColorBag/月亮黄.gif differ diff --git a/常用工具集/Assets/ColorBag/桃色.gif b/常用工具集/Assets/ColorBag/桃色.gif new file mode 100644 index 0000000..4aefb6e Binary files /dev/null and b/常用工具集/Assets/ColorBag/桃色.gif differ diff --git a/常用工具集/Assets/ColorBag/梨色.gif b/常用工具集/Assets/ColorBag/梨色.gif new file mode 100644 index 0000000..180e212 Binary files /dev/null and b/常用工具集/Assets/ColorBag/梨色.gif differ diff --git a/常用工具集/Assets/ColorBag/椰棕色.gif b/常用工具集/Assets/ColorBag/椰棕色.gif new file mode 100644 index 0000000..90ef482 Binary files /dev/null and b/常用工具集/Assets/ColorBag/椰棕色.gif differ diff --git a/常用工具集/Assets/ColorBag/橘红色.gif b/常用工具集/Assets/ColorBag/橘红色.gif new file mode 100644 index 0000000..aca3c9b Binary files /dev/null and b/常用工具集/Assets/ColorBag/橘红色.gif differ diff --git a/常用工具集/Assets/ColorBag/正红.gif b/常用工具集/Assets/ColorBag/正红.gif new file mode 100644 index 0000000..71451b2 Binary files /dev/null and b/常用工具集/Assets/ColorBag/正红.gif differ diff --git a/常用工具集/Assets/ColorBag/洋红.gif b/常用工具集/Assets/ColorBag/洋红.gif new file mode 100644 index 0000000..288f797 Binary files /dev/null and b/常用工具集/Assets/ColorBag/洋红.gif differ diff --git a/常用工具集/Assets/ColorBag/浅灰.gif b/常用工具集/Assets/ColorBag/浅灰.gif new file mode 100644 index 0000000..b44dc8a Binary files /dev/null and b/常用工具集/Assets/ColorBag/浅灰.gif differ diff --git a/常用工具集/Assets/ColorBag/浅绿.gif b/常用工具集/Assets/ColorBag/浅绿.gif new file mode 100644 index 0000000..4dbc863 Binary files /dev/null and b/常用工具集/Assets/ColorBag/浅绿.gif differ diff --git a/常用工具集/Assets/ColorBag/浓蓝.gif b/常用工具集/Assets/ColorBag/浓蓝.gif new file mode 100644 index 0000000..8430f29 Binary files /dev/null and b/常用工具集/Assets/ColorBag/浓蓝.gif differ diff --git a/常用工具集/Assets/ColorBag/浓蓝绿.gif b/常用工具集/Assets/ColorBag/浓蓝绿.gif new file mode 100644 index 0000000..2ef23bd Binary files /dev/null and b/常用工具集/Assets/ColorBag/浓蓝绿.gif differ diff --git a/常用工具集/Assets/ColorBag/深蓝.gif b/常用工具集/Assets/ColorBag/深蓝.gif new file mode 100644 index 0000000..ea03624 Binary files /dev/null and b/常用工具集/Assets/ColorBag/深蓝.gif differ diff --git a/常用工具集/Assets/ColorBag/深青灰.gif b/常用工具集/Assets/ColorBag/深青灰.gif new file mode 100644 index 0000000..200f564 Binary files /dev/null and b/常用工具集/Assets/ColorBag/深青灰.gif differ diff --git a/常用工具集/Assets/ColorBag/濡雨色.gif b/常用工具集/Assets/ColorBag/濡雨色.gif new file mode 100644 index 0000000..bf64a7b Binary files /dev/null and b/常用工具集/Assets/ColorBag/濡雨色.gif differ diff --git a/常用工具集/Assets/ColorBag/灰瓷.gif b/常用工具集/Assets/ColorBag/灰瓷.gif new file mode 100644 index 0000000..4ff2e63 Binary files /dev/null and b/常用工具集/Assets/ColorBag/灰瓷.gif differ diff --git a/常用工具集/Assets/ColorBag/牡丹红.gif b/常用工具集/Assets/ColorBag/牡丹红.gif new file mode 100644 index 0000000..7fa2918 Binary files /dev/null and b/常用工具集/Assets/ColorBag/牡丹红.gif differ diff --git a/常用工具集/Assets/ColorBag/琉璃色.gif b/常用工具集/Assets/ColorBag/琉璃色.gif new file mode 100644 index 0000000..5761c43 Binary files /dev/null and b/常用工具集/Assets/ColorBag/琉璃色.gif differ diff --git a/常用工具集/Assets/ColorBag/秋菊黄.gif b/常用工具集/Assets/ColorBag/秋菊黄.gif new file mode 100644 index 0000000..b8b83cf Binary files /dev/null and b/常用工具集/Assets/ColorBag/秋菊黄.gif differ diff --git a/常用工具集/Assets/ColorBag/紫水晶.gif b/常用工具集/Assets/ColorBag/紫水晶.gif new file mode 100644 index 0000000..49fca6c Binary files /dev/null and b/常用工具集/Assets/ColorBag/紫水晶.gif differ diff --git a/常用工具集/Assets/ColorBag/紫色.gif b/常用工具集/Assets/ColorBag/紫色.gif new file mode 100644 index 0000000..d8414d0 Binary files /dev/null and b/常用工具集/Assets/ColorBag/紫色.gif differ diff --git a/常用工具集/Assets/ColorBag/绯红.gif b/常用工具集/Assets/ColorBag/绯红.gif new file mode 100644 index 0000000..8deb93d Binary files /dev/null and b/常用工具集/Assets/ColorBag/绯红.gif differ diff --git a/常用工具集/Assets/ColorBag/芥予色.gif b/常用工具集/Assets/ColorBag/芥予色.gif new file mode 100644 index 0000000..9f7a3c3 Binary files /dev/null and b/常用工具集/Assets/ColorBag/芥予色.gif differ diff --git a/常用工具集/Assets/ColorBag/草绿.gif b/常用工具集/Assets/ColorBag/草绿.gif new file mode 100644 index 0000000..ac99338 Binary files /dev/null and b/常用工具集/Assets/ColorBag/草绿.gif differ diff --git a/常用工具集/Assets/ColorBag/蔷薇色.gif b/常用工具集/Assets/ColorBag/蔷薇色.gif new file mode 100644 index 0000000..b295c4a Binary files /dev/null and b/常用工具集/Assets/ColorBag/蔷薇色.gif differ diff --git a/常用工具集/Assets/ColorBag/薰衣草.gif b/常用工具集/Assets/ColorBag/薰衣草.gif new file mode 100644 index 0000000..630454f Binary files /dev/null and b/常用工具集/Assets/ColorBag/薰衣草.gif differ diff --git a/常用工具集/Assets/ColorBag/贝色.gif b/常用工具集/Assets/ColorBag/贝色.gif new file mode 100644 index 0000000..d9608f4 Binary files /dev/null and b/常用工具集/Assets/ColorBag/贝色.gif differ diff --git a/常用工具集/Assets/ColorBag/那不勒斯黄.gif b/常用工具集/Assets/ColorBag/那不勒斯黄.gif new file mode 100644 index 0000000..61f5d5a Binary files /dev/null and b/常用工具集/Assets/ColorBag/那不勒斯黄.gif differ diff --git a/常用工具集/Assets/ColorBag/酒红.gif b/常用工具集/Assets/ColorBag/酒红.gif new file mode 100644 index 0000000..bf10399 Binary files /dev/null and b/常用工具集/Assets/ColorBag/酒红.gif differ diff --git a/常用工具集/Assets/ColorBag/钴蓝.gif b/常用工具集/Assets/ColorBag/钴蓝.gif new file mode 100644 index 0000000..5772427 Binary files /dev/null and b/常用工具集/Assets/ColorBag/钴蓝.gif differ diff --git a/常用工具集/Assets/ColorBag/铬绿.gif b/常用工具集/Assets/ColorBag/铬绿.gif new file mode 100644 index 0000000..a96b69d Binary files /dev/null and b/常用工具集/Assets/ColorBag/铬绿.gif differ diff --git a/常用工具集/Assets/ColorBag/铬黄.gif b/常用工具集/Assets/ColorBag/铬黄.gif new file mode 100644 index 0000000..b244d62 Binary files /dev/null and b/常用工具集/Assets/ColorBag/铬黄.gif differ diff --git a/常用工具集/Assets/ColorBag/闪光绿.gif b/常用工具集/Assets/ColorBag/闪光绿.gif new file mode 100644 index 0000000..b0e9f4b Binary files /dev/null and b/常用工具集/Assets/ColorBag/闪光绿.gif differ diff --git a/常用工具集/Assets/ColorBag/青灰绿.gif b/常用工具集/Assets/ColorBag/青灰绿.gif new file mode 100644 index 0000000..fd1050a Binary files /dev/null and b/常用工具集/Assets/ColorBag/青灰绿.gif differ diff --git a/常用工具集/Assets/ColorBag/青紫色.gif b/常用工具集/Assets/ColorBag/青紫色.gif new file mode 100644 index 0000000..bd18a06 Binary files /dev/null and b/常用工具集/Assets/ColorBag/青紫色.gif differ diff --git a/常用工具集/Assets/ColorBag/青金石.gif b/常用工具集/Assets/ColorBag/青金石.gif new file mode 100644 index 0000000..b6cc80c Binary files /dev/null and b/常用工具集/Assets/ColorBag/青金石.gif differ diff --git a/常用工具集/Assets/ColorBag/鲜黄色.gif b/常用工具集/Assets/ColorBag/鲜黄色.gif new file mode 100644 index 0000000..25b7637 Binary files /dev/null and b/常用工具集/Assets/ColorBag/鲜黄色.gif differ diff --git a/常用工具集/Assets/ColorBag/鸠羽灰.gif b/常用工具集/Assets/ColorBag/鸠羽灰.gif new file mode 100644 index 0000000..e8b0d0c Binary files /dev/null and b/常用工具集/Assets/ColorBag/鸠羽灰.gif differ diff --git a/常用工具集/Assets/ColorBag/麦色.gif b/常用工具集/Assets/ColorBag/麦色.gif new file mode 100644 index 0000000..e1c0a90 Binary files /dev/null and b/常用工具集/Assets/ColorBag/麦色.gif differ diff --git a/常用工具集/Assets/ColorBag/黄橙色.gif b/常用工具集/Assets/ColorBag/黄橙色.gif new file mode 100644 index 0000000..6973c17 Binary files /dev/null and b/常用工具集/Assets/ColorBag/黄橙色.gif differ diff --git a/常用工具集/Assets/ColorBag/黑灰.gif b/常用工具集/Assets/ColorBag/黑灰.gif new file mode 100644 index 0000000..b3b1fa0 Binary files /dev/null and b/常用工具集/Assets/ColorBag/黑灰.gif differ diff --git a/常用工具集/Assets/FTP/File.png b/常用工具集/Assets/FTP/File.png new file mode 100644 index 0000000..a196564 Binary files /dev/null and b/常用工具集/Assets/FTP/File.png differ diff --git a/常用工具集/Assets/FTP/Folder.png b/常用工具集/Assets/FTP/Folder.png new file mode 100644 index 0000000..8c677f3 Binary files /dev/null and b/常用工具集/Assets/FTP/Folder.png differ diff --git a/常用工具集/Assets/Navi/background.png b/常用工具集/Assets/Navi/background.png new file mode 100644 index 0000000..9336cd8 Binary files /dev/null and b/常用工具集/Assets/Navi/background.png differ diff --git a/常用工具集/Assets/Navi/down.png b/常用工具集/Assets/Navi/down.png new file mode 100644 index 0000000..200da67 Binary files /dev/null and b/常用工具集/Assets/Navi/down.png differ diff --git a/常用工具集/Assets/Navi/left.png b/常用工具集/Assets/Navi/left.png new file mode 100644 index 0000000..a49b6b7 Binary files /dev/null and b/常用工具集/Assets/Navi/left.png differ diff --git a/常用工具集/Assets/WPS/show.jpg b/常用工具集/Assets/WPS/show.jpg new file mode 100644 index 0000000..12275e7 Binary files /dev/null and b/常用工具集/Assets/WPS/show.jpg differ diff --git a/常用工具集/Assets/favicon.ico b/常用工具集/Assets/favicon.ico new file mode 100644 index 0000000..99786c0 Binary files /dev/null and b/常用工具集/Assets/favicon.ico differ diff --git a/常用工具集/Base/DelegateCommand.cs b/常用工具集/Base/DelegateCommand.cs new file mode 100644 index 0000000..3f6d25e --- /dev/null +++ b/常用工具集/Base/DelegateCommand.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace 常用工具集.Base +{ + public class DelegateCommand : ICommand + { + public Action ExecuteCommand = null; + public Func CanExecuteCommand = null; + + public event EventHandler CanExecuteChanged; + + public DelegateCommand() + { + } + + public DelegateCommand(Action Command) + { + ExecuteCommand = Command; + } + + public bool CanExecute(object paramter) + { + if (CanExecuteCommand != null) + { + return CanExecuteCommand(paramter); + } + else + { + return true; + } + } + + public void Execute(object paramter) + { + if (ExecuteCommand != null) + ExecuteCommand(paramter); + } + + public void RaiseCanExecuteChanged() + { + if (CanExecuteChanged != null) + CanExecuteChanged(this, EventArgs.Empty); + } + } +} diff --git a/常用工具集/Base/GlobalValues.cs b/常用工具集/Base/GlobalValues.cs new file mode 100644 index 0000000..9390a89 --- /dev/null +++ b/常用工具集/Base/GlobalValues.cs @@ -0,0 +1,51 @@ +using Avalonia.Controls; +using Avalonia.Controls.Notifications; +using Avalonia.Input.Platform; +using Avalonia.Platform.Storage; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace 常用工具集.Base +{ + internal class GlobalValues + { + public static Ursa.Controls.WindowNotificationManager? NotificationManager { get; set; } + public static Window MainWindow { get; internal set; } + public static IStorageProvider StorageProvider { get; internal set; } + public static IClipboard Clipboard { get; internal set; } + + public static void ShowNotification(string title, string message, NotificationType type) + { + if (NotificationManager != null) + { + NotificationManager?.Show(new Notification(title, message), showIcon: true, showClose: false, type: type); + } + } + + public static void Error(string message) + { + if (NotificationManager != null) + { + NotificationManager?.Show(message, NotificationType.Error, TimeSpan.FromSeconds(1), true, false); + } + } + + public static void Warning(string message) + { + if (NotificationManager != null) + { + NotificationManager?.Show(message, NotificationType.Warning, TimeSpan.FromSeconds(1), true, false); + } + } + public static void Success(string message) + { + if (NotificationManager != null) + { + NotificationManager?.Show(message, NotificationType.Success, TimeSpan.FromSeconds(1), true, false); + } + } + } +} diff --git a/常用工具集/Base/ViewModelBase.cs b/常用工具集/Base/ViewModelBase.cs new file mode 100644 index 0000000..a1c9ccc --- /dev/null +++ b/常用工具集/Base/ViewModelBase.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace 常用工具集.Base +{ + public class ViewModelBase : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + public void NotifyPropertyChanged([CallerMemberName] string propertyname = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname)); + } + } +} diff --git a/常用工具集/FodyWeavers.xml b/常用工具集/FodyWeavers.xml new file mode 100644 index 0000000..d5abfed --- /dev/null +++ b/常用工具集/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/常用工具集/FodyWeavers.xsd b/常用工具集/FodyWeavers.xsd new file mode 100644 index 0000000..69dbe48 --- /dev/null +++ b/常用工具集/FodyWeavers.xsd @@ -0,0 +1,74 @@ + + + + + + + + + + + Used to control if the On_PropertyName_Changed feature is enabled. + + + + + Used to control if the Dependent properties feature is enabled. + + + + + Used to control if the IsChanged property feature is enabled. + + + + + Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form. + + + + + Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project. + + + + + Used to control if equality checks should use the Equals method resolved from the base class. + + + + + Used to control if equality checks should use the static Equals method resolved from the base class. + + + + + Used to turn off build warnings from this weaver. + + + + + Used to turn off build warnings about mismatched On_PropertyName_Changed methods. + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/常用工具集/Program.cs b/常用工具集/Program.cs new file mode 100644 index 0000000..dc77de1 --- /dev/null +++ b/常用工具集/Program.cs @@ -0,0 +1,23 @@ +using System; +using Avalonia; +using Avalonia.Dialogs; + +namespace 常用工具集 +{ + internal sealed class Program + { + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace(); + } +} diff --git a/常用工具集/Utility/BindingProxy.cs b/常用工具集/Utility/BindingProxy.cs new file mode 100644 index 0000000..ce97e13 --- /dev/null +++ b/常用工具集/Utility/BindingProxy.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace 常用工具集.Utility +{ + //public class BindingProxy : Freezable + //{ + // #region Overrides of Freezable + + // protected override Freezable CreateInstanceCore() + // { + // return new BindingProxy(); + // } + + // #endregion Overrides of Freezable + + // public object Data + // { + // get { return (object)GetValue(DataProperty); } + // set { SetValue(DataProperty, value); } + // } + + // // Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc... + // public static readonly DependencyProperty DataProperty = + // DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null)); + //} +} diff --git a/常用工具集/Utility/CZGL.SystemInfo/CPU/CPUHelper.cs b/常用工具集/Utility/CZGL.SystemInfo/CPU/CPUHelper.cs new file mode 100644 index 0000000..4800829 --- /dev/null +++ b/常用工具集/Utility/CZGL.SystemInfo/CPU/CPUHelper.cs @@ -0,0 +1,39 @@ +using System.Runtime.InteropServices; + +namespace CZGL.SystemInfo +{ + /// + /// + /// + public static class CPUHelper + { + /// + /// 获取当前系统消耗的 CPU 时间 + /// + /// + public static CPUTime GetCPUTime() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return WindowsCPU.GetCPUTime(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + return LinuxCPU.GetCPUTime(); + return new CPUTime(); + } + + /// + /// 计算 CPU 使用率 + /// + /// + /// + /// + public static double CalculateCPULoad(CPUTime oldTime, CPUTime newTime) + { + ulong totalTicksSinceLastTime = newTime.SystemTime - oldTime.SystemTime; + ulong idleTicksSinceLastTime = newTime.IdleTime - oldTime.IdleTime; + + double ret = 1.0f - ((totalTicksSinceLastTime > 0) ? ((double)idleTicksSinceLastTime) / totalTicksSinceLastTime : 0); + + return ret; + } + } +} diff --git a/常用工具集/Utility/CZGL.SystemInfo/CPU/CPUTime.cs b/常用工具集/Utility/CZGL.SystemInfo/CPU/CPUTime.cs new file mode 100644 index 0000000..102a160 --- /dev/null +++ b/常用工具集/Utility/CZGL.SystemInfo/CPU/CPUTime.cs @@ -0,0 +1,29 @@ +namespace CZGL.SystemInfo +{ + /// + /// + /// + public struct CPUTime + { + /// + /// + /// + /// + /// + public CPUTime(ulong idleTime, ulong systemTime) + { + IdleTime = idleTime; + SystemTime = systemTime; + } + + /// + /// CPU 空闲时间 + /// + public ulong IdleTime { get; private set; } + + /// + /// CPU 工作时间 + /// + public ulong SystemTime { get; private set; } + } +} diff --git a/常用工具集/Utility/CZGL.SystemInfo/CPU/FILETIME.cs b/常用工具集/Utility/CZGL.SystemInfo/CPU/FILETIME.cs new file mode 100644 index 0000000..297986c --- /dev/null +++ b/常用工具集/Utility/CZGL.SystemInfo/CPU/FILETIME.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace CZGL.SystemInfo +{ + /// + /// + /// + [StructLayout(LayoutKind.Sequential)] + public struct FILETIME + { + /// + /// 时间的低位部分 + /// + public uint DateTimeLow; + + /// + /// 时间的高位部分 + /// + public uint DateTimeHigh; + } +} diff --git a/常用工具集/Utility/CZGL.SystemInfo/CPU/LinuxCPU.cs b/常用工具集/Utility/CZGL.SystemInfo/CPU/LinuxCPU.cs new file mode 100644 index 0000000..bfe9ecc --- /dev/null +++ b/常用工具集/Utility/CZGL.SystemInfo/CPU/LinuxCPU.cs @@ -0,0 +1,55 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace CZGL.SystemInfo +{ + + /// + /// Linux + /// + public static class LinuxCPU + { + const string Path = "/proc/stat"; + + /// + /// 获取 CPU 时间 + /// + /// + public static CPUTime GetCPUTime() + { + ulong IdleTime = 0; + ulong SystemTime = 0; + try + { + var text = File.ReadAllLines(Path); + + foreach (var item in text) + { + if (!item.StartsWith("cpu")) continue; +#if NET6_0_OR_GREATER + var values = item.Split(" ", StringSplitOptions.RemoveEmptyEntries).ToArray(); + SystemTime += (ulong)(values[1..].Select(x => decimal.Parse(x)).Sum()); +#else + var values = item.Split(new char[] { ' '}, StringSplitOptions.RemoveEmptyEntries).ToArray(); + SystemTime += (ulong)(values.ToList().GetRange(1, values.Length).Select(x => decimal.Parse(x)).Sum()); +#endif + + IdleTime += ulong.Parse(values[4]); + } + } + catch (Exception ex) + { + Debug.WriteLine(ex.ToString()); + Debug.Assert(false, ex.Message); + throw new PlatformNotSupportedException($"{RuntimeInformation.OSArchitecture.ToString()} {Environment.OSVersion.Platform.ToString()} {Environment.OSVersion.ToString()}"); + } + + return new CPUTime(IdleTime, SystemTime); + } + } + + +} \ No newline at end of file diff --git a/常用工具集/Utility/CZGL.SystemInfo/CPU/WindowsCPU.cs b/常用工具集/Utility/CZGL.SystemInfo/CPU/WindowsCPU.cs new file mode 100644 index 0000000..7c77a7a --- /dev/null +++ b/常用工具集/Utility/CZGL.SystemInfo/CPU/WindowsCPU.cs @@ -0,0 +1,71 @@ +using System.Runtime.InteropServices; + +namespace CZGL.SystemInfo +{ + /// + /// Windows + /// + public partial class WindowsCPU + { + /* + IdleTime 空闲时间 + KernelTime 内核时间 + UserTime 用户时间 + + 系统时间 = 内核时间 + 用户时间 + SystemTime = KernelTime + UserTime + + */ + + /// + /// 在多处理器系统上,返回的值是所有处理器指定时间的总和 + /// + /// + /// 指向 FILETIME 结构的指针,该结构接收系统空闲的时间量 + /// 指向 FILETIME 结构的指针,该结构接收系统在内核模式下执行的时间量(包括所有进程中的所有线程以及所有处理器上的所有线程)。此时间值还包括系统空闲的时间 + /// 指向 FILETIME 结构的指针,该结构接收系统在 User 模式下执行的时间量(包括所有进程中的所有线程以及所有处理器上的所有线程) + /// +#if NET7_0_OR_GREATER + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool GetSystemTimes(out FILETIME lpIdleTime, out FILETIME lpKernelTime, out FILETIME lpUserTime); +#else + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetSystemTimes(out FILETIME lpIdleTime, out FILETIME lpKernelTime, out FILETIME lpUserTime); +#endif + /// + /// 获取 CPU 工作时间 + /// + /// + /// + /// + /// + public static CPUTime GetCPUTime(FILETIME lpIdleTime, FILETIME lpKernelTime, FILETIME lpUserTime) + { + var IdleTime = ((ulong)lpIdleTime.DateTimeHigh << 32) | lpIdleTime.DateTimeLow; + var KernelTime = ((ulong)lpKernelTime.DateTimeHigh << 32) | lpKernelTime.DateTimeLow; + var UserTime = ((ulong)lpUserTime.DateTimeHigh << 32) | lpUserTime.DateTimeLow; + + var SystemTime = KernelTime + UserTime; + + return new CPUTime(IdleTime, SystemTime); + } + + /// + /// 获取 CPU 工作时间 + /// + /// + public static CPUTime GetCPUTime() + { + FILETIME lpIdleTime = default; + FILETIME lpKernelTime = default; + FILETIME lpUserTime = default; + if (!GetSystemTimes(out lpIdleTime, out lpKernelTime, out lpUserTime)) + { + return default; + } + return GetCPUTime(lpIdleTime, lpKernelTime, lpUserTime); + } + } +} diff --git a/常用工具集/Utility/CZGL.SystemInfo/Disk/DiskInfo.cs b/常用工具集/Utility/CZGL.SystemInfo/Disk/DiskInfo.cs new file mode 100644 index 0000000..91f1780 --- /dev/null +++ b/常用工具集/Utility/CZGL.SystemInfo/Disk/DiskInfo.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace CZGL.SystemInfo +{ + /// + /// 磁盘信息 + /// + public class DiskInfo + { + private readonly DriveInfo _info; + + + /// + /// 获取磁盘类 + /// + public DriveInfo DriveInfo => _info; + + private DiskInfo(DriveInfo info) + { + _info = info; + } + + /// + /// 驱动器名称 + /// ex: C:\ + /// + public string Id => _info.Name; + + /// + /// 磁盘名称 + /// ex:
+ /// Windows: system
+ /// Linux: /dev + ///
+ ///
+ public string Name => _info.Name; + + /// + /// 获取驱动器类型 + /// + /// 获取驱动器类型,如 CD-ROM、可移动、网络或固定 + public DriveType DriveType => _info.DriveType; + + /// + /// 文件系统 + /// + /// Windows: NTFS、 CDFS...
+ /// Linux: rootfs、tmpfs、binfmt_misc... + ///
+ ///
+ public string FileSystem => _info.DriveFormat; + + /// + /// 磁盘剩余容量(以字节为单位) + /// + public long FreeSpace => _info.AvailableFreeSpace; + + /// + /// 磁盘总容量(以字节为单位) + /// + public long TotalSize => _info.TotalSize; + + /// + /// 磁盘剩余可用容量 + /// + public long UsedSize => TotalSize - FreeSpace; + + /// + /// 磁盘根目录位置 + /// + public string RootPath => _info.RootDirectory.FullName; + + /// + /// 获取本地所有磁盘信息 + /// + /// + public static DiskInfo[] GetDisks() + { + return DriveInfo.GetDrives().Where(x => x.IsReady).Select(x => new DiskInfo(x)).ToArray(); + } + + /// + /// 获取 Docker 运行的容器其容器文件系统在主机中的存储位置 + /// + /// 程序需要在宿主机运行才有效果,在容器中运行,调用此API获取不到相关信息 + /// + public static DiskInfo[] GetDockerMerge() + { + return DriveInfo.GetDrives() + .Where(x => x.DriveFormat.Equals("overlay", StringComparison.OrdinalIgnoreCase) && x.DriveFormat.Contains("docker")) + .Select(x => new DiskInfo(x)).ToArray(); + } + + + /// + /// 筛选出真正能够使用的磁盘 + /// + /// + public static DiskInfo[] GetRealDisk() + { + var disks = DriveInfo.GetDrives() + .Where(x => + x.DriveType == DriveType.Fixed && + x.TotalSize != 0 && x.DriveFormat != "overlay"); + + return disks.Select(x => new DiskInfo(x)) + .Distinct(new DiskInfoEquality()).ToArray(); + } + + /// + /// 筛选重复项 + /// + private class DiskInfoEquality : IEqualityComparer + { + public bool Equals(DiskInfo x, DiskInfo y) + { + return x?.Id == y?.Id; + } + + public int GetHashCode(DiskInfo obj) + { + return obj.Id.GetHashCode(); + } + } + } +} diff --git a/常用工具集/Utility/CZGL.SystemInfo/Memory/LinuxMemory.cs b/常用工具集/Utility/CZGL.SystemInfo/Memory/LinuxMemory.cs new file mode 100644 index 0000000..2f5bf05 --- /dev/null +++ b/常用工具集/Utility/CZGL.SystemInfo/Memory/LinuxMemory.cs @@ -0,0 +1,54 @@ +using CZGL.SystemInfo.Memory; +using System.Runtime.InteropServices; + +namespace CZGL.SystemInfo +{ + + /// + /// + /// + public partial class LinuxMemory + { + /// + /// + /// + /// + public static MemoryValue GetMemory() + { + Sysinfo info = new Sysinfo(); + if (sysinfo(ref info) != 0) + { + return default; + } + var usedPercentage = (((double)info.totalram - info.freeram) / (double)info.totalram) * 100; + MemoryValue value = new MemoryValue(info.totalram, info.freeram, (ulong)usedPercentage, info.totalswap, info.freeswap); + return value; + } + +#if NET7_0_OR_GREATER + + /// + /// 返回整个系统统计信息, + /// + /// int sysinfo(struct sysinfo *info); + /// + /// + [LibraryImport("libc.so.6", SetLastError = true)] + [return: MarshalAs(UnmanagedType.I4)] + public static partial System.Int32 sysinfo(ref Sysinfo info); + +#else + + /// + /// 返回整个系统统计信息, + /// + /// int sysinfo(struct sysinfo *info); + /// + /// + [DllImport("libc.so.6", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.I4)] + public static extern System.Int32 sysinfo(ref Sysinfo info); + +#endif + } +} diff --git a/常用工具集/Utility/CZGL.SystemInfo/Memory/MEMORYSTATUS.cs b/常用工具集/Utility/CZGL.SystemInfo/Memory/MEMORYSTATUS.cs new file mode 100644 index 0000000..1db59ec --- /dev/null +++ b/常用工具集/Utility/CZGL.SystemInfo/Memory/MEMORYSTATUS.cs @@ -0,0 +1,17 @@ +namespace CZGL.SystemInfo +{ + /// + /// + /// + public struct MEMORYSTATUS + { + public uint dwLength; + public uint dwMemoryLoad; + public uint dwTotalPhys; + public uint dwAvailPhys; + public uint dwTotalPageFile; + public uint dwAvailPageFile; + public uint dwTotalVirtual; + public uint dwAvailVirtual; + } +} diff --git a/常用工具集/Utility/CZGL.SystemInfo/Memory/MemoryHelper.cs b/常用工具集/Utility/CZGL.SystemInfo/Memory/MemoryHelper.cs new file mode 100644 index 0000000..633a4c6 --- /dev/null +++ b/常用工具集/Utility/CZGL.SystemInfo/Memory/MemoryHelper.cs @@ -0,0 +1,24 @@ +using System.Runtime.InteropServices; + +namespace CZGL.SystemInfo +{ + /// + /// + /// + public static class MemoryHelper + { + /// + /// 获取当前系统的内存信息 + /// + /// + public static MemoryValue GetMemoryValue() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return WindowsMemory.GetMemory(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + return LinuxMemory.GetMemory(); + + return default; + } + } +} diff --git a/常用工具集/Utility/CZGL.SystemInfo/Memory/MemoryStatusExE.cs b/常用工具集/Utility/CZGL.SystemInfo/Memory/MemoryStatusExE.cs new file mode 100644 index 0000000..ba60945 --- /dev/null +++ b/常用工具集/Utility/CZGL.SystemInfo/Memory/MemoryStatusExE.cs @@ -0,0 +1,68 @@ +using System.Runtime.InteropServices; + +namespace CZGL.SystemInfo.Memory +{ + /// + /// 包含有关物理内存和虚拟内存(包括扩展内存)的当前状态的信息。该 GlobalMemoryStatusEx在这个构造函数存储信息。 + /// + /// + public struct MemoryStatusExE + { + /// + /// 结构的大小,以字节为单位,必须在调用 GlobalMemoryStatusEx 之前设置此成员,可以用 Init 方法提前处理 + /// + /// 应当使用本对象提供的 Init ,而不是使用构造函数! + public uint dwLength; + + /// + /// 一个介于 0 和 100 之间的数字,用于指定正在使用的物理内存的大致百分比(0 表示没有内存使用,100 表示内存已满)。 + /// + public uint dwMemoryLoad; + + /// + /// 实际物理内存量,以字节为单位 + /// + public ulong ullTotalPhys; + + /// + /// 当前可用的物理内存量,以字节为单位。这是可以立即重用而无需先将其内容写入磁盘的物理内存量。它是备用列表、空闲列表和零列表的大小之和 + /// + public ulong ullAvailPhys; + + /// + /// 系统或当前进程的当前已提交内存限制,以字节为单位,以较小者为准。要获得系统范围的承诺内存限制,请调用GetPerformanceInfo + /// + public ulong ullTotalPageFile; + + /// + /// 当前进程可以提交的最大内存量,以字节为单位。该值等于或小于系统范围的可用提交值。要计算整个系统的可承诺值,调用GetPerformanceInfo核减价值CommitTotal从价值CommitLimit + /// + + public ulong ullAvailPageFile; + + /// + /// 调用进程的虚拟地址空间的用户模式部分的大小,以字节为单位。该值取决于进程类型、处理器类型和操作系统的配置。例如,对于 x86 处理器上的大多数 32 位进程,此值约为 2 GB,对于在启用4 GB 调整的系统上运行的具有大地址感知能力的 32 位进程约为 3 GB 。 + /// + + public ulong ullTotalVirtual; + + /// + /// 当前在调用进程的虚拟地址空间的用户模式部分中未保留和未提交的内存量,以字节为单位 + /// + public ulong ullAvailVirtual; + + + /// + /// 预订的。该值始终为 0 + /// + public ulong ullAvailExtendedVirtual; + + /// + /// + /// + public void Init() + { + dwLength = checked((uint)Marshal.SizeOf(typeof(MemoryStatusExE))); + } + } +} diff --git a/常用工具集/Utility/CZGL.SystemInfo/Memory/MemoryValue.cs b/常用工具集/Utility/CZGL.SystemInfo/Memory/MemoryValue.cs new file mode 100644 index 0000000..a585f8e --- /dev/null +++ b/常用工具集/Utility/CZGL.SystemInfo/Memory/MemoryValue.cs @@ -0,0 +1,66 @@ +namespace CZGL.SystemInfo +{ + /// + /// 内存值表示 + /// + public struct MemoryValue + { + /// + /// + /// + /// 物理内存字节数 + /// 可用的物理内存字节数 + /// 已用物理内存百分比 + /// 虚拟内存字节数 + /// 可用虚拟内存字节数 + public MemoryValue( + ulong totalPhysicalMemory, + ulong availablePhysicalMemory, + double usedPercentage, + ulong totalVirtualMemory, + ulong availableVirtualMemory) + { + TotalPhysicalMemory = totalPhysicalMemory; + AvailablePhysicalMemory = availablePhysicalMemory; + UsedPercentage = usedPercentage; + TotalVirtualMemory = totalVirtualMemory; + AvailableVirtualMemory = availableVirtualMemory; + } + + /// + /// 物理内存字节数 + /// + public ulong TotalPhysicalMemory { get; private set; } + + /// + /// 可用的物理内存字节数 + /// + public ulong AvailablePhysicalMemory { get; private set; } + + /// + /// 已用物理内存字节数 + /// + public ulong UsedPhysicalMemory => TotalPhysicalMemory - AvailablePhysicalMemory; + + /// + /// 已用物理内存百分比,0~100,100表示内存已用尽 + /// + public double UsedPercentage { get; private set; } + + /// + /// 虚拟内存字节数 + /// + public ulong TotalVirtualMemory { get; private set; } + + /// + /// 可用虚拟内存字节数 + /// + public ulong AvailableVirtualMemory { get; private set; } + + /// + /// 已用虚拟内存字节数 + /// + public ulong UsedVirtualMemory => TotalVirtualMemory - AvailableVirtualMemory; + } + +} \ No newline at end of file diff --git a/常用工具集/Utility/CZGL.SystemInfo/Memory/Sysinfo.cs b/常用工具集/Utility/CZGL.SystemInfo/Memory/Sysinfo.cs new file mode 100644 index 0000000..27f3f6c --- /dev/null +++ b/常用工具集/Utility/CZGL.SystemInfo/Memory/Sysinfo.cs @@ -0,0 +1,74 @@ +using System.Runtime.InteropServices; + +namespace CZGL.SystemInfo +{ + /// + /// + /// + public struct Sysinfo + { + /// + /// Seconds since boot + /// + public long uptime; + + /// + /// 获取 1,5,15 分钟内存的平均使用量,数组大小为 3 + /// + unsafe public fixed ulong loads[3]; + /// + /// 总物理内存 + /// + public ulong totalram; + + /// + /// 可用内存 + /// + public ulong freeram; + + /// + /// 共享内存 + /// + public ulong sharedram; + + /// + /// Memory used by buffers + /// + public ulong bufferram; + + /// + /// Total swap space size + /// + public ulong totalswap; + + /// + /// swap space still available + /// + public ulong freeswap; + + /// + /// Number of current processes + /// + public ushort procs; + + /// + /// Total high memory size + /// + public ulong totalhigh; + + /// + /// Available high memory size + /// + public ulong freehigh; + + /// + /// Memory unit size in bytes + /// + public uint mem_unit; + + /// + /// Padding to 64 bytes + /// + unsafe public fixed byte _f[64]; + } +} diff --git a/常用工具集/Utility/CZGL.SystemInfo/Memory/WindowsMemory.cs b/常用工具集/Utility/CZGL.SystemInfo/Memory/WindowsMemory.cs new file mode 100644 index 0000000..14af1f9 --- /dev/null +++ b/常用工具集/Utility/CZGL.SystemInfo/Memory/WindowsMemory.cs @@ -0,0 +1,86 @@ +using CZGL.SystemInfo.Memory; +using System; +using System.Runtime.InteropServices; + +namespace CZGL.SystemInfo +{ + /// + /// + /// + public partial class WindowsMemory + { + +#if NET7_0_OR_GREATER + + /// + /// 在内存超过 4 GB 的计算机上, GlobalMemoryStatus函数可能返回不正确的信息,报告值 –1 表示溢出。因此,应用程序应改用 GlobalMemoryStatusEx函数。 + /// + /// Windows XP [仅限桌面应用程序];最低支持服务器 Windows Server 2003 [仅限桌面应用程序] + /// + [LibraryImport("Kernel32.dll", SetLastError = true)] + public static partial void GlobalMemoryStatus(ref MEMORYSTATUS lpBuffer); + + /// + /// 检索有关系统当前使用物理和虚拟内存的信息 + /// + /// + /// + /// + [LibraryImport("Kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial Boolean GlobalMemoryStatusEx(ref MemoryStatusExE lpBuffer); + +#else + + + /// + /// 在内存超过 4 GB 的计算机上, GlobalMemoryStatus函数可能返回不正确的信息,报告值 –1 表示溢出。因此,应用程序应改用 GlobalMemoryStatusEx函数。 + /// + /// Windows XP [仅限桌面应用程序];最低支持服务器 Windows Server 2003 [仅限桌面应用程序] + /// + [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern void GlobalMemoryStatus(ref MEMORYSTATUS lpBuffer); + + /// + /// 检索有关系统当前使用物理和虚拟内存的信息 + /// + /// + /// + /// + [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern Boolean GlobalMemoryStatusEx(ref MemoryStatusExE lpBuffer); +#endif + /// + /// + /// + /// + public static MemoryValue GetMemory() + { + // 检查 Windows 内核版本,是否为旧系统 + if (Environment.OSVersion.Version.Major < 5) + { + // https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions"); + return default; + } + + MemoryStatusExE memoryStatusEx = new MemoryStatusExE(); + // 初始化结构的大小 + memoryStatusEx.Init(); + // 刷新值 + if (!GlobalMemoryStatusEx(ref memoryStatusEx)) return default; + + var TotalPhysicalMemory = memoryStatusEx.ullTotalPhys; + var AvailablePhysicalMemory = memoryStatusEx.ullAvailPhys; + var TotalVirtualMemory = memoryStatusEx.ullTotalVirtual; + var AvailableVirtualMemory = memoryStatusEx.ullAvailVirtual; + var UsedPercentage = memoryStatusEx.dwMemoryLoad; + return new MemoryValue( + TotalPhysicalMemory, + AvailablePhysicalMemory, + UsedPercentage, + TotalVirtualMemory, + AvailableVirtualMemory); + } + } +} diff --git a/常用工具集/Utility/CZGL.SystemInfo/Network/NetworkInfo.cs b/常用工具集/Utility/CZGL.SystemInfo/Network/NetworkInfo.cs new file mode 100644 index 0000000..16b8cbe --- /dev/null +++ b/常用工具集/Utility/CZGL.SystemInfo/Network/NetworkInfo.cs @@ -0,0 +1,231 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace CZGL.SystemInfo +{ + /// + /// 网络接口信息 + /// + public class NetworkInfo + { + private NetworkInterface _instance; + + private NetworkInfo(NetworkInterface network) + { + _instance = network; + } + + /// + /// 当前实例使用的网络接口 + /// + public NetworkInterface NetworkInterface => _instance; + + + #region 基础信息 + + /// + /// 获取网络适配器的标识符 + /// + /// ex:{92D3E528-5363-43C7-82E8-D143DC6617ED} + public string Id => _instance.Id; + + /// + /// 网络的 Mac 地址 + /// + /// ex: 1C997AF108E3 + public string Mac => _instance.GetPhysicalAddress().ToString(); + + /// + /// 网卡名称 + /// + /// ex:以太网,WLAN + public string Name => _instance.Name; + + + /// + /// 描述网络接口的用户可读文本, + /// 在 Windows 上,它通常描述接口供应商、类型 (例如,以太网) 、品牌和型号; + /// + /// ex:Realtek PCIe GbE Family Controller、 Realtek 8822CE Wireless LAN 802.11ac PCI-E NIC + public string Trademark => _instance.Description; + + /// + /// 获取网络连接的当前操作状态
+ ///
+ public OperationalStatus Status => _instance.OperationalStatus; + + /// + /// 获取网卡接口类型
+ ///
+ public NetworkInterfaceType NetworkType => _instance.NetworkInterfaceType; + + /// + /// 网卡链接速度,每字节/秒为单位 + /// + /// 如果是-1,则说明无法获取此网卡的链接速度;例如 270_000_000 表示是 270MB 的链接速度 + public long Speed => _instance.Speed; + + /// + /// 是否支持 Ipv4 + /// + public bool IsSupportIpv4 => _instance.Supports(NetworkInterfaceComponent.IPv4); + + /// + /// 获取分配给此接口的任意广播 IP 地址。只支持 Windows + /// + /// 一般情况下为空数组 + public IPAddress[] AnycastAddresses + { + get + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return _instance.GetIPProperties().AnycastAddresses.Select(x => x.Address).ToArray(); + } + else + { + return Array.Empty(); + } + } + } + + /// + /// 获取分配给此接口的多播地址,ipv4、ipv6 + /// + /// ex:ff01::1%9 ff02::1%9
+ /// ff02::fb%9
+ /// ff02::1:3%9
+ /// ff02::1:ff61:9ae7%9
+ /// 224.0.0.1
+ public IPAddress[] MulticastAddresses => _instance.GetIPProperties().MulticastAddresses.Select(x => x.Address).ToArray(); + + /// + /// 获取分配给此接口的单播地址,ipv4、ipv6 + /// + /// ex:192.168.3.38 + public IPAddress[] UnicastAddresses => _instance.GetIPProperties().UnicastAddresses.Select(x => x.Address).ToArray(); + + /// + /// 获取此接口的 IPv4 网关地址,ipv4、ipv6 + /// + /// ex:fe80::1677:40ff:fef9:bf95%5、192.168.3.1 + public IPAddress[] GatewayAddresses => _instance.GetIPProperties().GatewayAddresses.Select(x => x.Address).ToArray(); + + /// + /// 获取此接口的域名系统 (DNS) 服务器的地址,ipv4、ipv6 + /// + /// ex:fe80::1677:40ff:fef9:bf95%5、192.168.3.1 + public IPAddress[] DnsAddresses => _instance.GetIPProperties().DnsAddresses.ToArray(); + + /// + /// 是否支持 Ipv6 + /// + public bool IsSupportIpv6 => _instance.Supports(NetworkInterfaceComponent.IPv6); + + #endregion + + + + /// + /// 当前主机是否能够与其他计算机通讯(公网或内网),如果任何网络接口标记为 "up" 且不是环回或隧道接口,则认为网络连接可用。 + /// + public static bool GetIsNetworkAvailable => NetworkInterface.GetIsNetworkAvailable(); + + /// + /// 计算 IPV4 的网络流量 + /// + /// + /// 当前网卡不支持 IPV4 + public Rate GetIpv4Speed() + { + // 当前网卡不支持 IPV4 + if (!IsSupportIpv4) return default; + var ipv4Statistics = _instance.GetIPv4Statistics(); + var speed = new Rate(DateTime.Now, ipv4Statistics.BytesReceived, ipv4Statistics.BytesSent); + return speed; + } + + /// + /// 计算 IPV4 、IPV6 的网络流量 + /// + /// + public Rate IpvSpeed() + { + var ipvStatistics = _instance.GetIPStatistics(); + var speed = new Rate(DateTime.Now, ipvStatistics.BytesReceived, ipvStatistics.BytesSent); + return speed; + } + + /// + /// 获取所有 IP 地址 + /// + /// + public static IPAddress[] GetIPAddresses() + { + var hostName = Dns.GetHostName(); + return Dns.GetHostAddresses(hostName); + } + + /// + /// 获取当前真实 IP + /// + /// + public static IPAddress TryGetRealIpv4() + { + var addrs = GetIPAddresses(); + var ipv4 = addrs.FirstOrDefault(x => x.AddressFamily == AddressFamily.InterNetwork); + return ipv4; + } + + /// + /// 获取真实网卡 + /// + /// + public static NetworkInfo TryGetRealNetworkInfo() + { + var realIp = TryGetRealIpv4(); + if (realIp == null) + { + return default; + } + var infos = NetworkInfo.GetNetworkInfos().ToArray(); + var info = infos.FirstOrDefault(x => x.UnicastAddresses.Any(i => i.MapToIPv4().ToString() == realIp.MapToIPv4().ToString())); + if (info == null) + { + return default; + } + return info; + } + + /// + /// 获取此主机中所有网卡接口 + /// + /// + public static NetworkInfo[] GetNetworkInfos() + { + return NetworkInterface.GetAllNetworkInterfaces().Select(x => new NetworkInfo(x)).ToArray(); + } + + /// + /// 计算网络流量速率 + /// + /// + /// + /// + public static (SizeInfo Received, SizeInfo Sent) GetSpeed(Rate oldRate, Rate newRate) + { + var receive = newRate.ReceivedLength - oldRate.ReceivedLength; + var send = newRate.SendLength - oldRate.SendLength; + var interval = Math.Round((newRate.StartTime - oldRate.StartTime).TotalSeconds, 2); + + long rSpeed = (long)(receive / interval); + long sSpeed = (long)(send / interval); + + return (SizeInfo.Get(rSpeed), SizeInfo.Get(sSpeed)); + } + } +} diff --git a/常用工具集/Utility/CZGL.SystemInfo/Network/Rate.cs b/常用工具集/Utility/CZGL.SystemInfo/Network/Rate.cs new file mode 100644 index 0000000..aa72372 --- /dev/null +++ b/常用工具集/Utility/CZGL.SystemInfo/Network/Rate.cs @@ -0,0 +1,34 @@ +using System; + +namespace CZGL.SystemInfo +{ + + /// + /// + /// + public struct Rate + { + public Rate(DateTime startTime, long receivedLength, long sendLength) + { + StartTime = startTime; + ReceivedLength = receivedLength; + SendLength = sendLength; + } + + /// + /// 记录时间 + /// + public DateTime StartTime { get; private set; } + + /// + /// 此网卡总接收网络流量字节数 + /// + public long ReceivedLength { get; private set; } + + /// + /// 此网卡总发送网络流量字节数 + /// + public long SendLength { get; private set; } + } + +} diff --git a/常用工具集/Utility/CZGL.SystemInfo/SizeInfo.cs b/常用工具集/Utility/CZGL.SystemInfo/SizeInfo.cs new file mode 100644 index 0000000..e806cbb --- /dev/null +++ b/常用工具集/Utility/CZGL.SystemInfo/SizeInfo.cs @@ -0,0 +1,61 @@ +using System; + +namespace CZGL.SystemInfo +{ + /// + /// 大小信息 + /// + public struct SizeInfo + { + /// + /// Byte 长度 + /// + public long ByteLength { get; private set; } + + /// + /// 大小 + /// + public decimal Size { get; set; } + + /// + /// 单位 + /// + public UnitType SizeType { get; set; } + + + /// + /// 将字节单位转换为合适的单位 + /// + /// 字节长度 + /// + public static SizeInfo Get(long byteLength) + { + UnitType unit = 0; + decimal number = byteLength; + if (byteLength < 1000) + { + return new SizeInfo() + { + ByteLength = byteLength, + Size = byteLength, + SizeType = UnitType.B + }; + } + // 避免出现 1023B 这种情况;这样 1023B 会显示 0.99KB + while (Math.Round(number / 1000) >= 1) + { + number = number / 1024; + unit++; + } + + return new SizeInfo + { + Size = Math.Round(number, 2), + SizeType = unit, + ByteLength = byteLength + }; + + throw new Exception(); + } + } +} diff --git a/常用工具集/Utility/CZGL.SystemInfo/System/SystemPlatformInfo.cs b/常用工具集/Utility/CZGL.SystemInfo/System/SystemPlatformInfo.cs new file mode 100644 index 0000000..366da76 --- /dev/null +++ b/常用工具集/Utility/CZGL.SystemInfo/System/SystemPlatformInfo.cs @@ -0,0 +1,133 @@ +using System; +using System.Runtime.InteropServices; + +namespace CZGL.SystemInfo +{ + /// + /// 提供有关 .NET 运行时安装的信息、程序系统信息等。 + /// + public static class SystemPlatformInfo + { + + /// + /// .NET Fx/Core Runtime version + /// ex: .NET Core 3.1.9 + /// + public static string FrameworkDescription => RuntimeInformation.FrameworkDescription; + + /// + /// .NET Fx/Core version + /// + /// ex:
+ /// 3.1.9 + ///
+ ///
+ public static string FrameworkVersion => Environment.Version.ToString(); + + /// + /// 操作系统平台架构,可点击 获取详细的信息 + /// + /// ex:
+ /// X86
+ /// X64
+ /// Arm
+ /// Arm64 + ///
+ ///
+ public static string OSArchitecture => RuntimeInformation.OSArchitecture.ToString(); + + /// + /// 获取操作系统的类型 + /// + /// ex:
+ /// Win32S、Win32Windows、Win32NT、WinCE、Unix、Xbox、MacOSX + ///
+ ///
+ public static string OSPlatformID => Environment.OSVersion.Platform.ToString(); + + /// + /// 操作系统内核版本 + /// + /// ex:
+ /// Microsoft Windows NT 6.2.9200.0
+ /// Unix 4.4.0.19041 + ///
+ ///
+ public static string OSVersion => Environment.OSVersion.ToString(); + + /// + /// 操作系统的版本描述 + /// + /// ex:
+ /// Microsoft Windows 10.0.19041 + ///
+ /// Linux 4.4.0-19041-Microsoft #488-Microsoft Mon Sep 01 13:43:00 PST 2020 + ///
+ ///
+ public static string OSDescription => RuntimeInformation.OSDescription; + + /// + /// 本进程的架构,可点击 获取详细的信息 + /// + /// ex:
+ /// X86
+ /// X64
+ /// Arm
+ /// Arm64 + ///
+ ///
+ public static string ProcessArchitecture => RuntimeInformation.ProcessArchitecture.ToString(); + + /// + /// 当前计算机上的处理器数 + /// + /// 如 4核心8线程的 CPU,这里会获取到 8 + public static int ProcessorCount => Environment.ProcessorCount; + + /// + /// 计算机名称 + /// + public static string MachineName => Environment.MachineName; + + /// + /// 当前登录到此系统的用户名称 + /// + public static string UserName => Environment.UserName; + + /// + /// 用户网络域名称,即 hostname + /// + public static string UserDomainName => Environment.UserDomainName; + + + /// + /// 是否在交互模式中运行 + /// + public static bool IsUserInteractive => Environment.UserInteractive; + + /// + /// 系统的磁盘和分区列表 + /// + /// ex:
+ /// Windows: D:\, E:\, F:\, G:\, H:\, J:\, X:\
+ /// Linux: /, /dev, /sys, /proc, /dev/pts, /run, /run/lock, /run/shm ... + ///
+ ///
+ public static string[] GetLogicalDrives => Environment.GetLogicalDrives(); + + /// + /// 系统根目录完全路径。Linux 没有系统根目录 + /// + /// ex:
+ /// Windows: X:\WINDOWS\system32

+ /// Linux : null + ///
+ ///
+ public static string SystemDirectory => Environment.SystemDirectory; + + /// + /// 操作系统内存页一页的字节数 + /// + public static int MemoryPageSize => Environment.SystemPageSize; + } +} diff --git a/常用工具集/Utility/CZGL.SystemInfo/UnitType.cs b/常用工具集/Utility/CZGL.SystemInfo/UnitType.cs new file mode 100644 index 0000000..cc1f007 --- /dev/null +++ b/常用工具集/Utility/CZGL.SystemInfo/UnitType.cs @@ -0,0 +1,38 @@ +namespace CZGL.SystemInfo +{ + /// + /// 单位 + /// + public enum UnitType : int + { + /// + /// Byte + /// + /// + B = 0, + /// + /// KB + /// + KB, + + /// + /// MB + /// + MB, + + /// + /// GB + /// + GB, + + /// + /// TB + /// + TB, + + /// + /// PB + /// + PB + } +} diff --git a/常用工具集/Utility/Core/Checker.cs b/常用工具集/Utility/Core/Checker.cs new file mode 100644 index 0000000..7e26964 --- /dev/null +++ b/常用工具集/Utility/Core/Checker.cs @@ -0,0 +1,394 @@ +using System; +using System.Text; +using System.Text.RegularExpressions; + +namespace MES.Utility.Core +{ + public static class Checker + { + #region 验证IP + /// + /// 验证IP + /// + /// + /// + public static bool IsIP(this string source) + { + return Regex.IsMatch(source, @"^(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])$", RegexOptions.IgnoreCase); + } + public static bool HasIP(this string source) + { + return Regex.IsMatch(source, @"(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])", RegexOptions.IgnoreCase); + } + #endregion + + #region 验证EMail是否合法 + /// + /// 验证EMail是否合法 + /// + /// 要验证的Email + public static bool IsEmail(this string source) + { + return Regex.IsMatch(source, @"^[A-Za-z0-9](([_\.\-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([\.\-]?[a-zA-Z0-9]+)*)\.([A-Za-z]{2,})$", RegexOptions.IgnoreCase); + } + public static bool HasEmail(this string source) + { + return Regex.IsMatch(source, @"[A-Za-z0-9](([_\.\-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([\.\-]?[a-zA-Z0-9]+)*)\.([A-Za-z]{2,})", RegexOptions.IgnoreCase); + } + #endregion + + #region 验证网址 + /// + /// 验证网址 + /// + /// + /// + public static bool IsUrl(this string source) + { + return Regex.IsMatch(source, @"^(((file|gopher|news|nntp|telnet|http|ftp|https|ftps|sftp)://)|(www\.))+(([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(/[a-zA-Z0-9\&%_\./-~-]*)?$", RegexOptions.IgnoreCase); + } + public static bool HasUrl(this string source) + { + return Regex.IsMatch(source, @"(((file|gopher|news|nntp|telnet|http|ftp|https|ftps|sftp)://)|(www\.))+(([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(/[a-zA-Z0-9\&%_\./-~-]*)?", RegexOptions.IgnoreCase); + } + #endregion + + #region 验证日期 + /// + /// 验证日期 + /// + /// + /// + public static bool IsDateTime(this string source) + { + try + { + DateTime time = Convert.ToDateTime(source); + return true; + } + catch + { + return false; + } + } + #endregion + + + #region 验证手机号 + /// + /// 验证手机号 + /// + /// + /// + public static bool IsMobile(this string source) + { + return Regex.IsMatch(source, @"^1[35678]\d{9}$", RegexOptions.IgnoreCase); + } + public static bool HasMobile(this string source) + { + return Regex.IsMatch(source, @"1[35678]\d{9}", RegexOptions.IgnoreCase); + } + #endregion + + #region 验证身份证是否有效 + /// + /// 验证身份证是否有效 + /// + /// + /// + public static bool IsIDCard(this string Id) + { + if (Id.Length == 18) + { + bool check = IsIDCard18(Id); + return check; + } + else if (Id.Length == 15) + { + bool check = IsIDCard15(Id); + return check; + } + else + { + return false; + } + } + public static bool IsIDCard18(this string Id) + { + long n = 0; + if (long.TryParse(Id.Remove(17), out n) == false || n < Math.Pow(10, 16) || long.TryParse(Id.Replace('x', '0').Replace('X', '0'), out n) == false) + { + return false;//数字验证 + } + string address = "11x22x35x44x53x12x23x36x45x54x13x31x37x46x61x14x32x41x50x62x15x33x42x51x63x21x34x43x52x64x65x71x81x82x91"; + if (address.IndexOf(Id.Remove(2)) == -1) + { + return false;//省份验证 + } + string birth = Id.Substring(6, 8).Insert(6, "-").Insert(4, "-"); + DateTime time = new DateTime(); + if (DateTime.TryParse(birth, out time) == false) + { + return false;//生日验证 + } + string[] arrVarifyCode = ("1,0,x,9,8,7,6,5,4,3,2").Split(','); + string[] Wi = ("7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2").Split(','); + char[] Ai = Id.Remove(17).ToCharArray(); + int sum = 0; + for (int i = 0; i < 17; i++) + { + sum += int.Parse(Wi[i]) * int.Parse(Ai[i].ToString()); + } + int y = -1; + Math.DivRem(sum, 11, out y); + if (arrVarifyCode[y] != Id.Substring(17, 1).ToLower()) + { + return false;//校验码验证 + } + return true;//符合GB11643-1999标准 + } + public static bool IsIDCard15(this string Id) + { + long n = 0; + if (long.TryParse(Id, out n) == false || n < Math.Pow(10, 14)) + { + return false;//数字验证 + } + string address = "11x22x35x44x53x12x23x36x45x54x13x31x37x46x61x14x32x41x50x62x15x33x42x51x63x21x34x43x52x64x65x71x81x82x91"; + if (address.IndexOf(Id.Remove(2)) == -1) + { + return false;//省份验证 + } + string birth = Id.Substring(6, 6).Insert(4, "-").Insert(2, "-"); + DateTime time = new DateTime(); + if (DateTime.TryParse(birth, out time) == false) + { + return false;//生日验证 + } + return true;//符合15位身份证标准 + } + #endregion + + + + #region 是不是Int型的 + /// + /// 是不是Int型的 + /// + /// + /// + public static bool IsInt(this string source) + { + Regex regex = new Regex(@"^(-){0,1}\d+$"); + if (regex.Match(source).Success) + { + if ((long.Parse(source) > 0x7fffffffL) || (long.Parse(source) < -2147483648L)) + { + return false; + } + return true; + } + return false; + } + #endregion + + #region 看字符串的长度是不是在限定数之间 一个中文为两个字符 + /// + /// 看字符串的长度是不是在限定数之间 一个中文为两个字符 + /// + /// 字符串 + /// 大于等于 + /// 小于等于 + /// + public static bool IsLengthStr(this string source, int begin, int end) + { + int length = Regex.Replace(source, @"[^\x00-\xff]", "OK").Length; + if ((length <= begin) && (length >= end)) + { + return false; + } + return true; + } + #endregion + + #region 是不是中国电话,格式010-85849685 + /// + /// 是不是中国电话,格式010-85849685 + /// + /// + /// + public static bool IsTel(this string source) + { + return Regex.IsMatch(source, @"^\d{3,4}-?\d{6,8}$", RegexOptions.IgnoreCase); + } + #endregion + + #region 邮政编码 6个数字 + /// + /// 邮政编码 6个数字 + /// + /// + /// + public static bool IsPostCode(this string source) + { + return Regex.IsMatch(source, @"^\d{6}$", RegexOptions.IgnoreCase); + } + #endregion + #region 中文 + /// + /// 中文 + /// + /// + /// + public static bool IsChinese(this string source) + { + return Regex.IsMatch(source, @"^[\u4e00-\u9fa5]+$", RegexOptions.IgnoreCase); + } + public static bool hasChinese(this string source) + { + return Regex.IsMatch(source, @"[\u4e00-\u9fa5]+", RegexOptions.IgnoreCase); + } + #endregion + + #region 验证是不是正常字符 字母,数字,下划线的组合 + /// + /// 验证是不是正常字符 字母,数字,下划线的组合 + /// + /// + /// + public static bool IsNormalChar(this string source) + { + return Regex.IsMatch(source, @"[\w\d_]+", RegexOptions.IgnoreCase); + } + #endregion + + #region 验证用户名:必须以字母开头,可以包含字母、数字、“_”、“.”,至少5个字符 + /// + /// 验证用户名:必须以字母开头,可以包含字母、数字、“_”、“.”,至少5个字符 + /// + /// + /// + public static bool checkUserId(this string str) + { + Regex regex = new Regex("[a-zA-Z]{1}([a-zA-Z0-9]|[._]){4,19}"); + if (regex.Match(str).Success) + if (regex.Matches(str)[0].Value.Length == str.Length) + return true; + return false; + } + #endregion + + /// + /// 是否是Base64字符串 + /// + /// + /// + public static bool IsBase64(string eStr) + { + if ((eStr.Length % 4) != 0) + { + return false; + } + if (!Regex.IsMatch(eStr, "^[A-Z0-9/+=]*$", RegexOptions.IgnoreCase)) + { + return false; + } + return true; + } + + #region 验证是否为小数 + public static bool IsValidDecimal(this string strIn) + { + return Regex.IsMatch(strIn, @"[0].d{1,2}|[1]"); + } + #endregion + #region 验证年月日 + public static bool IsValidDate(this string strIn) + { + return Regex.IsMatch(strIn, @"^2d{3}-(?:0?[1-9]|1[0-2])-(?:0?[1-9]|[1-2]d|3[0-1])(?:0?[1-9]|1d|2[0-3]):(?:0?[1-9]|[1-5]d):(?:0?[1-9]|[1-5]d)$"); + } + #endregion + #region 验证日期格式 + //检察是否正确的日期格式 + public static bool IsDate(this string str) + { + //考虑到了4年一度的366天,还有特殊的2月的日期 + Regex reg = new Regex(@"^((((1[6-9]|[2-9]\d)\d{2})-(0?[13578]|1[02])-(0?[1-9]|[12]\d|3[01]))|(((1[6-9]|[2-9]\d)\d{2})-(0?[13456789]|1[012])-(0?[1-9]|[12]\d|30))|(((1[6-9]|[2-9]\d)\d{2})-0?2-(0?[1-9]|1\d|2[0-8]))|(((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))-0?2-29-)) (20|21|22|23|[0-1]?\d):[0-5]?\d:[0-5]?\d$"); + return reg.IsMatch(str); + } + #endregion + #region 验证后缀名 + public static bool IsValidPostfix(this string strIn) + { + return Regex.IsMatch(strIn, @".(?i:gif|jpg)$"); + } + #endregion + #region 验证字符是否在4至12之间 + public static bool IsValidByte(this string strIn) + { + return Regex.IsMatch(strIn, @"^[a-z]{4,12}$"); + } + #endregion + #region 判断字符串是否为数字 + /// + /// 判断字符串是否为数字 + /// + /// 待验证的字符窜 + /// bool + public static bool IsNumber(this string str) + { + bool result = true; + foreach (char ar in str) + { + if (!char.IsNumber(ar)) + { + result = false; + break; + } + } + return result; + } + #endregion + #region 是否为数字型 + /// + /// 是否为数字型 + /// + /// + /// + public static bool IsDecimal(this string strNumber) + { + return new System.Text.RegularExpressions.Regex(@"^([0-9])[0-9]*(\.\w*)?$").IsMatch(strNumber); + } + #endregion + #region 验证是否包含汉语/全部汉语 + /// + /// 验证是否包含汉语 + /// + /// + /// + public static bool IsHanyu(this string str) + { + Regex regex = new Regex("[\u4e00-\u9fa5]"); + if (regex.Match(str).Success) + return true; + else + return false; + } + /// + /// 验证是否全部汉语 + /// + /// + /// + public static bool IsHanyuAll(this string str) + { + Regex regex = new Regex("[\u4e00-\u9fa5]"); + //匹配的内容长度和被验证的内容长度相同时,验证通过 + if (regex.Match(str).Success) + if (regex.Matches(str).Count == str.Length) + return true; + //其它,未通过 + return false; + } + #endregion + } +} diff --git a/常用工具集/Utility/Core/ChineseDateTime.cs b/常用工具集/Utility/Core/ChineseDateTime.cs new file mode 100644 index 0000000..e6c743f --- /dev/null +++ b/常用工具集/Utility/Core/ChineseDateTime.cs @@ -0,0 +1,464 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Text; + +namespace MES.Utility.Core +{ + /// + /// ChineseDateTime + /// 一日有十二时辰,一时辰有四刻,一刻有三盏茶,一盏茶有两柱香 + /// 一柱香有五分,一分有六弹指,一弹指有十刹那,一刹那为一念 + /// + public class ChineseDateTime + { + #region ====== 内部常量 ====== + private readonly ChineseLunisolarCalendar _chineseDateTime; + private readonly DateTime _dateTime; + private readonly int _serialMonth; + + private static readonly string[] _chineseNumber = { "〇", "一", "二", "三", "四", "五", "六", "七", "八", "九" }; + private static readonly string[] _chineseMonth = + { + "正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "冬", "腊" + }; + private static readonly string[] _chineseDay = + { + "初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十", + "十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十", + "廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十" + }; + private static readonly string[] _chineseWeek = + { + "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" + }; + + private static readonly string[] _celestialStem = { "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸" }; + private static readonly string[] _terrestrialBranch = { "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥" }; + private static readonly string[] _chineseZodiac = { "鼠", "牛", "虎", "免", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪" }; + + private static readonly string[] _solarTerm = + { + "小寒", "大寒", "立春", "雨水", "惊蛰", "春分", + "清明", "谷雨", "立夏", "小满", "芒种", "夏至", + "小暑", "大暑", "立秋", "处暑", "白露", "秋分", + "寒露", "霜降", "立冬", "小雪", "大雪", "冬至" + }; + private static readonly int[] _solarTermInfo = { + 0, 21208, 42467, 63836, 85337, 107014, 128867, 150921, 173149, 195551, 218072, 240693, 263343, 285989, + 308563, 331033, 353350, 375494, 397447, 419210, 440795, 462224, 483532, 504758 + }; + #endregion + + #region ======= 构建日期 ====== + + public ChineseDateTime(DateTime dateTime) + { + _chineseDateTime = new ChineseLunisolarCalendar(); + if (dateTime < _chineseDateTime.MinSupportedDateTime || dateTime > _chineseDateTime.MaxSupportedDateTime) + { + throw new ArgumentOutOfRangeException( + $"参数日期不在有效的范围内:只支持{_chineseDateTime.MinSupportedDateTime.ToShortTimeString()}到{_chineseDateTime.MaxSupportedDateTime}"); + } + + Year = _chineseDateTime.GetYear(dateTime); + Month = _chineseDateTime.GetMonth(dateTime); + Day = _chineseDateTime.GetDayOfMonth(dateTime); + IsLeep = _chineseDateTime.IsLeapMonth(Year, Month); + _dateTime = dateTime; + _serialMonth = Month; + var leepMonth = _chineseDateTime.GetLeapMonth(Year); + if (leepMonth > 0 && leepMonth <= Month) Month--; + } + + /// + /// 参数为农历的年月日及是否润月 + /// + /// + /// + /// + /// + public ChineseDateTime(int year, int month, int day, bool isLeap = false) + : this(year, month, day, 0, 0, 0, isLeap) + { + + } + + public ChineseDateTime(int year, int month, int day, int hour, int minute, int second, bool isLeap = false) + : this(year, month, day, hour, minute, second, 0, isLeap) + { + + } + + public ChineseDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, bool isLeap = false) + { + _chineseDateTime = new ChineseLunisolarCalendar(); + if (year < _chineseDateTime.MinSupportedDateTime.Year || year >= _chineseDateTime.MaxSupportedDateTime.Year) + { + throw new ArgumentOutOfRangeException( + $"参数年份不在有效的范围内,只支持{_chineseDateTime.MinSupportedDateTime.Year}到{_chineseDateTime.MaxSupportedDateTime.Year - 1}"); + } + + if (month < 1 || month > 12) throw new ArgumentOutOfRangeException($"月份只支持1-12"); + IsLeep = isLeap; + var leepMonth = _chineseDateTime.GetLeapMonth(year); + if (leepMonth - 1 != month) + IsLeep = false; + _serialMonth = month; + if (leepMonth > 0 && (month == leepMonth - 1 && isLeap || month > leepMonth - 1)) + _serialMonth = month + 1; + + if (_chineseDateTime.GetDaysInMonth(year, _serialMonth) < day || day < 1) + throw new ArgumentOutOfRangeException($"指定的月份天数,不在有效的范围内"); + + Year = year; + Month = month; + Day = day; + _dateTime = _chineseDateTime.ToDateTime(Year, _serialMonth, Day, hour, minute, second, millisecond); + } + + public static ChineseDateTime Now => new ChineseDateTime(DateTime.Now); + + #endregion + + #region ====== 年月日润属性 ====== + public int Year { get; } + public int Month { get; } + public int Day { get; } + + /// + /// 是否为润月 + /// + public bool IsLeep { get; } + #endregion + + #region ====== 输出常规日期 ====== + /// + /// 转换为公历 + /// + /// + public DateTime ToDateTime() + { + return _chineseDateTime.ToDateTime(Year, _serialMonth, Day, _dateTime.Hour, + _dateTime.Minute, + _dateTime.Second, _dateTime.Millisecond); + } + + /// + /// 短日期(农历) + /// + /// + public string ToShortDateString() + { + return $"{Year}-{GetLeap(false)}{Month}-{Day}"; + } + + /// + /// 长日期(农历) + /// + /// + public string ToLongDateString() + { + return $"{Year}年{GetLeap()}{Month}月-{Day}日"; + } + + public new string ToString() + { + return $"{Year}-{GetLeap(false)}{Month}-{Day} {_dateTime.Hour}:{_dateTime.Minute}:{_dateTime.Second}"; + } + #endregion + + #region ====== 输出中文日期及星期 ====== + public string ToChineseString() + { + return ToChineseString("yMd"); + } + + public string GetChineseDate() + { + var date = new StringBuilder(); + date.Append(GetMonth() + "月"); + date.Append(GetDay() + ""); + date.AppendLine(); + date.Append(GetEraYear() + ChineseZodiac + "年"); + date.AppendLine(); + return date.ToString(); + } + + public string ToChineseString(string format) + { + var year = GetYear(); + var month = GetMonth(); + var day = GetDay(); + + var date = new StringBuilder(); + foreach (var item in format.ToCharArray()) + { + switch (item) + { + case 'y': + date.Append($"{year}年"); + break; + case 'M': + date.Append($"{month}月"); + break; + case 'd': + date.Append($"{day}"); + break; + default: + date.Append(item); + break; + } + } + var def = $"{year}年{month}月{day}"; + var result = date.ToString(); + return string.IsNullOrEmpty(result) ? def : result; + } + + public string ChineseWeek => _chineseWeek[(int)_dateTime.DayOfWeek]; + #endregion + + #region ====== 输出天干地支生肖 ====== + + public string ToChineseEraString() + { + return ToChineseEraString("yMdHm"); + } + + public string ToChineseEraString(string format) + { + var year = GetEraYear(); + var month = GetEraMonth(); + var day = GetEraDay(); + var hour = GetEraHour(); + var minute = GetEraMinute(); + + var date = new StringBuilder(); + foreach (var item in format.ToCharArray()) + { + switch (item) + { + case 'y': + date.Append($"{year}年"); + break; + case 'M': + date.Append($"{month}月"); + break; + case 'd': + date.Append($"{day}日"); + break; + case 'H': + date.Append($"{hour}时"); + break; + case 'm': + date.Append($"{minute}刻"); + break; + default: + date.Append(item); + break; + } + } + var def = $"{year}年{month}月{day}日{hour}时"; + var result = date.ToString(); + return result.IsNullOrEmpty() ? def : result; + } + + public string ChineseZodiac => _chineseZodiac[(Year - 4) % 12]; + #endregion + + #region ====== 辅助方法(Chinese) ====== + private string GetYear() + { + var yearArray = Array.ConvertAll(Year.ToString().ToCharArray(), x => int.Parse(x.ToString())); + var year = new StringBuilder(); + foreach (var item in yearArray) + year.Append(_chineseNumber[item]); + return year.ToString(); + } + + private string GetMonth() + { + return $"{GetLeap()}{_chineseMonth[Month - 1]}"; + } + + private string GetDay() + { + return _chineseDay[Day - 1]; + } + + private string GetLeap(bool isChinese = true) + { + return IsLeep ? isChinese ? "润" : "L" : ""; + } + #endregion + + #region ====== 输助方法(天干地支)====== + //年采用的头尾法,月采用的是节令法,主流日历基本上都这种结合,如百度的日历 + + private string GetEraYear() + { + var sexagenaryYear = _chineseDateTime.GetSexagenaryYear(_dateTime); + var stemIndex = _chineseDateTime.GetCelestialStem(sexagenaryYear) - 1; + var branchIndex = _chineseDateTime.GetTerrestrialBranch(sexagenaryYear) - 1; + return $"{_celestialStem[stemIndex]}{_terrestrialBranch[branchIndex]}"; + } + + private string GetEraMonth() + { + #region ====== 节令法 ====== + var solarIndex = SolarTermFunc((x, y) => x <= y, out var dt); + solarIndex = solarIndex == -1 ? 23 : solarIndex; + var currentIndex = (int)Math.Floor(solarIndex / (decimal)2); + + //天干 + var solarMonth = currentIndex == 0 ? 11 : currentIndex - 1; //计算天干序(月份) + var sexagenaryYear = _chineseDateTime.GetSexagenaryYear(_dateTime); + var stemYear = _chineseDateTime.GetCelestialStem(sexagenaryYear) - 1; + if (solarMonth == 0) //立春时,春节前后的不同处理 + { + var year = _chineseDateTime.GetYear(dt); + var month = _chineseDateTime.GetMonth(dt); + stemYear = year == Year && month != 1 ? stemYear + 1 : stemYear; + } + if (solarMonth == 11) //立春在春节后,对前一节气春节前后不同处理 + { + var year = _chineseDateTime.GetYear(dt); + stemYear = year != Year ? stemYear - 1 : stemYear; + } + int stemIndex; + switch (stemYear) + { + case 0: + case 5: + stemIndex = 3; + break; + case 1: + case 6: + stemIndex = 5; + break; + case 2: + case 7: + stemIndex = 7; + break; + case 3: + case 8: + stemIndex = 9; + break; + default: + stemIndex = 1; + break; + } + //天干序 + stemIndex = (stemIndex - 1 + solarMonth) % 10; + + //地支序 + var branchIndex = currentIndex >= 11 ? currentIndex - 11 : currentIndex + 1; + + return $"{_celestialStem[stemIndex]}{_terrestrialBranch[branchIndex]}"; + + #endregion + + #region ====== 头尾法 ====== + //这里算法要容易些,原理和节令法一样,只需取农历整年整月即可。未贴上来 + #endregion + } + + private string GetEraDay() + { + var ts = _dateTime - new DateTime(1901, 2, 15); + var offset = ts.Days; + var sexagenaryDay = offset % 60; + return $"{_celestialStem[sexagenaryDay % 10]}{_terrestrialBranch[sexagenaryDay % 12]}"; + } + + private string GetEraHour() + { + var hourIndex = (int)Math.Floor((_dateTime.Hour + 1) / (decimal)2); + hourIndex = hourIndex == 12 ? 0 : hourIndex; + return _terrestrialBranch[hourIndex]; + } + + private string GetEraMinute() + { + var realMinute = (_dateTime.Hour % 2 == 0 ? 60 : 0) + _dateTime.Minute; + return $"{_chineseNumber[(int)Math.Floor(realMinute / (decimal)30) + 1]}"; + } + #endregion + + #region ====== 24节气 ====== + /// + /// 当前节气,没有则返回空 + /// + public string SolarTerm + { + get + { + var i = SolarTermFunc((x, y) => x == y, out var dt); + return i == -1 ? "" : _solarTerm[i]; + } + } + + /// + /// 上一个节气 + /// + public string SolarTermPrev + { + get + { + var i = SolarTermFunc((x, y) => x < y, out var dt); + return i == -1 ? "" : _solarTerm[i]; + } + } + + /// + /// 下一个节气 + /// + public string SolarTermNext + { + get + { + var i = SolarTermFunc((x, y) => x > y, out var dt); + return i == -1 ? "" : $"{_solarTerm[i]}"; + } + } + + /// + /// 节气计算(当前年),返回指定条件的节气序及日期(公历) + /// + /// + /// + /// -1时即没找到 + private int SolarTermFunc(Expression> func, out DateTime dateTime) + { + var baseDateAndTime = new DateTime(1900, 1, 6, 2, 5, 0); //#1/6/1900 2:05:00 AM# + var year = _dateTime.Year; + int[] solar = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 }; + var expressionType = func.Body.NodeType; + if (expressionType != ExpressionType.LessThan && expressionType != ExpressionType.LessThanOrEqual && + expressionType != ExpressionType.GreaterThan && expressionType != ExpressionType.GreaterThanOrEqual && + expressionType != ExpressionType.Equal) + { + throw new NotSupportedException("不受支持的操作符"); + } + + if (expressionType == ExpressionType.LessThan || expressionType == ExpressionType.LessThanOrEqual) + { + solar = solar.OrderByDescending(x => x).ToArray(); + } + foreach (var item in solar) + { + var num = 525948.76 * (year - 1900) + _solarTermInfo[item - 1]; + var newDate = baseDateAndTime.AddMinutes(num); //按分钟计算 + if (func.Compile()(newDate.DayOfYear, _dateTime.DayOfYear)) + { + dateTime = newDate; + return item - 1; + } + } + dateTime = _chineseDateTime.MinSupportedDateTime; + return -1; + } + #endregion + } +} diff --git a/常用工具集/Utility/Core/ConvertHelper.cs b/常用工具集/Utility/Core/ConvertHelper.cs new file mode 100644 index 0000000..1670f50 --- /dev/null +++ b/常用工具集/Utility/Core/ConvertHelper.cs @@ -0,0 +1,1233 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices.ComTypes; +using System.Text; + +namespace MES.Utility.Core +{ + /// + /// 处理数据类型转换,数制转换、编码转换相关的类 + /// + public static class ConvertHelper + { + /// + /// decimal保留指定位数小数 + /// + /// 原始数量 + /// 保留小数位数 + /// 截取指定小数位数后的数量字符串 + public static string ToString(this decimal num, int scale) + { + string numToString = num.ToString(); + + int index = numToString.IndexOf("."); + int length = numToString.Length; + + if (index != -1) + { + return string.Format("{0}.{1}", + numToString.Substring(0, index), + numToString.Substring(index + 1, Math.Min(length - index - 1, scale))); + } + else + { + return num.ToString(); + } + } + + + /// + /// 数字科学计数法处理 + /// + /// + /// + public static bool ChangeToDecimal(this string value, out Decimal dData) + { + try + { + dData = 0.0M; + if (value.Contains("E") || value.Contains("e")) + { + dData = Convert.ToDecimal(Decimal.Parse(value.ToString(), System.Globalization.NumberStyles.Float)); + } + else + { + dData = Convert.ToDecimal(value); + } + return true; + } + catch + { + dData = 0.0M; + return false; + } + } + + + public static decimal ChangeToDecimal2(this string value) + { + try + { + decimal dData = 0.0M; + if (value.Contains("E") || value.Contains("e")) + { + dData = Convert.ToDecimal(Decimal.Parse(value.ToString(), System.Globalization.NumberStyles.Float)); + } + else + { + dData = Convert.ToDecimal(value); + } + return dData; + } + catch + { + return 0.0M; + } + } + + public static DataTable ToDataTable(this List data) + { + PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T)); + DataTable dataTable = new DataTable(); + for (int i = 0; i < properties.Count; i++) + { + PropertyDescriptor property = properties[i]; + dataTable.Columns.Add(property.Name, Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType); + } + object[] values = new object[properties.Count]; + foreach (T item in data) + { + for (int i = 0; i < values.Length; i++) + { + values[i] = properties[i].GetValue(item); + } + dataTable.Rows.Add(values); + } + return dataTable; + } + + public static List ToList(this DataTable table) + { + if (table == null) + { + return null; + } + List rows = new List(); + foreach (DataRow row in table.Rows) + { + rows.Add(row); + } + return rows.ToList(); + } + + public static List ToList(this List rows) + { + List list = null; + if (rows != null) + { + list = new List(); + + foreach (DataRow row in rows) + { + T item = row.ToObject(); + list.Add(item); + } + } + + return list; + } + + public static T ToObject(this DataRow row) + { + T obj = default(T); + if (row != null) + { + obj = Activator.CreateInstance(); + + foreach (DataColumn column in row.Table.Columns) + { + PropertyInfo prop = obj.GetType().GetProperty(column.ColumnName); + try + { + object value = row[column.ColumnName]; + prop.SetValue(obj, value, null); + } + catch (Exception ex) + { //You can log something here + //throw; + } + } + } + return obj; + } + + #region 各进制数间转换 + /// + /// 实现各进制数间的转换。ConvertBase("15",10,16)表示将十进制数15转换为16进制的数。 + /// + /// 要转换的值,即原值 + /// 原值的进制,只能是2,8,10,16四个值。 + /// 要转换到的目标进制,只能是2,8,10,16四个值。 + public static string ConvertBase(string value, int from, int to) + { + if (!isBaseNumber(from)) + throw new ArgumentException("参数from只能是2,8,10,16四个值。"); + + if (!isBaseNumber(to)) + throw new ArgumentException("参数to只能是2,8,10,16四个值。"); + + int intValue = Convert.ToInt32(value, from); //先转成10进制 + string result = Convert.ToString(intValue, to); //再转成目标进制 + if (to == 2) + { + int resultLength = result.Length; //获取二进制的长度 + switch (resultLength) + { + case 7: + result = "0" + result; + break; + case 6: + result = "00" + result; + break; + case 5: + result = "000" + result; + break; + case 4: + result = "0000" + result; + break; + case 3: + result = "00000" + result; + break; + } + } + return result; + } + + /// + /// 判断是否是 2 8 10 16 + /// + /// + /// + private static bool isBaseNumber(int baseNumber) + { + if (baseNumber == 2 || baseNumber == 8 || baseNumber == 10 || baseNumber == 16) + return true; + return false; + } + + #endregion + + #region 使用指定字符集将string转换成byte[] + + /// + /// 将string转换成byte[]。 + /// + /// 要转换的字符串 + public static byte[] StringToBytes(string text) + { + return Encoding.Default.GetBytes(text); + } + + /// + /// 使用指定字符集将string转换成byte[] + /// + /// 要转换的字符串 + /// 字符编码 + public static byte[] StringToBytes(string text, Encoding encoding) + { + return encoding.GetBytes(text); + } + + #endregion + + #region 使用指定字符集将byte[]转换成string + + /// + /// 将byte[]转换成string + /// + /// 要转换的字节数组 + public static string BytesToString(byte[] bytes) + { + return Encoding.Default.GetString(bytes); + } + + /// + /// 使用指定字符集将byte[]转换成string。 + /// + /// 要转换的字节数组 + /// 字符编码 + public static string BytesToString(byte[] bytes, Encoding encoding) + { + return encoding.GetString(bytes); + } + #endregion + + + #region string转long + /// + /// string转long + /// + public static long ToInt64(this string value) + { + long? val = ToNullableInt64(value); + if (val == null) + return 0; + return val.Value; + } + #endregion + + #region string转long? + /// + /// string转long? + /// + public static long? ToNullableInt64(this string value) + { + long? result = null; + + if (!string.IsNullOrWhiteSpace(value)) + { + long d; + if (long.TryParse(value, out d)) + { + result = d; + } + } + + return result; + } + #endregion + + #region 将byte[]转换成int + /// + /// 将byte[]转换成int + /// + /// 需要转换成整数的byte数组 + public static int BytesToInt32(byte[] data) + { + //如果传入的字节数组长度小于4,则返回0 + if (data.Length < 4) + { + return 0; + } + + //定义要返回的整数 + int num = 0; + + //如果传入的字节数组长度大于4,需要进行处理 + if (data.Length >= 4) + { + //创建一个临时缓冲区 + byte[] tempBuffer = new byte[4]; + + //将传入的字节数组的前4个字节复制到临时缓冲区 + Buffer.BlockCopy(data, 0, tempBuffer, 0, 4); + + //将临时缓冲区的值转换成整数,并赋给num + num = BitConverter.ToInt32(tempBuffer, 0); + } + + //返回整数 + return num; + } + #endregion + + #region 将数据转换为整型 + #region string转int + /// + /// string转int + /// + public static int ToInt32(this string value) + { + int? val = ToNullableInt32(value); + if (val == null) + return 0; + return val.Value; + } + #endregion + #region string转int? + /// + /// string转int? + /// + public static int? ToNullableInt32(this string value) + { + int? result = null; + + if (!string.IsNullOrWhiteSpace(value)) + { + int d; + if (int.TryParse(value, out d)) + { + result = d; + } + } + return result; + } + #endregion + /// + /// 将数据转换为整型 转换失败返回默认值 + /// + /// 数据类型 + /// 数据 + /// 默认值 + /// + public static int ToInt32(T data, int defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToInt32(data); + } + catch + { + return defValue; + } + + } + + /// + /// 将数据转换为整型 转换失败返回默认值。 + /// + /// 数据 + /// 默认值 + /// + public static int ToInt32(string data, int defValue) + { + //如果为空则返回默认值 + if (string.IsNullOrEmpty(data)) + { + return defValue; + } + + int temp = 0; + if (Int32.TryParse(data, out temp)) + { + return temp; + } + else + { + return defValue; + } + } + + /// + /// 将数据转换为整型 转换失败返回默认值 + /// + /// 数据 + /// 默认值 + /// + public static int ToInt32(object data, int defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToInt32(data); + } + catch + { + return defValue; + } + } + + #endregion + + #region 将数据转换为布尔型 + + /// + /// 将数据转换为布尔类型 转换失败返回默认值 + /// + /// 数据类型 + /// 数据 + /// 默认值 + /// + public static bool ToBoolean(T data, bool defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToBoolean(data); + } + catch + { + return defValue; + } + } + + /// + /// 将数据转换为布尔类型 转换失败返回 默认值 + /// + /// 数据 + /// 默认值 + /// + public static bool ToBoolean(string data, bool defValue) + { + //如果为空则返回默认值 + if (string.IsNullOrEmpty(data)) + { + return defValue; + } + + bool temp = false; + if (bool.TryParse(data, out temp)) + { + return temp; + } + else + { + return defValue; + } + } + + + /// + /// 将数据转换为布尔类型 转换失败返回 默认值 + /// + /// 数据 + /// 默认值 + /// + public static bool ToBoolean(object data, bool defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToBoolean(data); + } + catch + { + return defValue; + } + } + + + #endregion + + #region 将数据转换为单精度浮点型 + + + /// + /// 将数据转换为单精度浮点型 转换失败 返回默认值 + /// + /// 数据类型 + /// 数据 + /// 默认值 + /// + public static float ToFloat(T data, float defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToSingle(data); + } + catch + { + return defValue; + } + } + + /// + /// 将数据转换为单精度浮点型 转换失败返回默认值 + /// + /// 数据 + /// 默认值 + /// + public static float ToFloat(object data, float defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToSingle(data); + } + catch + { + return defValue; + } + } + + /// + /// 将数据转换为单精度浮点型 转换失败返回默认值 + /// + /// 数据 + /// 默认值 + /// + public static float ToFloat(string data, float defValue) + { + //如果为空则返回默认值 + if (string.IsNullOrEmpty(data)) + { + return defValue; + } + + float temp = 0; + + if (float.TryParse(data, out temp)) + { + return temp; + } + else + { + return defValue; + } + } + + + #endregion + + #region 将数据转换为双精度浮点型 + + #region string转double + /// + /// string转double + /// + public static double ToDouble(this string value) + { + double? val = ToNullableDouble(value); + if (val == null) + return 0; + return val.Value; + } + #endregion + + #region string转double? + /// + /// string转double? + /// + public static double? ToNullableDouble(this string value) + { + double? result = null; + + if (!string.IsNullOrWhiteSpace(value)) + { + double d; + if (double.TryParse(value, out d)) + { + result = d; + } + } + + return result; + } + #endregion + /// + /// 将数据转换为双精度浮点型 转换失败返回默认值 + /// + /// 数据的类型 + /// 要转换的数据 + /// 默认值 + /// + public static double ToDouble(T data, double defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToDouble(data); + } + catch + { + return defValue; + } + } + + /// + /// 将数据转换为双精度浮点型,并设置小数位 转换失败返回默认值 + /// + /// 数据的类型 + /// 要转换的数据 + /// 小数的位数 + /// 默认值 + /// + public static double ToDouble(T data, int decimals, double defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Math.Round(Convert.ToDouble(data), decimals); + } + catch + { + return defValue; + } + } + + + + /// + /// 将数据转换为双精度浮点型 转换失败返回默认值 + /// + /// 要转换的数据 + /// 默认值 + /// + public static double ToDouble(object data, double defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToDouble(data); + } + catch + { + return defValue; + } + + } + + /// + /// 将数据转换为双精度浮点型 转换失败返回默认值 + /// + /// 要转换的数据 + /// 默认值 + /// + public static double ToDouble(string data, double defValue) + { + //如果为空则返回默认值 + if (string.IsNullOrEmpty(data)) + { + return defValue; + } + + double temp = 0; + + if (double.TryParse(data, out temp)) + { + return temp; + } + else + { + return defValue; + } + + } + + + /// + /// 将数据转换为双精度浮点型,并设置小数位 转换失败返回默认值 + /// + /// 要转换的数据 + /// 小数的位数 + /// 默认值 + /// + public static double ToDouble(object data, int decimals, double defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Math.Round(Convert.ToDouble(data), decimals); + } + catch + { + return defValue; + } + } + + /// + /// 将数据转换为双精度浮点型,并设置小数位 转换失败返回默认值 + /// + /// 要转换的数据 + /// 小数的位数 + /// 默认值 + /// + public static double ToDouble(string data, int decimals, double defValue) + { + //如果为空则返回默认值 + if (string.IsNullOrEmpty(data)) + { + return defValue; + } + + double temp = 0; + + if (double.TryParse(data, out temp)) + { + return Math.Round(temp, decimals); + } + else + { + return defValue; + } + } + + + #endregion + + #region 将数据转换为指定类型 + /// + /// 将数据转换为指定类型 + /// + /// 转换的数据 + /// 转换的目标类型 + public static object ConvertTo(object data, Type targetType) + { + if (data == null || Convert.IsDBNull(data)) + { + return null; + } + + Type type2 = data.GetType(); + if (targetType == type2) + { + return data; + } + if (((targetType == typeof(Guid)) || (targetType == typeof(Guid?))) && (type2 == typeof(string))) + { + if (string.IsNullOrEmpty(data.ToString())) + { + return null; + } + return new Guid(data.ToString()); + } + + if (targetType.IsEnum) + { + try + { + return Enum.Parse(targetType, data.ToString(), true); + } + catch + { + return Enum.ToObject(targetType, data); + } + } + + if (targetType.IsGenericType) + { + targetType = targetType.GetGenericArguments()[0]; + } + + return Convert.ChangeType(data, targetType); + } + + /// + /// 将数据转换为指定类型 + /// + /// 转换的目标类型 + /// 转换的数据 + public static T ConvertTo(object data) + { + if (data == null || Convert.IsDBNull(data)) + return default(T); + + object obj = ConvertTo(data, typeof(T)); + if (obj == null) + { + return default(T); + } + return (T)obj; + } + #endregion + + #region 将数据转换Decimal + + + #region string转decimal + /// + /// string转decimal + /// + public static decimal ToDecimal(this string value) + { + decimal? val = ToNullableDecimal(value); + if (null == val) + { + return 0; + } + return val.Value; + } + #endregion + + #region string转decimal? + /// + /// string转decimal? + /// + public static decimal? ToNullableDecimal(this string value) + { + decimal? result = null; + + if (!string.IsNullOrWhiteSpace(value)) + { + decimal d; + if (decimal.TryParse(value, out d)) + { + result = d; + } + } + + return result; + } + #endregion + #region decimal?转decimal + /// + /// decimal?转decimal + /// + public static decimal ToDecimal(this decimal? value) + { + if (value != null) return value.Value; + return 0; + } + #endregion + /// + /// 将数据转换为Decimal 转换失败返回默认值 + /// + /// 数据类型 + /// 数据 + /// 默认值 + /// + public static Decimal ToDecimal(T data, Decimal defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToDecimal(data); + } + catch + { + return defValue; + } + } + + + /// + /// 将数据转换为Decimal 转换失败返回 默认值 + /// + /// 数据 + /// 默认值 + /// + public static Decimal ToDecimal(object data, Decimal defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToDecimal(data); + } + catch + { + return defValue; + } + } + + /// + /// 将数据转换为Decimal 转换失败返回 默认值 + /// + /// 数据 + /// 默认值 + /// + public static Decimal ToDecimal(string data, Decimal defValue) + { + //如果为空则返回默认值 + if (string.IsNullOrEmpty(data)) + { + return defValue; + } + + decimal temp = 0; + + if (decimal.TryParse(data, out temp)) + { + return temp; + } + else + { + return defValue; + } + } + + + #endregion + + #region 将数据转换为DateTime + /// + /// string转DateTime + /// + public static DateTime ToDateTime(this string value) + { + DateTime? val = ToNullableDateTime(value); + if (val == null) + return DateTime.MinValue; + return val.Value; + } + + /// + /// string转DateTime? + /// + public static DateTime? ToNullableDateTime(this string value) + { + DateTime? result = null; + + if (!string.IsNullOrWhiteSpace(value)) + { + DateTime dt; + if (DateTime.TryParse(value, out dt)) + { + result = dt; + } + } + + return result; + } + /// + /// 将数据转换为DateTime 转换失败返回默认值 + /// + /// 数据类型 + /// 数据 + /// 默认值 + /// + public static DateTime ToDateTime(T data, DateTime defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToDateTime(data); + } + catch + { + return defValue; + } + } + + + /// + /// 将数据转换为DateTime 转换失败返回 默认值 + /// + /// 数据 + /// 默认值 + /// + public static DateTime ToDateTime(object data, DateTime defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToDateTime(data); + } + catch + { + return defValue; + } + } + + /// + /// 将数据转换为DateTime 转换失败返回 默认值 + /// + /// 数据 + /// 默认值 + /// + public static DateTime ToDateTime(string data, DateTime defValue) + { + //如果为空则返回默认值 + if (string.IsNullOrEmpty(data)) + { + return defValue; + } + + DateTime temp = DateTime.Now; + + if (DateTime.TryParse(data, out temp)) + { + return temp; + } + else + { + return defValue; + } + } + + #endregion + + #region 半角全角转换 + /// + /// 转全角的函数(SBC case) + /// + /// 任意字符串 + /// 全角字符串 + /// + ///全角空格为12288,半角空格为32 + ///其他字符半角(33-126)与全角(65281-65374)的对应关系是:均相差65248 + /// + public static string ConvertToSBC(string input) + { + //半角转全角: + char[] c = input.ToCharArray(); + for (int i = 0; i < c.Length; i++) + { + if (c[i] == 32) + { + c[i] = (char)12288; + continue; + } + if (c[i] < 127) + { + c[i] = (char)(c[i] + 65248); + } + } + return new string(c); + } + + + /// 转半角的函数(DBC case) + /// 任意字符串 + /// 半角字符串 + /// + ///全角空格为12288,半角空格为32 + ///其他字符半角(33-126)与全角(65281-65374)的对应关系是:均相差65248 + /// + public static string ConvertToDBC(string input) + { + char[] c = input.ToCharArray(); + for (int i = 0; i < c.Length; i++) + { + if (c[i] == 12288) + { + c[i] = (char)32; + continue; + } + if (c[i] > 65280 && c[i] < 65375) + c[i] = (char)(c[i] - 65248); + } + return new string(c); + } + #endregion + + + + public static long ToTimeStamp(this DateTime dateTime) + { + return (long)(dateTime - DateTime.Parse("1970-01-01")).TotalMilliseconds; + } + + public static DateTime ToDateTime(this long timestamp) + { + return DateTime.Parse("1970-01-01").AddMilliseconds(timestamp); + } + + + + + + + + #region object转string + /// + /// object转string + /// + public static string ToString(this object value) + { + string result = null; + + if (value != null) + { + result = value.ToString(); + } + + return result; + } + #endregion + + + + + + + + #region double?转double + /// + /// double?转double + /// + public static double ToDouble(this double? value) + { + if (value != null) return value.Value; + return 0; + } + #endregion + + #region long?转long + /// + /// long?转long + /// + public static long ToInt64(this long? value) + { + if (value != null) return value.Value; + return 0; + } + #endregion + + #region int?转int + /// + /// int?转int + /// + public static int ToInt32(this int? value) + { + if (value != null) return value.Value; + return 0; + } + #endregion + } +} diff --git a/常用工具集/Utility/Core/EnumHelper.cs b/常用工具集/Utility/Core/EnumHelper.cs new file mode 100644 index 0000000..4ab3bc4 --- /dev/null +++ b/常用工具集/Utility/Core/EnumHelper.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using System.Text; + +namespace MES.Utility.Core +{ + /// + /// 枚举类型操作公共类。 + /// + public static class EnumHelper + { + /// + /// 获取枚举所有成员名称。 + /// + /// 枚举类型 + public static string[] GetNames() + { + return Enum.GetNames(typeof(T)); + } + + /// + /// 检测枚举是否包含指定成员。 + /// + /// 枚举类型 + /// 成员名或成员值 + public static bool IsDefined(this Enum value) + { + Type type = value.GetType(); + return Enum.IsDefined(type, value); + } + + /// + /// 返回指定枚举类型的指定值的描述。 + /// + /// 枚举类型 + /// 枚举值 + /// + public static string GetDescription(this Enum value) + { + try + { + Type type = value.GetType(); + FieldInfo field = type.GetField(value.ToString()); + DescriptionAttribute[] attributes = (DescriptionAttribute[])field.GetCustomAttributes(typeof(DescriptionAttribute), false); + return (attributes.Length > 0) ? attributes[0].Description : string.Empty; + } + catch + { + return string.Empty; + } + } + } +} diff --git a/常用工具集/Utility/Core/ExtDateTime.cs b/常用工具集/Utility/Core/ExtDateTime.cs new file mode 100644 index 0000000..3b114c9 --- /dev/null +++ b/常用工具集/Utility/Core/ExtDateTime.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MES.Utility.Core +{ + public static class ExtDateTime + { + /// + /// 获取格式化字符串,不带时分秒。格式:"yyyy-MM-dd" + /// + /// 日期 + public static string ToDateString(this DateTime dateTime) + { + return dateTime.ToString("yyyy-MM-dd"); + } + + /// + /// 获取格式化字符串,不带时分秒。格式:"yyyy-MM-dd" + /// + /// 日期 + public static string ToDateString(this DateTime? dateTime) + { + if (dateTime == null) + { + return string.Empty; + } + return ToDateString(dateTime.Value); + } + + /// + /// 获取格式化字符串,不带年月日,格式:"HH:mm:ss" + /// + /// 日期 + public static string ToTimeString(this DateTime dateTime) + { + return dateTime.ToString("HH:mm:ss"); + } + + /// + /// 获取格式化字符串,不带年月日,格式:"HH:mm:ss" + /// + /// 日期 + public static string ToTimeString(this DateTime? dateTime) + { + if (dateTime == null) + { + return string.Empty; + } + return ToTimeString(dateTime.Value); + } + + /// + /// 获取格式化字符串,带毫秒,格式:"yyyy-MM-dd HH:mm:ss.fff" + /// + /// 日期 + public static string ToMillisecondString(this DateTime dateTime) + { + return dateTime.ToString("yyyy-MM-dd HH:mm:ss.fff"); + } + + /// + /// 获取格式化字符串,带毫秒,格式:"yyyy-MM-dd HH:mm:ss.fff" + /// + /// 日期 + public static string ToMillisecondString(this DateTime? dateTime) + { + if (dateTime == null) + { + return string.Empty; + } + return ToMillisecondString(dateTime.Value); + } + + /// + /// 获取格式化字符串,不带时分秒,格式:"yyyy年MM月dd日" + /// + /// 日期 + public static string ToChineseDateString(this DateTime dateTime) + { + return string.Format("{0}年{1}月{2}日", dateTime.Year, dateTime.Month, dateTime.Day); + } + + /// + /// 获取格式化字符串,带时分秒,格式:"yyyy年MM月dd日 HH时mm分" + /// + /// 日期 + /// 是否移除秒 + public static string ToChineseDateTimeString(this DateTime dateTime, bool isRemoveSecond = false) + { + StringBuilder result = new StringBuilder(); + result.AppendFormat("{0}年{1}月{2}日", dateTime.Year, dateTime.Month, dateTime.Day); + result.AppendFormat(" {0}时{1}分", dateTime.Hour, dateTime.Minute); + + if (isRemoveSecond == false) + { + result.AppendFormat("{0}秒", dateTime.Second); + } + + return result.ToString(); + } + + /// + /// 获取格式化字符串,带时分秒,格式:"yyyy年MM月dd日 HH时mm分" + /// + /// 日期 + /// 是否移除秒 + public static string ToChineseDateTimeString(this DateTime? dateTime, bool isRemoveSecond = false) + { + if (dateTime == null) + { + return string.Empty; + } + + return ToChineseDateTimeString(dateTime.Value); + } + + /// + /// 返回指定日期起始时间。 + /// + /// + /// + public static DateTime StartDateTime(this DateTime dateTime) + { + if (dateTime == null) + { + throw new ArgumentNullException(); + } + + return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 0, 0, 0); + } + /// + /// 返回指定日期结束时间。 + /// + /// + /// + public static DateTime EndDateTime(this DateTime dateTime) + { + if (dateTime == null) + { + throw new ArgumentNullException(); + } + + return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 23, 59, 59); + } + + + #region 获取时间戳 + /// + /// 获取时间戳 + /// + public static string GetTimeStamp(DateTime dateTime) + { + DateTime dtStart = new DateTime(1970, 1, 1, 8, 0, 0); + return Convert.ToInt64(dateTime.Subtract(dtStart).TotalMilliseconds).ToString(); + } + #endregion + + #region 根据时间戳获取时间 + /// + /// 根据时间戳获取时间 + /// + public static DateTime TimeStampToDateTime(string timeStamp) + { + DateTime dtStart = new DateTime(1970, 1, 1, 8, 0, 0); + return dtStart.AddMilliseconds(Convert.ToInt64(timeStamp)); + } + #endregion + + #region 本周开始时间 + /// + /// 本周开始时间 + /// + public static DateTime GetCurrentWeekStart() + { + DateTime now = DateTime.Now; + int day = Convert.ToInt32(now.DayOfWeek.ToString("d")); + return now.AddDays(1 - day).Date; + } + #endregion + + #region 本周结束时间 + /// + /// 本周结束时间 + /// + public static DateTime GetCurrentWeekEnd() + { + return GetCurrentWeekStart().AddDays(7).AddSeconds(-1); + } + #endregion + + #region 本月开始时间 + /// + /// 本月开始时间 + /// + public static DateTime GetCurrentMonthStart() + { + DateTime now = DateTime.Now; + return now.AddDays(1 - now.Day).Date; + } + #endregion + + #region 本月结束时间 + /// + /// 本月结束时间 + /// + public static DateTime GetCurrentMonthEnd() + { + return GetCurrentWeekStart().AddMonths(1).AddSeconds(-1); + } + #endregion + + #region 本季度开始时间 + /// + /// 本季度开始时间 + /// + public static DateTime GetCurrentQuarterStart() + { + DateTime now = DateTime.Now; + return now.AddMonths(0 - (now.Month - 1) % 3).AddDays(1 - now.Day).Date; + } + #endregion + + #region 本季度结束时间 + /// + /// 本季度结束时间 + /// + public static DateTime GetCurrentQuarterthEnd() + { + return GetCurrentWeekStart().AddMonths(3).AddSeconds(-1); + } + #endregion + + #region 本年开始时间 + /// + /// 本年开始时间 + /// + public static DateTime GetCurrentYearStart() + { + return new DateTime(DateTime.Now.Year, 1, 1); + } + #endregion + + #region 本年结束时间 + /// + /// 本年结束时间 + /// + public static DateTime GetCurrentYearEnd() + { + return new DateTime(DateTime.Now.Year, 12, 31, 23, 59, 59); + } + #endregion + + } +} diff --git a/常用工具集/Utility/Core/JsonHelper.cs b/常用工具集/Utility/Core/JsonHelper.cs new file mode 100644 index 0000000..3b7c818 --- /dev/null +++ b/常用工具集/Utility/Core/JsonHelper.cs @@ -0,0 +1,143 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace MES.Utility.Core +{ + public static class JsonHelper + { + /// + /// 对象序列化成JSON字符串。 + /// + /// 序列化对象 + /// 设置需要忽略的属性 + /// + public static string ToJson(this object obj) + { + if (obj == null) + return string.Empty; + IsoDateTimeConverter timeConverter = new IsoDateTimeConverter(); + timeConverter.DateTimeFormat = "yyyy-MM-dd HH:mm:ss"; + return JsonConvert.SerializeObject(obj, timeConverter); + } + + /// + /// JSON字符串序列化成对象。 + /// + /// 对象类型 + /// JSON字符串 + /// + public static T ToObject(this string json) + { + //var setting = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; + return json == null ? default(T) : JsonConvert.DeserializeObject(json);//, setting); + } + + + /// + /// JSON字符串序列化成集合。 + /// + /// 集合类型 + /// JSON字符串 + /// + public static List ToList(this string json) + { + //var setting = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; + return json == null ? null : JsonConvert.DeserializeObject>(json);//, setting); + } + + + /// + /// JSON字符串序列化成DataTable。 + /// + /// JSON字符串 + /// + public static DataTable ToTable(this string json) + { + return json == null ? null : JsonConvert.DeserializeObject(json); + } + + /// + /// 将JSON字符串反序列化成对象 + /// + /// + /// + /// + /// + public static T Json2Obj(T baseEntity, string strJson) + { + return JsonConvert.DeserializeAnonymousType(strJson, baseEntity); + } + + /// + /// 将对象转换层JSON字符串 + /// + /// + /// + /// + public static string Obj2Json(T data) + { + return JsonConvert.SerializeObject(data); + } + + + public static List JsonToList(string strJson) + { + T[] list = JsonConvert.DeserializeObject(strJson); + return list.ToList(); + } + + public static T Json2Obj(string strJson) + { + return JsonConvert.DeserializeObject(strJson); + } + + public static DataTable ToDataTable(this string json) + { + return json.ToTable(); + } + + public static string RemoveComments(string code) + { + code = Regex.Replace(code, @"(?s)(?<=)", ""); + code = Regex.Replace(code, @"/\*[\s\S]*?\*/", "", RegexOptions.IgnoreCase); + code = Regex.Replace(code, @"^\s*//[\s\S]*?$", "", RegexOptions.Multiline); + code = Regex.Replace(code, @"^\s*$\n", "", RegexOptions.Multiline); + code = Regex.Replace(code, @"^\s*//[\s\S]*", "", RegexOptions.Multiline); + return code; + } + + public static string FormatJson(this string json) + { + //格式化json字符串 + JsonSerializer serializer = new JsonSerializer(); + TextReader tr = new StringReader(json); + JsonTextReader jtr = new JsonTextReader(tr); + object obj = serializer.Deserialize(jtr); + if (obj != null) + { + StringWriter textWriter = new StringWriter(); + JsonTextWriter jsonWriter = new JsonTextWriter(textWriter) + { + Formatting = Formatting.Indented, + Indentation = 4, + IndentChar = ' ' + }; + serializer.Serialize(jsonWriter, obj); + return textWriter.ToString(); + } + else + { + return json; + } + } + } +} diff --git a/常用工具集/Utility/Core/LinqExtension.cs b/常用工具集/Utility/Core/LinqExtension.cs new file mode 100644 index 0000000..be5eb24 --- /dev/null +++ b/常用工具集/Utility/Core/LinqExtension.cs @@ -0,0 +1,255 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; + +namespace MES.Utility.Core +{ + /// + /// LINQ扩展方法 + /// + public static class LinqExtension + { + /// + /// 与连接 + /// + /// 类型 + /// 左条件 + /// 右条件 + /// 新表达式 + public static Expression> And(this Expression> left, Expression> right) + { + return CombineLambdas(left, right, ExpressionType.AndAlso); + } + + /// + /// 或连接 + /// + /// 类型 + /// 左条件 + /// 右条件 + /// 新表达式 + public static Expression> Or(this Expression> left, Expression> right) + { + return CombineLambdas(left, right, ExpressionType.OrElse); + } + + private static Expression> CombineLambdas(this Expression> left, Expression> right, ExpressionType expressionType) + { + var visitor = new SubstituteParameterVisitor + { + Sub = + { + [right.Parameters[0]] = left.Parameters[0] + } + }; + + Expression body = Expression.MakeBinary(expressionType, left.Body, visitor.Visit(right.Body)); + return Expression.Lambda>(body, left.Parameters[0]); + } + + /// + /// 取最大值 + /// + /// + /// + /// + /// + /// + public static TResult MaxOrDefault(this IQueryable source, Expression> selector) => source.Select(selector).OrderByDescending(_ => _).FirstOrDefault(); + + /// + /// 取最大值 + /// + /// + /// + /// + /// + /// + /// + public static TResult MaxOrDefault(this IQueryable source, Expression> selector, TResult defaultValue) + { + TResult result = source.Select(selector).OrderByDescending(_ => _).FirstOrDefault(); + if (result != null) + return result; + return defaultValue; + } + + /// + /// 取最大值 + /// + /// + /// + /// + public static TSource MaxOrDefault(this IQueryable source) + { + return source.OrderByDescending(_ => _).FirstOrDefault(); + } + + /// + /// 取最大值 + /// + /// + /// + /// + /// + public static TSource MaxOrDefault(this IQueryable source, TSource defaultValue) + { + TSource result = source.OrderByDescending(_ => _).FirstOrDefault(); + if (result != null) + return result; + return defaultValue; + } + + /// + /// 取最大值 + /// + /// + /// + /// + /// + /// + /// + public static TResult MaxOrDefault(this IEnumerable source, Func selector, TResult defaultValue) + { + TResult result = source.Select(selector).OrderByDescending(_ => _).FirstOrDefault(); + if (result != null) + return result; + return defaultValue; + } + + /// + /// 取最大值 + /// + /// + /// + /// + public static TSource MaxOrDefault(this IEnumerable source) => source.OrderByDescending(_ => _).FirstOrDefault(); + + /// + /// 取最大值 + /// + /// + /// + /// + /// + public static TSource MaxOrDefault(this IEnumerable source, TSource defaultValue) + { + TSource result = source.OrderByDescending(_ => _).FirstOrDefault(); + if (result != null) + return result; + return defaultValue; + } + + /// + /// 取最小值 + /// + /// + /// + /// + /// + /// + public static TResult MinOrDefault(this IQueryable source, Expression> selector) => source.Select(selector).OrderBy(_ => _).FirstOrDefault(); + + /// + /// 取最小值 + /// + /// + /// + /// + /// + /// + /// + public static TResult MinOrDefault(this IQueryable source, Expression> selector, TResult defaultValue) + { + TResult result = source.Select(selector).OrderBy(_ => _).FirstOrDefault(); + if (result != null) + return result; + return defaultValue; + } + + /// + /// 取最小值 + /// + /// + /// + /// + public static TSource MinOrDefault(this IQueryable source) => source.OrderBy(_ => _).FirstOrDefault(); + + /// + /// 取最小值 + /// + /// + /// + /// + /// + public static TSource MinOrDefault(this IQueryable source, TSource defaultValue) + { + TSource result = source.OrderBy(_ => _).FirstOrDefault(); + if (result != null) + return result; + return defaultValue; + } + + /// + /// 取最小值 + /// + /// + /// + /// + /// + /// + public static TResult MinOrDefault(this IEnumerable source, Func selector) => source.Select(selector).OrderBy(_ => _).FirstOrDefault(); + + /// + /// 取最小值 + /// + /// + /// + /// + /// + /// + /// + public static TResult MinOrDefault(this IEnumerable source, Func selector, TResult defaultValue) + { + TResult result = source.Select(selector).OrderBy(_ => _).FirstOrDefault(); + if (result != null) + return result; + return defaultValue; + } + + /// + /// 取最小值 + /// + /// + /// + /// + public static TSource MinOrDefault(this IEnumerable source) => source.OrderBy(_ => _).FirstOrDefault(); + + /// + /// 取最小值 + /// + /// + /// + /// + /// + public static TSource MinOrDefault(this IEnumerable source, TSource defaultValue) + { + TSource result = source.OrderBy(_ => _).FirstOrDefault(); + if (result != null) + return result; + return defaultValue; + } + } + + internal class SubstituteParameterVisitor : ExpressionVisitor + { + public Dictionary Sub = new Dictionary(); + + protected override Expression VisitParameter(ParameterExpression node) + { + return Sub.TryGetValue(node, out var newValue) ? newValue : node; + } + } +} diff --git a/常用工具集/Utility/Core/RMB.cs b/常用工具集/Utility/Core/RMB.cs new file mode 100644 index 0000000..72d45af --- /dev/null +++ b/常用工具集/Utility/Core/RMB.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MES.Utility.Core +{ + public static class RMB + {/// + /// 转换人民币大小金额 + /// + /// 金额 + /// 返回大写形式 + public static string ToRMB(this decimal num) + { + string str1 = "零壹贰叁肆伍陆柒捌玖"; //0-9所对应的汉字 + string str2 = "万仟佰拾亿仟佰拾万仟佰拾元角分"; //数字位所对应的汉字 + string str3 = ""; //从原num值中取出的值 + string str4 = ""; //数字的字符串形式 + string str5 = ""; //人民币大写金额形式 + int i; //循环变量 + int j; //num的值乘以100的字符串长度 + string ch1 = ""; //数字的汉语读法 + string ch2 = ""; //数字位的汉字读法 + int nzero = 0; //用来计算连续的零值是几个 + int temp; //从原num值中取出的值 + + num = Math.Round(Math.Abs(num), 2); //将num取绝对值并四舍五入取2位小数 + str4 = ((long)(num * 100)).ToString(); //将num乘100并转换成字符串形式 + j = str4.Length; //找出最高位 + if (j > 15) { return "溢出"; } + str2 = str2.Substring(15 - j); //取出对应位数的str2的值。如:200.55,j为5所以str2=佰拾元角分 + + //循环取出每一位需要转换的值 + for (i = 0; i < j; i++) + { + str3 = str4.Substring(i, 1); //取出需转换的某一位的值 + temp = Convert.ToInt32(str3); //转换为数字 + if (i != (j - 3) && i != (j - 7) && i != (j - 11) && i != (j - 15)) + { + //当所取位数不为元、万、亿、万亿上的数字时 + if (str3 == "0") + { + ch1 = ""; + ch2 = ""; + nzero = nzero + 1; + } + else + { + if (str3 != "0" && nzero != 0) + { + ch1 = "零" + str1.Substring(temp * 1, 1); + ch2 = str2.Substring(i, 1); + nzero = 0; + } + else + { + ch1 = str1.Substring(temp * 1, 1); + ch2 = str2.Substring(i, 1); + nzero = 0; + } + } + } + else + { + //该位是万亿,亿,万,元位等关键位 + if (str3 != "0" && nzero != 0) + { + ch1 = "零" + str1.Substring(temp * 1, 1); + ch2 = str2.Substring(i, 1); + nzero = 0; + } + else + { + if (str3 != "0" && nzero == 0) + { + ch1 = str1.Substring(temp * 1, 1); + ch2 = str2.Substring(i, 1); + nzero = 0; + } + else + { + if (str3 == "0" && nzero >= 3) + { + ch1 = ""; + ch2 = ""; + nzero = nzero + 1; + } + else + { + if (j >= 11) + { + ch1 = ""; + nzero = nzero + 1; + } + else + { + ch1 = ""; + ch2 = str2.Substring(i, 1); + nzero = nzero + 1; + } + } + } + } + } + if (i == (j - 11) || i == (j - 3)) + { + //如果该位是亿位或元位,则必须写上 + ch2 = str2.Substring(i, 1); + } + str5 = str5 + ch1 + ch2; + + if (i == j - 1 && str3 == "0") + { + //最后一位(分)为0时,加上“整” + str5 = str5 + '整'; + } + } + if (num == 0) + { + str5 = "零元整"; + } + return str5; + } + + public static string ToRMB(this int num) + { + return ToRMB(Convert.ToString(num)); + } + + public static string ToRMB(this float num) + { + return ToRMB(Convert.ToString(num)); + } + + public static string ToRMB(this double num) + { + return ToRMB(Convert.ToString(num)); + } + + public static string ToRMB(this long num) + { + return ToRMB(Convert.ToString(num)); + } + /// + /// 一个重载,将字符串先转换成数字在调用CmycurD(decimal num) + /// + /// 用户输入的金额,字符串形式未转成decimal + /// + public static string ToRMB(this string numstr) + { + try + { + decimal num = Convert.ToDecimal(numstr); + return ToRMB(num); + } + catch + { + return "非数字形式!"; + } + } + } +} diff --git a/常用工具集/Utility/Core/RandomHelper.cs b/常用工具集/Utility/Core/RandomHelper.cs new file mode 100644 index 0000000..2e7e778 --- /dev/null +++ b/常用工具集/Utility/Core/RandomHelper.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MES.Utility.Core +{ + /// + /// 使用Random类生成伪随机数 + /// + public static class RandomHelper + { + + /// + /// 生成一个指定范围的随机整数,该随机数范围包括最小值,但不包括最大值 + /// + /// 最小值 + /// 最大值 + public static int GetRandomInt(int minNum, int maxNum) + { + return new Random().Next(minNum, maxNum); + } + + /// + /// 生成一个0.0到1.0的随机小数 + /// + public static double GetRandomDouble() + { + return new Random().NextDouble(); + } + + /// + /// 对一个数组进行随机排序 + /// + /// 数组的类型 + /// 需要随机排序的数组 + public static void GetRandomArray(T[] arr) + { + //对数组进行随机排序的算法:随机选择两个位置,将两个位置上的值交换 + + //交换的次数,这里使用数组的长度作为交换次数 + int count = arr.Length; + + //开始交换 + for (int i = 0; i < count; i++) + { + //生成两个随机数位置 + int targetIndex1 = GetRandomInt(0, arr.Length); + int targetIndex2 = GetRandomInt(0, arr.Length); + + //定义临时变量 + T temp; + + //交换两个随机数位置的值 + temp = arr[targetIndex1]; + arr[targetIndex1] = arr[targetIndex2]; + arr[targetIndex2] = temp; + } + } + } +} diff --git a/常用工具集/Utility/Core/RegexHelper.cs b/常用工具集/Utility/Core/RegexHelper.cs new file mode 100644 index 0000000..53149fa --- /dev/null +++ b/常用工具集/Utility/Core/RegexHelper.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace MES.Utility.Core +{ + /// + /// 操作正则表达式的公共类 + /// + public class RegexHelper + { + /// + /// 验证输入字符串是否与模式字符串匹配,匹配返回true + /// + /// 输入字符串 + /// 模式字符串 + public static bool IsMatch(string input, string pattern) + { + return IsMatch(input, pattern, RegexOptions.IgnoreCase); + } + + /// + /// 验证输入字符串是否与模式字符串匹配,匹配返回true + /// + /// 输入的字符串 + /// 模式字符串 + /// 筛选条件 + public static bool IsMatch(string input, string pattern, RegexOptions options) + { + return Regex.IsMatch(input, pattern, options); + } + } +} diff --git a/常用工具集/Utility/Core/RegisterHelper.cs b/常用工具集/Utility/Core/RegisterHelper.cs new file mode 100644 index 0000000..239979c --- /dev/null +++ b/常用工具集/Utility/Core/RegisterHelper.cs @@ -0,0 +1,277 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Win32; +using System.Reflection; + +namespace MES.Utility.Core +{ + + /// + /// 注册表辅助类 + /// + public class RegisterHelper + { + /// + /// 默认注册表基项 + /// + private string baseKey = "Software"; + + #region 构造函数 + /// + /// 构造函数 + /// + /// 基项的名称 + public RegisterHelper() + { + + } + + /// + /// 构造函数 + /// + /// 基项的名称 + public RegisterHelper(string baseKey) + { + this.baseKey = baseKey; + + } + #endregion + + #region 公共方法 + + /// + /// 写入注册表,如果指定项已经存在,则修改指定项的值 + /// + /// 注册表基项枚举 + /// 注册表项,不包括基项 + /// 值名称 + /// 值 + public void SetValue(KeyType keytype, string key, string name, string values) + { + RegistryKey rk = (RegistryKey)GetRegistryKey(keytype); + RegistryKey software = rk.OpenSubKey(baseKey, true); + RegistryKey rkt = software.CreateSubKey(key); + if (rkt != null) + { + rkt.SetValue(name, values); + } + } + + + /// + /// 读取注册表 + /// + /// 注册表基项枚举 + /// 注册表项,不包括基项 + /// 值名称 + /// 返回字符串 + public string GetValue(KeyType keytype, string key, string name) + { + RegistryKey rk = (RegistryKey)GetRegistryKey(keytype); + RegistryKey software = rk.OpenSubKey(baseKey, true); + RegistryKey rkt = software.OpenSubKey(key); + + if (rkt != null) + { + return rkt.GetValue(name).ToString(); + } + else + { + return string.Empty; + } + } + + + /// + /// 删除注册表中的值 + /// + /// 注册表基项枚举 + /// 注册表项名称,不包括基项 + /// 值名称 + public void DeleteValue(KeyType keytype, string key, string name) + { + RegistryKey rk = (RegistryKey)GetRegistryKey(keytype); + RegistryKey software = rk.OpenSubKey(baseKey, true); + RegistryKey rkt = software.OpenSubKey(key, true); + + if (rkt != null) + { + object value = rkt.GetValue(name); + if (value != null) + { + rkt.DeleteValue(name, true); + } + } + } + + + /// + /// 删除注册表中的指定项 + /// + /// 注册表基项枚举 + /// 注册表中的项,不包括基项 + /// 返回布尔值,指定操作是否成功 + public void DeleteSubKey(KeyType keytype, string key) + { + RegistryKey rk = (RegistryKey)GetRegistryKey(keytype); + RegistryKey software = rk.OpenSubKey(baseKey, true); + if (software != null) + { + software.DeleteSubKeyTree(key); + } + } + + + /// + /// 判断指定项是否存在 + /// + /// 基项枚举 + /// 指定项字符串 + /// 返回布尔值,说明指定项是否存在 + public bool IsExist(KeyType keytype, string key) + { + RegistryKey rk = (RegistryKey)GetRegistryKey(keytype); + RegistryKey software = rk.OpenSubKey(baseKey); + RegistryKey rkt = software.OpenSubKey(key); + if (rkt != null) + { + return true; + } + else + { + return false; + } + } + + + /// + /// 检索指定项关联的所有值 + /// + /// 基项枚举 + /// 指定项字符串 + /// 返回指定项关联的所有值的字符串数组 + public string[] GetValues(KeyType keytype, string key) + { + RegistryKey rk = (RegistryKey)GetRegistryKey(keytype); + RegistryKey software = rk.OpenSubKey(baseKey, true); + RegistryKey rkt = software.OpenSubKey(key); + string[] names = rkt.GetValueNames(); + + if (names.Length == 0) + { + return names; + } + else + { + string[] values = new string[names.Length]; + + int i = 0; + + foreach (string name in names) + { + values[i] = rkt.GetValue(name).ToString(); + + i++; + } + + return values; + } + + } + + /// + /// 将对象所有属性写入指定注册表中 + /// + /// 注册表基项枚举 + /// 注册表项,不包括基项 + /// 传入的对象 + public void SetObjectValue(KeyType keyType, string key, Object obj) + { + if (obj != null) + { + Type t = obj.GetType(); + + string name; + object value; + foreach (var p in t.GetProperties()) + { + if (p != null) + { + name = p.Name; + value = p.GetValue(obj, null); + this.SetValue(keyType, key, name, value.ToString()); + } + } + } + } + + #endregion + + #region 私有方法 + + /// + /// 返回RegistryKey对象 + /// + /// 注册表基项枚举 + /// + private object GetRegistryKey(KeyType keyType) + { + RegistryKey rk = null; + + switch (keyType) + { + case KeyType.HKEY_CLASS_ROOT: + rk = Registry.ClassesRoot; + break; + case KeyType.HKEY_CURRENT_USER: + rk = Registry.CurrentUser; + break; + case KeyType.HKEY_LOCAL_MACHINE: + rk = Registry.LocalMachine; + break; + case KeyType.HKEY_USERS: + rk = Registry.Users; + break; + case KeyType.HKEY_CURRENT_CONFIG: + rk = Registry.CurrentConfig; + break; + } + + return rk; + } + + #endregion + + #region 枚举 + /// + /// 注册表基项枚举 + /// + public enum KeyType : int + { + /// + /// 注册表基项 HKEY_CLASSES_ROOT + /// + HKEY_CLASS_ROOT, + /// + /// 注册表基项 HKEY_CURRENT_USER + /// + HKEY_CURRENT_USER, + /// + /// 注册表基项 HKEY_LOCAL_MACHINE + /// + HKEY_LOCAL_MACHINE, + /// + /// 注册表基项 HKEY_USERS + /// + HKEY_USERS, + /// + /// 注册表基项 HKEY_CURRENT_CONFIG + /// + HKEY_CURRENT_CONFIG + } + #endregion + + } +} diff --git a/常用工具集/Utility/Core/StringHelper.cs b/常用工具集/Utility/Core/StringHelper.cs new file mode 100644 index 0000000..3d94518 --- /dev/null +++ b/常用工具集/Utility/Core/StringHelper.cs @@ -0,0 +1,318 @@ + +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace MES.Utility.Core +{ + /// + /// 字符串操作类 + /// + public static class StringHelper + { + /// + /// 把字符串按照分隔符转换成 List + /// + /// 源字符串 + /// 分隔符 + /// 是否转换为小写 + /// + public static List SplitToList(this string str, char speater = ',', bool toLower = false) + { + List list = new List(); + if (str == null) + return list; + string[] ss = str.Split(speater); + foreach (string s in ss) + { + if (!string.IsNullOrEmpty(s) && s != speater.ToString()) + { + string strVal = s; + if (toLower) + { + strVal = s.ToLower(); + } + list.Add(strVal); + } + } + return list; + } + + /// + /// 把 List 按照分隔符组装成 string + /// + /// + /// + /// + public static string GetStrArray(this List list, string speater = ",") + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < list.Count; i++) + { + if (i == list.Count - 1) + { + sb.Append(list[i]); + } + else + { + sb.Append(list[i]); + sb.Append(speater); + } + } + return sb.ToString(); + } + + + /// + /// 删除最后结尾的指定字符后的字符 + /// + public static string DelLastChar(this string str, string strChar = ",") + { + return str.Substring(0, str.LastIndexOf(strChar)); + } + + + /// + /// 转全角的函数(SBC case) + /// + /// + /// + public static string ToSBC(string input) + { + //半角转全角: + char[] c = input.ToCharArray(); + for (int i = 0; i < c.Length; i++) + { + if (c[i] == 32) + { + c[i] = (char)12288; + continue; + } + if (c[i] < 127) + c[i] = (char)(c[i] + 65248); + } + return new string(c); + } + + /// + /// 转半角的函数(SBC case) + /// + /// 输入 + /// + public static string ToDBC(string input) + { + char[] c = input.ToCharArray(); + for (int i = 0; i < c.Length; i++) + { + if (c[i] == 12288) + { + c[i] = (char)32; + continue; + } + if (c[i] > 65280 && c[i] < 65375) + c[i] = (char)(c[i] - 65248); + } + return new string(c); + } + + + /// + /// 获取正确的Id,如果不是正整数,返回0 + /// + /// + /// 返回正确的整数ID,失败返回0 + public static int ToInt32(this string value) + { + if (IsNumberId(value)) + return int.Parse(value); + else + return 0; + } + + /// + /// 检查一个字符串是否是纯数字构成的,一般用于查询字符串参数的有效性验证。(0除外) + /// + /// 需验证的字符串。。 + /// 是否合法的bool值。 + public static bool IsNumberId(string _value) + { + return QuickValidate("^[1-9]*[0-9]*$", _value); + } + + /// + /// 快速验证一个字符串是否符合指定的正则表达式。 + /// + /// 正则表达式的内容。 + /// 需验证的字符串。 + /// 是否合法的bool值。 + public static bool QuickValidate(string _express, string _value) + { + if (_value == null) return false; + Regex myRegex = new Regex(_express); + if (_value.Length == 0) + { + return false; + } + return myRegex.IsMatch(_value); + } + /// + /// 得到字符串长度,一个汉字长度为2 + /// + /// 参数字符串 + /// + public static int StrLength(this string inputString) + { + System.Text.ASCIIEncoding ascii = new System.Text.ASCIIEncoding(); + int tempLen = 0; + byte[] s = ascii.GetBytes(inputString); + for (int i = 0; i < s.Length; i++) + { + if ((int)s[i] == 63) + tempLen += 2; + else + tempLen += 1; + } + return tempLen; + } + + + /// + /// 截取指定长度字符串 + /// + /// 要处理的字符串 + /// 指定长度 + /// 返回处理后的字符串 + public static string splitString(this string inputString, int len) + { + bool isShowFix = false; + if (len % 2 == 1) + { + isShowFix = true; + len--; + } + System.Text.ASCIIEncoding ascii = new System.Text.ASCIIEncoding(); + int tempLen = 0; + string tempString = ""; + byte[] s = ascii.GetBytes(inputString); + for (int i = 0; i < s.Length; i++) + { + if ((int)s[i] == 63) + tempLen += 2; + else + tempLen += 1; + + try + { + tempString += inputString.Substring(i, 1); + } + catch + { + break; + } + + if (tempLen > len) + break; + } + + byte[] mybyte = System.Text.Encoding.Default.GetBytes(inputString); + if (isShowFix && mybyte.Length > len) + tempString += "…"; + return tempString; + } + + + /// + /// HTML转行成TEXT + /// + /// + /// + public static string HtmlToTxt(this string strHtml) + { + string[] aryReg ={ + @"]*?>.*?", + @"<(\/\s*)?!?((\w+:)?\w+)(\w+(\s*=?\s*(([""'])(\\[""'tbnr]|[^\7])*?\7|\w+)|.{0})|\s)*?(\/\s*)?>", + @"([\r\n])[\s]+", + @"&(quot|#34);", + @"&(amp|#38);", + @"&(lt|#60);", + @"&(gt|#62);", + @"&(nbsp|#160);", + @"&(iexcl|#161);", + @"&(cent|#162);", + @"&(pound|#163);", + @"&(copy|#169);", + @"&#(\d+);", + @"-->", + @" INIT_FINISHING_STATE ---. + * / | (2) (5) | + * / v (5) | + * (3)| SETDICT_STATE ---> SETDICT_FINISHING_STATE |(3) + * \ | (3) | ,--------' + * | | | (3) / + * v v (5) v v + * (1) -> BUSY_STATE ----> FINISHING_STATE + * | (6) + * v + * FINISHED_STATE + * \_____________________________________/ + * | (7) + * v + * CLOSED_STATE + * + * (1) If we should produce a header we start in INIT_STATE, otherwise + * we start in BUSY_STATE. + * (2) A dictionary may be set only when we are in INIT_STATE, then + * we change the state as indicated. + * (3) Whether a dictionary is set or not, on the first call of deflate + * we change to BUSY_STATE. + * (4) -- intentionally left blank -- :) + * (5) FINISHING_STATE is entered, when flush() is called to indicate that + * there is no more INPUT. There are also states indicating, that + * the header wasn't written yet. + * (6) FINISHED_STATE is entered, when everything has been flushed to the + * internal pending output buffer. + * (7) At any time (7) + * + */ + + #endregion Deflater Documentation + + #region Public Constants + + /// + /// The best and slowest compression level. This tries to find very + /// long and distant string repetitions. + /// + public const int BEST_COMPRESSION = 9; + + /// + /// The worst but fastest compression level. + /// + public const int BEST_SPEED = 1; + + /// + /// The default compression level. + /// + public const int DEFAULT_COMPRESSION = -1; + + /// + /// This level won't compress at all but output uncompressed blocks. + /// + public const int NO_COMPRESSION = 0; + + /// + /// The compression method. This is the only method supported so far. + /// There is no need to use this constant at all. + /// + public const int DEFLATED = 8; + + #endregion Public Constants + + #region Public Enum + + /// + /// Compression Level as an enum for safer use + /// + public enum CompressionLevel + { + /// + /// The best and slowest compression level. This tries to find very + /// long and distant string repetitions. + /// + BEST_COMPRESSION = Deflater.BEST_COMPRESSION, + + /// + /// The worst but fastest compression level. + /// + BEST_SPEED = Deflater.BEST_SPEED, + + /// + /// The default compression level. + /// + DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION, + + /// + /// This level won't compress at all but output uncompressed blocks. + /// + NO_COMPRESSION = Deflater.NO_COMPRESSION, + + /// + /// The compression method. This is the only method supported so far. + /// There is no need to use this constant at all. + /// + DEFLATED = Deflater.DEFLATED + } + + #endregion Public Enum + + #region Local Constants + + private const int IS_SETDICT = 0x01; + private const int IS_FLUSHING = 0x04; + private const int IS_FINISHING = 0x08; + + private const int INIT_STATE = 0x00; + private const int SETDICT_STATE = 0x01; + + // private static int INIT_FINISHING_STATE = 0x08; + // private static int SETDICT_FINISHING_STATE = 0x09; + private const int BUSY_STATE = 0x10; + + private const int FLUSHING_STATE = 0x14; + private const int FINISHING_STATE = 0x1c; + private const int FINISHED_STATE = 0x1e; + private const int CLOSED_STATE = 0x7f; + + #endregion Local Constants + + #region Constructors + + /// + /// Creates a new deflater with default compression level. + /// + public Deflater() : this(DEFAULT_COMPRESSION, false) + { + } + + /// + /// Creates a new deflater with given compression level. + /// + /// + /// the compression level, a value between NO_COMPRESSION + /// and BEST_COMPRESSION, or DEFAULT_COMPRESSION. + /// + /// if lvl is out of range. + public Deflater(int level) : this(level, false) + { + } + + /// + /// Creates a new deflater with given compression level. + /// + /// + /// the compression level, a value between NO_COMPRESSION + /// and BEST_COMPRESSION. + /// + /// + /// true, if we should suppress the Zlib/RFC1950 header at the + /// beginning and the adler checksum at the end of the output. This is + /// useful for the GZIP/PKZIP formats. + /// + /// if lvl is out of range. + public Deflater(int level, bool noZlibHeaderOrFooter) + { + if (level == DEFAULT_COMPRESSION) + { + level = 6; + } + else if (level < NO_COMPRESSION || level > BEST_COMPRESSION) + { + throw new ArgumentOutOfRangeException(nameof(level)); + } + + pending = new DeflaterPending(); + engine = new DeflaterEngine(pending, noZlibHeaderOrFooter); + this.noZlibHeaderOrFooter = noZlibHeaderOrFooter; + SetStrategy(DeflateStrategy.Default); + SetLevel(level); + Reset(); + } + + #endregion Constructors + + /// + /// Resets the deflater. The deflater acts afterwards as if it was + /// just created with the same compression level and strategy as it + /// had before. + /// + public void Reset() + { + state = (noZlibHeaderOrFooter ? BUSY_STATE : INIT_STATE); + totalOut = 0; + pending.Reset(); + engine.Reset(); + } + + /// + /// Gets the current adler checksum of the data that was processed so far. + /// + public int Adler + { + get + { + return engine.Adler; + } + } + + /// + /// Gets the number of input bytes processed so far. + /// + public long TotalIn + { + get + { + return engine.TotalIn; + } + } + + /// + /// Gets the number of output bytes so far. + /// + public long TotalOut + { + get + { + return totalOut; + } + } + + /// + /// Flushes the current input block. Further calls to deflate() will + /// produce enough output to inflate everything in the current input + /// block. This is not part of Sun's JDK so I have made it package + /// private. It is used by DeflaterOutputStream to implement + /// flush(). + /// + public void Flush() + { + state |= IS_FLUSHING; + } + + /// + /// Finishes the deflater with the current input block. It is an error + /// to give more input after this method was called. This method must + /// be called to force all bytes to be flushed. + /// + public void Finish() + { + state |= (IS_FLUSHING | IS_FINISHING); + } + + /// + /// Returns true if the stream was finished and no more output bytes + /// are available. + /// + public bool IsFinished + { + get + { + return (state == FINISHED_STATE) && pending.IsFlushed; + } + } + + /// + /// Returns true, if the input buffer is empty. + /// You should then call setInput(). + /// NOTE: This method can also return true when the stream + /// was finished. + /// + public bool IsNeedingInput + { + get + { + return engine.NeedsInput(); + } + } + + /// + /// Sets the data which should be compressed next. This should be only + /// called when needsInput indicates that more input is needed. + /// If you call setInput when needsInput() returns false, the + /// previous input that is still pending will be thrown away. + /// The given byte array should not be changed, before needsInput() returns + /// true again. + /// This call is equivalent to setInput(input, 0, input.length). + /// + /// + /// the buffer containing the input data. + /// + /// + /// if the buffer was finished() or ended(). + /// + public void SetInput(byte[] input) + { + SetInput(input, 0, input.Length); + } + + /// + /// Sets the data which should be compressed next. This should be + /// only called when needsInput indicates that more input is needed. + /// The given byte array should not be changed, before needsInput() returns + /// true again. + /// + /// + /// the buffer containing the input data. + /// + /// + /// the start of the data. + /// + /// + /// the number of data bytes of input. + /// + /// + /// if the buffer was Finish()ed or if previous input is still pending. + /// + public void SetInput(byte[] input, int offset, int count) + { + if ((state & IS_FINISHING) != 0) + { + throw new InvalidOperationException("Finish() already called"); + } + engine.SetInput(input, offset, count); + } + + /// + /// Sets the compression level. There is no guarantee of the exact + /// position of the change, but if you call this when needsInput is + /// true the change of compression level will occur somewhere near + /// before the end of the so far given input. + /// + /// + /// the new compression level. + /// + public void SetLevel(int level) + { + if (level == DEFAULT_COMPRESSION) + { + level = 6; + } + else if (level < NO_COMPRESSION || level > BEST_COMPRESSION) + { + throw new ArgumentOutOfRangeException(nameof(level)); + } + + if (this.level != level) + { + this.level = level; + engine.SetLevel(level); + } + } + + /// + /// Get current compression level + /// + /// Returns the current compression level + public int GetLevel() + { + return level; + } + + /// + /// Sets the compression strategy. Strategy is one of + /// DEFAULT_STRATEGY, HUFFMAN_ONLY and FILTERED. For the exact + /// position where the strategy is changed, the same as for + /// SetLevel() applies. + /// + /// + /// The new compression strategy. + /// + public void SetStrategy(DeflateStrategy strategy) + { + engine.Strategy = strategy; + } + + /// + /// Deflates the current input block with to the given array. + /// + /// + /// The buffer where compressed data is stored + /// + /// + /// The number of compressed bytes added to the output, or 0 if either + /// IsNeedingInput() or IsFinished returns true or length is zero. + /// + public int Deflate(byte[] output) + { + return Deflate(output, 0, output.Length); + } + + /// + /// Deflates the current input block to the given array. + /// + /// + /// Buffer to store the compressed data. + /// + /// + /// Offset into the output array. + /// + /// + /// The maximum number of bytes that may be stored. + /// + /// + /// The number of compressed bytes added to the output, or 0 if either + /// needsInput() or finished() returns true or length is zero. + /// + /// + /// If Finish() was previously called. + /// + /// + /// If offset or length don't match the array length. + /// + public int Deflate(byte[] output, int offset, int length) + { + int origLength = length; + + if (state == CLOSED_STATE) + { + throw new InvalidOperationException("Deflater closed"); + } + + if (state < BUSY_STATE) + { + // output header + int header = (DEFLATED + + ((DeflaterConstants.MAX_WBITS - 8) << 4)) << 8; + int level_flags = (level - 1) >> 1; + if (level_flags < 0 || level_flags > 3) + { + level_flags = 3; + } + header |= level_flags << 6; + if ((state & IS_SETDICT) != 0) + { + // Dictionary was set + header |= DeflaterConstants.PRESET_DICT; + } + header += 31 - (header % 31); + + pending.WriteShortMSB(header); + if ((state & IS_SETDICT) != 0) + { + int chksum = engine.Adler; + engine.ResetAdler(); + pending.WriteShortMSB(chksum >> 16); + pending.WriteShortMSB(chksum & 0xffff); + } + + state = BUSY_STATE | (state & (IS_FLUSHING | IS_FINISHING)); + } + + for (; ; ) + { + int count = pending.Flush(output, offset, length); + offset += count; + totalOut += count; + length -= count; + + if (length == 0 || state == FINISHED_STATE) + { + break; + } + + if (!engine.Deflate((state & IS_FLUSHING) != 0, (state & IS_FINISHING) != 0)) + { + switch (state) + { + case BUSY_STATE: + // We need more input now + return origLength - length; + + case FLUSHING_STATE: + if (level != NO_COMPRESSION) + { + /* We have to supply some lookahead. 8 bit lookahead + * is needed by the zlib inflater, and we must fill + * the next byte, so that all bits are flushed. + */ + int neededbits = 8 + ((-pending.BitCount) & 7); + while (neededbits > 0) + { + /* write a static tree block consisting solely of + * an EOF: + */ + pending.WriteBits(2, 10); + neededbits -= 10; + } + } + state = BUSY_STATE; + break; + + case FINISHING_STATE: + pending.AlignToByte(); + + // Compressed data is complete. Write footer information if required. + if (!noZlibHeaderOrFooter) + { + int adler = engine.Adler; + pending.WriteShortMSB(adler >> 16); + pending.WriteShortMSB(adler & 0xffff); + } + state = FINISHED_STATE; + break; + } + } + } + return origLength - length; + } + + /// + /// Sets the dictionary which should be used in the deflate process. + /// This call is equivalent to setDictionary(dict, 0, dict.Length). + /// + /// + /// the dictionary. + /// + /// + /// if SetInput () or Deflate () were already called or another dictionary was already set. + /// + public void SetDictionary(byte[] dictionary) + { + SetDictionary(dictionary, 0, dictionary.Length); + } + + /// + /// Sets the dictionary which should be used in the deflate process. + /// The dictionary is a byte array containing strings that are + /// likely to occur in the data which should be compressed. The + /// dictionary is not stored in the compressed output, only a + /// checksum. To decompress the output you need to supply the same + /// dictionary again. + /// + /// + /// The dictionary data + /// + /// + /// The index where dictionary information commences. + /// + /// + /// The number of bytes in the dictionary. + /// + /// + /// If SetInput () or Deflate() were already called or another dictionary was already set. + /// + public void SetDictionary(byte[] dictionary, int index, int count) + { + if (state != INIT_STATE) + { + throw new InvalidOperationException(); + } + + state = SETDICT_STATE; + engine.SetDictionary(dictionary, index, count); + } + + #region Instance Fields + + /// + /// Compression level. + /// + private int level; + + /// + /// If true no Zlib/RFC1950 headers or footers are generated + /// + private bool noZlibHeaderOrFooter; + + /// + /// The current state. + /// + private int state; + + /// + /// The total bytes of output written. + /// + private long totalOut; + + /// + /// The pending output. + /// + private DeflaterPending pending; + + /// + /// The deflater engine. + /// + private DeflaterEngine engine; + + #endregion Instance Fields + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterConstants.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterConstants.cs new file mode 100644 index 0000000..b7c7d2a --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterConstants.cs @@ -0,0 +1,146 @@ +using System; + +namespace ICSharpCode.SharpZipLib.Zip.Compression +{ + /// + /// This class contains constants used for deflation. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")] + public static class DeflaterConstants + { + /// + /// Set to true to enable debugging + /// + public const bool DEBUGGING = false; + + /// + /// Written to Zip file to identify a stored block + /// + public const int STORED_BLOCK = 0; + + /// + /// Identifies static tree in Zip file + /// + public const int STATIC_TREES = 1; + + /// + /// Identifies dynamic tree in Zip file + /// + public const int DYN_TREES = 2; + + /// + /// Header flag indicating a preset dictionary for deflation + /// + public const int PRESET_DICT = 0x20; + + /// + /// Sets internal buffer sizes for Huffman encoding + /// + public const int DEFAULT_MEM_LEVEL = 8; + + /// + /// Internal compression engine constant + /// + public const int MAX_MATCH = 258; + + /// + /// Internal compression engine constant + /// + public const int MIN_MATCH = 3; + + /// + /// Internal compression engine constant + /// + public const int MAX_WBITS = 15; + + /// + /// Internal compression engine constant + /// + public const int WSIZE = 1 << MAX_WBITS; + + /// + /// Internal compression engine constant + /// + public const int WMASK = WSIZE - 1; + + /// + /// Internal compression engine constant + /// + public const int HASH_BITS = DEFAULT_MEM_LEVEL + 7; + + /// + /// Internal compression engine constant + /// + public const int HASH_SIZE = 1 << HASH_BITS; + + /// + /// Internal compression engine constant + /// + public const int HASH_MASK = HASH_SIZE - 1; + + /// + /// Internal compression engine constant + /// + public const int HASH_SHIFT = (HASH_BITS + MIN_MATCH - 1) / MIN_MATCH; + + /// + /// Internal compression engine constant + /// + public const int MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1; + + /// + /// Internal compression engine constant + /// + public const int MAX_DIST = WSIZE - MIN_LOOKAHEAD; + + /// + /// Internal compression engine constant + /// + public const int PENDING_BUF_SIZE = 1 << (DEFAULT_MEM_LEVEL + 8); + + /// + /// Internal compression engine constant + /// + public static int MAX_BLOCK_SIZE = Math.Min(65535, PENDING_BUF_SIZE - 5); + + /// + /// Internal compression engine constant + /// + public const int DEFLATE_STORED = 0; + + /// + /// Internal compression engine constant + /// + public const int DEFLATE_FAST = 1; + + /// + /// Internal compression engine constant + /// + public const int DEFLATE_SLOW = 2; + + /// + /// Internal compression engine constant + /// + public static int[] GOOD_LENGTH = { 0, 4, 4, 4, 4, 8, 8, 8, 32, 32 }; + + /// + /// Internal compression engine constant + /// + public static int[] MAX_LAZY = { 0, 4, 5, 6, 4, 16, 16, 32, 128, 258 }; + + /// + /// Internal compression engine constant + /// + public static int[] NICE_LENGTH = { 0, 8, 16, 32, 16, 32, 128, 128, 258, 258 }; + + /// + /// Internal compression engine constant + /// + public static int[] MAX_CHAIN = { 0, 4, 8, 32, 16, 32, 128, 256, 1024, 4096 }; + + /// + /// Internal compression engine constant + /// + public static int[] COMPR_FUNC = { 0, 1, 1, 1, 1, 2, 2, 2, 2, 2 }; + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterEngine.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterEngine.cs new file mode 100644 index 0000000..556911c --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterEngine.cs @@ -0,0 +1,946 @@ +using ICSharpCode.SharpZipLib.Checksum; +using System; + +namespace ICSharpCode.SharpZipLib.Zip.Compression +{ + /// + /// Strategies for deflater + /// + public enum DeflateStrategy + { + /// + /// The default strategy + /// + Default = 0, + + /// + /// This strategy will only allow longer string repetitions. It is + /// useful for random data with a small character set. + /// + Filtered = 1, + + /// + /// This strategy will not look for string repetitions at all. It + /// only encodes with Huffman trees (which means, that more common + /// characters get a smaller encoding. + /// + HuffmanOnly = 2 + } + + // DEFLATE ALGORITHM: + // + // The uncompressed stream is inserted into the window array. When + // the window array is full the first half is thrown away and the + // second half is copied to the beginning. + // + // The head array is a hash table. Three characters build a hash value + // and they the value points to the corresponding index in window of + // the last string with this hash. The prev array implements a + // linked list of matches with the same hash: prev[index & WMASK] points + // to the previous index with the same hash. + // + + /// + /// Low level compression engine for deflate algorithm which uses a 32K sliding window + /// with secondary compression from Huffman/Shannon-Fano codes. + /// + public class DeflaterEngine + { + #region Constants + + private const int TooFar = 4096; + + #endregion Constants + + #region Constructors + + /// + /// Construct instance with pending buffer + /// Adler calculation will be performed + /// + /// + /// Pending buffer to use + /// + public DeflaterEngine(DeflaterPending pending) + : this (pending, false) + { + } + + + + /// + /// Construct instance with pending buffer + /// + /// + /// Pending buffer to use + /// + /// + /// If no adler calculation should be performed + /// + public DeflaterEngine(DeflaterPending pending, bool noAdlerCalculation) + { + this.pending = pending; + huffman = new DeflaterHuffman(pending); + if (!noAdlerCalculation) + adler = new Adler32(); + + window = new byte[2 * DeflaterConstants.WSIZE]; + head = new short[DeflaterConstants.HASH_SIZE]; + prev = new short[DeflaterConstants.WSIZE]; + + // We start at index 1, to avoid an implementation deficiency, that + // we cannot build a repeat pattern at index 0. + blockStart = strstart = 1; + } + + #endregion Constructors + + /// + /// Deflate drives actual compression of data + /// + /// True to flush input buffers + /// Finish deflation with the current input. + /// Returns true if progress has been made. + public bool Deflate(bool flush, bool finish) + { + bool progress; + do + { + FillWindow(); + bool canFlush = flush && (inputOff == inputEnd); + +#if DebugDeflation + if (DeflaterConstants.DEBUGGING) { + Console.WriteLine("window: [" + blockStart + "," + strstart + "," + + lookahead + "], " + compressionFunction + "," + canFlush); + } +#endif + switch (compressionFunction) + { + case DeflaterConstants.DEFLATE_STORED: + progress = DeflateStored(canFlush, finish); + break; + + case DeflaterConstants.DEFLATE_FAST: + progress = DeflateFast(canFlush, finish); + break; + + case DeflaterConstants.DEFLATE_SLOW: + progress = DeflateSlow(canFlush, finish); + break; + + default: + throw new InvalidOperationException("unknown compressionFunction"); + } + } while (pending.IsFlushed && progress); // repeat while we have no pending output and progress was made + return progress; + } + + /// + /// Sets input data to be deflated. Should only be called when NeedsInput() + /// returns true + /// + /// The buffer containing input data. + /// The offset of the first byte of data. + /// The number of bytes of data to use as input. + public void SetInput(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (inputOff < inputEnd) + { + throw new InvalidOperationException("Old input was not completely processed"); + } + + int end = offset + count; + + /* We want to throw an ArrayIndexOutOfBoundsException early. The + * check is very tricky: it also handles integer wrap around. + */ + if ((offset > end) || (end > buffer.Length)) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + inputBuf = buffer; + inputOff = offset; + inputEnd = end; + } + + /// + /// Determines if more input is needed. + /// + /// Return true if input is needed via SetInput + public bool NeedsInput() + { + return (inputEnd == inputOff); + } + + /// + /// Set compression dictionary + /// + /// The buffer containing the dictionary data + /// The offset in the buffer for the first byte of data + /// The length of the dictionary data. + public void SetDictionary(byte[] buffer, int offset, int length) + { +#if DebugDeflation + if (DeflaterConstants.DEBUGGING && (strstart != 1) ) + { + throw new InvalidOperationException("strstart not 1"); + } +#endif + adler?.Update(new ArraySegment(buffer, offset, length)); + if (length < DeflaterConstants.MIN_MATCH) + { + return; + } + + if (length > DeflaterConstants.MAX_DIST) + { + offset += length - DeflaterConstants.MAX_DIST; + length = DeflaterConstants.MAX_DIST; + } + + System.Array.Copy(buffer, offset, window, strstart, length); + + UpdateHash(); + --length; + while (--length > 0) + { + InsertString(); + strstart++; + } + strstart += 2; + blockStart = strstart; + } + + /// + /// Reset internal state + /// + public void Reset() + { + huffman.Reset(); + adler?.Reset(); + blockStart = strstart = 1; + lookahead = 0; + totalIn = 0; + prevAvailable = false; + matchLen = DeflaterConstants.MIN_MATCH - 1; + + for (int i = 0; i < DeflaterConstants.HASH_SIZE; i++) + { + head[i] = 0; + } + + for (int i = 0; i < DeflaterConstants.WSIZE; i++) + { + prev[i] = 0; + } + } + + /// + /// Reset Adler checksum + /// + public void ResetAdler() + { + adler?.Reset(); + } + + /// + /// Get current value of Adler checksum + /// + public int Adler + { + get + { + return (adler != null) ? unchecked((int)adler.Value) : 0; + } + } + + /// + /// Total data processed + /// + public long TotalIn + { + get + { + return totalIn; + } + } + + /// + /// Get/set the deflate strategy + /// + public DeflateStrategy Strategy + { + get + { + return strategy; + } + set + { + strategy = value; + } + } + + /// + /// Set the deflate level (0-9) + /// + /// The value to set the level to. + public void SetLevel(int level) + { + if ((level < 0) || (level > 9)) + { + throw new ArgumentOutOfRangeException(nameof(level)); + } + + goodLength = DeflaterConstants.GOOD_LENGTH[level]; + max_lazy = DeflaterConstants.MAX_LAZY[level]; + niceLength = DeflaterConstants.NICE_LENGTH[level]; + max_chain = DeflaterConstants.MAX_CHAIN[level]; + + if (DeflaterConstants.COMPR_FUNC[level] != compressionFunction) + { +#if DebugDeflation + if (DeflaterConstants.DEBUGGING) { + Console.WriteLine("Change from " + compressionFunction + " to " + + DeflaterConstants.COMPR_FUNC[level]); + } +#endif + switch (compressionFunction) + { + case DeflaterConstants.DEFLATE_STORED: + if (strstart > blockStart) + { + huffman.FlushStoredBlock(window, blockStart, + strstart - blockStart, false); + blockStart = strstart; + } + UpdateHash(); + break; + + case DeflaterConstants.DEFLATE_FAST: + if (strstart > blockStart) + { + huffman.FlushBlock(window, blockStart, strstart - blockStart, + false); + blockStart = strstart; + } + break; + + case DeflaterConstants.DEFLATE_SLOW: + if (prevAvailable) + { + huffman.TallyLit(window[strstart - 1] & 0xff); + } + if (strstart > blockStart) + { + huffman.FlushBlock(window, blockStart, strstart - blockStart, false); + blockStart = strstart; + } + prevAvailable = false; + matchLen = DeflaterConstants.MIN_MATCH - 1; + break; + } + compressionFunction = DeflaterConstants.COMPR_FUNC[level]; + } + } + + /// + /// Fill the window + /// + public void FillWindow() + { + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + if (strstart >= DeflaterConstants.WSIZE + DeflaterConstants.MAX_DIST) + { + SlideWindow(); + } + + /* If there is not enough lookahead, but still some input left, + * read in the input + */ + if (lookahead < DeflaterConstants.MIN_LOOKAHEAD && inputOff < inputEnd) + { + int more = 2 * DeflaterConstants.WSIZE - lookahead - strstart; + + if (more > inputEnd - inputOff) + { + more = inputEnd - inputOff; + } + + System.Array.Copy(inputBuf, inputOff, window, strstart + lookahead, more); + adler?.Update(new ArraySegment(inputBuf, inputOff, more)); + + inputOff += more; + totalIn += more; + lookahead += more; + } + + if (lookahead >= DeflaterConstants.MIN_MATCH) + { + UpdateHash(); + } + } + + private void UpdateHash() + { + /* + if (DEBUGGING) { + Console.WriteLine("updateHash: "+strstart); + } + */ + ins_h = (window[strstart] << DeflaterConstants.HASH_SHIFT) ^ window[strstart + 1]; + } + + /// + /// Inserts the current string in the head hash and returns the previous + /// value for this hash. + /// + /// The previous hash value + private int InsertString() + { + short match; + int hash = ((ins_h << DeflaterConstants.HASH_SHIFT) ^ window[strstart + (DeflaterConstants.MIN_MATCH - 1)]) & DeflaterConstants.HASH_MASK; + +#if DebugDeflation + if (DeflaterConstants.DEBUGGING) + { + if (hash != (((window[strstart] << (2*HASH_SHIFT)) ^ + (window[strstart + 1] << HASH_SHIFT) ^ + (window[strstart + 2])) & HASH_MASK)) { + throw new SharpZipBaseException("hash inconsistent: " + hash + "/" + +window[strstart] + "," + +window[strstart + 1] + "," + +window[strstart + 2] + "," + HASH_SHIFT); + } + } +#endif + prev[strstart & DeflaterConstants.WMASK] = match = head[hash]; + head[hash] = unchecked((short)strstart); + ins_h = hash; + return match & 0xffff; + } + + private void SlideWindow() + { + Array.Copy(window, DeflaterConstants.WSIZE, window, 0, DeflaterConstants.WSIZE); + matchStart -= DeflaterConstants.WSIZE; + strstart -= DeflaterConstants.WSIZE; + blockStart -= DeflaterConstants.WSIZE; + + // Slide the hash table (could be avoided with 32 bit values + // at the expense of memory usage). + for (int i = 0; i < DeflaterConstants.HASH_SIZE; ++i) + { + int m = head[i] & 0xffff; + head[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0); + } + + // Slide the prev table. + for (int i = 0; i < DeflaterConstants.WSIZE; i++) + { + int m = prev[i] & 0xffff; + prev[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0); + } + } + + /// + /// Find the best (longest) string in the window matching the + /// string starting at strstart. + /// + /// Preconditions: + /// + /// strstart + DeflaterConstants.MAX_MATCH <= window.length. + /// + /// + /// True if a match greater than the minimum length is found + private bool FindLongestMatch(int curMatch) + { + int match; + int scan = strstart; + // scanMax is the highest position that we can look at + int scanMax = scan + Math.Min(DeflaterConstants.MAX_MATCH, lookahead) - 1; + int limit = Math.Max(scan - DeflaterConstants.MAX_DIST, 0); + + byte[] window = this.window; + short[] prev = this.prev; + int chainLength = this.max_chain; + int niceLength = Math.Min(this.niceLength, lookahead); + + matchLen = Math.Max(matchLen, DeflaterConstants.MIN_MATCH - 1); + + if (scan + matchLen > scanMax) return false; + + byte scan_end1 = window[scan + matchLen - 1]; + byte scan_end = window[scan + matchLen]; + + // Do not waste too much time if we already have a good match: + if (matchLen >= this.goodLength) chainLength >>= 2; + + do + { + match = curMatch; + scan = strstart; + + if (window[match + matchLen] != scan_end + || window[match + matchLen - 1] != scan_end1 + || window[match] != window[scan] + || window[++match] != window[++scan]) + { + continue; + } + + // scan is set to strstart+1 and the comparison passed, so + // scanMax - scan is the maximum number of bytes we can compare. + // below we compare 8 bytes at a time, so first we compare + // (scanMax - scan) % 8 bytes, so the remainder is a multiple of 8 + + switch ((scanMax - scan) % 8) + { + case 1: + if (window[++scan] == window[++match]) break; + break; + + case 2: + if (window[++scan] == window[++match] + && window[++scan] == window[++match]) break; + break; + + case 3: + if (window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match]) break; + break; + + case 4: + if (window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match]) break; + break; + + case 5: + if (window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match]) break; + break; + + case 6: + if (window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match]) break; + break; + + case 7: + if (window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match]) break; + break; + } + + if (window[scan] == window[match]) + { + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart + 258 unless lookahead is + * exhausted first. + */ + do + { + if (scan == scanMax) + { + ++scan; // advance to first position not matched + ++match; + + break; + } + } + while (window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match]); + } + + if (scan - strstart > matchLen) + { +#if DebugDeflation + if (DeflaterConstants.DEBUGGING && (ins_h == 0) ) + Console.Error.WriteLine("Found match: " + curMatch + "-" + (scan - strstart)); +#endif + + matchStart = curMatch; + matchLen = scan - strstart; + + if (matchLen >= niceLength) + break; + + scan_end1 = window[scan - 1]; + scan_end = window[scan]; + } + } while ((curMatch = (prev[curMatch & DeflaterConstants.WMASK] & 0xffff)) > limit && 0 != --chainLength); + + return matchLen >= DeflaterConstants.MIN_MATCH; + } + + private bool DeflateStored(bool flush, bool finish) + { + if (!flush && (lookahead == 0)) + { + return false; + } + + strstart += lookahead; + lookahead = 0; + + int storedLength = strstart - blockStart; + + if ((storedLength >= DeflaterConstants.MAX_BLOCK_SIZE) || // Block is full + (blockStart < DeflaterConstants.WSIZE && storedLength >= DeflaterConstants.MAX_DIST) || // Block may move out of window + flush) + { + bool lastBlock = finish; + if (storedLength > DeflaterConstants.MAX_BLOCK_SIZE) + { + storedLength = DeflaterConstants.MAX_BLOCK_SIZE; + lastBlock = false; + } + +#if DebugDeflation + if (DeflaterConstants.DEBUGGING) + { + Console.WriteLine("storedBlock[" + storedLength + "," + lastBlock + "]"); + } +#endif + + huffman.FlushStoredBlock(window, blockStart, storedLength, lastBlock); + blockStart += storedLength; + return !(lastBlock || storedLength == 0); + } + return true; + } + + private bool DeflateFast(bool flush, bool finish) + { + if (lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush) + { + return false; + } + + while (lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) + { + if (lookahead == 0) + { + // We are flushing everything + huffman.FlushBlock(window, blockStart, strstart - blockStart, finish); + blockStart = strstart; + return false; + } + + if (strstart > 2 * DeflaterConstants.WSIZE - DeflaterConstants.MIN_LOOKAHEAD) + { + /* slide window, as FindLongestMatch needs this. + * This should only happen when flushing and the window + * is almost full. + */ + SlideWindow(); + } + + int hashHead; + if (lookahead >= DeflaterConstants.MIN_MATCH && + (hashHead = InsertString()) != 0 && + strategy != DeflateStrategy.HuffmanOnly && + strstart - hashHead <= DeflaterConstants.MAX_DIST && + FindLongestMatch(hashHead)) + { + // longestMatch sets matchStart and matchLen +#if DebugDeflation + if (DeflaterConstants.DEBUGGING) + { + for (int i = 0 ; i < matchLen; i++) { + if (window[strstart + i] != window[matchStart + i]) { + throw new SharpZipBaseException("Match failure"); + } + } + } +#endif + + bool full = huffman.TallyDist(strstart - matchStart, matchLen); + + lookahead -= matchLen; + if (matchLen <= max_lazy && lookahead >= DeflaterConstants.MIN_MATCH) + { + while (--matchLen > 0) + { + ++strstart; + InsertString(); + } + ++strstart; + } + else + { + strstart += matchLen; + if (lookahead >= DeflaterConstants.MIN_MATCH - 1) + { + UpdateHash(); + } + } + matchLen = DeflaterConstants.MIN_MATCH - 1; + if (!full) + { + continue; + } + } + else + { + // No match found + huffman.TallyLit(window[strstart] & 0xff); + ++strstart; + --lookahead; + } + + if (huffman.IsFull()) + { + bool lastBlock = finish && (lookahead == 0); + huffman.FlushBlock(window, blockStart, strstart - blockStart, lastBlock); + blockStart = strstart; + return !lastBlock; + } + } + return true; + } + + private bool DeflateSlow(bool flush, bool finish) + { + if (lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush) + { + return false; + } + + while (lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) + { + if (lookahead == 0) + { + if (prevAvailable) + { + huffman.TallyLit(window[strstart - 1] & 0xff); + } + prevAvailable = false; + + // We are flushing everything +#if DebugDeflation + if (DeflaterConstants.DEBUGGING && !flush) + { + throw new SharpZipBaseException("Not flushing, but no lookahead"); + } +#endif + huffman.FlushBlock(window, blockStart, strstart - blockStart, + finish); + blockStart = strstart; + return false; + } + + if (strstart >= 2 * DeflaterConstants.WSIZE - DeflaterConstants.MIN_LOOKAHEAD) + { + /* slide window, as FindLongestMatch needs this. + * This should only happen when flushing and the window + * is almost full. + */ + SlideWindow(); + } + + int prevMatch = matchStart; + int prevLen = matchLen; + if (lookahead >= DeflaterConstants.MIN_MATCH) + { + int hashHead = InsertString(); + + if (strategy != DeflateStrategy.HuffmanOnly && + hashHead != 0 && + strstart - hashHead <= DeflaterConstants.MAX_DIST && + FindLongestMatch(hashHead)) + { + // longestMatch sets matchStart and matchLen + + // Discard match if too small and too far away + if (matchLen <= 5 && (strategy == DeflateStrategy.Filtered || (matchLen == DeflaterConstants.MIN_MATCH && strstart - matchStart > TooFar))) + { + matchLen = DeflaterConstants.MIN_MATCH - 1; + } + } + } + + // previous match was better + if ((prevLen >= DeflaterConstants.MIN_MATCH) && (matchLen <= prevLen)) + { +#if DebugDeflation + if (DeflaterConstants.DEBUGGING) + { + for (int i = 0 ; i < matchLen; i++) { + if (window[strstart-1+i] != window[prevMatch + i]) + throw new SharpZipBaseException(); + } + } +#endif + huffman.TallyDist(strstart - 1 - prevMatch, prevLen); + prevLen -= 2; + do + { + strstart++; + lookahead--; + if (lookahead >= DeflaterConstants.MIN_MATCH) + { + InsertString(); + } + } while (--prevLen > 0); + + strstart++; + lookahead--; + prevAvailable = false; + matchLen = DeflaterConstants.MIN_MATCH - 1; + } + else + { + if (prevAvailable) + { + huffman.TallyLit(window[strstart - 1] & 0xff); + } + prevAvailable = true; + strstart++; + lookahead--; + } + + if (huffman.IsFull()) + { + int len = strstart - blockStart; + if (prevAvailable) + { + len--; + } + bool lastBlock = (finish && (lookahead == 0) && !prevAvailable); + huffman.FlushBlock(window, blockStart, len, lastBlock); + blockStart += len; + return !lastBlock; + } + } + return true; + } + + #region Instance Fields + + // Hash index of string to be inserted + private int ins_h; + + /// + /// Hashtable, hashing three characters to an index for window, so + /// that window[index]..window[index+2] have this hash code. + /// Note that the array should really be unsigned short, so you need + /// to and the values with 0xffff. + /// + private short[] head; + + /// + /// prev[index & WMASK] points to the previous index that has the + /// same hash code as the string starting at index. This way + /// entries with the same hash code are in a linked list. + /// Note that the array should really be unsigned short, so you need + /// to and the values with 0xffff. + /// + private short[] prev; + + private int matchStart; + + // Length of best match + private int matchLen; + + // Set if previous match exists + private bool prevAvailable; + + private int blockStart; + + /// + /// Points to the current character in the window. + /// + private int strstart; + + /// + /// lookahead is the number of characters starting at strstart in + /// window that are valid. + /// So window[strstart] until window[strstart+lookahead-1] are valid + /// characters. + /// + private int lookahead; + + /// + /// This array contains the part of the uncompressed stream that + /// is of relevance. The current character is indexed by strstart. + /// + private byte[] window; + + private DeflateStrategy strategy; + private int max_chain, max_lazy, niceLength, goodLength; + + /// + /// The current compression function. + /// + private int compressionFunction; + + /// + /// The input data for compression. + /// + private byte[] inputBuf; + + /// + /// The total bytes of input read. + /// + private long totalIn; + + /// + /// The offset into inputBuf, where input data starts. + /// + private int inputOff; + + /// + /// The end offset of the input data. + /// + private int inputEnd; + + private DeflaterPending pending; + private DeflaterHuffman huffman; + + /// + /// The adler checksum + /// + private Adler32 adler; + + #endregion Instance Fields + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterHuffman.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterHuffman.cs new file mode 100644 index 0000000..2f71366 --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterHuffman.cs @@ -0,0 +1,959 @@ +using System; + +namespace ICSharpCode.SharpZipLib.Zip.Compression +{ + /// + /// This is the DeflaterHuffman class. + /// + /// This class is not thread safe. This is inherent in the API, due + /// to the split of Deflate and SetInput. + /// + /// author of the original java version : Jochen Hoenicke + /// + public class DeflaterHuffman + { + private const int BUFSIZE = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6); + private const int LITERAL_NUM = 286; + + // Number of distance codes + private const int DIST_NUM = 30; + + // Number of codes used to transfer bit lengths + private const int BITLEN_NUM = 19; + + // repeat previous bit length 3-6 times (2 bits of repeat count) + private const int REP_3_6 = 16; + + // repeat a zero length 3-10 times (3 bits of repeat count) + private const int REP_3_10 = 17; + + // repeat a zero length 11-138 times (7 bits of repeat count) + private const int REP_11_138 = 18; + + private const int EOF_SYMBOL = 256; + + // The lengths of the bit length codes are sent in order of decreasing + // probability, to avoid transmitting the lengths for unused bit length codes. + private static readonly int[] BL_ORDER = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + + private static readonly byte[] bit4Reverse = { + 0, + 8, + 4, + 12, + 2, + 10, + 6, + 14, + 1, + 9, + 5, + 13, + 3, + 11, + 7, + 15 + }; + + private static short[] staticLCodes; + private static byte[] staticLLength; + private static short[] staticDCodes; + private static byte[] staticDLength; + + private class Tree + { + #region Instance Fields + + public short[] freqs; + + public byte[] length; + + public int minNumCodes; + + public int numCodes; + + private short[] codes; + private readonly int[] bl_counts; + private readonly int maxLength; + private DeflaterHuffman dh; + + #endregion Instance Fields + + #region Constructors + + public Tree(DeflaterHuffman dh, int elems, int minCodes, int maxLength) + { + this.dh = dh; + this.minNumCodes = minCodes; + this.maxLength = maxLength; + freqs = new short[elems]; + bl_counts = new int[maxLength]; + } + + #endregion Constructors + + /// + /// Resets the internal state of the tree + /// + public void Reset() + { + for (int i = 0; i < freqs.Length; i++) + { + freqs[i] = 0; + } + codes = null; + length = null; + } + + public void WriteSymbol(int code) + { + // if (DeflaterConstants.DEBUGGING) { + // freqs[code]--; + // // Console.Write("writeSymbol("+freqs.length+","+code+"): "); + // } + dh.pending.WriteBits(codes[code] & 0xffff, length[code]); + } + + /// + /// Check that all frequencies are zero + /// + /// + /// At least one frequency is non-zero + /// + public void CheckEmpty() + { + bool empty = true; + for (int i = 0; i < freqs.Length; i++) + { + empty &= freqs[i] == 0; + } + + if (!empty) + { + throw new SharpZipBaseException("!Empty"); + } + } + + /// + /// Set static codes and length + /// + /// new codes + /// length for new codes + public void SetStaticCodes(short[] staticCodes, byte[] staticLengths) + { + codes = staticCodes; + length = staticLengths; + } + + /// + /// Build dynamic codes and lengths + /// + public void BuildCodes() + { + int numSymbols = freqs.Length; + int[] nextCode = new int[maxLength]; + int code = 0; + + codes = new short[freqs.Length]; + + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("buildCodes: "+freqs.Length); + // } + + for (int bits = 0; bits < maxLength; bits++) + { + nextCode[bits] = code; + code += bl_counts[bits] << (15 - bits); + + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("bits: " + ( bits + 1) + " count: " + bl_counts[bits] + // +" nextCode: "+code); + // } + } + +#if DebugDeflation + if ( DeflaterConstants.DEBUGGING && (code != 65536) ) + { + throw new SharpZipBaseException("Inconsistent bl_counts!"); + } +#endif + for (int i = 0; i < numCodes; i++) + { + int bits = length[i]; + if (bits > 0) + { + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("codes["+i+"] = rev(" + nextCode[bits-1]+"), + // +bits); + // } + + codes[i] = BitReverse(nextCode[bits - 1]); + nextCode[bits - 1] += 1 << (16 - bits); + } + } + } + + public void BuildTree() + { + int numSymbols = freqs.Length; + + /* heap is a priority queue, sorted by frequency, least frequent + * nodes first. The heap is a binary tree, with the property, that + * the parent node is smaller than both child nodes. This assures + * that the smallest node is the first parent. + * + * The binary tree is encoded in an array: 0 is root node and + * the nodes 2*n+1, 2*n+2 are the child nodes of node n. + */ + int[] heap = new int[numSymbols]; + int heapLen = 0; + int maxCode = 0; + for (int n = 0; n < numSymbols; n++) + { + int freq = freqs[n]; + if (freq != 0) + { + // Insert n into heap + int pos = heapLen++; + int ppos; + while (pos > 0 && freqs[heap[ppos = (pos - 1) / 2]] > freq) + { + heap[pos] = heap[ppos]; + pos = ppos; + } + heap[pos] = n; + + maxCode = n; + } + } + + /* We could encode a single literal with 0 bits but then we + * don't see the literals. Therefore we force at least two + * literals to avoid this case. We don't care about order in + * this case, both literals get a 1 bit code. + */ + while (heapLen < 2) + { + int node = maxCode < 2 ? ++maxCode : 0; + heap[heapLen++] = node; + } + + numCodes = Math.Max(maxCode + 1, minNumCodes); + + int numLeafs = heapLen; + int[] childs = new int[4 * heapLen - 2]; + int[] values = new int[2 * heapLen - 1]; + int numNodes = numLeafs; + for (int i = 0; i < heapLen; i++) + { + int node = heap[i]; + childs[2 * i] = node; + childs[2 * i + 1] = -1; + values[i] = freqs[node] << 8; + heap[i] = i; + } + + /* Construct the Huffman tree by repeatedly combining the least two + * frequent nodes. + */ + do + { + int first = heap[0]; + int last = heap[--heapLen]; + + // Propagate the hole to the leafs of the heap + int ppos = 0; + int path = 1; + + while (path < heapLen) + { + if (path + 1 < heapLen && values[heap[path]] > values[heap[path + 1]]) + { + path++; + } + + heap[ppos] = heap[path]; + ppos = path; + path = path * 2 + 1; + } + + /* Now propagate the last element down along path. Normally + * it shouldn't go too deep. + */ + int lastVal = values[last]; + while ((path = ppos) > 0 && values[heap[ppos = (path - 1) / 2]] > lastVal) + { + heap[path] = heap[ppos]; + } + heap[path] = last; + + int second = heap[0]; + + // Create a new node father of first and second + last = numNodes++; + childs[2 * last] = first; + childs[2 * last + 1] = second; + int mindepth = Math.Min(values[first] & 0xff, values[second] & 0xff); + values[last] = lastVal = values[first] + values[second] - mindepth + 1; + + // Again, propagate the hole to the leafs + ppos = 0; + path = 1; + + while (path < heapLen) + { + if (path + 1 < heapLen && values[heap[path]] > values[heap[path + 1]]) + { + path++; + } + + heap[ppos] = heap[path]; + ppos = path; + path = ppos * 2 + 1; + } + + // Now propagate the new element down along path + while ((path = ppos) > 0 && values[heap[ppos = (path - 1) / 2]] > lastVal) + { + heap[path] = heap[ppos]; + } + heap[path] = last; + } while (heapLen > 1); + + if (heap[0] != childs.Length / 2 - 1) + { + throw new SharpZipBaseException("Heap invariant violated"); + } + + BuildLength(childs); + } + + /// + /// Get encoded length + /// + /// Encoded length, the sum of frequencies * lengths + public int GetEncodedLength() + { + int len = 0; + for (int i = 0; i < freqs.Length; i++) + { + len += freqs[i] * length[i]; + } + return len; + } + + /// + /// Scan a literal or distance tree to determine the frequencies of the codes + /// in the bit length tree. + /// + public void CalcBLFreq(Tree blTree) + { + int max_count; /* max repeat count */ + int min_count; /* min repeat count */ + int count; /* repeat count of the current code */ + int curlen = -1; /* length of current code */ + + int i = 0; + while (i < numCodes) + { + count = 1; + int nextlen = length[i]; + if (nextlen == 0) + { + max_count = 138; + min_count = 3; + } + else + { + max_count = 6; + min_count = 3; + if (curlen != nextlen) + { + blTree.freqs[nextlen]++; + count = 0; + } + } + curlen = nextlen; + i++; + + while (i < numCodes && curlen == length[i]) + { + i++; + if (++count >= max_count) + { + break; + } + } + + if (count < min_count) + { + blTree.freqs[curlen] += (short)count; + } + else if (curlen != 0) + { + blTree.freqs[REP_3_6]++; + } + else if (count <= 10) + { + blTree.freqs[REP_3_10]++; + } + else + { + blTree.freqs[REP_11_138]++; + } + } + } + + /// + /// Write tree values + /// + /// Tree to write + public void WriteTree(Tree blTree) + { + int max_count; // max repeat count + int min_count; // min repeat count + int count; // repeat count of the current code + int curlen = -1; // length of current code + + int i = 0; + while (i < numCodes) + { + count = 1; + int nextlen = length[i]; + if (nextlen == 0) + { + max_count = 138; + min_count = 3; + } + else + { + max_count = 6; + min_count = 3; + if (curlen != nextlen) + { + blTree.WriteSymbol(nextlen); + count = 0; + } + } + curlen = nextlen; + i++; + + while (i < numCodes && curlen == length[i]) + { + i++; + if (++count >= max_count) + { + break; + } + } + + if (count < min_count) + { + while (count-- > 0) + { + blTree.WriteSymbol(curlen); + } + } + else if (curlen != 0) + { + blTree.WriteSymbol(REP_3_6); + dh.pending.WriteBits(count - 3, 2); + } + else if (count <= 10) + { + blTree.WriteSymbol(REP_3_10); + dh.pending.WriteBits(count - 3, 3); + } + else + { + blTree.WriteSymbol(REP_11_138); + dh.pending.WriteBits(count - 11, 7); + } + } + } + + private void BuildLength(int[] childs) + { + this.length = new byte[freqs.Length]; + int numNodes = childs.Length / 2; + int numLeafs = (numNodes + 1) / 2; + int overflow = 0; + + for (int i = 0; i < maxLength; i++) + { + bl_counts[i] = 0; + } + + // First calculate optimal bit lengths + int[] lengths = new int[numNodes]; + lengths[numNodes - 1] = 0; + + for (int i = numNodes - 1; i >= 0; i--) + { + if (childs[2 * i + 1] != -1) + { + int bitLength = lengths[i] + 1; + if (bitLength > maxLength) + { + bitLength = maxLength; + overflow++; + } + lengths[childs[2 * i]] = lengths[childs[2 * i + 1]] = bitLength; + } + else + { + // A leaf node + int bitLength = lengths[i]; + bl_counts[bitLength - 1]++; + this.length[childs[2 * i]] = (byte)lengths[i]; + } + } + + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("Tree "+freqs.Length+" lengths:"); + // for (int i=0; i < numLeafs; i++) { + // //Console.WriteLine("Node "+childs[2*i]+" freq: "+freqs[childs[2*i]] + // + " len: "+length[childs[2*i]]); + // } + // } + + if (overflow == 0) + { + return; + } + + int incrBitLen = maxLength - 1; + do + { + // Find the first bit length which could increase: + while (bl_counts[--incrBitLen] == 0) + { + } + + // Move this node one down and remove a corresponding + // number of overflow nodes. + do + { + bl_counts[incrBitLen]--; + bl_counts[++incrBitLen]++; + overflow -= 1 << (maxLength - 1 - incrBitLen); + } while (overflow > 0 && incrBitLen < maxLength - 1); + } while (overflow > 0); + + /* We may have overshot above. Move some nodes from maxLength to + * maxLength-1 in that case. + */ + bl_counts[maxLength - 1] += overflow; + bl_counts[maxLength - 2] -= overflow; + + /* Now recompute all bit lengths, scanning in increasing + * frequency. It is simpler to reconstruct all lengths instead of + * fixing only the wrong ones. This idea is taken from 'ar' + * written by Haruhiko Okumura. + * + * The nodes were inserted with decreasing frequency into the childs + * array. + */ + int nodePtr = 2 * numLeafs; + for (int bits = maxLength; bits != 0; bits--) + { + int n = bl_counts[bits - 1]; + while (n > 0) + { + int childPtr = 2 * childs[nodePtr++]; + if (childs[childPtr + 1] == -1) + { + // We found another leaf + length[childs[childPtr]] = (byte)bits; + n--; + } + } + } + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("*** After overflow elimination. ***"); + // for (int i=0; i < numLeafs; i++) { + // //Console.WriteLine("Node "+childs[2*i]+" freq: "+freqs[childs[2*i]] + // + " len: "+length[childs[2*i]]); + // } + // } + } + } + + #region Instance Fields + + /// + /// Pending buffer to use + /// + public DeflaterPending pending; + + private Tree literalTree; + private Tree distTree; + private Tree blTree; + + // Buffer for distances + private short[] d_buf; + + private byte[] l_buf; + private int last_lit; + private int extra_bits; + + #endregion Instance Fields + + static DeflaterHuffman() + { + // See RFC 1951 3.2.6 + // Literal codes + staticLCodes = new short[LITERAL_NUM]; + staticLLength = new byte[LITERAL_NUM]; + + int i = 0; + while (i < 144) + { + staticLCodes[i] = BitReverse((0x030 + i) << 8); + staticLLength[i++] = 8; + } + + while (i < 256) + { + staticLCodes[i] = BitReverse((0x190 - 144 + i) << 7); + staticLLength[i++] = 9; + } + + while (i < 280) + { + staticLCodes[i] = BitReverse((0x000 - 256 + i) << 9); + staticLLength[i++] = 7; + } + + while (i < LITERAL_NUM) + { + staticLCodes[i] = BitReverse((0x0c0 - 280 + i) << 8); + staticLLength[i++] = 8; + } + + // Distance codes + staticDCodes = new short[DIST_NUM]; + staticDLength = new byte[DIST_NUM]; + for (i = 0; i < DIST_NUM; i++) + { + staticDCodes[i] = BitReverse(i << 11); + staticDLength[i] = 5; + } + } + + /// + /// Construct instance with pending buffer + /// + /// Pending buffer to use + public DeflaterHuffman(DeflaterPending pending) + { + this.pending = pending; + + literalTree = new Tree(this, LITERAL_NUM, 257, 15); + distTree = new Tree(this, DIST_NUM, 1, 15); + blTree = new Tree(this, BITLEN_NUM, 4, 7); + + d_buf = new short[BUFSIZE]; + l_buf = new byte[BUFSIZE]; + } + + /// + /// Reset internal state + /// + public void Reset() + { + last_lit = 0; + extra_bits = 0; + literalTree.Reset(); + distTree.Reset(); + blTree.Reset(); + } + + /// + /// Write all trees to pending buffer + /// + /// The number/rank of treecodes to send. + public void SendAllTrees(int blTreeCodes) + { + blTree.BuildCodes(); + literalTree.BuildCodes(); + distTree.BuildCodes(); + pending.WriteBits(literalTree.numCodes - 257, 5); + pending.WriteBits(distTree.numCodes - 1, 5); + pending.WriteBits(blTreeCodes - 4, 4); + for (int rank = 0; rank < blTreeCodes; rank++) + { + pending.WriteBits(blTree.length[BL_ORDER[rank]], 3); + } + literalTree.WriteTree(blTree); + distTree.WriteTree(blTree); + +#if DebugDeflation + if (DeflaterConstants.DEBUGGING) { + blTree.CheckEmpty(); + } +#endif + } + + /// + /// Compress current buffer writing data to pending buffer + /// + public void CompressBlock() + { + for (int i = 0; i < last_lit; i++) + { + int litlen = l_buf[i] & 0xff; + int dist = d_buf[i]; + if (dist-- != 0) + { + // if (DeflaterConstants.DEBUGGING) { + // Console.Write("["+(dist+1)+","+(litlen+3)+"]: "); + // } + + int lc = Lcode(litlen); + literalTree.WriteSymbol(lc); + + int bits = (lc - 261) / 4; + if (bits > 0 && bits <= 5) + { + pending.WriteBits(litlen & ((1 << bits) - 1), bits); + } + + int dc = Dcode(dist); + distTree.WriteSymbol(dc); + + bits = dc / 2 - 1; + if (bits > 0) + { + pending.WriteBits(dist & ((1 << bits) - 1), bits); + } + } + else + { + // if (DeflaterConstants.DEBUGGING) { + // if (litlen > 32 && litlen < 127) { + // Console.Write("("+(char)litlen+"): "); + // } else { + // Console.Write("{"+litlen+"}: "); + // } + // } + literalTree.WriteSymbol(litlen); + } + } + +#if DebugDeflation + if (DeflaterConstants.DEBUGGING) { + Console.Write("EOF: "); + } +#endif + literalTree.WriteSymbol(EOF_SYMBOL); + +#if DebugDeflation + if (DeflaterConstants.DEBUGGING) { + literalTree.CheckEmpty(); + distTree.CheckEmpty(); + } +#endif + } + + /// + /// Flush block to output with no compression + /// + /// Data to write + /// Index of first byte to write + /// Count of bytes to write + /// True if this is the last block + public void FlushStoredBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock) + { +#if DebugDeflation + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("Flushing stored block "+ storedLength); + // } +#endif + pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3); + pending.AlignToByte(); + pending.WriteShort(storedLength); + pending.WriteShort(~storedLength); + pending.WriteBlock(stored, storedOffset, storedLength); + Reset(); + } + + /// + /// Flush block to output with compression + /// + /// Data to flush + /// Index of first byte to flush + /// Count of bytes to flush + /// True if this is the last block + public void FlushBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock) + { + literalTree.freqs[EOF_SYMBOL]++; + + // Build trees + literalTree.BuildTree(); + distTree.BuildTree(); + + // Calculate bitlen frequency + literalTree.CalcBLFreq(blTree); + distTree.CalcBLFreq(blTree); + + // Build bitlen tree + blTree.BuildTree(); + + int blTreeCodes = 4; + for (int i = 18; i > blTreeCodes; i--) + { + if (blTree.length[BL_ORDER[i]] > 0) + { + blTreeCodes = i + 1; + } + } + int opt_len = 14 + blTreeCodes * 3 + blTree.GetEncodedLength() + + literalTree.GetEncodedLength() + distTree.GetEncodedLength() + + extra_bits; + + int static_len = extra_bits; + for (int i = 0; i < LITERAL_NUM; i++) + { + static_len += literalTree.freqs[i] * staticLLength[i]; + } + for (int i = 0; i < DIST_NUM; i++) + { + static_len += distTree.freqs[i] * staticDLength[i]; + } + if (opt_len >= static_len) + { + // Force static trees + opt_len = static_len; + } + + if (storedOffset >= 0 && storedLength + 4 < opt_len >> 3) + { + // Store Block + + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("Storing, since " + storedLength + " < " + opt_len + // + " <= " + static_len); + // } + FlushStoredBlock(stored, storedOffset, storedLength, lastBlock); + } + else if (opt_len == static_len) + { + // Encode with static tree + pending.WriteBits((DeflaterConstants.STATIC_TREES << 1) + (lastBlock ? 1 : 0), 3); + literalTree.SetStaticCodes(staticLCodes, staticLLength); + distTree.SetStaticCodes(staticDCodes, staticDLength); + CompressBlock(); + Reset(); + } + else + { + // Encode with dynamic tree + pending.WriteBits((DeflaterConstants.DYN_TREES << 1) + (lastBlock ? 1 : 0), 3); + SendAllTrees(blTreeCodes); + CompressBlock(); + Reset(); + } + } + + /// + /// Get value indicating if internal buffer is full + /// + /// true if buffer is full + public bool IsFull() + { + return last_lit >= BUFSIZE; + } + + /// + /// Add literal to buffer + /// + /// Literal value to add to buffer. + /// Value indicating internal buffer is full + public bool TallyLit(int literal) + { + // if (DeflaterConstants.DEBUGGING) { + // if (lit > 32 && lit < 127) { + // //Console.WriteLine("("+(char)lit+")"); + // } else { + // //Console.WriteLine("{"+lit+"}"); + // } + // } + d_buf[last_lit] = 0; + l_buf[last_lit++] = (byte)literal; + literalTree.freqs[literal]++; + return IsFull(); + } + + /// + /// Add distance code and length to literal and distance trees + /// + /// Distance code + /// Length + /// Value indicating if internal buffer is full + public bool TallyDist(int distance, int length) + { + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("[" + distance + "," + length + "]"); + // } + + d_buf[last_lit] = (short)distance; + l_buf[last_lit++] = (byte)(length - 3); + + int lc = Lcode(length - 3); + literalTree.freqs[lc]++; + if (lc >= 265 && lc < 285) + { + extra_bits += (lc - 261) / 4; + } + + int dc = Dcode(distance - 1); + distTree.freqs[dc]++; + if (dc >= 4) + { + extra_bits += dc / 2 - 1; + } + return IsFull(); + } + + /// + /// Reverse the bits of a 16 bit value. + /// + /// Value to reverse bits + /// Value with bits reversed + public static short BitReverse(int toReverse) + { + return (short)(bit4Reverse[toReverse & 0xF] << 12 | + bit4Reverse[(toReverse >> 4) & 0xF] << 8 | + bit4Reverse[(toReverse >> 8) & 0xF] << 4 | + bit4Reverse[toReverse >> 12]); + } + + private static int Lcode(int length) + { + if (length == 255) + { + return 285; + } + + int code = 257; + while (length >= 8) + { + code += 4; + length >>= 1; + } + return code + length; + } + + private static int Dcode(int distance) + { + int code = 0; + while (distance >= 4) + { + code += 2; + distance >>= 1; + } + return code + distance; + } + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterPending.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterPending.cs new file mode 100644 index 0000000..80d3e21 --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/DeflaterPending.cs @@ -0,0 +1,17 @@ +namespace ICSharpCode.SharpZipLib.Zip.Compression +{ + /// + /// This class stores the pending output of the Deflater. + /// + /// author of the original java version : Jochen Hoenicke + /// + public class DeflaterPending : PendingBuffer + { + /// + /// Construct instance with default buffer size + /// + public DeflaterPending() : base(DeflaterConstants.PENDING_BUF_SIZE) + { + } + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/Inflater.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/Inflater.cs new file mode 100644 index 0000000..439b4c6 --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/Inflater.cs @@ -0,0 +1,887 @@ +using ICSharpCode.SharpZipLib.Checksum; +using ICSharpCode.SharpZipLib.Zip.Compression.Streams; +using System; + +namespace ICSharpCode.SharpZipLib.Zip.Compression +{ + /// + /// Inflater is used to decompress data that has been compressed according + /// to the "deflate" standard described in rfc1951. + /// + /// By default Zlib (rfc1950) headers and footers are expected in the input. + /// You can use constructor public Inflater(bool noHeader) passing true + /// if there is no Zlib header information + /// + /// The usage is as following. First you have to set some input with + /// SetInput(), then Inflate() it. If inflate doesn't + /// inflate any bytes there may be three reasons: + ///
    + ///
  • IsNeedingInput() returns true because the input buffer is empty. + /// You have to provide more input with SetInput(). + /// NOTE: IsNeedingInput() also returns true when, the stream is finished. + ///
  • + ///
  • IsNeedingDictionary() returns true, you have to provide a preset + /// dictionary with SetDictionary().
  • + ///
  • IsFinished returns true, the inflater has finished.
  • + ///
+ /// Once the first output byte is produced, a dictionary will not be + /// needed at a later stage. + /// + /// author of the original java version : John Leuner, Jochen Hoenicke + ///
+ public class Inflater + { + #region Constants/Readonly + + /// + /// Copy lengths for literal codes 257..285 + /// + private static readonly int[] CPLENS = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258 + }; + + /// + /// Extra bits for literal codes 257..285 + /// + private static readonly int[] CPLEXT = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 + }; + + /// + /// Copy offsets for distance codes 0..29 + /// + private static readonly int[] CPDIST = { + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577 + }; + + /// + /// Extra bits for distance codes + /// + private static readonly int[] CPDEXT = { + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13 + }; + + /// + /// These are the possible states for an inflater + /// + private const int DECODE_HEADER = 0; + + private const int DECODE_DICT = 1; + private const int DECODE_BLOCKS = 2; + private const int DECODE_STORED_LEN1 = 3; + private const int DECODE_STORED_LEN2 = 4; + private const int DECODE_STORED = 5; + private const int DECODE_DYN_HEADER = 6; + private const int DECODE_HUFFMAN = 7; + private const int DECODE_HUFFMAN_LENBITS = 8; + private const int DECODE_HUFFMAN_DIST = 9; + private const int DECODE_HUFFMAN_DISTBITS = 10; + private const int DECODE_CHKSUM = 11; + private const int FINISHED = 12; + + #endregion Constants/Readonly + + #region Instance Fields + + /// + /// This variable contains the current state. + /// + private int mode; + + /// + /// The adler checksum of the dictionary or of the decompressed + /// stream, as it is written in the header resp. footer of the + /// compressed stream. + /// Only valid if mode is DECODE_DICT or DECODE_CHKSUM. + /// + private int readAdler; + + /// + /// The number of bits needed to complete the current state. This + /// is valid, if mode is DECODE_DICT, DECODE_CHKSUM, + /// DECODE_HUFFMAN_LENBITS or DECODE_HUFFMAN_DISTBITS. + /// + private int neededBits; + + private int repLength; + private int repDist; + private int uncomprLen; + + /// + /// True, if the last block flag was set in the last block of the + /// inflated stream. This means that the stream ends after the + /// current block. + /// + private bool isLastBlock; + + /// + /// The total number of inflated bytes. + /// + private long totalOut; + + /// + /// The total number of bytes set with setInput(). This is not the + /// value returned by the TotalIn property, since this also includes the + /// unprocessed input. + /// + private long totalIn; + + /// + /// This variable stores the noHeader flag that was given to the constructor. + /// True means, that the inflated stream doesn't contain a Zlib header or + /// footer. + /// + private bool noHeader; + + private readonly StreamManipulator input; + private OutputWindow outputWindow; + private InflaterDynHeader dynHeader; + private InflaterHuffmanTree litlenTree, distTree; + private Adler32 adler; + + #endregion Instance Fields + + #region Constructors + + /// + /// Creates a new inflater or RFC1951 decompressor + /// RFC1950/Zlib headers and footers will be expected in the input data + /// + public Inflater() : this(false) + { + } + + /// + /// Creates a new inflater. + /// + /// + /// True if no RFC1950/Zlib header and footer fields are expected in the input data + /// + /// This is used for GZIPed/Zipped input. + /// + /// For compatibility with + /// Sun JDK you should provide one byte of input more than needed in + /// this case. + /// + public Inflater(bool noHeader) + { + this.noHeader = noHeader; + if (!noHeader) + this.adler = new Adler32(); + input = new StreamManipulator(); + outputWindow = new OutputWindow(); + mode = noHeader ? DECODE_BLOCKS : DECODE_HEADER; + } + + #endregion Constructors + + /// + /// Resets the inflater so that a new stream can be decompressed. All + /// pending input and output will be discarded. + /// + public void Reset() + { + mode = noHeader ? DECODE_BLOCKS : DECODE_HEADER; + totalIn = 0; + totalOut = 0; + input.Reset(); + outputWindow.Reset(); + dynHeader = null; + litlenTree = null; + distTree = null; + isLastBlock = false; + adler?.Reset(); + } + + /// + /// Decodes a zlib/RFC1950 header. + /// + /// + /// False if more input is needed. + /// + /// + /// The header is invalid. + /// + private bool DecodeHeader() + { + int header = input.PeekBits(16); + if (header < 0) + { + return false; + } + input.DropBits(16); + + // The header is written in "wrong" byte order + header = ((header << 8) | (header >> 8)) & 0xffff; + if (header % 31 != 0) + { + throw new SharpZipBaseException("Header checksum illegal"); + } + + if ((header & 0x0f00) != (Deflater.DEFLATED << 8)) + { + throw new SharpZipBaseException("Compression Method unknown"); + } + + /* Maximum size of the backwards window in bits. + * We currently ignore this, but we could use it to make the + * inflater window more space efficient. On the other hand the + * full window (15 bits) is needed most times, anyway. + int max_wbits = ((header & 0x7000) >> 12) + 8; + */ + + if ((header & 0x0020) == 0) + { // Dictionary flag? + mode = DECODE_BLOCKS; + } + else + { + mode = DECODE_DICT; + neededBits = 32; + } + return true; + } + + /// + /// Decodes the dictionary checksum after the deflate header. + /// + /// + /// False if more input is needed. + /// + private bool DecodeDict() + { + while (neededBits > 0) + { + int dictByte = input.PeekBits(8); + if (dictByte < 0) + { + return false; + } + input.DropBits(8); + readAdler = (readAdler << 8) | dictByte; + neededBits -= 8; + } + return false; + } + + /// + /// Decodes the huffman encoded symbols in the input stream. + /// + /// + /// false if more input is needed, true if output window is + /// full or the current block ends. + /// + /// + /// if deflated stream is invalid. + /// + private bool DecodeHuffman() + { + int free = outputWindow.GetFreeSpace(); + while (free >= 258) + { + int symbol; + switch (mode) + { + case DECODE_HUFFMAN: + // This is the inner loop so it is optimized a bit + while (((symbol = litlenTree.GetSymbol(input)) & ~0xff) == 0) + { + outputWindow.Write(symbol); + if (--free < 258) + { + return true; + } + } + + if (symbol < 257) + { + if (symbol < 0) + { + return false; + } + else + { + // symbol == 256: end of block + distTree = null; + litlenTree = null; + mode = DECODE_BLOCKS; + return true; + } + } + + try + { + repLength = CPLENS[symbol - 257]; + neededBits = CPLEXT[symbol - 257]; + } + catch (Exception) + { + throw new SharpZipBaseException("Illegal rep length code"); + } + goto case DECODE_HUFFMAN_LENBITS; // fall through + + case DECODE_HUFFMAN_LENBITS: + if (neededBits > 0) + { + mode = DECODE_HUFFMAN_LENBITS; + int i = input.PeekBits(neededBits); + if (i < 0) + { + return false; + } + input.DropBits(neededBits); + repLength += i; + } + mode = DECODE_HUFFMAN_DIST; + goto case DECODE_HUFFMAN_DIST; // fall through + + case DECODE_HUFFMAN_DIST: + symbol = distTree.GetSymbol(input); + if (symbol < 0) + { + return false; + } + + try + { + repDist = CPDIST[symbol]; + neededBits = CPDEXT[symbol]; + } + catch (Exception) + { + throw new SharpZipBaseException("Illegal rep dist code"); + } + + goto case DECODE_HUFFMAN_DISTBITS; // fall through + + case DECODE_HUFFMAN_DISTBITS: + if (neededBits > 0) + { + mode = DECODE_HUFFMAN_DISTBITS; + int i = input.PeekBits(neededBits); + if (i < 0) + { + return false; + } + input.DropBits(neededBits); + repDist += i; + } + + outputWindow.Repeat(repLength, repDist); + free -= repLength; + mode = DECODE_HUFFMAN; + break; + + default: + throw new SharpZipBaseException("Inflater unknown mode"); + } + } + return true; + } + + /// + /// Decodes the adler checksum after the deflate stream. + /// + /// + /// false if more input is needed. + /// + /// + /// If checksum doesn't match. + /// + private bool DecodeChksum() + { + while (neededBits > 0) + { + int chkByte = input.PeekBits(8); + if (chkByte < 0) + { + return false; + } + input.DropBits(8); + readAdler = (readAdler << 8) | chkByte; + neededBits -= 8; + } + + if ((int)adler?.Value != readAdler) + { + throw new SharpZipBaseException("Adler chksum doesn't match: " + (int)adler?.Value + " vs. " + readAdler); + } + + mode = FINISHED; + return false; + } + + /// + /// Decodes the deflated stream. + /// + /// + /// false if more input is needed, or if finished. + /// + /// + /// if deflated stream is invalid. + /// + private bool Decode() + { + switch (mode) + { + case DECODE_HEADER: + return DecodeHeader(); + + case DECODE_DICT: + return DecodeDict(); + + case DECODE_CHKSUM: + return DecodeChksum(); + + case DECODE_BLOCKS: + if (isLastBlock) + { + if (noHeader) + { + mode = FINISHED; + return false; + } + else + { + input.SkipToByteBoundary(); + neededBits = 32; + mode = DECODE_CHKSUM; + return true; + } + } + + int type = input.PeekBits(3); + if (type < 0) + { + return false; + } + input.DropBits(3); + + isLastBlock |= (type & 1) != 0; + switch (type >> 1) + { + case DeflaterConstants.STORED_BLOCK: + input.SkipToByteBoundary(); + mode = DECODE_STORED_LEN1; + break; + + case DeflaterConstants.STATIC_TREES: + litlenTree = InflaterHuffmanTree.defLitLenTree; + distTree = InflaterHuffmanTree.defDistTree; + mode = DECODE_HUFFMAN; + break; + + case DeflaterConstants.DYN_TREES: + dynHeader = new InflaterDynHeader(input); + mode = DECODE_DYN_HEADER; + break; + + default: + throw new SharpZipBaseException("Unknown block type " + type); + } + return true; + + case DECODE_STORED_LEN1: + { + if ((uncomprLen = input.PeekBits(16)) < 0) + { + return false; + } + input.DropBits(16); + mode = DECODE_STORED_LEN2; + } + goto case DECODE_STORED_LEN2; // fall through + + case DECODE_STORED_LEN2: + { + int nlen = input.PeekBits(16); + if (nlen < 0) + { + return false; + } + input.DropBits(16); + if (nlen != (uncomprLen ^ 0xffff)) + { + throw new SharpZipBaseException("broken uncompressed block"); + } + mode = DECODE_STORED; + } + goto case DECODE_STORED; // fall through + + case DECODE_STORED: + { + int more = outputWindow.CopyStored(input, uncomprLen); + uncomprLen -= more; + if (uncomprLen == 0) + { + mode = DECODE_BLOCKS; + return true; + } + return !input.IsNeedingInput; + } + + case DECODE_DYN_HEADER: + if (!dynHeader.AttemptRead()) + { + return false; + } + + litlenTree = dynHeader.LiteralLengthTree; + distTree = dynHeader.DistanceTree; + mode = DECODE_HUFFMAN; + goto case DECODE_HUFFMAN; // fall through + + case DECODE_HUFFMAN: + case DECODE_HUFFMAN_LENBITS: + case DECODE_HUFFMAN_DIST: + case DECODE_HUFFMAN_DISTBITS: + return DecodeHuffman(); + + case FINISHED: + return false; + + default: + throw new SharpZipBaseException("Inflater.Decode unknown mode"); + } + } + + /// + /// Sets the preset dictionary. This should only be called, if + /// needsDictionary() returns true and it should set the same + /// dictionary, that was used for deflating. The getAdler() + /// function returns the checksum of the dictionary needed. + /// + /// + /// The dictionary. + /// + public void SetDictionary(byte[] buffer) + { + SetDictionary(buffer, 0, buffer.Length); + } + + /// + /// Sets the preset dictionary. This should only be called, if + /// needsDictionary() returns true and it should set the same + /// dictionary, that was used for deflating. The getAdler() + /// function returns the checksum of the dictionary needed. + /// + /// + /// The dictionary. + /// + /// + /// The index into buffer where the dictionary starts. + /// + /// + /// The number of bytes in the dictionary. + /// + /// + /// No dictionary is needed. + /// + /// + /// The adler checksum for the buffer is invalid + /// + public void SetDictionary(byte[] buffer, int index, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (!IsNeedingDictionary) + { + throw new InvalidOperationException("Dictionary is not needed"); + } + + adler?.Update(new ArraySegment(buffer, index, count)); + + if (adler != null && (int)adler.Value != readAdler) + { + throw new SharpZipBaseException("Wrong adler checksum"); + } + adler?.Reset(); + outputWindow.CopyDict(buffer, index, count); + mode = DECODE_BLOCKS; + } + + /// + /// Sets the input. This should only be called, if needsInput() + /// returns true. + /// + /// + /// the input. + /// + public void SetInput(byte[] buffer) + { + SetInput(buffer, 0, buffer.Length); + } + + /// + /// Sets the input. This should only be called, if needsInput() + /// returns true. + /// + /// + /// The source of input data + /// + /// + /// The index into buffer where the input starts. + /// + /// + /// The number of bytes of input to use. + /// + /// + /// No input is needed. + /// + /// + /// The index and/or count are wrong. + /// + public void SetInput(byte[] buffer, int index, int count) + { + input.SetInput(buffer, index, count); + totalIn += (long)count; + } + + /// + /// Inflates the compressed stream to the output buffer. If this + /// returns 0, you should check, whether IsNeedingDictionary(), + /// IsNeedingInput() or IsFinished() returns true, to determine why no + /// further output is produced. + /// + /// + /// the output buffer. + /// + /// + /// The number of bytes written to the buffer, 0 if no further + /// output can be produced. + /// + /// + /// if buffer has length 0. + /// + /// + /// if deflated stream is invalid. + /// + public int Inflate(byte[] buffer) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + return Inflate(buffer, 0, buffer.Length); + } + + /// + /// Inflates the compressed stream to the output buffer. If this + /// returns 0, you should check, whether needsDictionary(), + /// needsInput() or finished() returns true, to determine why no + /// further output is produced. + /// + /// + /// the output buffer. + /// + /// + /// the offset in buffer where storing starts. + /// + /// + /// the maximum number of bytes to output. + /// + /// + /// the number of bytes written to the buffer, 0 if no further output can be produced. + /// + /// + /// if count is less than 0. + /// + /// + /// if the index and / or count are wrong. + /// + /// + /// if deflated stream is invalid. + /// + public int Inflate(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "count cannot be negative"); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), "offset cannot be negative"); + } + + if (offset + count > buffer.Length) + { + throw new ArgumentException("count exceeds buffer bounds"); + } + + // Special case: count may be zero + if (count == 0) + { + if (!IsFinished) + { // -jr- 08-Nov-2003 INFLATE_BUG fix.. + Decode(); + } + return 0; + } + + int bytesCopied = 0; + + do + { + if (mode != DECODE_CHKSUM) + { + /* Don't give away any output, if we are waiting for the + * checksum in the input stream. + * + * With this trick we have always: + * IsNeedingInput() and not IsFinished() + * implies more output can be produced. + */ + int more = outputWindow.CopyOutput(buffer, offset, count); + if (more > 0) + { + adler?.Update(new ArraySegment(buffer, offset, more)); + offset += more; + bytesCopied += more; + totalOut += (long)more; + count -= more; + if (count == 0) + { + return bytesCopied; + } + } + } + } while (Decode() || ((outputWindow.GetAvailable() > 0) && (mode != DECODE_CHKSUM))); + return bytesCopied; + } + + /// + /// Returns true, if the input buffer is empty. + /// You should then call setInput(). + /// NOTE: This method also returns true when the stream is finished. + /// + public bool IsNeedingInput + { + get + { + return input.IsNeedingInput; + } + } + + /// + /// Returns true, if a preset dictionary is needed to inflate the input. + /// + public bool IsNeedingDictionary + { + get + { + return mode == DECODE_DICT && neededBits == 0; + } + } + + /// + /// Returns true, if the inflater has finished. This means, that no + /// input is needed and no output can be produced. + /// + public bool IsFinished + { + get + { + return mode == FINISHED && outputWindow.GetAvailable() == 0; + } + } + + /// + /// Gets the adler checksum. This is either the checksum of all + /// uncompressed bytes returned by inflate(), or if needsDictionary() + /// returns true (and thus no output was yet produced) this is the + /// adler checksum of the expected dictionary. + /// + /// + /// the adler checksum. + /// + public int Adler + { + get + { + if (IsNeedingDictionary) + { + return readAdler; + } + else if (adler != null) + { + return (int)adler.Value; + } + else + { + return 0; + } + } + } + + /// + /// Gets the total number of output bytes returned by Inflate(). + /// + /// + /// the total number of output bytes. + /// + public long TotalOut + { + get + { + return totalOut; + } + } + + /// + /// Gets the total number of processed compressed input bytes. + /// + /// + /// The total number of bytes of processed input bytes. + /// + public long TotalIn + { + get + { + return totalIn - (long)RemainingInput; + } + } + + /// + /// Gets the number of unprocessed input bytes. Useful, if the end of the + /// stream is reached and you want to further process the bytes after + /// the deflate stream. + /// + /// + /// The number of bytes of the input which have not been processed. + /// + public int RemainingInput + { + // TODO: This should be a long? + get + { + return input.AvailableBytes; + } + } + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/InflaterDynHeader.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/InflaterDynHeader.cs new file mode 100644 index 0000000..8e0196b --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/InflaterDynHeader.cs @@ -0,0 +1,151 @@ +using ICSharpCode.SharpZipLib.Zip.Compression.Streams; +using System; +using System.Collections.Generic; + +namespace ICSharpCode.SharpZipLib.Zip.Compression +{ + internal class InflaterDynHeader + { + #region Constants + + // maximum number of literal/length codes + private const int LITLEN_MAX = 286; + + // maximum number of distance codes + private const int DIST_MAX = 30; + + // maximum data code lengths to read + private const int CODELEN_MAX = LITLEN_MAX + DIST_MAX; + + // maximum meta code length codes to read + private const int META_MAX = 19; + + private static readonly int[] MetaCodeLengthIndex = + { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + + #endregion Constants + + /// + /// Continue decoding header from until more bits are needed or decoding has been completed + /// + /// Returns whether decoding could be completed + public bool AttemptRead() + => !state.MoveNext() || state.Current; + + public InflaterDynHeader(StreamManipulator input) + { + this.input = input; + stateMachine = CreateStateMachine(); + state = stateMachine.GetEnumerator(); + } + + private IEnumerable CreateStateMachine() + { + // Read initial code length counts from header + while (!input.TryGetBits(5, ref litLenCodeCount, 257)) yield return false; + while (!input.TryGetBits(5, ref distanceCodeCount, 1)) yield return false; + while (!input.TryGetBits(4, ref metaCodeCount, 4)) yield return false; + var dataCodeCount = litLenCodeCount + distanceCodeCount; + + if (litLenCodeCount > LITLEN_MAX) throw new ValueOutOfRangeException(nameof(litLenCodeCount)); + if (distanceCodeCount > DIST_MAX) throw new ValueOutOfRangeException(nameof(distanceCodeCount)); + if (metaCodeCount > META_MAX) throw new ValueOutOfRangeException(nameof(metaCodeCount)); + + // Load code lengths for the meta tree from the header bits + for (int i = 0; i < metaCodeCount; i++) + { + while (!input.TryGetBits(3, ref codeLengths, MetaCodeLengthIndex[i])) yield return false; + } + + var metaCodeTree = new InflaterHuffmanTree(codeLengths); + + // Decompress the meta tree symbols into the data table code lengths + int index = 0; + while (index < dataCodeCount) + { + byte codeLength; + int symbol; + + while ((symbol = metaCodeTree.GetSymbol(input)) < 0) yield return false; + + if (symbol < 16) + { + // append literal code length + codeLengths[index++] = (byte)symbol; + } + else + { + int repeatCount = 0; + + if (symbol == 16) // Repeat last code length 3..6 times + { + if (index == 0) + throw new StreamDecodingException("Cannot repeat previous code length when no other code length has been read"); + + codeLength = codeLengths[index - 1]; + + // 2 bits + 3, [3..6] + while (!input.TryGetBits(2, ref repeatCount, 3)) yield return false; + } + else if (symbol == 17) // Repeat zero 3..10 times + { + codeLength = 0; + + // 3 bits + 3, [3..10] + while (!input.TryGetBits(3, ref repeatCount, 3)) yield return false; + } + else // (symbol == 18), Repeat zero 11..138 times + { + codeLength = 0; + + // 7 bits + 11, [11..138] + while (!input.TryGetBits(7, ref repeatCount, 11)) yield return false; + } + + if (index + repeatCount > dataCodeCount) + throw new StreamDecodingException("Cannot repeat code lengths past total number of data code lengths"); + + while (repeatCount-- > 0) + codeLengths[index++] = codeLength; + } + } + + if (codeLengths[256] == 0) + throw new StreamDecodingException("Inflater dynamic header end-of-block code missing"); + + litLenTree = new InflaterHuffmanTree(new ArraySegment(codeLengths, 0, litLenCodeCount)); + distTree = new InflaterHuffmanTree(new ArraySegment(codeLengths, litLenCodeCount, distanceCodeCount)); + + yield return true; + } + + /// + /// Get literal/length huffman tree, must not be used before has returned true + /// + /// If hader has not been successfully read by the state machine + public InflaterHuffmanTree LiteralLengthTree + => litLenTree ?? throw new StreamDecodingException("Header properties were accessed before header had been successfully read"); + + /// + /// Get distance huffman tree, must not be used before has returned true + /// + /// If hader has not been successfully read by the state machine + public InflaterHuffmanTree DistanceTree + => distTree ?? throw new StreamDecodingException("Header properties were accessed before header had been successfully read"); + + #region Instance Fields + + private readonly StreamManipulator input; + private readonly IEnumerator state; + private readonly IEnumerable stateMachine; + + private byte[] codeLengths = new byte[CODELEN_MAX]; + + private InflaterHuffmanTree litLenTree; + private InflaterHuffmanTree distTree; + + private int litLenCodeCount, distanceCodeCount, metaCodeCount; + + #endregion Instance Fields + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs new file mode 100644 index 0000000..ed31882 --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs @@ -0,0 +1,237 @@ +using ICSharpCode.SharpZipLib.Zip.Compression.Streams; +using System; +using System.Collections.Generic; + +namespace ICSharpCode.SharpZipLib.Zip.Compression +{ + /// + /// Huffman tree used for inflation + /// + public class InflaterHuffmanTree + { + #region Constants + + private const int MAX_BITLEN = 15; + + #endregion Constants + + #region Instance Fields + + private short[] tree; + + #endregion Instance Fields + + /// + /// Literal length tree + /// + public static InflaterHuffmanTree defLitLenTree; + + /// + /// Distance tree + /// + public static InflaterHuffmanTree defDistTree; + + static InflaterHuffmanTree() + { + try + { + byte[] codeLengths = new byte[288]; + int i = 0; + while (i < 144) + { + codeLengths[i++] = 8; + } + while (i < 256) + { + codeLengths[i++] = 9; + } + while (i < 280) + { + codeLengths[i++] = 7; + } + while (i < 288) + { + codeLengths[i++] = 8; + } + defLitLenTree = new InflaterHuffmanTree(codeLengths); + + codeLengths = new byte[32]; + i = 0; + while (i < 32) + { + codeLengths[i++] = 5; + } + defDistTree = new InflaterHuffmanTree(codeLengths); + } + catch (Exception) + { + throw new SharpZipBaseException("InflaterHuffmanTree: static tree length illegal"); + } + } + + #region Constructors + + /// + /// Constructs a Huffman tree from the array of code lengths. + /// + /// + /// the array of code lengths + /// + public InflaterHuffmanTree(IList codeLengths) + { + BuildTree(codeLengths); + } + + #endregion Constructors + + private void BuildTree(IList codeLengths) + { + int[] blCount = new int[MAX_BITLEN + 1]; + int[] nextCode = new int[MAX_BITLEN + 1]; + + for (int i = 0; i < codeLengths.Count; i++) + { + int bits = codeLengths[i]; + if (bits > 0) + { + blCount[bits]++; + } + } + + int code = 0; + int treeSize = 512; + for (int bits = 1; bits <= MAX_BITLEN; bits++) + { + nextCode[bits] = code; + code += blCount[bits] << (16 - bits); + if (bits >= 10) + { + /* We need an extra table for bit lengths >= 10. */ + int start = nextCode[bits] & 0x1ff80; + int end = code & 0x1ff80; + treeSize += (end - start) >> (16 - bits); + } + } + + /* -jr comment this out! doesnt work for dynamic trees and pkzip 2.04g + if (code != 65536) + { + throw new SharpZipBaseException("Code lengths don't add up properly."); + } + */ + /* Now create and fill the extra tables from longest to shortest + * bit len. This way the sub trees will be aligned. + */ + tree = new short[treeSize]; + int treePtr = 512; + for (int bits = MAX_BITLEN; bits >= 10; bits--) + { + int end = code & 0x1ff80; + code -= blCount[bits] << (16 - bits); + int start = code & 0x1ff80; + for (int i = start; i < end; i += 1 << 7) + { + tree[DeflaterHuffman.BitReverse(i)] = (short)((-treePtr << 4) | bits); + treePtr += 1 << (bits - 9); + } + } + + for (int i = 0; i < codeLengths.Count; i++) + { + int bits = codeLengths[i]; + if (bits == 0) + { + continue; + } + code = nextCode[bits]; + int revcode = DeflaterHuffman.BitReverse(code); + if (bits <= 9) + { + do + { + tree[revcode] = (short)((i << 4) | bits); + revcode += 1 << bits; + } while (revcode < 512); + } + else + { + int subTree = tree[revcode & 511]; + int treeLen = 1 << (subTree & 15); + subTree = -(subTree >> 4); + do + { + tree[subTree | (revcode >> 9)] = (short)((i << 4) | bits); + revcode += 1 << bits; + } while (revcode < treeLen); + } + nextCode[bits] = code + (1 << (16 - bits)); + } + } + + /// + /// Reads the next symbol from input. The symbol is encoded using the + /// huffman tree. + /// + /// + /// input the input source. + /// + /// + /// the next symbol, or -1 if not enough input is available. + /// + public int GetSymbol(StreamManipulator input) + { + int lookahead, symbol; + if ((lookahead = input.PeekBits(9)) >= 0) + { + symbol = tree[lookahead]; + int bitlen = symbol & 15; + + if (symbol >= 0) + { + if(bitlen == 0){ + throw new SharpZipBaseException("Encountered invalid codelength 0"); + } + input.DropBits(bitlen); + return symbol >> 4; + } + int subtree = -(symbol >> 4); + if ((lookahead = input.PeekBits(bitlen)) >= 0) + { + symbol = tree[subtree | (lookahead >> 9)]; + input.DropBits(symbol & 15); + return symbol >> 4; + } + else + { + int bits = input.AvailableBits; + lookahead = input.PeekBits(bits); + symbol = tree[subtree | (lookahead >> 9)]; + if ((symbol & 15) <= bits) + { + input.DropBits(symbol & 15); + return symbol >> 4; + } + else + { + return -1; + } + } + } + else // Less than 9 bits + { + int bits = input.AvailableBits; + lookahead = input.PeekBits(bits); + symbol = tree[lookahead]; + if (symbol >= 0 && (symbol & 15) <= bits) + { + input.DropBits(symbol & 15); + return symbol >> 4; + } + else + { + return -1; + } + } + } + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/PendingBuffer.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/PendingBuffer.cs new file mode 100644 index 0000000..6ed7e4a --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/PendingBuffer.cs @@ -0,0 +1,268 @@ +namespace ICSharpCode.SharpZipLib.Zip.Compression +{ + /// + /// This class is general purpose class for writing data to a buffer. + /// + /// It allows you to write bits as well as bytes + /// Based on DeflaterPending.java + /// + /// author of the original java version : Jochen Hoenicke + /// + public class PendingBuffer + { + #region Instance Fields + + /// + /// Internal work buffer + /// + private readonly byte[] buffer; + + private int start; + private int end; + + private uint bits; + private int bitCount; + + #endregion Instance Fields + + #region Constructors + + /// + /// construct instance using default buffer size of 4096 + /// + public PendingBuffer() : this(4096) + { + } + + /// + /// construct instance using specified buffer size + /// + /// + /// size to use for internal buffer + /// + public PendingBuffer(int bufferSize) + { + buffer = new byte[bufferSize]; + } + + #endregion Constructors + + /// + /// Clear internal state/buffers + /// + public void Reset() + { + start = end = bitCount = 0; + } + + /// + /// Write a byte to buffer + /// + /// + /// The value to write + /// + public void WriteByte(int value) + { +#if DebugDeflation + if (DeflaterConstants.DEBUGGING && (start != 0) ) + { + throw new SharpZipBaseException("Debug check: start != 0"); + } +#endif + buffer[end++] = unchecked((byte)value); + } + + /// + /// Write a short value to buffer LSB first + /// + /// + /// The value to write. + /// + public void WriteShort(int value) + { +#if DebugDeflation + if (DeflaterConstants.DEBUGGING && (start != 0) ) + { + throw new SharpZipBaseException("Debug check: start != 0"); + } +#endif + buffer[end++] = unchecked((byte)value); + buffer[end++] = unchecked((byte)(value >> 8)); + } + + /// + /// write an integer LSB first + /// + /// The value to write. + public void WriteInt(int value) + { +#if DebugDeflation + if (DeflaterConstants.DEBUGGING && (start != 0) ) + { + throw new SharpZipBaseException("Debug check: start != 0"); + } +#endif + buffer[end++] = unchecked((byte)value); + buffer[end++] = unchecked((byte)(value >> 8)); + buffer[end++] = unchecked((byte)(value >> 16)); + buffer[end++] = unchecked((byte)(value >> 24)); + } + + /// + /// Write a block of data to buffer + /// + /// data to write + /// offset of first byte to write + /// number of bytes to write + public void WriteBlock(byte[] block, int offset, int length) + { +#if DebugDeflation + if (DeflaterConstants.DEBUGGING && (start != 0) ) + { + throw new SharpZipBaseException("Debug check: start != 0"); + } +#endif + System.Array.Copy(block, offset, buffer, end, length); + end += length; + } + + /// + /// The number of bits written to the buffer + /// + public int BitCount + { + get + { + return bitCount; + } + } + + /// + /// Align internal buffer on a byte boundary + /// + public void AlignToByte() + { +#if DebugDeflation + if (DeflaterConstants.DEBUGGING && (start != 0) ) + { + throw new SharpZipBaseException("Debug check: start != 0"); + } +#endif + if (bitCount > 0) + { + buffer[end++] = unchecked((byte)bits); + if (bitCount > 8) + { + buffer[end++] = unchecked((byte)(bits >> 8)); + } + } + bits = 0; + bitCount = 0; + } + + /// + /// Write bits to internal buffer + /// + /// source of bits + /// number of bits to write + public void WriteBits(int b, int count) + { +#if DebugDeflation + if (DeflaterConstants.DEBUGGING && (start != 0) ) + { + throw new SharpZipBaseException("Debug check: start != 0"); + } + + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("writeBits("+b+","+count+")"); + // } +#endif + bits |= (uint)(b << bitCount); + bitCount += count; + if (bitCount >= 16) + { + buffer[end++] = unchecked((byte)bits); + buffer[end++] = unchecked((byte)(bits >> 8)); + bits >>= 16; + bitCount -= 16; + } + } + + /// + /// Write a short value to internal buffer most significant byte first + /// + /// value to write + public void WriteShortMSB(int s) + { +#if DebugDeflation + if (DeflaterConstants.DEBUGGING && (start != 0) ) + { + throw new SharpZipBaseException("Debug check: start != 0"); + } +#endif + buffer[end++] = unchecked((byte)(s >> 8)); + buffer[end++] = unchecked((byte)s); + } + + /// + /// Indicates if buffer has been flushed + /// + public bool IsFlushed + { + get + { + return end == 0; + } + } + + /// + /// Flushes the pending buffer into the given output array. If the + /// output array is to small, only a partial flush is done. + /// + /// The output array. + /// The offset into output array. + /// The maximum number of bytes to store. + /// The number of bytes flushed. + public int Flush(byte[] output, int offset, int length) + { + if (bitCount >= 8) + { + buffer[end++] = unchecked((byte)bits); + bits >>= 8; + bitCount -= 8; + } + + if (length > end - start) + { + length = end - start; + System.Array.Copy(buffer, start, output, offset, length); + start = 0; + end = 0; + } + else + { + System.Array.Copy(buffer, start, output, offset, length); + start += length; + } + return length; + } + + /// + /// Convert internal buffer to byte array. + /// Buffer is empty on completion + /// + /// + /// The internal buffer contents converted to a byte array. + /// + public byte[] ToByteArray() + { + AlignToByte(); + + byte[] result = new byte[end - start]; + System.Array.Copy(buffer, start, result, 0, result.Length); + start = 0; + end = 0; + return result; + } + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs new file mode 100644 index 0000000..b6d4025 --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs @@ -0,0 +1,438 @@ +using ICSharpCode.SharpZipLib.Encryption; +using System; +using System.IO; +using System.Security.Cryptography; + +namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams +{ + /// + /// A special stream deflating or compressing the bytes that are + /// written to it. It uses a Deflater to perform actual deflating.
+ /// Authors of the original java version : Tom Tromey, Jochen Hoenicke + ///
+ public class DeflaterOutputStream : Stream + { + #region Constructors + + /// + /// Creates a new DeflaterOutputStream with a default Deflater and default buffer size. + /// + /// + /// the output stream where deflated output should be written. + /// + public DeflaterOutputStream(Stream baseOutputStream) + : this(baseOutputStream, new Deflater(), 512) + { + } + + /// + /// Creates a new DeflaterOutputStream with the given Deflater and + /// default buffer size. + /// + /// + /// the output stream where deflated output should be written. + /// + /// + /// the underlying deflater. + /// + public DeflaterOutputStream(Stream baseOutputStream, Deflater deflater) + : this(baseOutputStream, deflater, 512) + { + } + + /// + /// Creates a new DeflaterOutputStream with the given Deflater and + /// buffer size. + /// + /// + /// The output stream where deflated output is written. + /// + /// + /// The underlying deflater to use + /// + /// + /// The buffer size in bytes to use when deflating (minimum value 512) + /// + /// + /// bufsize is less than or equal to zero. + /// + /// + /// baseOutputStream does not support writing + /// + /// + /// deflater instance is null + /// + public DeflaterOutputStream(Stream baseOutputStream, Deflater deflater, int bufferSize) + { + if (baseOutputStream == null) + { + throw new ArgumentNullException(nameof(baseOutputStream)); + } + + if (baseOutputStream.CanWrite == false) + { + throw new ArgumentException("Must support writing", nameof(baseOutputStream)); + } + + if (bufferSize < 512) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + } + + baseOutputStream_ = baseOutputStream; + buffer_ = new byte[bufferSize]; + deflater_ = deflater ?? throw new ArgumentNullException(nameof(deflater)); + } + + #endregion Constructors + + #region Public API + + /// + /// Finishes the stream by calling finish() on the deflater. + /// + /// + /// Not all input is deflated + /// + public virtual void Finish() + { + deflater_.Finish(); + while (!deflater_.IsFinished) + { + int len = deflater_.Deflate(buffer_, 0, buffer_.Length); + if (len <= 0) + { + break; + } + + if (cryptoTransform_ != null) + { + EncryptBlock(buffer_, 0, len); + } + + baseOutputStream_.Write(buffer_, 0, len); + } + + if (!deflater_.IsFinished) + { + throw new SharpZipBaseException("Can't deflate all input?"); + } + + baseOutputStream_.Flush(); + + if (cryptoTransform_ != null) + { + if (cryptoTransform_ is ZipAESTransform) + { + AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode(); + } + cryptoTransform_.Dispose(); + cryptoTransform_ = null; + } + } + + /// + /// Gets or sets a flag indicating ownership of underlying stream. + /// When the flag is true will close the underlying stream also. + /// + /// The default value is true. + public bool IsStreamOwner { get; set; } = true; + + /// + /// Allows client to determine if an entry can be patched after its added + /// + public bool CanPatchEntries + { + get + { + return baseOutputStream_.CanSeek; + } + } + + #endregion Public API + + #region Encryption + + /// + /// The CryptoTransform currently being used to encrypt the compressed data. + /// + protected ICryptoTransform cryptoTransform_; + + /// + /// Returns the 10 byte AUTH CODE to be appended immediately following the AES data stream. + /// + protected byte[] AESAuthCode; + + /// + /// Encrypt a block of data + /// + /// + /// Data to encrypt. NOTE the original contents of the buffer are lost + /// + /// + /// Offset of first byte in buffer to encrypt + /// + /// + /// Number of bytes in buffer to encrypt + /// + protected void EncryptBlock(byte[] buffer, int offset, int length) + { + cryptoTransform_.TransformBlock(buffer, 0, length, buffer, 0); + } + + #endregion Encryption + + #region Deflation Support + + /// + /// Deflates everything in the input buffers. This will call + /// def.deflate() until all bytes from the input buffers + /// are processed. + /// + protected void Deflate() + { + Deflate(false); + } + + private void Deflate(bool flushing) + { + while (flushing || !deflater_.IsNeedingInput) + { + int deflateCount = deflater_.Deflate(buffer_, 0, buffer_.Length); + + if (deflateCount <= 0) + { + break; + } + if (cryptoTransform_ != null) + { + EncryptBlock(buffer_, 0, deflateCount); + } + + baseOutputStream_.Write(buffer_, 0, deflateCount); + } + + if (!deflater_.IsNeedingInput) + { + throw new SharpZipBaseException("DeflaterOutputStream can't deflate all input?"); + } + } + + #endregion Deflation Support + + #region Stream Overrides + + /// + /// Gets value indicating stream can be read from + /// + public override bool CanRead + { + get + { + return false; + } + } + + /// + /// Gets a value indicating if seeking is supported for this stream + /// This property always returns false + /// + public override bool CanSeek + { + get + { + return false; + } + } + + /// + /// Get value indicating if this stream supports writing + /// + public override bool CanWrite + { + get + { + return baseOutputStream_.CanWrite; + } + } + + /// + /// Get current length of stream + /// + public override long Length + { + get + { + return baseOutputStream_.Length; + } + } + + /// + /// Gets the current position within the stream. + /// + /// Any attempt to set position + public override long Position + { + get + { + return baseOutputStream_.Position; + } + set + { + throw new NotSupportedException("Position property not supported"); + } + } + + /// + /// Sets the current position of this stream to the given value. Not supported by this class! + /// + /// The offset relative to the to seek. + /// The to seek from. + /// The new position in the stream. + /// Any access + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("DeflaterOutputStream Seek not supported"); + } + + /// + /// Sets the length of this stream to the given value. Not supported by this class! + /// + /// The new stream length. + /// Any access + public override void SetLength(long value) + { + throw new NotSupportedException("DeflaterOutputStream SetLength not supported"); + } + + /// + /// Read a byte from stream advancing position by one + /// + /// The byte read cast to an int. THe value is -1 if at the end of the stream. + /// Any access + public override int ReadByte() + { + throw new NotSupportedException("DeflaterOutputStream ReadByte not supported"); + } + + /// + /// Read a block of bytes from stream + /// + /// The buffer to store read data in. + /// The offset to start storing at. + /// The maximum number of bytes to read. + /// The actual number of bytes read. Zero if end of stream is detected. + /// Any access + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("DeflaterOutputStream Read not supported"); + } + + /// + /// Flushes the stream by calling Flush on the deflater and then + /// on the underlying stream. This ensures that all bytes are flushed. + /// + public override void Flush() + { + deflater_.Flush(); + Deflate(true); + baseOutputStream_.Flush(); + } + + /// + /// Calls and closes the underlying + /// stream when is true. + /// + protected override void Dispose(bool disposing) + { + if (!isClosed_) + { + isClosed_ = true; + + try + { + Finish(); + if (cryptoTransform_ != null) + { + GetAuthCodeIfAES(); + cryptoTransform_.Dispose(); + cryptoTransform_ = null; + } + } + finally + { + if (IsStreamOwner) + { + baseOutputStream_.Dispose(); + } + } + } + } + + /// + /// Get the Auth code for AES encrypted entries + /// + protected void GetAuthCodeIfAES() + { + if (cryptoTransform_ is ZipAESTransform) + { + AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode(); + } + } + + /// + /// Writes a single byte to the compressed output stream. + /// + /// + /// The byte value. + /// + public override void WriteByte(byte value) + { + byte[] b = new byte[1]; + b[0] = value; + Write(b, 0, 1); + } + + /// + /// Writes bytes from an array to the compressed stream. + /// + /// + /// The byte array + /// + /// + /// The offset into the byte array where to start. + /// + /// + /// The number of bytes to write. + /// + public override void Write(byte[] buffer, int offset, int count) + { + deflater_.SetInput(buffer, offset, count); + Deflate(); + } + + #endregion Stream Overrides + + #region Instance Fields + + /// + /// This buffer is used temporarily to retrieve the bytes from the + /// deflater and write them to the underlying output stream. + /// + private byte[] buffer_; + + /// + /// The deflater which is used to deflate the stream. + /// + protected Deflater deflater_; + + /// + /// Base stream the deflater depends on. + /// + protected Stream baseOutputStream_; + + private bool isClosed_; + + #endregion Instance Fields + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs new file mode 100644 index 0000000..7790474 --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs @@ -0,0 +1,713 @@ +using System; +using System.IO; +using System.Security.Cryptography; + +namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams +{ + /// + /// An input buffer customised for use by + /// + /// + /// The buffer supports decryption of incoming data. + /// + public class InflaterInputBuffer + { + #region Constructors + + /// + /// Initialise a new instance of with a default buffer size + /// + /// The stream to buffer. + public InflaterInputBuffer(Stream stream) : this(stream, 4096) + { + } + + /// + /// Initialise a new instance of + /// + /// The stream to buffer. + /// The size to use for the buffer + /// A minimum buffer size of 1KB is permitted. Lower sizes are treated as 1KB. + public InflaterInputBuffer(Stream stream, int bufferSize) + { + inputStream = stream; + if (bufferSize < 1024) + { + bufferSize = 1024; + } + rawData = new byte[bufferSize]; + clearText = rawData; + } + + #endregion Constructors + + /// + /// Get the length of bytes in the + /// + public int RawLength + { + get + { + return rawLength; + } + } + + /// + /// Get the contents of the raw data buffer. + /// + /// This may contain encrypted data. + public byte[] RawData + { + get + { + return rawData; + } + } + + /// + /// Get the number of useable bytes in + /// + public int ClearTextLength + { + get + { + return clearTextLength; + } + } + + /// + /// Get the contents of the clear text buffer. + /// + public byte[] ClearText + { + get + { + return clearText; + } + } + + /// + /// Get/set the number of bytes available + /// + public int Available + { + get { return available; } + set { available = value; } + } + + /// + /// Call passing the current clear text buffer contents. + /// + /// The inflater to set input for. + public void SetInflaterInput(Inflater inflater) + { + if (available > 0) + { + inflater.SetInput(clearText, clearTextLength - available, available); + available = 0; + } + } + + /// + /// Fill the buffer from the underlying input stream. + /// + public void Fill() + { + rawLength = 0; + int toRead = rawData.Length; + + while (toRead > 0 && inputStream.CanRead) + { + int count = inputStream.Read(rawData, rawLength, toRead); + if (count <= 0) + { + break; + } + rawLength += count; + toRead -= count; + } + + if (cryptoTransform != null) + { + clearTextLength = cryptoTransform.TransformBlock(rawData, 0, rawLength, clearText, 0); + } + else + { + clearTextLength = rawLength; + } + + available = clearTextLength; + } + + /// + /// Read a buffer directly from the input stream + /// + /// The buffer to fill + /// Returns the number of bytes read. + public int ReadRawBuffer(byte[] buffer) + { + return ReadRawBuffer(buffer, 0, buffer.Length); + } + + /// + /// Read a buffer directly from the input stream + /// + /// The buffer to read into + /// The offset to start reading data into. + /// The number of bytes to read. + /// Returns the number of bytes read. + public int ReadRawBuffer(byte[] outBuffer, int offset, int length) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + int currentOffset = offset; + int currentLength = length; + + while (currentLength > 0) + { + if (available <= 0) + { + Fill(); + if (available <= 0) + { + return 0; + } + } + int toCopy = Math.Min(currentLength, available); + System.Array.Copy(rawData, rawLength - (int)available, outBuffer, currentOffset, toCopy); + currentOffset += toCopy; + currentLength -= toCopy; + available -= toCopy; + } + return length; + } + + /// + /// Read clear text data from the input stream. + /// + /// The buffer to add data to. + /// The offset to start adding data at. + /// The number of bytes to read. + /// Returns the number of bytes actually read. + public int ReadClearTextBuffer(byte[] outBuffer, int offset, int length) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + int currentOffset = offset; + int currentLength = length; + + while (currentLength > 0) + { + if (available <= 0) + { + Fill(); + if (available <= 0) + { + return 0; + } + } + + int toCopy = Math.Min(currentLength, available); + Array.Copy(clearText, clearTextLength - (int)available, outBuffer, currentOffset, toCopy); + currentOffset += toCopy; + currentLength -= toCopy; + available -= toCopy; + } + return length; + } + + /// + /// Read a from the input stream. + /// + /// Returns the byte read. + public byte ReadLeByte() + { + if (available <= 0) + { + Fill(); + if (available <= 0) + { + throw new ZipException("EOF in header"); + } + } + byte result = rawData[rawLength - available]; + available -= 1; + return result; + } + + /// + /// Read an in little endian byte order. + /// + /// The short value read case to an int. + public int ReadLeShort() + { + return ReadLeByte() | (ReadLeByte() << 8); + } + + /// + /// Read an in little endian byte order. + /// + /// The int value read. + public int ReadLeInt() + { + return ReadLeShort() | (ReadLeShort() << 16); + } + + /// + /// Read a in little endian byte order. + /// + /// The long value read. + public long ReadLeLong() + { + return (uint)ReadLeInt() | ((long)ReadLeInt() << 32); + } + + /// + /// Get/set the to apply to any data. + /// + /// Set this value to null to have no transform applied. + public ICryptoTransform CryptoTransform + { + set + { + cryptoTransform = value; + if (cryptoTransform != null) + { + if (rawData == clearText) + { + if (internalClearText == null) + { + internalClearText = new byte[rawData.Length]; + } + clearText = internalClearText; + } + clearTextLength = rawLength; + if (available > 0) + { + cryptoTransform.TransformBlock(rawData, rawLength - available, available, clearText, rawLength - available); + } + } + else + { + clearText = rawData; + clearTextLength = rawLength; + } + } + } + + #region Instance Fields + + private int rawLength; + private byte[] rawData; + + private int clearTextLength; + private byte[] clearText; + private byte[] internalClearText; + + private int available; + + private ICryptoTransform cryptoTransform; + private Stream inputStream; + + #endregion Instance Fields + } + + /// + /// This filter stream is used to decompress data compressed using the "deflate" + /// format. The "deflate" format is described in RFC 1951. + /// + /// This stream may form the basis for other decompression filters, such + /// as the GZipInputStream. + /// + /// Author of the original java version : John Leuner. + /// + public class InflaterInputStream : Stream + { + #region Constructors + + /// + /// Create an InflaterInputStream with the default decompressor + /// and a default buffer size of 4KB. + /// + /// + /// The InputStream to read bytes from + /// + public InflaterInputStream(Stream baseInputStream) + : this(baseInputStream, new Inflater(), 4096) + { + } + + /// + /// Create an InflaterInputStream with the specified decompressor + /// and a default buffer size of 4KB. + /// + /// + /// The source of input data + /// + /// + /// The decompressor used to decompress data read from baseInputStream + /// + public InflaterInputStream(Stream baseInputStream, Inflater inf) + : this(baseInputStream, inf, 4096) + { + } + + /// + /// Create an InflaterInputStream with the specified decompressor + /// and the specified buffer size. + /// + /// + /// The InputStream to read bytes from + /// + /// + /// The decompressor to use + /// + /// + /// Size of the buffer to use + /// + public InflaterInputStream(Stream baseInputStream, Inflater inflater, int bufferSize) + { + if (baseInputStream == null) + { + throw new ArgumentNullException(nameof(baseInputStream)); + } + + if (inflater == null) + { + throw new ArgumentNullException(nameof(inflater)); + } + + if (bufferSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + } + + this.baseInputStream = baseInputStream; + this.inf = inflater; + + inputBuffer = new InflaterInputBuffer(baseInputStream, bufferSize); + } + + #endregion Constructors + + /// + /// Gets or sets a flag indicating ownership of underlying stream. + /// When the flag is true will close the underlying stream also. + /// + /// The default value is true. + public bool IsStreamOwner { get; set; } = true; + + /// + /// Skip specified number of bytes of uncompressed data + /// + /// + /// Number of bytes to skip + /// + /// + /// The number of bytes skipped, zero if the end of + /// stream has been reached + /// + /// + /// The number of bytes to skip is less than or equal to zero. + /// + public long Skip(long count) + { + if (count <= 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + // v0.80 Skip by seeking if underlying stream supports it... + if (baseInputStream.CanSeek) + { + baseInputStream.Seek(count, SeekOrigin.Current); + return count; + } + else + { + int length = 2048; + if (count < length) + { + length = (int)count; + } + + byte[] tmp = new byte[length]; + int readCount = 1; + long toSkip = count; + + while ((toSkip > 0) && (readCount > 0)) + { + if (toSkip < length) + { + length = (int)toSkip; + } + + readCount = baseInputStream.Read(tmp, 0, length); + toSkip -= readCount; + } + + return count - toSkip; + } + } + + /// + /// Clear any cryptographic state. + /// + protected void StopDecrypting() + { + inputBuffer.CryptoTransform = null; + } + + /// + /// Returns 0 once the end of the stream (EOF) has been reached. + /// Otherwise returns 1. + /// + public virtual int Available + { + get + { + return inf.IsFinished ? 0 : 1; + } + } + + /// + /// Fills the buffer with more data to decompress. + /// + /// + /// Stream ends early + /// + protected void Fill() + { + // Protect against redundant calls + if (inputBuffer.Available <= 0) + { + inputBuffer.Fill(); + if (inputBuffer.Available <= 0) + { + throw new SharpZipBaseException("Unexpected EOF"); + } + } + inputBuffer.SetInflaterInput(inf); + } + + #region Stream Overrides + + /// + /// Gets a value indicating whether the current stream supports reading + /// + public override bool CanRead + { + get + { + return baseInputStream.CanRead; + } + } + + /// + /// Gets a value of false indicating seeking is not supported for this stream. + /// + public override bool CanSeek + { + get + { + return false; + } + } + + /// + /// Gets a value of false indicating that this stream is not writeable. + /// + public override bool CanWrite + { + get + { + return false; + } + } + + /// + /// A value representing the length of the stream in bytes. + /// + public override long Length + { + get + { + //return inputBuffer.RawLength; + throw new NotSupportedException("InflaterInputStream Length is not supported"); + } + } + + /// + /// The current position within the stream. + /// Throws a NotSupportedException when attempting to set the position + /// + /// Attempting to set the position + public override long Position + { + get + { + return baseInputStream.Position; + } + set + { + throw new NotSupportedException("InflaterInputStream Position not supported"); + } + } + + /// + /// Flushes the baseInputStream + /// + public override void Flush() + { + baseInputStream.Flush(); + } + + /// + /// Sets the position within the current stream + /// Always throws a NotSupportedException + /// + /// The relative offset to seek to. + /// The defining where to seek from. + /// The new position in the stream. + /// Any access + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("Seek not supported"); + } + + /// + /// Set the length of the current stream + /// Always throws a NotSupportedException + /// + /// The new length value for the stream. + /// Any access + public override void SetLength(long value) + { + throw new NotSupportedException("InflaterInputStream SetLength not supported"); + } + + /// + /// Writes a sequence of bytes to stream and advances the current position + /// This method always throws a NotSupportedException + /// + /// The buffer containing data to write. + /// The offset of the first byte to write. + /// The number of bytes to write. + /// Any access + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("InflaterInputStream Write not supported"); + } + + /// + /// Writes one byte to the current stream and advances the current position + /// Always throws a NotSupportedException + /// + /// The byte to write. + /// Any access + public override void WriteByte(byte value) + { + throw new NotSupportedException("InflaterInputStream WriteByte not supported"); + } + + /// + /// Closes the input stream. When + /// is true the underlying stream is also closed. + /// + protected override void Dispose(bool disposing) + { + if (!isClosed) + { + isClosed = true; + if (IsStreamOwner) + { + baseInputStream.Dispose(); + } + } + } + + /// + /// Reads decompressed data into the provided buffer byte array + /// + /// + /// The array to read and decompress data into + /// + /// + /// The offset indicating where the data should be placed + /// + /// + /// The number of bytes to decompress + /// + /// The number of bytes read. Zero signals the end of stream + /// + /// Inflater needs a dictionary + /// + public override int Read(byte[] buffer, int offset, int count) + { + if (inf.IsNeedingDictionary) + { + throw new SharpZipBaseException("Need a dictionary"); + } + + int remainingBytes = count; + while (true) + { + int bytesRead = inf.Inflate(buffer, offset, remainingBytes); + offset += bytesRead; + remainingBytes -= bytesRead; + + if (remainingBytes == 0 || inf.IsFinished) + { + break; + } + + if (inf.IsNeedingInput) + { + Fill(); + } + else if (bytesRead == 0) + { + throw new ZipException("Invalid input data"); + } + } + return count - remainingBytes; + } + + #endregion Stream Overrides + + #region Instance Fields + + /// + /// Decompressor for this stream + /// + protected Inflater inf; + + /// + /// Input buffer for this stream. + /// + protected InflaterInputBuffer inputBuffer; + + /// + /// Base stream the inflater reads from. + /// + private Stream baseInputStream; + + /// + /// The compressed size + /// + protected long csize; + + /// + /// Flag indicating whether this instance has been closed or not. + /// + private bool isClosed; + + #endregion Instance Fields + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/Streams/OutputWindow.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/Streams/OutputWindow.cs new file mode 100644 index 0000000..d8241c1 --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/Streams/OutputWindow.cs @@ -0,0 +1,220 @@ +using System; + +namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams +{ + /// + /// Contains the output from the Inflation process. + /// We need to have a window so that we can refer backwards into the output stream + /// to repeat stuff.
+ /// Author of the original java version : John Leuner + ///
+ public class OutputWindow + { + #region Constants + + private const int WindowSize = 1 << 15; + private const int WindowMask = WindowSize - 1; + + #endregion Constants + + #region Instance Fields + + private byte[] window = new byte[WindowSize]; //The window is 2^15 bytes + private int windowEnd; + private int windowFilled; + + #endregion Instance Fields + + /// + /// Write a byte to this output window + /// + /// value to write + /// + /// if window is full + /// + public void Write(int value) + { + if (windowFilled++ == WindowSize) + { + throw new InvalidOperationException("Window full"); + } + window[windowEnd++] = (byte)value; + windowEnd &= WindowMask; + } + + private void SlowRepeat(int repStart, int length, int distance) + { + while (length-- > 0) + { + window[windowEnd++] = window[repStart++]; + windowEnd &= WindowMask; + repStart &= WindowMask; + } + } + + /// + /// Append a byte pattern already in the window itself + /// + /// length of pattern to copy + /// distance from end of window pattern occurs + /// + /// If the repeated data overflows the window + /// + public void Repeat(int length, int distance) + { + if ((windowFilled += length) > WindowSize) + { + throw new InvalidOperationException("Window full"); + } + + int repStart = (windowEnd - distance) & WindowMask; + int border = WindowSize - length; + if ((repStart <= border) && (windowEnd < border)) + { + if (length <= distance) + { + System.Array.Copy(window, repStart, window, windowEnd, length); + windowEnd += length; + } + else + { + // We have to copy manually, since the repeat pattern overlaps. + while (length-- > 0) + { + window[windowEnd++] = window[repStart++]; + } + } + } + else + { + SlowRepeat(repStart, length, distance); + } + } + + /// + /// Copy from input manipulator to internal window + /// + /// source of data + /// length of data to copy + /// the number of bytes copied + public int CopyStored(StreamManipulator input, int length) + { + length = Math.Min(Math.Min(length, WindowSize - windowFilled), input.AvailableBytes); + int copied; + + int tailLen = WindowSize - windowEnd; + if (length > tailLen) + { + copied = input.CopyBytes(window, windowEnd, tailLen); + if (copied == tailLen) + { + copied += input.CopyBytes(window, 0, length - tailLen); + } + } + else + { + copied = input.CopyBytes(window, windowEnd, length); + } + + windowEnd = (windowEnd + copied) & WindowMask; + windowFilled += copied; + return copied; + } + + /// + /// Copy dictionary to window + /// + /// source dictionary + /// offset of start in source dictionary + /// length of dictionary + /// + /// If window isnt empty + /// + public void CopyDict(byte[] dictionary, int offset, int length) + { + if (dictionary == null) + { + throw new ArgumentNullException(nameof(dictionary)); + } + + if (windowFilled > 0) + { + throw new InvalidOperationException(); + } + + if (length > WindowSize) + { + offset += length - WindowSize; + length = WindowSize; + } + System.Array.Copy(dictionary, offset, window, 0, length); + windowEnd = length & WindowMask; + } + + /// + /// Get remaining unfilled space in window + /// + /// Number of bytes left in window + public int GetFreeSpace() + { + return WindowSize - windowFilled; + } + + /// + /// Get bytes available for output in window + /// + /// Number of bytes filled + public int GetAvailable() + { + return windowFilled; + } + + /// + /// Copy contents of window to output + /// + /// buffer to copy to + /// offset to start at + /// number of bytes to count + /// The number of bytes copied + /// + /// If a window underflow occurs + /// + public int CopyOutput(byte[] output, int offset, int len) + { + int copyEnd = windowEnd; + if (len > windowFilled) + { + len = windowFilled; + } + else + { + copyEnd = (windowEnd - windowFilled + len) & WindowMask; + } + + int copied = len; + int tailLen = len - copyEnd; + + if (tailLen > 0) + { + System.Array.Copy(window, WindowSize - tailLen, output, offset, tailLen); + offset += tailLen; + len = copyEnd; + } + System.Array.Copy(window, copyEnd - len, output, offset, len); + windowFilled -= copied; + if (windowFilled < 0) + { + throw new InvalidOperationException(); + } + return copied; + } + + /// + /// Reset by clearing window so GetAvailable returns 0 + /// + public void Reset() + { + windowFilled = windowEnd = 0; + } + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs new file mode 100644 index 0000000..aff6a9c --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs @@ -0,0 +1,298 @@ +using System; + +namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams +{ + /// + /// This class allows us to retrieve a specified number of bits from + /// the input buffer, as well as copy big byte blocks. + /// + /// It uses an int buffer to store up to 31 bits for direct + /// manipulation. This guarantees that we can get at least 16 bits, + /// but we only need at most 15, so this is all safe. + /// + /// There are some optimizations in this class, for example, you must + /// never peek more than 8 bits more than needed, and you must first + /// peek bits before you may drop them. This is not a general purpose + /// class but optimized for the behaviour of the Inflater. + /// + /// authors of the original java version : John Leuner, Jochen Hoenicke + /// + public class StreamManipulator + { + /// + /// Get the next sequence of bits but don't increase input pointer. bitCount must be + /// less or equal 16 and if this call succeeds, you must drop + /// at least n - 8 bits in the next call. + /// + /// The number of bits to peek. + /// + /// the value of the bits, or -1 if not enough bits available. */ + /// + public int PeekBits(int bitCount) + { + if (bitsInBuffer_ < bitCount) + { + if (windowStart_ == windowEnd_) + { + return -1; // ok + } + buffer_ |= (uint)((window_[windowStart_++] & 0xff | + (window_[windowStart_++] & 0xff) << 8) << bitsInBuffer_); + bitsInBuffer_ += 16; + } + return (int)(buffer_ & ((1 << bitCount) - 1)); + } + + /// + /// Tries to grab the next bits from the input and + /// sets to the value, adding . + /// + /// true if enough bits could be read, otherwise false + public bool TryGetBits(int bitCount, ref int output, int outputOffset = 0) + { + var bits = PeekBits(bitCount); + if (bits < 0) + { + return false; + } + output = bits + outputOffset; + DropBits(bitCount); + return true; + } + + /// + /// Tries to grab the next bits from the input and + /// sets of to the value. + /// + /// true if enough bits could be read, otherwise false + public bool TryGetBits(int bitCount, ref byte[] array, int index) + { + var bits = PeekBits(bitCount); + if (bits < 0) + { + return false; + } + array[index] = (byte)bits; + DropBits(bitCount); + return true; + } + + /// + /// Drops the next n bits from the input. You should have called PeekBits + /// with a bigger or equal n before, to make sure that enough bits are in + /// the bit buffer. + /// + /// The number of bits to drop. + public void DropBits(int bitCount) + { + buffer_ >>= bitCount; + bitsInBuffer_ -= bitCount; + } + + /// + /// Gets the next n bits and increases input pointer. This is equivalent + /// to followed by , except for correct error handling. + /// + /// The number of bits to retrieve. + /// + /// the value of the bits, or -1 if not enough bits available. + /// + public int GetBits(int bitCount) + { + int bits = PeekBits(bitCount); + if (bits >= 0) + { + DropBits(bitCount); + } + return bits; + } + + /// + /// Gets the number of bits available in the bit buffer. This must be + /// only called when a previous PeekBits() returned -1. + /// + /// + /// the number of bits available. + /// + public int AvailableBits + { + get + { + return bitsInBuffer_; + } + } + + /// + /// Gets the number of bytes available. + /// + /// + /// The number of bytes available. + /// + public int AvailableBytes + { + get + { + return windowEnd_ - windowStart_ + (bitsInBuffer_ >> 3); + } + } + + /// + /// Skips to the next byte boundary. + /// + public void SkipToByteBoundary() + { + buffer_ >>= (bitsInBuffer_ & 7); + bitsInBuffer_ &= ~7; + } + + /// + /// Returns true when SetInput can be called + /// + public bool IsNeedingInput + { + get + { + return windowStart_ == windowEnd_; + } + } + + /// + /// Copies bytes from input buffer to output buffer starting + /// at output[offset]. You have to make sure, that the buffer is + /// byte aligned. If not enough bytes are available, copies fewer + /// bytes. + /// + /// + /// The buffer to copy bytes to. + /// + /// + /// The offset in the buffer at which copying starts + /// + /// + /// The length to copy, 0 is allowed. + /// + /// + /// The number of bytes copied, 0 if no bytes were available. + /// + /// + /// Length is less than zero + /// + /// + /// Bit buffer isnt byte aligned + /// + public int CopyBytes(byte[] output, int offset, int length) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + if ((bitsInBuffer_ & 7) != 0) + { + // bits_in_buffer may only be 0 or a multiple of 8 + throw new InvalidOperationException("Bit buffer is not byte aligned!"); + } + + int count = 0; + while ((bitsInBuffer_ > 0) && (length > 0)) + { + output[offset++] = (byte)buffer_; + buffer_ >>= 8; + bitsInBuffer_ -= 8; + length--; + count++; + } + + if (length == 0) + { + return count; + } + + int avail = windowEnd_ - windowStart_; + if (length > avail) + { + length = avail; + } + System.Array.Copy(window_, windowStart_, output, offset, length); + windowStart_ += length; + + if (((windowStart_ - windowEnd_) & 1) != 0) + { + // We always want an even number of bytes in input, see peekBits + buffer_ = (uint)(window_[windowStart_++] & 0xff); + bitsInBuffer_ = 8; + } + return count + length; + } + + /// + /// Resets state and empties internal buffers + /// + public void Reset() + { + buffer_ = 0; + windowStart_ = windowEnd_ = bitsInBuffer_ = 0; + } + + /// + /// Add more input for consumption. + /// Only call when IsNeedingInput returns true + /// + /// data to be input + /// offset of first byte of input + /// number of bytes of input to add. + public void SetInput(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative"); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative"); + } + + if (windowStart_ < windowEnd_) + { + throw new InvalidOperationException("Old input was not completely processed"); + } + + int end = offset + count; + + // We want to throw an ArrayIndexOutOfBoundsException early. + // Note the check also handles integer wrap around. + if ((offset > end) || (end > buffer.Length)) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if ((count & 1) != 0) + { + // We always want an even number of bytes in input, see PeekBits + buffer_ |= (uint)((buffer[offset++] & 0xff) << bitsInBuffer_); + bitsInBuffer_ += 8; + } + + window_ = buffer; + windowStart_ = offset; + windowEnd_ = end; + } + + #region Instance Fields + + private byte[] window_; + private int windowStart_; + private int windowEnd_; + + private uint buffer_; + private int bitsInBuffer_; + + #endregion Instance Fields + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/FastZip.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/FastZip.cs new file mode 100644 index 0000000..01725f4 --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/FastZip.cs @@ -0,0 +1,975 @@ +using ICSharpCode.SharpZipLib.Core; +using ICSharpCode.SharpZipLib.Zip.Compression; +using System; +using System.IO; +using static ICSharpCode.SharpZipLib.Zip.Compression.Deflater; +using static ICSharpCode.SharpZipLib.Zip.ZipEntryFactory; + +namespace ICSharpCode.SharpZipLib.Zip +{ + /// + /// FastZipEvents supports all events applicable to FastZip operations. + /// + public class FastZipEvents + { + /// + /// Delegate to invoke when processing directories. + /// + public event EventHandler ProcessDirectory; + + /// + /// Delegate to invoke when processing files. + /// + public ProcessFileHandler ProcessFile; + + /// + /// Delegate to invoke during processing of files. + /// + public ProgressHandler Progress; + + /// + /// Delegate to invoke when processing for a file has been completed. + /// + public CompletedFileHandler CompletedFile; + + /// + /// Delegate to invoke when processing directory failures. + /// + public DirectoryFailureHandler DirectoryFailure; + + /// + /// Delegate to invoke when processing file failures. + /// + public FileFailureHandler FileFailure; + + /// + /// Raise the directory failure event. + /// + /// The directory causing the failure. + /// The exception for this event. + /// A boolean indicating if execution should continue or not. + public bool OnDirectoryFailure(string directory, Exception e) + { + bool result = false; + DirectoryFailureHandler handler = DirectoryFailure; + + if (handler != null) + { + var args = new ScanFailureEventArgs(directory, e); + handler(this, args); + result = args.ContinueRunning; + } + return result; + } + + /// + /// Fires the file failure handler delegate. + /// + /// The file causing the failure. + /// The exception for this failure. + /// A boolean indicating if execution should continue or not. + public bool OnFileFailure(string file, Exception e) + { + FileFailureHandler handler = FileFailure; + bool result = (handler != null); + + if (result) + { + var args = new ScanFailureEventArgs(file, e); + handler(this, args); + result = args.ContinueRunning; + } + return result; + } + + /// + /// Fires the ProcessFile delegate. + /// + /// The file being processed. + /// A boolean indicating if execution should continue or not. + public bool OnProcessFile(string file) + { + bool result = true; + ProcessFileHandler handler = ProcessFile; + + if (handler != null) + { + var args = new ScanEventArgs(file); + handler(this, args); + result = args.ContinueRunning; + } + return result; + } + + /// + /// Fires the delegate + /// + /// The file whose processing has been completed. + /// A boolean indicating if execution should continue or not. + public bool OnCompletedFile(string file) + { + bool result = true; + CompletedFileHandler handler = CompletedFile; + if (handler != null) + { + var args = new ScanEventArgs(file); + handler(this, args); + result = args.ContinueRunning; + } + return result; + } + + /// + /// Fires the process directory delegate. + /// + /// The directory being processed. + /// Flag indicating if the directory has matching files as determined by the current filter. + /// A of true if the operation should continue; false otherwise. + public bool OnProcessDirectory(string directory, bool hasMatchingFiles) + { + bool result = true; + EventHandler handler = ProcessDirectory; + if (handler != null) + { + var args = new DirectoryEventArgs(directory, hasMatchingFiles); + handler(this, args); + result = args.ContinueRunning; + } + return result; + } + + /// + /// The minimum timespan between events. + /// + /// The minimum period of time between events. + /// + /// The default interval is three seconds. + public TimeSpan ProgressInterval + { + get { return progressInterval_; } + set { progressInterval_ = value; } + } + + #region Instance Fields + + private TimeSpan progressInterval_ = TimeSpan.FromSeconds(3); + + #endregion Instance Fields + } + + /// + /// FastZip provides facilities for creating and extracting zip files. + /// + public class FastZip + { + #region Enumerations + + /// + /// Defines the desired handling when overwriting files during extraction. + /// + public enum Overwrite + { + /// + /// Prompt the user to confirm overwriting + /// + Prompt, + + /// + /// Never overwrite files. + /// + Never, + + /// + /// Always overwrite files. + /// + Always + } + + #endregion Enumerations + + #region Constructors + + /// + /// Initialise a default instance of . + /// + public FastZip() + { + } + + /// + /// Initialise a new instance of using the specified + /// + /// The time setting to use when creating or extracting Zip entries. + /// Using TimeSetting.LastAccessTime[Utc] when + /// creating an archive will set the file time to the moment of reading. + /// + public FastZip(TimeSetting timeSetting) + { + entryFactory_ = new ZipEntryFactory(timeSetting); + restoreDateTimeOnExtract_ = true; + } + + /// + /// Initialise a new instance of using the specified + /// + /// The time to set all values for created or extracted Zip Entries. + public FastZip(DateTime time) + { + entryFactory_ = new ZipEntryFactory(time); + restoreDateTimeOnExtract_ = true; + } + + /// + /// Initialise a new instance of + /// + /// The events to use during operations. + public FastZip(FastZipEvents events) + { + events_ = events; + } + + #endregion Constructors + + #region Properties + + /// + /// Get/set a value indicating whether empty directories should be created. + /// + public bool CreateEmptyDirectories + { + get { return createEmptyDirectories_; } + set { createEmptyDirectories_ = value; } + } + + /// + /// Get / set the password value. + /// + public string Password + { + get { return password_; } + set { password_ = value; } + } + + /// + /// Get / set the method of encrypting entries. + /// + /// + /// Only applies when is set. + /// Defaults to ZipCrypto for backwards compatibility purposes. + /// + public ZipEncryptionMethod EntryEncryptionMethod { get; set; } = ZipEncryptionMethod.ZipCrypto; + + /// + /// Get or set the active when creating Zip files. + /// + /// + public INameTransform NameTransform + { + get { return entryFactory_.NameTransform; } + set + { + entryFactory_.NameTransform = value; + } + } + + /// + /// Get or set the active when creating Zip files. + /// + public IEntryFactory EntryFactory + { + get { return entryFactory_; } + set + { + if (value == null) + { + entryFactory_ = new ZipEntryFactory(); + } + else + { + entryFactory_ = value; + } + } + } + + /// + /// Gets or sets the setting for Zip64 handling when writing. + /// + /// + /// The default value is dynamic which is not backwards compatible with old + /// programs and can cause problems with XP's built in compression which cant + /// read Zip64 archives. However it does avoid the situation were a large file + /// is added and cannot be completed correctly. + /// NOTE: Setting the size for entries before they are added is the best solution! + /// By default the EntryFactory used by FastZip will set the file size. + /// + public UseZip64 UseZip64 + { + get { return useZip64_; } + set { useZip64_ = value; } + } + + /// + /// Get/set a value indicating whether file dates and times should + /// be restored when extracting files from an archive. + /// + /// The default value is false. + public bool RestoreDateTimeOnExtract + { + get + { + return restoreDateTimeOnExtract_; + } + set + { + restoreDateTimeOnExtract_ = value; + } + } + + /// + /// Get/set a value indicating whether file attributes should + /// be restored during extract operations + /// + public bool RestoreAttributesOnExtract + { + get { return restoreAttributesOnExtract_; } + set { restoreAttributesOnExtract_ = value; } + } + + /// + /// Get/set the Compression Level that will be used + /// when creating the zip + /// + public Deflater.CompressionLevel CompressionLevel + { + get { return compressionLevel_; } + set { compressionLevel_ = value; } + } + + #endregion Properties + + #region Delegates + + /// + /// Delegate called when confirming overwriting of files. + /// + public delegate bool ConfirmOverwriteDelegate(string fileName); + + #endregion Delegates + + #region CreateZip + + /// + /// Create a zip file. + /// + /// The name of the zip file to create. + /// The directory to source files from. + /// True to recurse directories, false for no recursion. + /// The file filter to apply. + /// The directory filter to apply. + public void CreateZip(string zipFileName, string sourceDirectory, + bool recurse, string fileFilter, string directoryFilter) + { + CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, directoryFilter); + } + + /// + /// Create a zip file/archive. + /// + /// The name of the zip file to create. + /// The directory to obtain files and directories from. + /// True to recurse directories, false for no recursion. + /// The file filter to apply. + public void CreateZip(string zipFileName, string sourceDirectory, bool recurse, string fileFilter) + { + CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, null); + } + + /// + /// Create a zip archive sending output to the passed. + /// + /// The stream to write archive data to. + /// The directory to source files from. + /// True to recurse directories, false for no recursion. + /// The file filter to apply. + /// The directory filter to apply. + /// The is closed after creation. + public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, string fileFilter, string directoryFilter) + { + CreateZip(outputStream, sourceDirectory, recurse, fileFilter, directoryFilter, false); + } + + /// + /// Create a zip archive sending output to the passed. + /// + /// The stream to write archive data to. + /// The directory to source files from. + /// True to recurse directories, false for no recursion. + /// The file filter to apply. + /// The directory filter to apply. + /// true to leave open after the zip has been created, false to dispose it. + public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, string fileFilter, string directoryFilter, bool leaveOpen) + { + var scanner = new FileSystemScanner(fileFilter, directoryFilter); + CreateZip(outputStream, sourceDirectory, recurse, scanner, leaveOpen); + } + + /// + /// Create a zip file. + /// + /// The name of the zip file to create. + /// The directory to source files from. + /// True to recurse directories, false for no recursion. + /// The file filter to apply. + /// The directory filter to apply. + public void CreateZip(string zipFileName, string sourceDirectory, + bool recurse, IScanFilter fileFilter, IScanFilter directoryFilter) + { + CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, directoryFilter, false); + } + + /// + /// Create a zip archive sending output to the passed. + /// + /// The stream to write archive data to. + /// The directory to source files from. + /// True to recurse directories, false for no recursion. + /// The file filter to apply. + /// The directory filter to apply. + /// true to leave open after the zip has been created, false to dispose it. + public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, IScanFilter fileFilter, IScanFilter directoryFilter, bool leaveOpen = false) + { + var scanner = new FileSystemScanner(fileFilter, directoryFilter); + CreateZip(outputStream, sourceDirectory, recurse, scanner, leaveOpen); + } + + /// + /// Create a zip archive sending output to the passed. + /// + /// The stream to write archive data to. + /// The directory to source files from. + /// True to recurse directories, false for no recursion. + /// For performing the actual file system scan + /// true to leave open after the zip has been created, false to dispose it. + /// The is closed after creation. + private void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, FileSystemScanner scanner, bool leaveOpen) + { + NameTransform = new ZipNameTransform(sourceDirectory); + sourceDirectory_ = sourceDirectory; + + using (outputStream_ = new ZipOutputStream(outputStream)) + { + outputStream_.SetLevel((int)CompressionLevel); + outputStream_.IsStreamOwner = !leaveOpen; + outputStream_.NameTransform = null; // all required transforms handled by us + + if (false == string.IsNullOrEmpty(password_) && EntryEncryptionMethod != ZipEncryptionMethod.None) + { + outputStream_.Password = password_; + } + + outputStream_.UseZip64 = UseZip64; + scanner.ProcessFile += ProcessFile; + if (this.CreateEmptyDirectories) + { + scanner.ProcessDirectory += ProcessDirectory; + } + + if (events_ != null) + { + if (events_.FileFailure != null) + { + scanner.FileFailure += events_.FileFailure; + } + + if (events_.DirectoryFailure != null) + { + scanner.DirectoryFailure += events_.DirectoryFailure; + } + } + + scanner.Scan(sourceDirectory, recurse); + } + } + + #endregion CreateZip + + #region ExtractZip + + /// + /// Extract the contents of a zip file. + /// + /// The zip file to extract from. + /// The directory to save extracted information in. + /// A filter to apply to files. + public void ExtractZip(string zipFileName, string targetDirectory, string fileFilter) + { + ExtractZip(zipFileName, targetDirectory, Overwrite.Always, null, fileFilter, null, restoreDateTimeOnExtract_); + } + + /// + /// Extract the contents of a zip file. + /// + /// The zip file to extract from. + /// The directory to save extracted information in. + /// The style of overwriting to apply. + /// A delegate to invoke when confirming overwriting. + /// A filter to apply to files. + /// A filter to apply to directories. + /// Flag indicating whether to restore the date and time for extracted files. + /// Allow parent directory traversal in file paths (e.g. ../file) + public void ExtractZip(string zipFileName, string targetDirectory, + Overwrite overwrite, ConfirmOverwriteDelegate confirmDelegate, + string fileFilter, string directoryFilter, bool restoreDateTime, bool allowParentTraversal = false) + { + Stream inputStream = File.Open(zipFileName, FileMode.Open, FileAccess.Read, FileShare.Read); + ExtractZip(inputStream, targetDirectory, overwrite, confirmDelegate, fileFilter, directoryFilter, restoreDateTime, true, allowParentTraversal); + } + + /// + /// Extract the contents of a zip file held in a stream. + /// + /// The seekable input stream containing the zip to extract from. + /// The directory to save extracted information in. + /// The style of overwriting to apply. + /// A delegate to invoke when confirming overwriting. + /// A filter to apply to files. + /// A filter to apply to directories. + /// Flag indicating whether to restore the date and time for extracted files. + /// Flag indicating whether the inputStream will be closed by this method. + /// Allow parent directory traversal in file paths (e.g. ../file) + public void ExtractZip(Stream inputStream, string targetDirectory, + Overwrite overwrite, ConfirmOverwriteDelegate confirmDelegate, + string fileFilter, string directoryFilter, bool restoreDateTime, + bool isStreamOwner, bool allowParentTraversal = false) + { + if ((overwrite == Overwrite.Prompt) && (confirmDelegate == null)) + { + throw new ArgumentNullException(nameof(confirmDelegate)); + } + + continueRunning_ = true; + overwrite_ = overwrite; + confirmDelegate_ = confirmDelegate; + extractNameTransform_ = new WindowsNameTransform(targetDirectory, allowParentTraversal); + + fileFilter_ = new NameFilter(fileFilter); + directoryFilter_ = new NameFilter(directoryFilter); + restoreDateTimeOnExtract_ = restoreDateTime; + + using (zipFile_ = new ZipFile(inputStream, !isStreamOwner)) + { + if (password_ != null) + { + zipFile_.Password = password_; + } + + System.Collections.IEnumerator enumerator = zipFile_.GetEnumerator(); + while (continueRunning_ && enumerator.MoveNext()) + { + var entry = (ZipEntry)enumerator.Current; + if (entry.IsFile) + { + // TODO Path.GetDirectory can fail here on invalid characters. + if (directoryFilter_.IsMatch(Path.GetDirectoryName(entry.Name)) && fileFilter_.IsMatch(entry.Name)) + { + ExtractEntry(entry); + } + } + else if (entry.IsDirectory) + { + if (directoryFilter_.IsMatch(entry.Name) && CreateEmptyDirectories) + { + ExtractEntry(entry); + } + } + else + { + // Do nothing for volume labels etc... + } + } + } + } + + #endregion ExtractZip + + #region Internal Processing + + private void ProcessDirectory(object sender, DirectoryEventArgs e) + { + if (!e.HasMatchingFiles && CreateEmptyDirectories) + { + if (events_ != null) + { + events_.OnProcessDirectory(e.Name, e.HasMatchingFiles); + } + + if (e.ContinueRunning) + { + if (e.Name != sourceDirectory_) + { + ZipEntry entry = entryFactory_.MakeDirectoryEntry(e.Name); + outputStream_.PutNextEntry(entry); + } + } + } + } + + private void ProcessFile(object sender, ScanEventArgs e) + { + if ((events_ != null) && (events_.ProcessFile != null)) + { + events_.ProcessFile(sender, e); + } + + if (e.ContinueRunning) + { + try + { + // The open below is equivalent to OpenRead which guarantees that if opened the + // file will not be changed by subsequent openers, but precludes opening in some cases + // were it could succeed. ie the open may fail as its already open for writing and the share mode should reflect that. + using (FileStream stream = File.Open(e.Name, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + ZipEntry entry = entryFactory_.MakeFileEntry(e.Name); + + // Set up AES encryption for the entry if required. + ConfigureEntryEncryption(entry); + + outputStream_.PutNextEntry(entry); + AddFileContents(e.Name, stream); + } + } + catch (Exception ex) + { + if (events_ != null) + { + continueRunning_ = events_.OnFileFailure(e.Name, ex); + } + else + { + continueRunning_ = false; + throw; + } + } + } + } + + // Set up the encryption method to use for the specific entry. + private void ConfigureEntryEncryption(ZipEntry entry) + { + // Only alter the entries options if AES isn't already enabled for it + // (it might have been set up by the entry factory, and if so we let that take precedence) + if (!string.IsNullOrEmpty(Password) && entry.AESEncryptionStrength == 0) + { + switch (EntryEncryptionMethod) + { + case ZipEncryptionMethod.AES128: + entry.AESKeySize = 128; + break; + + case ZipEncryptionMethod.AES256: + entry.AESKeySize = 256; + break; + } + } + } + + private void AddFileContents(string name, Stream stream) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (buffer_ == null) + { + buffer_ = new byte[4096]; + } + + if ((events_ != null) && (events_.Progress != null)) + { + StreamUtils.Copy(stream, outputStream_, buffer_, + events_.Progress, events_.ProgressInterval, this, name); + } + else + { + StreamUtils.Copy(stream, outputStream_, buffer_); + } + + if (events_ != null) + { + continueRunning_ = events_.OnCompletedFile(name); + } + } + + private void ExtractFileEntry(ZipEntry entry, string targetName) + { + bool proceed = true; + if (overwrite_ != Overwrite.Always) + { + if (File.Exists(targetName)) + { + if ((overwrite_ == Overwrite.Prompt) && (confirmDelegate_ != null)) + { + proceed = confirmDelegate_(targetName); + } + else + { + proceed = false; + } + } + } + + if (proceed) + { + if (events_ != null) + { + continueRunning_ = events_.OnProcessFile(entry.Name); + } + + if (continueRunning_) + { + try + { + using (FileStream outputStream = File.Create(targetName)) + { + if (buffer_ == null) + { + buffer_ = new byte[4096]; + } + + using (var inputStream = zipFile_.GetInputStream(entry)) + { + if ((events_ != null) && (events_.Progress != null)) + { + StreamUtils.Copy(inputStream, outputStream, buffer_, + events_.Progress, events_.ProgressInterval, this, entry.Name, entry.Size); + } + else + { + StreamUtils.Copy(inputStream, outputStream, buffer_); + } + } + + if (events_ != null) + { + continueRunning_ = events_.OnCompletedFile(entry.Name); + } + } + + if (restoreDateTimeOnExtract_) + { + switch (entryFactory_.Setting) + { + case TimeSetting.CreateTime: + File.SetCreationTime(targetName, entry.DateTime); + break; + + case TimeSetting.CreateTimeUtc: + File.SetCreationTimeUtc(targetName, entry.DateTime); + break; + + case TimeSetting.LastAccessTime: + File.SetLastAccessTime(targetName, entry.DateTime); + break; + + case TimeSetting.LastAccessTimeUtc: + File.SetLastAccessTimeUtc(targetName, entry.DateTime); + break; + + case TimeSetting.LastWriteTime: + File.SetLastWriteTime(targetName, entry.DateTime); + break; + + case TimeSetting.LastWriteTimeUtc: + File.SetLastWriteTimeUtc(targetName, entry.DateTime); + break; + + case TimeSetting.Fixed: + File.SetLastWriteTime(targetName, entryFactory_.FixedDateTime); + break; + + default: + throw new ZipException("Unhandled time setting in ExtractFileEntry"); + } + } + + if (RestoreAttributesOnExtract && entry.IsDOSEntry && (entry.ExternalFileAttributes != -1)) + { + var fileAttributes = (FileAttributes)entry.ExternalFileAttributes; + // TODO: FastZip - Setting of other file attributes on extraction is a little trickier. + fileAttributes &= (FileAttributes.Archive | FileAttributes.Normal | FileAttributes.ReadOnly | FileAttributes.Hidden); + File.SetAttributes(targetName, fileAttributes); + } + } + catch (Exception ex) + { + if (events_ != null) + { + continueRunning_ = events_.OnFileFailure(targetName, ex); + } + else + { + continueRunning_ = false; + throw; + } + } + } + } + } + + private void ExtractEntry(ZipEntry entry) + { + bool doExtraction = entry.IsCompressionMethodSupported(); + string targetName = entry.Name; + + if (doExtraction) + { + if (entry.IsFile) + { + targetName = extractNameTransform_.TransformFile(targetName); + } + else if (entry.IsDirectory) + { + targetName = extractNameTransform_.TransformDirectory(targetName); + } + + doExtraction = !(string.IsNullOrEmpty(targetName)); + } + + // TODO: Fire delegate/throw exception were compression method not supported, or name is invalid? + + string dirName = string.Empty; + + if (doExtraction) + { + if (entry.IsDirectory) + { + dirName = targetName; + } + else + { + dirName = Path.GetDirectoryName(Path.GetFullPath(targetName)); + } + } + + if (doExtraction && !Directory.Exists(dirName)) + { + if (!entry.IsDirectory || CreateEmptyDirectories) + { + try + { + continueRunning_ = events_?.OnProcessDirectory(dirName, true) ?? true; + if (continueRunning_) + { + Directory.CreateDirectory(dirName); + if (entry.IsDirectory && restoreDateTimeOnExtract_) + { + switch (entryFactory_.Setting) + { + case TimeSetting.CreateTime: + Directory.SetCreationTime(dirName, entry.DateTime); + break; + + case TimeSetting.CreateTimeUtc: + Directory.SetCreationTimeUtc(dirName, entry.DateTime); + break; + + case TimeSetting.LastAccessTime: + Directory.SetLastAccessTime(dirName, entry.DateTime); + break; + + case TimeSetting.LastAccessTimeUtc: + Directory.SetLastAccessTimeUtc(dirName, entry.DateTime); + break; + + case TimeSetting.LastWriteTime: + Directory.SetLastWriteTime(dirName, entry.DateTime); + break; + + case TimeSetting.LastWriteTimeUtc: + Directory.SetLastWriteTimeUtc(dirName, entry.DateTime); + break; + + case TimeSetting.Fixed: + Directory.SetLastWriteTime(dirName, entryFactory_.FixedDateTime); + break; + + default: + throw new ZipException("Unhandled time setting in ExtractEntry"); + } + } + } + else + { + doExtraction = false; + } + } + catch (Exception ex) + { + doExtraction = false; + if (events_ != null) + { + if (entry.IsDirectory) + { + continueRunning_ = events_.OnDirectoryFailure(targetName, ex); + } + else + { + continueRunning_ = events_.OnFileFailure(targetName, ex); + } + } + else + { + continueRunning_ = false; + throw; + } + } + } + } + + if (doExtraction && entry.IsFile) + { + ExtractFileEntry(entry, targetName); + } + } + + private static int MakeExternalAttributes(FileInfo info) + { + return (int)info.Attributes; + } + + private static bool NameIsValid(string name) + { + return !string.IsNullOrEmpty(name) && + (name.IndexOfAny(Path.GetInvalidPathChars()) < 0); + } + + #endregion Internal Processing + + #region Instance Fields + + private bool continueRunning_; + private byte[] buffer_; + private ZipOutputStream outputStream_; + private ZipFile zipFile_; + private string sourceDirectory_; + private NameFilter fileFilter_; + private NameFilter directoryFilter_; + private Overwrite overwrite_; + private ConfirmOverwriteDelegate confirmDelegate_; + + private bool restoreDateTimeOnExtract_; + private bool restoreAttributesOnExtract_; + private bool createEmptyDirectories_; + private FastZipEvents events_; + private IEntryFactory entryFactory_ = new ZipEntryFactory(); + private INameTransform extractNameTransform_; + private UseZip64 useZip64_ = UseZip64.Dynamic; + private CompressionLevel compressionLevel_ = CompressionLevel.DEFAULT_COMPRESSION; + + private string password_; + + #endregion Instance Fields + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/IEntryFactory.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/IEntryFactory.cs new file mode 100644 index 0000000..d7ec181 --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/IEntryFactory.cs @@ -0,0 +1,67 @@ +using System; +using ICSharpCode.SharpZipLib.Core; +using static ICSharpCode.SharpZipLib.Zip.ZipEntryFactory; + +namespace ICSharpCode.SharpZipLib.Zip +{ + /// + /// Defines factory methods for creating new values. + /// + public interface IEntryFactory + { + /// + /// Create a for a file given its name + /// + /// The name of the file to create an entry for. + /// Returns a file entry based on the passed. + ZipEntry MakeFileEntry(string fileName); + + /// + /// Create a for a file given its name + /// + /// The name of the file to create an entry for. + /// If true get details from the file system if the file exists. + /// Returns a file entry based on the passed. + ZipEntry MakeFileEntry(string fileName, bool useFileSystem); + + /// + /// Create a for a file given its actual name and optional override name + /// + /// The name of the file to create an entry for. + /// An alternative name to be used for the new entry. Null if not applicable. + /// If true get details from the file system if the file exists. + /// Returns a file entry based on the passed. + ZipEntry MakeFileEntry(string fileName, string entryName, bool useFileSystem); + + /// + /// Create a for a directory given its name + /// + /// The name of the directory to create an entry for. + /// Returns a directory entry based on the passed. + ZipEntry MakeDirectoryEntry(string directoryName); + + /// + /// Create a for a directory given its name + /// + /// The name of the directory to create an entry for. + /// If true get details from the file system for this directory if it exists. + /// Returns a directory entry based on the passed. + ZipEntry MakeDirectoryEntry(string directoryName, bool useFileSystem); + + /// + /// Get/set the applicable. + /// + INameTransform NameTransform { get; set; } + + /// + /// Get the in use. + /// + TimeSetting Setting { get; } + + /// + /// Get the value to use when is set to , + /// or if not specified, the value of when the class was the initialized + /// + DateTime FixedDateTime { get; } + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/WindowsNameTransform.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/WindowsNameTransform.cs new file mode 100644 index 0000000..43aa614 --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/WindowsNameTransform.cs @@ -0,0 +1,266 @@ +using ICSharpCode.SharpZipLib.Core; +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace ICSharpCode.SharpZipLib.Zip +{ + /// + /// WindowsNameTransform transforms names to windows compatible ones. + /// + public class WindowsNameTransform : INameTransform + { + /// + /// The maximum windows path name permitted. + /// + /// This may not valid for all windows systems - CE?, etc but I cant find the equivalent in the CLR. + private const int MaxPath = 260; + + private string _baseDirectory; + private bool _trimIncomingPaths; + private char _replacementChar = '_'; + private bool _allowParentTraversal; + + /// + /// In this case we need Windows' invalid path characters. + /// Path.GetInvalidPathChars() only returns a subset invalid on all platforms. + /// + private static readonly char[] InvalidEntryChars = new char[] { + '"', '<', '>', '|', '\0', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', + '\u0006', '\a', '\b', '\t', '\n', '\v', '\f', '\r', '\u000e', '\u000f', + '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', + '\u0017', '\u0018', '\u0019', '\u001a', '\u001b', '\u001c', '\u001d', + '\u001e', '\u001f', + // extra characters for masks, etc. + '*', '?', ':' + }; + + /// + /// Initialises a new instance of + /// + /// + /// Allow parent directory traversal in file paths (e.g. ../file) + public WindowsNameTransform(string baseDirectory, bool allowParentTraversal = false) + { + BaseDirectory = baseDirectory ?? throw new ArgumentNullException(nameof(baseDirectory), "Directory name is invalid"); + AllowParentTraversal = allowParentTraversal; + } + + /// + /// Initialise a default instance of + /// + public WindowsNameTransform() + { + // Do nothing. + } + + /// + /// Gets or sets a value containing the target directory to prefix values with. + /// + public string BaseDirectory + { + get { return _baseDirectory; } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _baseDirectory = Path.GetFullPath(value); + } + } + + /// + /// Allow parent directory traversal in file paths (e.g. ../file) + /// + public bool AllowParentTraversal + { + get => _allowParentTraversal; + set => _allowParentTraversal = value; + } + + /// + /// Gets or sets a value indicating whether paths on incoming values should be removed. + /// + public bool TrimIncomingPaths + { + get { return _trimIncomingPaths; } + set { _trimIncomingPaths = value; } + } + + /// + /// Transform a Zip directory name to a windows directory name. + /// + /// The directory name to transform. + /// The transformed name. + public string TransformDirectory(string name) + { + name = TransformFile(name); + if (name.Length > 0) + { + while (name.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) + { + name = name.Remove(name.Length - 1, 1); + } + } + else + { + throw new InvalidNameException("Cannot have an empty directory name"); + } + return name; + } + + /// + /// Transform a Zip format file name to a windows style one. + /// + /// The file name to transform. + /// The transformed name. + public string TransformFile(string name) + { + if (name != null) + { + name = MakeValidName(name, _replacementChar); + + if (_trimIncomingPaths) + { + name = Path.GetFileName(name); + } + + // This may exceed windows length restrictions. + // Combine will throw a PathTooLongException in that case. + if (_baseDirectory != null) + { + name = Path.Combine(_baseDirectory, name); + + // Ensure base directory ends with directory separator ('/' or '\' depending on OS) + var pathBase = Path.GetFullPath(_baseDirectory); + if (pathBase[pathBase.Length - 1] != Path.DirectorySeparatorChar) + { + pathBase += Path.DirectorySeparatorChar; + } + + if (!_allowParentTraversal && !Path.GetFullPath(name).StartsWith(pathBase, StringComparison.InvariantCultureIgnoreCase)) + { + throw new InvalidNameException("Parent traversal in paths is not allowed"); + } + } + } + else + { + name = string.Empty; + } + return name; + } + + /// + /// Test a name to see if it is a valid name for a windows filename as extracted from a Zip archive. + /// + /// The name to test. + /// Returns true if the name is a valid zip name; false otherwise. + /// The filename isnt a true windows path in some fundamental ways like no absolute paths, no rooted paths etc. + public static bool IsValidName(string name) + { + bool result = + (name != null) && + (name.Length <= MaxPath) && + (string.Compare(name, MakeValidName(name, '_'), StringComparison.Ordinal) == 0) + ; + + return result; + } + + /// + /// Force a name to be valid by replacing invalid characters with a fixed value + /// + /// The name to make valid + /// The replacement character to use for any invalid characters. + /// Returns a valid name + public static string MakeValidName(string name, char replacement) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + name = PathUtils.DropPathRoot(name.Replace("/", Path.DirectorySeparatorChar.ToString())); + + // Drop any leading slashes. + while ((name.Length > 0) && (name[0] == Path.DirectorySeparatorChar)) + { + name = name.Remove(0, 1); + } + + // Drop any trailing slashes. + while ((name.Length > 0) && (name[name.Length - 1] == Path.DirectorySeparatorChar)) + { + name = name.Remove(name.Length - 1, 1); + } + + // Convert consecutive \\ characters to \ + int index = name.IndexOf(string.Format("{0}{0}", Path.DirectorySeparatorChar), StringComparison.Ordinal); + while (index >= 0) + { + name = name.Remove(index, 1); + index = name.IndexOf(string.Format("{0}{0}", Path.DirectorySeparatorChar), StringComparison.Ordinal); + } + + // Convert any invalid characters using the replacement one. + index = name.IndexOfAny(InvalidEntryChars); + if (index >= 0) + { + var builder = new StringBuilder(name); + + while (index >= 0) + { + builder[index] = replacement; + + if (index >= name.Length) + { + index = -1; + } + else + { + index = name.IndexOfAny(InvalidEntryChars, index + 1); + } + } + name = builder.ToString(); + } + + // Check for names greater than MaxPath characters. + // TODO: Were is CLR version of MaxPath defined? Can't find it in Environment. + if (name.Length > MaxPath) + { + throw new PathTooLongException(); + } + + return name; + } + + /// + /// Gets or set the character to replace invalid characters during transformations. + /// + public char Replacement + { + get { return _replacementChar; } + set + { + for (int i = 0; i < InvalidEntryChars.Length; ++i) + { + if (InvalidEntryChars[i] == value) + { + throw new ArgumentException("invalid path character"); + } + } + + if ((value == Path.DirectorySeparatorChar) || (value == Path.AltDirectorySeparatorChar)) + { + throw new ArgumentException("invalid replacement character"); + } + + _replacementChar = value; + } + } + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipConstants.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipConstants.cs new file mode 100644 index 0000000..eadf339 --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipConstants.cs @@ -0,0 +1,518 @@ +using System; + +namespace ICSharpCode.SharpZipLib.Zip +{ + #region Enumerations + + /// + /// Determines how entries are tested to see if they should use Zip64 extensions or not. + /// + public enum UseZip64 + { + /// + /// Zip64 will not be forced on entries during processing. + /// + /// An entry can have this overridden if required + Off, + + /// + /// Zip64 should always be used. + /// + On, + + /// + /// #ZipLib will determine use based on entry values when added to archive. + /// + Dynamic, + } + + /// + /// The kind of compression used for an entry in an archive + /// + public enum CompressionMethod + { + /// + /// A direct copy of the file contents is held in the archive + /// + Stored = 0, + + /// + /// Common Zip compression method using a sliding dictionary + /// of up to 32KB and secondary compression from Huffman/Shannon-Fano trees + /// + Deflated = 8, + + /// + /// An extension to deflate with a 64KB window. Not supported by #Zip currently + /// + Deflate64 = 9, + + /// + /// BZip2 compression. Not supported by #Zip. + /// + BZip2 = 12, + + /// + /// LZMA compression. Not supported by #Zip. + /// + LZMA = 14, + + /// + /// PPMd compression. Not supported by #Zip. + /// + PPMd = 98, + + /// + /// WinZip special for AES encryption, Now supported by #Zip. + /// + WinZipAES = 99, + } + + /// + /// Identifies the encryption algorithm used for an entry + /// + public enum EncryptionAlgorithm + { + /// + /// No encryption has been used. + /// + None = 0, + + /// + /// Encrypted using PKZIP 2.0 or 'classic' encryption. + /// + PkzipClassic = 1, + + /// + /// DES encryption has been used. + /// + Des = 0x6601, + + /// + /// RC2 encryption has been used for encryption. + /// + RC2 = 0x6602, + + /// + /// Triple DES encryption with 168 bit keys has been used for this entry. + /// + TripleDes168 = 0x6603, + + /// + /// Triple DES with 112 bit keys has been used for this entry. + /// + TripleDes112 = 0x6609, + + /// + /// AES 128 has been used for encryption. + /// + Aes128 = 0x660e, + + /// + /// AES 192 has been used for encryption. + /// + Aes192 = 0x660f, + + /// + /// AES 256 has been used for encryption. + /// + Aes256 = 0x6610, + + /// + /// RC2 corrected has been used for encryption. + /// + RC2Corrected = 0x6702, + + /// + /// Blowfish has been used for encryption. + /// + Blowfish = 0x6720, + + /// + /// Twofish has been used for encryption. + /// + Twofish = 0x6721, + + /// + /// RC4 has been used for encryption. + /// + RC4 = 0x6801, + + /// + /// An unknown algorithm has been used for encryption. + /// + Unknown = 0xffff + } + + /// + /// Defines the contents of the general bit flags field for an archive entry. + /// + [Flags] + public enum GeneralBitFlags + { + /// + /// Bit 0 if set indicates that the file is encrypted + /// + Encrypted = 0x0001, + + /// + /// Bits 1 and 2 - Two bits defining the compression method (only for Method 6 Imploding and 8,9 Deflating) + /// + Method = 0x0006, + + /// + /// Bit 3 if set indicates a trailing data descriptor is appended to the entry data + /// + Descriptor = 0x0008, + + /// + /// Bit 4 is reserved for use with method 8 for enhanced deflation + /// + ReservedPKware4 = 0x0010, + + /// + /// Bit 5 if set indicates the file contains Pkzip compressed patched data. + /// Requires version 2.7 or greater. + /// + Patched = 0x0020, + + /// + /// Bit 6 if set indicates strong encryption has been used for this entry. + /// + StrongEncryption = 0x0040, + + /// + /// Bit 7 is currently unused + /// + Unused7 = 0x0080, + + /// + /// Bit 8 is currently unused + /// + Unused8 = 0x0100, + + /// + /// Bit 9 is currently unused + /// + Unused9 = 0x0200, + + /// + /// Bit 10 is currently unused + /// + Unused10 = 0x0400, + + /// + /// Bit 11 if set indicates the filename and + /// comment fields for this file must be encoded using UTF-8. + /// + UnicodeText = 0x0800, + + /// + /// Bit 12 is documented as being reserved by PKware for enhanced compression. + /// + EnhancedCompress = 0x1000, + + /// + /// Bit 13 if set indicates that values in the local header are masked to hide + /// their actual values, and the central directory is encrypted. + /// + /// + /// Used when encrypting the central directory contents. + /// + HeaderMasked = 0x2000, + + /// + /// Bit 14 is documented as being reserved for use by PKware + /// + ReservedPkware14 = 0x4000, + + /// + /// Bit 15 is documented as being reserved for use by PKware + /// + ReservedPkware15 = 0x8000 + } + + #endregion Enumerations + + /// + /// This class contains constants used for Zip format files + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")] + public static class ZipConstants + { + #region Versions + + /// + /// The version made by field for entries in the central header when created by this library + /// + /// + /// This is also the Zip version for the library when comparing against the version required to extract + /// for an entry. See . + /// + public const int VersionMadeBy = 51; // was 45 before AES + + /// + /// The version made by field for entries in the central header when created by this library + /// + /// + /// This is also the Zip version for the library when comparing against the version required to extract + /// for an entry. See ZipInputStream.CanDecompressEntry. + /// + [Obsolete("Use VersionMadeBy instead")] + public const int VERSION_MADE_BY = 51; + + /// + /// The minimum version required to support strong encryption + /// + public const int VersionStrongEncryption = 50; + + /// + /// The minimum version required to support strong encryption + /// + [Obsolete("Use VersionStrongEncryption instead")] + public const int VERSION_STRONG_ENCRYPTION = 50; + + /// + /// Version indicating AES encryption + /// + public const int VERSION_AES = 51; + + /// + /// The version required for Zip64 extensions (4.5 or higher) + /// + public const int VersionZip64 = 45; + + /// + /// The version required for BZip2 compression (4.6 or higher) + /// + public const int VersionBZip2 = 46; + + #endregion Versions + + #region Header Sizes + + /// + /// Size of local entry header (excluding variable length fields at end) + /// + public const int LocalHeaderBaseSize = 30; + + /// + /// Size of local entry header (excluding variable length fields at end) + /// + [Obsolete("Use LocalHeaderBaseSize instead")] + public const int LOCHDR = 30; + + /// + /// Size of Zip64 data descriptor + /// + public const int Zip64DataDescriptorSize = 24; + + /// + /// Size of data descriptor + /// + public const int DataDescriptorSize = 16; + + /// + /// Size of data descriptor + /// + [Obsolete("Use DataDescriptorSize instead")] + public const int EXTHDR = 16; + + /// + /// Size of central header entry (excluding variable fields) + /// + public const int CentralHeaderBaseSize = 46; + + /// + /// Size of central header entry + /// + [Obsolete("Use CentralHeaderBaseSize instead")] + public const int CENHDR = 46; + + /// + /// Size of end of central record (excluding variable fields) + /// + public const int EndOfCentralRecordBaseSize = 22; + + /// + /// Size of end of central record (excluding variable fields) + /// + [Obsolete("Use EndOfCentralRecordBaseSize instead")] + public const int ENDHDR = 22; + + /// + /// Size of 'classic' cryptographic header stored before any entry data + /// + public const int CryptoHeaderSize = 12; + + /// + /// Size of cryptographic header stored before entry data + /// + [Obsolete("Use CryptoHeaderSize instead")] + public const int CRYPTO_HEADER_SIZE = 12; + + /// + /// The size of the Zip64 central directory locator. + /// + public const int Zip64EndOfCentralDirectoryLocatorSize = 20; + + #endregion Header Sizes + + #region Header Signatures + + /// + /// Signature for local entry header + /// + public const int LocalHeaderSignature = 'P' | ('K' << 8) | (3 << 16) | (4 << 24); + + /// + /// Signature for local entry header + /// + [Obsolete("Use LocalHeaderSignature instead")] + public const int LOCSIG = 'P' | ('K' << 8) | (3 << 16) | (4 << 24); + + /// + /// Signature for spanning entry + /// + public const int SpanningSignature = 'P' | ('K' << 8) | (7 << 16) | (8 << 24); + + /// + /// Signature for spanning entry + /// + [Obsolete("Use SpanningSignature instead")] + public const int SPANNINGSIG = 'P' | ('K' << 8) | (7 << 16) | (8 << 24); + + /// + /// Signature for temporary spanning entry + /// + public const int SpanningTempSignature = 'P' | ('K' << 8) | ('0' << 16) | ('0' << 24); + + /// + /// Signature for temporary spanning entry + /// + [Obsolete("Use SpanningTempSignature instead")] + public const int SPANTEMPSIG = 'P' | ('K' << 8) | ('0' << 16) | ('0' << 24); + + /// + /// Signature for data descriptor + /// + /// + /// This is only used where the length, Crc, or compressed size isnt known when the + /// entry is created and the output stream doesnt support seeking. + /// The local entry cannot be 'patched' with the correct values in this case + /// so the values are recorded after the data prefixed by this header, as well as in the central directory. + /// + public const int DataDescriptorSignature = 'P' | ('K' << 8) | (7 << 16) | (8 << 24); + + /// + /// Signature for data descriptor + /// + /// + /// This is only used where the length, Crc, or compressed size isnt known when the + /// entry is created and the output stream doesnt support seeking. + /// The local entry cannot be 'patched' with the correct values in this case + /// so the values are recorded after the data prefixed by this header, as well as in the central directory. + /// + [Obsolete("Use DataDescriptorSignature instead")] + public const int EXTSIG = 'P' | ('K' << 8) | (7 << 16) | (8 << 24); + + /// + /// Signature for central header + /// + [Obsolete("Use CentralHeaderSignature instead")] + public const int CENSIG = 'P' | ('K' << 8) | (1 << 16) | (2 << 24); + + /// + /// Signature for central header + /// + public const int CentralHeaderSignature = 'P' | ('K' << 8) | (1 << 16) | (2 << 24); + + /// + /// Signature for Zip64 central file header + /// + public const int Zip64CentralFileHeaderSignature = 'P' | ('K' << 8) | (6 << 16) | (6 << 24); + + /// + /// Signature for Zip64 central file header + /// + [Obsolete("Use Zip64CentralFileHeaderSignature instead")] + public const int CENSIG64 = 'P' | ('K' << 8) | (6 << 16) | (6 << 24); + + /// + /// Signature for Zip64 central directory locator + /// + public const int Zip64CentralDirLocatorSignature = 'P' | ('K' << 8) | (6 << 16) | (7 << 24); + + /// + /// Signature for archive extra data signature (were headers are encrypted). + /// + public const int ArchiveExtraDataSignature = 'P' | ('K' << 8) | (6 << 16) | (7 << 24); + + /// + /// Central header digital signature + /// + public const int CentralHeaderDigitalSignature = 'P' | ('K' << 8) | (5 << 16) | (5 << 24); + + /// + /// Central header digital signature + /// + [Obsolete("Use CentralHeaderDigitalSignaure instead")] + public const int CENDIGITALSIG = 'P' | ('K' << 8) | (5 << 16) | (5 << 24); + + /// + /// End of central directory record signature + /// + public const int EndOfCentralDirectorySignature = 'P' | ('K' << 8) | (5 << 16) | (6 << 24); + + /// + /// End of central directory record signature + /// + [Obsolete("Use EndOfCentralDirectorySignature instead")] + public const int ENDSIG = 'P' | ('K' << 8) | (5 << 16) | (6 << 24); + + #endregion Header Signatures + + /// + /// Default encoding used for string conversion. 0 gives the default system OEM code page. + /// Using the default code page isnt the full solution necessarily + /// there are many variable factors, codepage 850 is often a good choice for + /// European users, however be careful about compatability. + /// + [Obsolete("Use ZipStrings instead")] + public static int DefaultCodePage + { + get => ZipStrings.CodePage; + set => ZipStrings.CodePage = value; + } + + /// Deprecated wrapper for + [Obsolete("Use ZipStrings.ConvertToString instead")] + public static string ConvertToString(byte[] data, int count) + => ZipStrings.ConvertToString(data, count); + + /// Deprecated wrapper for + [Obsolete("Use ZipStrings.ConvertToString instead")] + public static string ConvertToString(byte[] data) + => ZipStrings.ConvertToString(data); + + /// Deprecated wrapper for + [Obsolete("Use ZipStrings.ConvertToStringExt instead")] + public static string ConvertToStringExt(int flags, byte[] data, int count) + => ZipStrings.ConvertToStringExt(flags, data, count); + + /// Deprecated wrapper for + [Obsolete("Use ZipStrings.ConvertToStringExt instead")] + public static string ConvertToStringExt(int flags, byte[] data) + => ZipStrings.ConvertToStringExt(flags, data); + + /// Deprecated wrapper for + [Obsolete("Use ZipStrings.ConvertToArray instead")] + public static byte[] ConvertToArray(string str) + => ZipStrings.ConvertToArray(str); + + /// Deprecated wrapper for + [Obsolete("Use ZipStrings.ConvertToArray instead")] + public static byte[] ConvertToArray(int flags, string str) + => ZipStrings.ConvertToArray(flags, str); + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipEncryptionMethod.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipEncryptionMethod.cs new file mode 100644 index 0000000..ed51559 --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipEncryptionMethod.cs @@ -0,0 +1,28 @@ +namespace ICSharpCode.SharpZipLib.Zip +{ + /// + /// The method of encrypting entries when creating zip archives. + /// + public enum ZipEncryptionMethod + { + /// + /// No encryption will be used. + /// + None, + + /// + /// Encrypt entries with ZipCrypto. + /// + ZipCrypto, + + /// + /// Encrypt entries with AES 128. + /// + AES128, + + /// + /// Encrypt entries with AES 256. + /// + AES256 + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs new file mode 100644 index 0000000..f14b005 --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs @@ -0,0 +1,1155 @@ +using System; +using System.IO; + +namespace ICSharpCode.SharpZipLib.Zip +{ + /// + /// Defines known values for the property. + /// + public enum HostSystemID + { + /// + /// Host system = MSDOS + /// + Msdos = 0, + + /// + /// Host system = Amiga + /// + Amiga = 1, + + /// + /// Host system = Open VMS + /// + OpenVms = 2, + + /// + /// Host system = Unix + /// + Unix = 3, + + /// + /// Host system = VMCms + /// + VMCms = 4, + + /// + /// Host system = Atari ST + /// + AtariST = 5, + + /// + /// Host system = OS2 + /// + OS2 = 6, + + /// + /// Host system = Macintosh + /// + Macintosh = 7, + + /// + /// Host system = ZSystem + /// + ZSystem = 8, + + /// + /// Host system = Cpm + /// + Cpm = 9, + + /// + /// Host system = Windows NT + /// + WindowsNT = 10, + + /// + /// Host system = MVS + /// + MVS = 11, + + /// + /// Host system = VSE + /// + Vse = 12, + + /// + /// Host system = Acorn RISC + /// + AcornRisc = 13, + + /// + /// Host system = VFAT + /// + Vfat = 14, + + /// + /// Host system = Alternate MVS + /// + AlternateMvs = 15, + + /// + /// Host system = BEOS + /// + BeOS = 16, + + /// + /// Host system = Tandem + /// + Tandem = 17, + + /// + /// Host system = OS400 + /// + OS400 = 18, + + /// + /// Host system = OSX + /// + OSX = 19, + + /// + /// Host system = WinZIP AES + /// + WinZipAES = 99, + } + + /// + /// This class represents an entry in a zip archive. This can be a file + /// or a directory + /// ZipFile and ZipInputStream will give you instances of this class as + /// information about the members in an archive. ZipOutputStream + /// uses an instance of this class when creating an entry in a Zip file. + ///
+ ///
Author of the original java version : Jochen Hoenicke + ///
+ public class ZipEntry + { + [Flags] + private enum Known : byte + { + None = 0, + Size = 0x01, + CompressedSize = 0x02, + Crc = 0x04, + Time = 0x08, + ExternalAttributes = 0x10, + } + + #region Constructors + + /// + /// Creates a zip entry with the given name. + /// + /// + /// The name for this entry. Can include directory components. + /// The convention for names is 'unix' style paths with relative names only. + /// There are with no device names and path elements are separated by '/' characters. + /// + /// + /// The name passed is null + /// + public ZipEntry(string name) + : this(name, 0, ZipConstants.VersionMadeBy, CompressionMethod.Deflated) + { + } + + /// + /// Creates a zip entry with the given name and version required to extract + /// + /// + /// The name for this entry. Can include directory components. + /// The convention for names is 'unix' style paths with no device names and + /// path elements separated by '/' characters. This is not enforced see CleanName + /// on how to ensure names are valid if this is desired. + /// + /// + /// The minimum 'feature version' required this entry + /// + /// + /// The name passed is null + /// + internal ZipEntry(string name, int versionRequiredToExtract) + : this(name, versionRequiredToExtract, ZipConstants.VersionMadeBy, + CompressionMethod.Deflated) + { + } + + /// + /// Initializes an entry with the given name and made by information + /// + /// Name for this entry + /// Version and HostSystem Information + /// Minimum required zip feature version required to extract this entry + /// Compression method for this entry. + /// + /// The name passed is null + /// + /// + /// versionRequiredToExtract should be 0 (auto-calculate) or > 10 + /// + /// + /// This constructor is used by the ZipFile class when reading from the central header + /// It is not generally useful, use the constructor specifying the name only. + /// + internal ZipEntry(string name, int versionRequiredToExtract, int madeByInfo, + CompressionMethod method) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (name.Length > 0xffff) + { + throw new ArgumentException("Name is too long", nameof(name)); + } + + if ((versionRequiredToExtract != 0) && (versionRequiredToExtract < 10)) + { + throw new ArgumentOutOfRangeException(nameof(versionRequiredToExtract)); + } + + this.DateTime = DateTime.Now; + this.name = name; + this.versionMadeBy = (ushort)madeByInfo; + this.versionToExtract = (ushort)versionRequiredToExtract; + this.method = method; + + IsUnicodeText = ZipStrings.UseUnicode; + } + + /// + /// Creates a deep copy of the given zip entry. + /// + /// + /// The entry to copy. + /// + [Obsolete("Use Clone instead")] + public ZipEntry(ZipEntry entry) + { + if (entry == null) + { + throw new ArgumentNullException(nameof(entry)); + } + + known = entry.known; + name = entry.name; + size = entry.size; + compressedSize = entry.compressedSize; + crc = entry.crc; + dateTime = entry.DateTime; + method = entry.method; + comment = entry.comment; + versionToExtract = entry.versionToExtract; + versionMadeBy = entry.versionMadeBy; + externalFileAttributes = entry.externalFileAttributes; + flags = entry.flags; + + zipFileIndex = entry.zipFileIndex; + offset = entry.offset; + + forceZip64_ = entry.forceZip64_; + + if (entry.extra != null) + { + extra = new byte[entry.extra.Length]; + Array.Copy(entry.extra, 0, extra, 0, entry.extra.Length); + } + } + + #endregion Constructors + + /// + /// Get a value indicating whether the entry has a CRC value available. + /// + public bool HasCrc => (known & Known.Crc) != 0; + + /// + /// Get/Set flag indicating if entry is encrypted. + /// A simple helper routine to aid interpretation of flags + /// + /// This is an assistant that interprets the flags property. + public bool IsCrypted + { + get => this.HasFlag(GeneralBitFlags.Encrypted); + set => this.SetFlag(GeneralBitFlags.Encrypted, value); + } + + /// + /// Get / set a flag indicating whether entry name and comment text are + /// encoded in unicode UTF8. + /// + /// This is an assistant that interprets the flags property. + public bool IsUnicodeText + { + get => this.HasFlag(GeneralBitFlags.UnicodeText); + set => this.SetFlag(GeneralBitFlags.UnicodeText, value); + } + + /// + /// Value used during password checking for PKZIP 2.0 / 'classic' encryption. + /// + internal byte CryptoCheckValue + { + get => cryptoCheckValue_; + set => cryptoCheckValue_ = value; + } + + /// + /// Get/Set general purpose bit flag for entry + /// + /// + /// General purpose bit flag
+ ///
+ /// Bit 0: If set, indicates the file is encrypted
+ /// Bit 1-2 Only used for compression type 6 Imploding, and 8, 9 deflating
+ /// Imploding:
+ /// Bit 1 if set indicates an 8K sliding dictionary was used. If clear a 4k dictionary was used
+ /// Bit 2 if set indicates 3 Shannon-Fanno trees were used to encode the sliding dictionary, 2 otherwise
+ ///
+ /// Deflating:
+ /// Bit 2 Bit 1
+ /// 0 0 Normal compression was used
+ /// 0 1 Maximum compression was used
+ /// 1 0 Fast compression was used
+ /// 1 1 Super fast compression was used
+ ///
+ /// Bit 3: If set, the fields crc-32, compressed size + /// and uncompressed size are were not able to be written during zip file creation + /// The correct values are held in a data descriptor immediately following the compressed data.
+ /// Bit 4: Reserved for use by PKZIP for enhanced deflating
+ /// Bit 5: If set indicates the file contains compressed patch data
+ /// Bit 6: If set indicates strong encryption was used.
+ /// Bit 7-10: Unused or reserved
+ /// Bit 11: If set the name and comments for this entry are in unicode.
+ /// Bit 12-15: Unused or reserved
+ ///
+ /// + /// + public int Flags + { + get => flags; + set => flags = value; + } + + /// + /// Get/Set index of this entry in Zip file + /// + /// This is only valid when the entry is part of a + public long ZipFileIndex + { + get => zipFileIndex; + set => zipFileIndex = value; + } + + /// + /// Get/set offset for use in central header + /// + public long Offset + { + get => offset; + set => offset = value; + } + + /// + /// Get/Set external file attributes as an integer. + /// The values of this are operating system dependent see + /// HostSystem for details + /// + public int ExternalFileAttributes + { + get => (known & Known.ExternalAttributes) == 0 ? -1 : externalFileAttributes; + + set + { + externalFileAttributes = value; + known |= Known.ExternalAttributes; + } + } + + /// + /// Get the version made by for this entry or zero if unknown. + /// The value / 10 indicates the major version number, and + /// the value mod 10 is the minor version number + /// + public int VersionMadeBy => versionMadeBy & 0xff; + + /// + /// Get a value indicating this entry is for a DOS/Windows system. + /// + public bool IsDOSEntry + => (HostSystem == (int)HostSystemID.Msdos) + || (HostSystem == (int)HostSystemID.WindowsNT); + + /// + /// Test the external attributes for this to + /// see if the external attributes are Dos based (including WINNT and variants) + /// and match the values + /// + /// The attributes to test. + /// Returns true if the external attributes are known to be DOS/Windows + /// based and have the same attributes set as the value passed. + private bool HasDosAttributes(int attributes) + { + bool result = false; + if ((known & Known.ExternalAttributes) != 0) + { + result |= (((HostSystem == (int)HostSystemID.Msdos) || + (HostSystem == (int)HostSystemID.WindowsNT)) && + (ExternalFileAttributes & attributes) == attributes); + } + return result; + } + + /// + /// Gets the compatibility information for the external file attribute + /// If the external file attributes are compatible with MS-DOS and can be read + /// by PKZIP for DOS version 2.04g then this value will be zero. Otherwise the value + /// will be non-zero and identify the host system on which the attributes are compatible. + /// + /// + /// + /// The values for this as defined in the Zip File format and by others are shown below. The values are somewhat + /// misleading in some cases as they are not all used as shown. You should consult the relevant documentation + /// to obtain up to date and correct information. The modified appnote by the infozip group is + /// particularly helpful as it documents a lot of peculiarities. The document is however a little dated. + /// + /// 0 - MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems) + /// 1 - Amiga + /// 2 - OpenVMS + /// 3 - Unix + /// 4 - VM/CMS + /// 5 - Atari ST + /// 6 - OS/2 HPFS + /// 7 - Macintosh + /// 8 - Z-System + /// 9 - CP/M + /// 10 - Windows NTFS + /// 11 - MVS (OS/390 - Z/OS) + /// 12 - VSE + /// 13 - Acorn Risc + /// 14 - VFAT + /// 15 - Alternate MVS + /// 16 - BeOS + /// 17 - Tandem + /// 18 - OS/400 + /// 19 - OS/X (Darwin) + /// 99 - WinZip AES + /// remainder - unused + /// + /// + public int HostSystem + { + get => (versionMadeBy >> 8) & 0xff; + + set + { + versionMadeBy &= 0x00ff; + versionMadeBy |= (ushort)((value & 0xff) << 8); + } + } + + /// + /// Get minimum Zip feature version required to extract this entry + /// + /// + /// Minimum features are defined as:
+ /// 1.0 - Default value
+ /// 1.1 - File is a volume label
+ /// 2.0 - File is a folder/directory
+ /// 2.0 - File is compressed using Deflate compression
+ /// 2.0 - File is encrypted using traditional encryption
+ /// 2.1 - File is compressed using Deflate64
+ /// 2.5 - File is compressed using PKWARE DCL Implode
+ /// 2.7 - File is a patch data set
+ /// 4.5 - File uses Zip64 format extensions
+ /// 4.6 - File is compressed using BZIP2 compression
+ /// 5.0 - File is encrypted using DES
+ /// 5.0 - File is encrypted using 3DES
+ /// 5.0 - File is encrypted using original RC2 encryption
+ /// 5.0 - File is encrypted using RC4 encryption
+ /// 5.1 - File is encrypted using AES encryption
+ /// 5.1 - File is encrypted using corrected RC2 encryption
+ /// 5.1 - File is encrypted using corrected RC2-64 encryption
+ /// 6.1 - File is encrypted using non-OAEP key wrapping
+ /// 6.2 - Central directory encryption (not confirmed yet)
+ /// 6.3 - File is compressed using LZMA
+ /// 6.3 - File is compressed using PPMD+
+ /// 6.3 - File is encrypted using Blowfish
+ /// 6.3 - File is encrypted using Twofish
+ ///
+ /// + public int Version + { + get + { + // Return recorded version if known. + if (versionToExtract != 0) + // Only lower order byte. High order is O/S file system. + return versionToExtract & 0x00ff; + + if (AESKeySize > 0) + // Ver 5.1 = AES + return ZipConstants.VERSION_AES; + + if (CompressionMethod.BZip2 == method) + return ZipConstants.VersionBZip2; + + if (CentralHeaderRequiresZip64) + return ZipConstants.VersionZip64; + + if (CompressionMethod.Deflated == method || IsDirectory || IsCrypted) + return 20; + + if (HasDosAttributes(0x08)) + return 11; + + return 10; + } + } + + /// + /// Get a value indicating whether this entry can be decompressed by the library. + /// + /// This is based on the and + /// whether the compression method is supported. + public bool CanDecompress + => Version <= ZipConstants.VersionMadeBy + && (Version == 10 || Version == 11 || Version == 20 || Version == 45 || Version == 46 || Version == 51) + && IsCompressionMethodSupported(); + + /// + /// Force this entry to be recorded using Zip64 extensions. + /// + public void ForceZip64() => forceZip64_ = true; + + /// + /// Get a value indicating whether Zip64 extensions were forced. + /// + /// A value of true if Zip64 extensions have been forced on; false if not. + public bool IsZip64Forced() => forceZip64_; + + /// + /// Gets a value indicating if the entry requires Zip64 extensions + /// to store the full entry values. + /// + /// A value of true if a local header requires Zip64 extensions; false if not. + public bool LocalHeaderRequiresZip64 + { + get + { + bool result = forceZip64_; + + if (!result) + { + ulong trueCompressedSize = compressedSize; + + if ((versionToExtract == 0) && IsCrypted) + { + trueCompressedSize += (ulong)this.EncryptionOverheadSize; + } + + // TODO: A better estimation of the true limit based on compression overhead should be used + // to determine when an entry should use Zip64. + result = + ((this.size >= uint.MaxValue) || (trueCompressedSize >= uint.MaxValue)) && + ((versionToExtract == 0) || (versionToExtract >= ZipConstants.VersionZip64)); + } + + return result; + } + } + + /// + /// Get a value indicating whether the central directory entry requires Zip64 extensions to be stored. + /// + public bool CentralHeaderRequiresZip64 + => LocalHeaderRequiresZip64 || (offset >= uint.MaxValue); + + /// + /// Get/Set DosTime value. + /// + /// + /// The MS-DOS date format can only represent dates between 1/1/1980 and 12/31/2107. + /// + public long DosTime + { + get + { + if ((known & Known.Time) == 0) + { + return 0; + } + + var year = (uint)DateTime.Year; + var month = (uint)DateTime.Month; + var day = (uint)DateTime.Day; + var hour = (uint)DateTime.Hour; + var minute = (uint)DateTime.Minute; + var second = (uint)DateTime.Second; + + if (year < 1980) + { + year = 1980; + month = 1; + day = 1; + hour = 0; + minute = 0; + second = 0; + } + else if (year > 2107) + { + year = 2107; + month = 12; + day = 31; + hour = 23; + minute = 59; + second = 59; + } + + return ((year - 1980) & 0x7f) << 25 | + (month << 21) | + (day << 16) | + (hour << 11) | + (minute << 5) | + (second >> 1); + } + + set + { + unchecked + { + var dosTime = (uint)value; + uint sec = Math.Min(59, 2 * (dosTime & 0x1f)); + uint min = Math.Min(59, (dosTime >> 5) & 0x3f); + uint hrs = Math.Min(23, (dosTime >> 11) & 0x1f); + uint mon = Math.Max(1, Math.Min(12, ((uint)(value >> 21) & 0xf))); + uint year = ((dosTime >> 25) & 0x7f) + 1980; + int day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((value >> 16) & 0x1f))); + DateTime = new DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec, DateTimeKind.Unspecified); + } + } + } + + /// + /// Gets/Sets the time of last modification of the entry. + /// + /// + /// The property is updated to match this as far as possible. + /// + public DateTime DateTime + { + get => dateTime; + + set + { + dateTime = value; + known |= Known.Time; + } + } + + /// + /// Returns the entry name. + /// + /// + /// The unix naming convention is followed. + /// Path components in the entry should always separated by forward slashes ('/'). + /// Dos device names like C: should also be removed. + /// See the class, or + /// + public string Name + { + get => name; + internal set => name = value; + } + + /// + /// Gets/Sets the size of the uncompressed data. + /// + /// + /// The size or -1 if unknown. + /// + /// Setting the size before adding an entry to an archive can help + /// avoid compatibility problems with some archivers which don't understand Zip64 extensions. + public long Size + { + get => (known & Known.Size) != 0 ? (long)size : -1L; + set + { + size = (ulong)value; + known |= Known.Size; + } + } + + /// + /// Gets/Sets the size of the compressed data. + /// + /// + /// The compressed entry size or -1 if unknown. + /// + public long CompressedSize + { + get => (known & Known.CompressedSize) != 0 ? (long)compressedSize : -1L; + set + { + compressedSize = (ulong)value; + known |= Known.CompressedSize; + } + } + + /// + /// Gets/Sets the crc of the uncompressed data. + /// + /// + /// Crc is not in the range 0..0xffffffffL + /// + /// + /// The crc value or -1 if unknown. + /// + public long Crc + { + get => (known & Known.Crc) != 0 ? crc & 0xffffffffL : -1L; + set + { + if ((crc & 0xffffffff00000000L) != 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + this.crc = (uint)value; + this.known |= Known.Crc; + } + } + + /// + /// Gets/Sets the compression method. + /// + /// + /// The compression method for this entry + /// + public CompressionMethod CompressionMethod + { + get => method; + set => method = value; + } + + /// + /// Gets the compression method for outputting to the local or central header. + /// Returns same value as CompressionMethod except when AES encrypting, which + /// places 99 in the method and places the real method in the extra data. + /// + internal CompressionMethod CompressionMethodForHeader + => (AESKeySize > 0) ? CompressionMethod.WinZipAES : method; + + /// + /// Gets/Sets the extra data. + /// + /// + /// Extra data is longer than 64KB (0xffff) bytes. + /// + /// + /// Extra data or null if not set. + /// + public byte[] ExtraData + { + // TODO: This is slightly safer but less efficient. Think about whether it should change. + // return (byte[]) extra.Clone(); + get => extra; + + set + { + if (value == null) + { + extra = null; + } + else + { + if (value.Length > 0xffff) + { + throw new System.ArgumentOutOfRangeException(nameof(value)); + } + + extra = new byte[value.Length]; + Array.Copy(value, 0, extra, 0, value.Length); + } + } + } + + /// + /// For AES encrypted files returns or sets the number of bits of encryption (128, 192 or 256). + /// When setting, only 0 (off), 128 or 256 is supported. + /// + public int AESKeySize + { + get + { + // the strength (1 or 3) is in the entry header + switch (_aesEncryptionStrength) + { + case 0: + return 0; // Not AES + case 1: + return 128; + + case 2: + return 192; // Not used by WinZip + case 3: + return 256; + + default: + throw new ZipException("Invalid AESEncryptionStrength " + _aesEncryptionStrength); + } + } + set + { + switch (value) + { + case 0: + _aesEncryptionStrength = 0; + break; + + case 128: + _aesEncryptionStrength = 1; + break; + + case 256: + _aesEncryptionStrength = 3; + break; + + default: + throw new ZipException("AESKeySize must be 0, 128 or 256: " + value); + } + } + } + + /// + /// AES Encryption strength for storage in extra data in entry header. + /// 1 is 128 bit, 2 is 192 bit, 3 is 256 bit. + /// + internal byte AESEncryptionStrength => (byte)_aesEncryptionStrength; + + /// + /// Returns the length of the salt, in bytes + /// + /// Key size -> Salt length: 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes. + internal int AESSaltLen => AESKeySize / 16; + + /// + /// Number of extra bytes required to hold the AES Header fields (Salt, Pwd verify, AuthCode) + /// + /// File format: + /// Bytes | Content + /// ---------+--------------------------- + /// Variable | Salt value + /// 2 | Password verification value + /// Variable | Encrypted file data + /// 10 | Authentication code + internal int AESOverheadSize => 12 + AESSaltLen; + + /// + /// Number of extra bytes required to hold the encryption header fields. + /// + internal int EncryptionOverheadSize => + !IsCrypted + // Entry is not encrypted - no overhead + ? 0 + : _aesEncryptionStrength == 0 + // Entry is encrypted using ZipCrypto + ? ZipConstants.CryptoHeaderSize + // Entry is encrypted using AES + : AESOverheadSize; + + /// + /// Process extra data fields updating the entry based on the contents. + /// + /// True if the extra data fields should be handled + /// for a local header, rather than for a central header. + /// + internal void ProcessExtraData(bool localHeader) + { + var extraData = new ZipExtraData(this.extra); + + if (extraData.Find(0x0001)) + { + // Version required to extract is ignored here as some archivers dont set it correctly + // in theory it should be version 45 or higher + + // The recorded size will change but remember that this is zip64. + forceZip64_ = true; + + if (extraData.ValueLength < 4) + { + throw new ZipException("Extra data extended Zip64 information length is invalid"); + } + + // (localHeader ||) was deleted, because actually there is no specific difference with reading sizes between local header & central directory + // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT + // ... + // 4.4 Explanation of fields + // ... + // 4.4.8 compressed size: (4 bytes) + // 4.4.9 uncompressed size: (4 bytes) + // + // The size of the file compressed (4.4.8) and uncompressed, + // (4.4.9) respectively. When a decryption header is present it + // will be placed in front of the file data and the value of the + // compressed file size will include the bytes of the decryption + // header. If bit 3 of the general purpose bit flag is set, + // these fields are set to zero in the local header and the + // correct values are put in the data descriptor and + // in the central directory. If an archive is in ZIP64 format + // and the value in this field is 0xFFFFFFFF, the size will be + // in the corresponding 8 byte ZIP64 extended information + // extra field. When encrypting the central directory, if the + // local header is not in ZIP64 format and general purpose bit + // flag 13 is set indicating masking, the value stored for the + // uncompressed size in the Local Header will be zero. + // + // Otherwise there is problem with minizip implementation + if (size == uint.MaxValue) + { + size = (ulong)extraData.ReadLong(); + } + + if (compressedSize == uint.MaxValue) + { + compressedSize = (ulong)extraData.ReadLong(); + } + + if (!localHeader && (offset == uint.MaxValue)) + { + offset = extraData.ReadLong(); + } + + // Disk number on which file starts is ignored + } + else + { + if ( + ((versionToExtract & 0xff) >= ZipConstants.VersionZip64) && + ((size == uint.MaxValue) || (compressedSize == uint.MaxValue)) + ) + { + throw new ZipException("Zip64 Extended information required but is missing."); + } + } + + DateTime = GetDateTime(extraData) ?? DateTime; + if (method == CompressionMethod.WinZipAES) + { + ProcessAESExtraData(extraData); + } + } + + private static DateTime? GetDateTime(ZipExtraData extraData) + { + // Check for NT timestamp + // NOTE: Disable by default to match behavior of InfoZIP +#if RESPECT_NT_TIMESTAMP + NTTaggedData ntData = extraData.GetData(); + if (ntData != null) + return ntData.LastModificationTime; +#endif + + // Check for Unix timestamp + ExtendedUnixData unixData = extraData.GetData(); + if (unixData != null && unixData.Include.HasFlag(ExtendedUnixData.Flags.ModificationTime)) + return unixData.ModificationTime; + + return null; + } + + // For AES the method in the entry is 99, and the real compression method is in the extradata + private void ProcessAESExtraData(ZipExtraData extraData) + { + if (extraData.Find(0x9901)) + { + // Set version for Zipfile.CreateAndInitDecryptionStream + versionToExtract = ZipConstants.VERSION_AES; // Ver 5.1 = AES see "Version" getter + + // + // Unpack AES extra data field see http://www.winzip.com/aes_info.htm + int length = extraData.ValueLength; // Data size currently 7 + if (length < 7) + throw new ZipException("AES Extra Data Length " + length + " invalid."); + int ver = extraData.ReadShort(); // Version number (1=AE-1 2=AE-2) + int vendorId = extraData.ReadShort(); // 2-character vendor ID 0x4541 = "AE" + int encrStrength = extraData.ReadByte(); // encryption strength 1 = 128 2 = 192 3 = 256 + int actualCompress = extraData.ReadShort(); // The actual compression method used to compress the file + _aesVer = ver; + _aesEncryptionStrength = encrStrength; + method = (CompressionMethod)actualCompress; + } + else + throw new ZipException("AES Extra Data missing"); + } + + /// + /// Gets/Sets the entry comment. + /// + /// + /// If comment is longer than 0xffff. + /// + /// + /// The comment or null if not set. + /// + /// + /// A comment is only available for entries when read via the class. + /// The class doesn't have the comment data available. + /// + public string Comment + { + get => comment; + set + { + // This test is strictly incorrect as the length is in characters + // while the storage limit is in bytes. + // While the test is partially correct in that a comment of this length or greater + // is definitely invalid, shorter comments may also have an invalid length + // where there are multi-byte characters + // The full test is not possible here however as the code page to apply conversions with + // isn't available. + if ((value != null) && (value.Length > 0xffff)) + { + throw new ArgumentOutOfRangeException(nameof(value), "cannot exceed 65535"); + } + + comment = value; + } + } + + /// + /// Gets a value indicating if the entry is a directory. + /// however. + /// + /// + /// A directory is determined by an entry name with a trailing slash '/'. + /// The external file attributes can also indicate an entry is for a directory. + /// Currently only dos/windows attributes are tested in this manner. + /// The trailing slash convention should always be followed. + /// + public bool IsDirectory + => name.Length > 0 + && (name[name.Length - 1] == '/' || name[name.Length - 1] == '\\') || HasDosAttributes(16); + + /// + /// Get a value of true if the entry appears to be a file; false otherwise + /// + /// + /// This only takes account of DOS/Windows attributes. Other operating systems are ignored. + /// For linux and others the result may be incorrect. + /// + public bool IsFile => !IsDirectory && !HasDosAttributes(8); + + /// + /// Test entry to see if data can be extracted. + /// + /// Returns true if data can be extracted for this entry; false otherwise. + public bool IsCompressionMethodSupported() => IsCompressionMethodSupported(CompressionMethod); + + #region ICloneable Members + + /// + /// Creates a copy of this zip entry. + /// + /// An that is a copy of the current instance. + public object Clone() + { + var result = (ZipEntry)this.MemberwiseClone(); + + // Ensure extra data is unique if it exists. + if (extra != null) + { + result.extra = new byte[extra.Length]; + Array.Copy(extra, 0, result.extra, 0, extra.Length); + } + + return result; + } + + #endregion ICloneable Members + + /// + /// Gets a string representation of this ZipEntry. + /// + /// A readable textual representation of this + public override string ToString() => name; + + /// + /// Test a compression method to see if this library + /// supports extracting data compressed with that method + /// + /// The compression method to test. + /// Returns true if the compression method is supported; false otherwise + public static bool IsCompressionMethodSupported(CompressionMethod method) + => method == CompressionMethod.Deflated + || method == CompressionMethod.Stored + || method == CompressionMethod.BZip2; + + /// + /// Cleans a name making it conform to Zip file conventions. + /// Devices names ('c:\') and UNC share names ('\\server\share') are removed + /// and forward slashes ('\') are converted to back slashes ('/'). + /// Names are made relative by trimming leading slashes which is compatible + /// with the ZIP naming convention. + /// + /// The name to clean + /// The 'cleaned' name. + /// + /// The Zip name transform class is more flexible. + /// + public static string CleanName(string name) + { + if (name == null) + { + return string.Empty; + } + + if (Path.IsPathRooted(name)) + { + // NOTE: + // for UNC names... \\machine\share\zoom\beet.txt gives \zoom\beet.txt + name = name.Substring(Path.GetPathRoot(name).Length); + } + + name = name.Replace(@"\", "/"); + + while ((name.Length > 0) && (name[0] == '/')) + { + name = name.Remove(0, 1); + } + return name; + } + + #region Instance Fields + + private Known known; + private int externalFileAttributes = -1; // contains external attributes (O/S dependant) + + private ushort versionMadeBy; // Contains host system and version information + // only relevant for central header entries + + private string name; + private ulong size; + private ulong compressedSize; + private ushort versionToExtract; // Version required to extract (library handles <= 2.0) + private uint crc; + private DateTime dateTime; + + private CompressionMethod method = CompressionMethod.Deflated; + private byte[] extra; + private string comment; + + private int flags; // general purpose bit flags + + private long zipFileIndex = -1; // used by ZipFile + private long offset; // used by ZipFile and ZipOutputStream + + private bool forceZip64_; + private byte cryptoCheckValue_; + private int _aesVer; // Version number (2 = AE-2 ?). Assigned but not used. + private int _aesEncryptionStrength; // Encryption strength 1 = 128 2 = 192 3 = 256 + + #endregion Instance Fields + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipEntryExtensions.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipEntryExtensions.cs new file mode 100644 index 0000000..927e94c --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipEntryExtensions.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ICSharpCode.SharpZipLib.Zip +{ + /// + /// General ZipEntry helper extensions + /// + public static class ZipEntryExtensions + { + /// + /// Efficiently check if a flag is set without enum un-/boxing + /// + /// + /// + /// Returns whether the flag was set + public static bool HasFlag(this ZipEntry entry, GeneralBitFlags flag) + => (entry.Flags & (int) flag) != 0; + + /// + /// Efficiently set a flag without enum un-/boxing + /// + /// + /// + /// Whether the passed flag should be set (1) or cleared (0) + public static void SetFlag(this ZipEntry entry, GeneralBitFlags flag, bool enabled = true) + => entry.Flags = enabled + ? entry.Flags | (int) flag + : entry.Flags & ~(int) flag; + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipEntryFactory.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipEntryFactory.cs new file mode 100644 index 0000000..1e40baa --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipEntryFactory.cs @@ -0,0 +1,375 @@ +using ICSharpCode.SharpZipLib.Core; +using System; +using System.IO; + +namespace ICSharpCode.SharpZipLib.Zip +{ + /// + /// Basic implementation of + /// + public class ZipEntryFactory : IEntryFactory + { + #region Enumerations + + /// + /// Defines the possible values to be used for the . + /// + public enum TimeSetting + { + /// + /// Use the recorded LastWriteTime value for the file. + /// + LastWriteTime, + + /// + /// Use the recorded LastWriteTimeUtc value for the file + /// + LastWriteTimeUtc, + + /// + /// Use the recorded CreateTime value for the file. + /// + CreateTime, + + /// + /// Use the recorded CreateTimeUtc value for the file. + /// + CreateTimeUtc, + + /// + /// Use the recorded LastAccessTime value for the file. + /// + LastAccessTime, + + /// + /// Use the recorded LastAccessTimeUtc value for the file. + /// + LastAccessTimeUtc, + + /// + /// Use a fixed value. + /// + /// The actual value used can be + /// specified via the constructor or + /// using the with the setting set + /// to which will use the when this class was constructed. + /// The property can also be used to set this value. + Fixed, + } + + #endregion Enumerations + + #region Constructors + + /// + /// Initialise a new instance of the class. + /// + /// A default , and the LastWriteTime for files is used. + public ZipEntryFactory() + { + nameTransform_ = new ZipNameTransform(); + isUnicodeText_ = ZipStrings.UseUnicode; + } + + /// + /// Initialise a new instance of using the specified + /// + /// The time setting to use when creating Zip entries. + public ZipEntryFactory(TimeSetting timeSetting) : this() + { + timeSetting_ = timeSetting; + } + + /// + /// Initialise a new instance of using the specified + /// + /// The time to set all values to. + public ZipEntryFactory(DateTime time) : this() + { + timeSetting_ = TimeSetting.Fixed; + FixedDateTime = time; + } + + #endregion Constructors + + #region Properties + + /// + /// Get / set the to be used when creating new values. + /// + /// + /// Setting this property to null will cause a default name transform to be used. + /// + public INameTransform NameTransform + { + get { return nameTransform_; } + set + { + if (value == null) + { + nameTransform_ = new ZipNameTransform(); + } + else + { + nameTransform_ = value; + } + } + } + + /// + /// Get / set the in use. + /// + public TimeSetting Setting + { + get { return timeSetting_; } + set { timeSetting_ = value; } + } + + /// + /// Get / set the value to use when is set to + /// + public DateTime FixedDateTime + { + get { return fixedDateTime_; } + set + { + if (value.Year < 1970) + { + throw new ArgumentException("Value is too old to be valid", nameof(value)); + } + fixedDateTime_ = value; + } + } + + /// + /// A bitmask defining the attributes to be retrieved from the actual file. + /// + /// The default is to get all possible attributes from the actual file. + public int GetAttributes + { + get { return getAttributes_; } + set { getAttributes_ = value; } + } + + /// + /// A bitmask defining which attributes are to be set on. + /// + /// By default no attributes are set on. + public int SetAttributes + { + get { return setAttributes_; } + set { setAttributes_ = value; } + } + + /// + /// Get set a value indicating whether unidoce text should be set on. + /// + public bool IsUnicodeText + { + get { return isUnicodeText_; } + set { isUnicodeText_ = value; } + } + + #endregion Properties + + #region IEntryFactory Members + + /// + /// Make a new for a file. + /// + /// The name of the file to create a new entry for. + /// Returns a new based on the . + public ZipEntry MakeFileEntry(string fileName) + { + return MakeFileEntry(fileName, null, true); + } + + /// + /// Make a new for a file. + /// + /// The name of the file to create a new entry for. + /// If true entry detail is retrieved from the file system if the file exists. + /// Returns a new based on the . + public ZipEntry MakeFileEntry(string fileName, bool useFileSystem) + { + return MakeFileEntry(fileName, null, useFileSystem); + } + + /// + /// Make a new from a name. + /// + /// The name of the file to create a new entry for. + /// An alternative name to be used for the new entry. Null if not applicable. + /// If true entry detail is retrieved from the file system if the file exists. + /// Returns a new based on the . + public ZipEntry MakeFileEntry(string fileName, string entryName, bool useFileSystem) + { + var result = new ZipEntry(nameTransform_.TransformFile(!string.IsNullOrEmpty(entryName) ? entryName : fileName)); + result.IsUnicodeText = isUnicodeText_; + + int externalAttributes = 0; + bool useAttributes = (setAttributes_ != 0); + + FileInfo fi = null; + if (useFileSystem) + { + fi = new FileInfo(fileName); + } + + if ((fi != null) && fi.Exists) + { + switch (timeSetting_) + { + case TimeSetting.CreateTime: + result.DateTime = fi.CreationTime; + break; + + case TimeSetting.CreateTimeUtc: + result.DateTime = fi.CreationTimeUtc; + break; + + case TimeSetting.LastAccessTime: + result.DateTime = fi.LastAccessTime; + break; + + case TimeSetting.LastAccessTimeUtc: + result.DateTime = fi.LastAccessTimeUtc; + break; + + case TimeSetting.LastWriteTime: + result.DateTime = fi.LastWriteTime; + break; + + case TimeSetting.LastWriteTimeUtc: + result.DateTime = fi.LastWriteTimeUtc; + break; + + case TimeSetting.Fixed: + result.DateTime = fixedDateTime_; + break; + + default: + throw new ZipException("Unhandled time setting in MakeFileEntry"); + } + + result.Size = fi.Length; + + useAttributes = true; + externalAttributes = ((int)fi.Attributes & getAttributes_); + } + else + { + if (timeSetting_ == TimeSetting.Fixed) + { + result.DateTime = fixedDateTime_; + } + } + + if (useAttributes) + { + externalAttributes |= setAttributes_; + result.ExternalFileAttributes = externalAttributes; + } + + return result; + } + + /// + /// Make a new for a directory. + /// + /// The raw untransformed name for the new directory + /// Returns a new representing a directory. + public ZipEntry MakeDirectoryEntry(string directoryName) + { + return MakeDirectoryEntry(directoryName, true); + } + + /// + /// Make a new for a directory. + /// + /// The raw untransformed name for the new directory + /// If true entry detail is retrieved from the file system if the file exists. + /// Returns a new representing a directory. + public ZipEntry MakeDirectoryEntry(string directoryName, bool useFileSystem) + { + var result = new ZipEntry(nameTransform_.TransformDirectory(directoryName)); + result.IsUnicodeText = isUnicodeText_; + result.Size = 0; + + int externalAttributes = 0; + + DirectoryInfo di = null; + + if (useFileSystem) + { + di = new DirectoryInfo(directoryName); + } + + if ((di != null) && di.Exists) + { + switch (timeSetting_) + { + case TimeSetting.CreateTime: + result.DateTime = di.CreationTime; + break; + + case TimeSetting.CreateTimeUtc: + result.DateTime = di.CreationTimeUtc; + break; + + case TimeSetting.LastAccessTime: + result.DateTime = di.LastAccessTime; + break; + + case TimeSetting.LastAccessTimeUtc: + result.DateTime = di.LastAccessTimeUtc; + break; + + case TimeSetting.LastWriteTime: + result.DateTime = di.LastWriteTime; + break; + + case TimeSetting.LastWriteTimeUtc: + result.DateTime = di.LastWriteTimeUtc; + break; + + case TimeSetting.Fixed: + result.DateTime = fixedDateTime_; + break; + + default: + throw new ZipException("Unhandled time setting in MakeDirectoryEntry"); + } + + externalAttributes = ((int)di.Attributes & getAttributes_); + } + else + { + if (timeSetting_ == TimeSetting.Fixed) + { + result.DateTime = fixedDateTime_; + } + } + + // Always set directory attribute on. + externalAttributes |= (setAttributes_ | 16); + result.ExternalFileAttributes = externalAttributes; + + return result; + } + + #endregion IEntryFactory Members + + #region Instance Fields + + private INameTransform nameTransform_; + private DateTime fixedDateTime_ = DateTime.Now; + private TimeSetting timeSetting_ = TimeSetting.LastWriteTime; + private bool isUnicodeText_; + + private int getAttributes_ = -1; + private int setAttributes_; + + #endregion Instance Fields + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipException.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipException.cs new file mode 100644 index 0000000..ef8142b --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipException.cs @@ -0,0 +1,54 @@ +using System; +using System.Runtime.Serialization; + +namespace ICSharpCode.SharpZipLib.Zip +{ + /// + /// ZipException represents exceptions specific to Zip classes and code. + /// + [Serializable] + public class ZipException : SharpZipBaseException + { + /// + /// Initialise a new instance of . + /// + public ZipException() + { + } + + /// + /// Initialise a new instance of with its message string. + /// + /// A that describes the error. + public ZipException(string message) + : base(message) + { + } + + /// + /// Initialise a new instance of . + /// + /// A that describes the error. + /// The that caused this exception. + public ZipException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the ZipException class with serialized data. + /// + /// + /// The System.Runtime.Serialization.SerializationInfo that holds the serialized + /// object data about the exception being thrown. + /// + /// + /// The System.Runtime.Serialization.StreamingContext that contains contextual information + /// about the source or destination. + /// + protected ZipException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipExtraData.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipExtraData.cs new file mode 100644 index 0000000..4e075dc --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipExtraData.cs @@ -0,0 +1,980 @@ +using System; +using System.IO; +using ICSharpCode.SharpZipLib.Core; + +namespace ICSharpCode.SharpZipLib.Zip +{ + // TODO: Sort out whether tagged data is useful and what a good implementation might look like. + // Its just a sketch of an idea at the moment. + + /// + /// ExtraData tagged value interface. + /// + public interface ITaggedData + { + /// + /// Get the ID for this tagged data value. + /// + short TagID { get; } + + /// + /// Set the contents of this instance from the data passed. + /// + /// The data to extract contents from. + /// The offset to begin extracting data from. + /// The number of bytes to extract. + void SetData(byte[] data, int offset, int count); + + /// + /// Get the data representing this instance. + /// + /// Returns the data for this instance. + byte[] GetData(); + } + + /// + /// A raw binary tagged value + /// + public class RawTaggedData : ITaggedData + { + /// + /// Initialise a new instance. + /// + /// The tag ID. + public RawTaggedData(short tag) + { + _tag = tag; + } + + #region ITaggedData Members + + /// + /// Get the ID for this tagged data value. + /// + public short TagID + { + get { return _tag; } + set { _tag = value; } + } + + /// + /// Set the data from the raw values provided. + /// + /// The raw data to extract values from. + /// The index to start extracting values from. + /// The number of bytes available. + public void SetData(byte[] data, int offset, int count) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + _data = new byte[count]; + Array.Copy(data, offset, _data, 0, count); + } + + /// + /// Get the binary data representing this instance. + /// + /// The raw binary data representing this instance. + public byte[] GetData() + { + return _data; + } + + #endregion ITaggedData Members + + /// + /// Get /set the binary data representing this instance. + /// + /// The raw binary data representing this instance. + public byte[] Data + { + get { return _data; } + set { _data = value; } + } + + #region Instance Fields + + /// + /// The tag ID for this instance. + /// + private short _tag; + + private byte[] _data; + + #endregion Instance Fields + } + + /// + /// Class representing extended unix date time values. + /// + public class ExtendedUnixData : ITaggedData + { + /// + /// Flags indicate which values are included in this instance. + /// + [Flags] + public enum Flags : byte + { + /// + /// The modification time is included + /// + ModificationTime = 0x01, + + /// + /// The access time is included + /// + AccessTime = 0x02, + + /// + /// The create time is included. + /// + CreateTime = 0x04, + } + + #region ITaggedData Members + + /// + /// Get the ID + /// + public short TagID + { + get { return 0x5455; } + } + + /// + /// Set the data from the raw values provided. + /// + /// The raw data to extract values from. + /// The index to start extracting values from. + /// The number of bytes available. + public void SetData(byte[] data, int index, int count) + { + using (MemoryStream ms = new MemoryStream(data, index, count, false)) + using (ZipHelperStream helperStream = new ZipHelperStream(ms)) + { + // bit 0 if set, modification time is present + // bit 1 if set, access time is present + // bit 2 if set, creation time is present + + _flags = (Flags)helperStream.ReadByte(); + if (((_flags & Flags.ModificationTime) != 0)) + { + int iTime = helperStream.ReadLEInt(); + + _modificationTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + + new TimeSpan(0, 0, 0, iTime, 0); + + // Central-header version is truncated after modification time + if (count <= 5) return; + } + + if ((_flags & Flags.AccessTime) != 0) + { + int iTime = helperStream.ReadLEInt(); + + _lastAccessTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + + new TimeSpan(0, 0, 0, iTime, 0); + } + + if ((_flags & Flags.CreateTime) != 0) + { + int iTime = helperStream.ReadLEInt(); + + _createTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + + new TimeSpan(0, 0, 0, iTime, 0); + } + } + } + + /// + /// Get the binary data representing this instance. + /// + /// The raw binary data representing this instance. + public byte[] GetData() + { + using (MemoryStream ms = new MemoryStream()) + using (ZipHelperStream helperStream = new ZipHelperStream(ms)) + { + helperStream.IsStreamOwner = false; + helperStream.WriteByte((byte)_flags); // Flags + if ((_flags & Flags.ModificationTime) != 0) + { + TimeSpan span = _modificationTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + var seconds = (int)span.TotalSeconds; + helperStream.WriteLEInt(seconds); + } + if ((_flags & Flags.AccessTime) != 0) + { + TimeSpan span = _lastAccessTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + var seconds = (int)span.TotalSeconds; + helperStream.WriteLEInt(seconds); + } + if ((_flags & Flags.CreateTime) != 0) + { + TimeSpan span = _createTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + var seconds = (int)span.TotalSeconds; + helperStream.WriteLEInt(seconds); + } + return ms.ToArray(); + } + } + + #endregion ITaggedData Members + + /// + /// Test a value to see if is valid and can be represented here. + /// + /// The value to test. + /// Returns true if the value is valid and can be represented; false if not. + /// The standard Unix time is a signed integer data type, directly encoding the Unix time number, + /// which is the number of seconds since 1970-01-01. + /// Being 32 bits means the values here cover a range of about 136 years. + /// The minimum representable time is 1901-12-13 20:45:52, + /// and the maximum representable time is 2038-01-19 03:14:07. + /// + public static bool IsValidValue(DateTime value) + { + return ((value >= new DateTime(1901, 12, 13, 20, 45, 52)) || + (value <= new DateTime(2038, 1, 19, 03, 14, 07))); + } + + /// + /// Get /set the Modification Time + /// + /// + /// + public DateTime ModificationTime + { + get { return _modificationTime; } + set + { + if (!IsValidValue(value)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _flags |= Flags.ModificationTime; + _modificationTime = value; + } + } + + /// + /// Get / set the Access Time + /// + /// + /// + public DateTime AccessTime + { + get { return _lastAccessTime; } + set + { + if (!IsValidValue(value)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _flags |= Flags.AccessTime; + _lastAccessTime = value; + } + } + + /// + /// Get / Set the Create Time + /// + /// + /// + public DateTime CreateTime + { + get { return _createTime; } + set + { + if (!IsValidValue(value)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _flags |= Flags.CreateTime; + _createTime = value; + } + } + + /// + /// Get/set the values to include. + /// + public Flags Include + { + get { return _flags; } + set { _flags = value; } + } + + #region Instance Fields + + private Flags _flags; + private DateTime _modificationTime = new DateTime(1970, 1, 1); + private DateTime _lastAccessTime = new DateTime(1970, 1, 1); + private DateTime _createTime = new DateTime(1970, 1, 1); + + #endregion Instance Fields + } + + /// + /// Class handling NT date time values. + /// + public class NTTaggedData : ITaggedData + { + /// + /// Get the ID for this tagged data value. + /// + public short TagID + { + get { return 10; } + } + + /// + /// Set the data from the raw values provided. + /// + /// The raw data to extract values from. + /// The index to start extracting values from. + /// The number of bytes available. + public void SetData(byte[] data, int index, int count) + { + using (MemoryStream ms = new MemoryStream(data, index, count, false)) + using (ZipHelperStream helperStream = new ZipHelperStream(ms)) + { + helperStream.ReadLEInt(); // Reserved + while (helperStream.Position < helperStream.Length) + { + int ntfsTag = helperStream.ReadLEShort(); + int ntfsLength = helperStream.ReadLEShort(); + if (ntfsTag == 1) + { + if (ntfsLength >= 24) + { + long lastModificationTicks = helperStream.ReadLELong(); + _lastModificationTime = DateTime.FromFileTimeUtc(lastModificationTicks); + + long lastAccessTicks = helperStream.ReadLELong(); + _lastAccessTime = DateTime.FromFileTimeUtc(lastAccessTicks); + + long createTimeTicks = helperStream.ReadLELong(); + _createTime = DateTime.FromFileTimeUtc(createTimeTicks); + } + break; + } + else + { + // An unknown NTFS tag so simply skip it. + helperStream.Seek(ntfsLength, SeekOrigin.Current); + } + } + } + } + + /// + /// Get the binary data representing this instance. + /// + /// The raw binary data representing this instance. + public byte[] GetData() + { + using (MemoryStream ms = new MemoryStream()) + using (ZipHelperStream helperStream = new ZipHelperStream(ms)) + { + helperStream.IsStreamOwner = false; + helperStream.WriteLEInt(0); // Reserved + helperStream.WriteLEShort(1); // Tag + helperStream.WriteLEShort(24); // Length = 3 x 8. + helperStream.WriteLELong(_lastModificationTime.ToFileTimeUtc()); + helperStream.WriteLELong(_lastAccessTime.ToFileTimeUtc()); + helperStream.WriteLELong(_createTime.ToFileTimeUtc()); + return ms.ToArray(); + } + } + + /// + /// Test a valuie to see if is valid and can be represented here. + /// + /// The value to test. + /// Returns true if the value is valid and can be represented; false if not. + /// + /// NTFS filetimes are 64-bit unsigned integers, stored in Intel + /// (least significant byte first) byte order. They determine the + /// number of 1.0E-07 seconds (1/10th microseconds!) past WinNT "epoch", + /// which is "01-Jan-1601 00:00:00 UTC". 28 May 60056 is the upper limit + /// + public static bool IsValidValue(DateTime value) + { + bool result = true; + try + { + value.ToFileTimeUtc(); + } + catch + { + result = false; + } + return result; + } + + /// + /// Get/set the last modification time. + /// + public DateTime LastModificationTime + { + get { return _lastModificationTime; } + set + { + if (!IsValidValue(value)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + _lastModificationTime = value; + } + } + + /// + /// Get /set the create time + /// + public DateTime CreateTime + { + get { return _createTime; } + set + { + if (!IsValidValue(value)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + _createTime = value; + } + } + + /// + /// Get /set the last access time. + /// + public DateTime LastAccessTime + { + get { return _lastAccessTime; } + set + { + if (!IsValidValue(value)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + _lastAccessTime = value; + } + } + + #region Instance Fields + + private DateTime _lastAccessTime = DateTime.FromFileTimeUtc(0); + private DateTime _lastModificationTime = DateTime.FromFileTimeUtc(0); + private DateTime _createTime = DateTime.FromFileTimeUtc(0); + + #endregion Instance Fields + } + + /// + /// A factory that creates tagged data instances. + /// + internal interface ITaggedDataFactory + { + /// + /// Get data for a specific tag value. + /// + /// The tag ID to find. + /// The data to search. + /// The offset to begin extracting data from. + /// The number of bytes to extract. + /// The located value found, or null if not found. + ITaggedData Create(short tag, byte[] data, int offset, int count); + } + + /// + /// + /// A class to handle the extra data field for Zip entries + /// + /// + /// Extra data contains 0 or more values each prefixed by a header tag and length. + /// They contain zero or more bytes of actual data. + /// The data is held internally using a copy on write strategy. This is more efficient but + /// means that for extra data created by passing in data can have the values modified by the caller + /// in some circumstances. + /// + sealed public class ZipExtraData : IDisposable + { + #region Constructors + + /// + /// Initialise a default instance. + /// + public ZipExtraData() + { + Clear(); + } + + /// + /// Initialise with known extra data. + /// + /// The extra data. + public ZipExtraData(byte[] data) + { + if (data == null) + { + _data = Empty.Array(); + } + else + { + _data = data; + } + } + + #endregion Constructors + + /// + /// Get the raw extra data value + /// + /// Returns the raw byte[] extra data this instance represents. + public byte[] GetEntryData() + { + if (Length > ushort.MaxValue) + { + throw new ZipException("Data exceeds maximum length"); + } + + return (byte[])_data.Clone(); + } + + /// + /// Clear the stored data. + /// + public void Clear() + { + if ((_data == null) || (_data.Length != 0)) + { + _data = Empty.Array(); + } + } + + /// + /// Gets the current extra data length. + /// + public int Length + { + get { return _data.Length; } + } + + /// + /// Get a read-only for the associated tag. + /// + /// The tag to locate data for. + /// Returns a containing tag data or null if no tag was found. + public Stream GetStreamForTag(int tag) + { + Stream result = null; + if (Find(tag)) + { + result = new MemoryStream(_data, _index, _readValueLength, false); + } + return result; + } + + /// + /// Get the tagged data for a tag. + /// + /// The tag to search for. + /// Returns a tagged value or null if none found. + public T GetData() + where T : class, ITaggedData, new() + { + T result = new T(); + if (Find(result.TagID)) + { + result.SetData(_data, _readValueStart, _readValueLength); + return result; + } + else return null; + } + + /// + /// Get the length of the last value found by + /// + /// This is only valid if has previously returned true. + public int ValueLength + { + get { return _readValueLength; } + } + + /// + /// Get the index for the current read value. + /// + /// This is only valid if has previously returned true. + /// Initially the result will be the index of the first byte of actual data. The value is updated after calls to + /// , and . + public int CurrentReadIndex + { + get { return _index; } + } + + /// + /// Get the number of bytes remaining to be read for the current value; + /// + public int UnreadCount + { + get + { + if ((_readValueStart > _data.Length) || + (_readValueStart < 4)) + { + throw new ZipException("Find must be called before calling a Read method"); + } + + return _readValueStart + _readValueLength - _index; + } + } + + /// + /// Find an extra data value + /// + /// The identifier for the value to find. + /// Returns true if the value was found; false otherwise. + public bool Find(int headerID) + { + _readValueStart = _data.Length; + _readValueLength = 0; + _index = 0; + + int localLength = _readValueStart; + int localTag = headerID - 1; + + // Trailing bytes that cant make up an entry (as there arent enough + // bytes for a tag and length) are ignored! + while ((localTag != headerID) && (_index < _data.Length - 3)) + { + localTag = ReadShortInternal(); + localLength = ReadShortInternal(); + if (localTag != headerID) + { + _index += localLength; + } + } + + bool result = (localTag == headerID) && ((_index + localLength) <= _data.Length); + + if (result) + { + _readValueStart = _index; + _readValueLength = localLength; + } + + return result; + } + + /// + /// Add a new entry to extra data. + /// + /// The value to add. + public void AddEntry(ITaggedData taggedData) + { + if (taggedData == null) + { + throw new ArgumentNullException(nameof(taggedData)); + } + AddEntry(taggedData.TagID, taggedData.GetData()); + } + + /// + /// Add a new entry to extra data + /// + /// The ID for this entry. + /// The data to add. + /// If the ID already exists its contents are replaced. + public void AddEntry(int headerID, byte[] fieldData) + { + if ((headerID > ushort.MaxValue) || (headerID < 0)) + { + throw new ArgumentOutOfRangeException(nameof(headerID)); + } + + int addLength = (fieldData == null) ? 0 : fieldData.Length; + + if (addLength > ushort.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(fieldData), "exceeds maximum length"); + } + + // Test for new length before adjusting data. + int newLength = _data.Length + addLength + 4; + + if (Find(headerID)) + { + newLength -= (ValueLength + 4); + } + + if (newLength > ushort.MaxValue) + { + throw new ZipException("Data exceeds maximum length"); + } + + Delete(headerID); + + byte[] newData = new byte[newLength]; + _data.CopyTo(newData, 0); + int index = _data.Length; + _data = newData; + SetShort(ref index, headerID); + SetShort(ref index, addLength); + if (fieldData != null) + { + fieldData.CopyTo(newData, index); + } + } + + /// + /// Start adding a new entry. + /// + /// Add data using , , , or . + /// The new entry is completed and actually added by calling + /// + public void StartNewEntry() + { + _newEntry = new MemoryStream(); + } + + /// + /// Add entry data added since using the ID passed. + /// + /// The identifier to use for this entry. + public void AddNewEntry(int headerID) + { + byte[] newData = _newEntry.ToArray(); + _newEntry = null; + AddEntry(headerID, newData); + } + + /// + /// Add a byte of data to the pending new entry. + /// + /// The byte to add. + /// + public void AddData(byte data) + { + _newEntry.WriteByte(data); + } + + /// + /// Add data to a pending new entry. + /// + /// The data to add. + /// + public void AddData(byte[] data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + _newEntry.Write(data, 0, data.Length); + } + + /// + /// Add a short value in little endian order to the pending new entry. + /// + /// The data to add. + /// + public void AddLeShort(int toAdd) + { + unchecked + { + _newEntry.WriteByte((byte)toAdd); + _newEntry.WriteByte((byte)(toAdd >> 8)); + } + } + + /// + /// Add an integer value in little endian order to the pending new entry. + /// + /// The data to add. + /// + public void AddLeInt(int toAdd) + { + unchecked + { + AddLeShort((short)toAdd); + AddLeShort((short)(toAdd >> 16)); + } + } + + /// + /// Add a long value in little endian order to the pending new entry. + /// + /// The data to add. + /// + public void AddLeLong(long toAdd) + { + unchecked + { + AddLeInt((int)(toAdd & 0xffffffff)); + AddLeInt((int)(toAdd >> 32)); + } + } + + /// + /// Delete an extra data field. + /// + /// The identifier of the field to delete. + /// Returns true if the field was found and deleted. + public bool Delete(int headerID) + { + bool result = false; + + if (Find(headerID)) + { + result = true; + int trueStart = _readValueStart - 4; + + byte[] newData = new byte[_data.Length - (ValueLength + 4)]; + Array.Copy(_data, 0, newData, 0, trueStart); + + int trueEnd = trueStart + ValueLength + 4; + Array.Copy(_data, trueEnd, newData, trueStart, _data.Length - trueEnd); + _data = newData; + } + return result; + } + + #region Reading Support + + /// + /// Read a long in little endian form from the last found data value + /// + /// Returns the long value read. + public long ReadLong() + { + ReadCheck(8); + return (ReadInt() & 0xffffffff) | (((long)ReadInt()) << 32); + } + + /// + /// Read an integer in little endian form from the last found data value. + /// + /// Returns the integer read. + public int ReadInt() + { + ReadCheck(4); + + int result = _data[_index] + (_data[_index + 1] << 8) + + (_data[_index + 2] << 16) + (_data[_index + 3] << 24); + _index += 4; + return result; + } + + /// + /// Read a short value in little endian form from the last found data value. + /// + /// Returns the short value read. + public int ReadShort() + { + ReadCheck(2); + int result = _data[_index] + (_data[_index + 1] << 8); + _index += 2; + return result; + } + + /// + /// Read a byte from an extra data + /// + /// The byte value read or -1 if the end of data has been reached. + public int ReadByte() + { + int result = -1; + if ((_index < _data.Length) && (_readValueStart + _readValueLength > _index)) + { + result = _data[_index]; + _index += 1; + } + return result; + } + + /// + /// Skip data during reading. + /// + /// The number of bytes to skip. + public void Skip(int amount) + { + ReadCheck(amount); + _index += amount; + } + + private void ReadCheck(int length) + { + if ((_readValueStart > _data.Length) || + (_readValueStart < 4)) + { + throw new ZipException("Find must be called before calling a Read method"); + } + + if (_index > _readValueStart + _readValueLength - length) + { + throw new ZipException("End of extra data"); + } + + if (_index + length < 4) + { + throw new ZipException("Cannot read before start of tag"); + } + } + + /// + /// Internal form of that reads data at any location. + /// + /// Returns the short value read. + private int ReadShortInternal() + { + if (_index > _data.Length - 2) + { + throw new ZipException("End of extra data"); + } + + int result = _data[_index] + (_data[_index + 1] << 8); + _index += 2; + return result; + } + + private void SetShort(ref int index, int source) + { + _data[index] = (byte)source; + _data[index + 1] = (byte)(source >> 8); + index += 2; + } + + #endregion Reading Support + + #region IDisposable Members + + /// + /// Dispose of this instance. + /// + public void Dispose() + { + if (_newEntry != null) + { + _newEntry.Dispose(); + } + } + + #endregion IDisposable Members + + #region Instance Fields + + private int _index; + private int _readValueStart; + private int _readValueLength; + + private MemoryStream _newEntry; + private byte[] _data; + + #endregion Instance Fields + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipFile.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipFile.cs new file mode 100644 index 0000000..3bd66ff --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipFile.cs @@ -0,0 +1,4915 @@ +using ICSharpCode.SharpZipLib.Checksum; +using ICSharpCode.SharpZipLib.Core; +using ICSharpCode.SharpZipLib.Encryption; +using ICSharpCode.SharpZipLib.Zip.Compression; +using ICSharpCode.SharpZipLib.Zip.Compression.Streams; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace ICSharpCode.SharpZipLib.Zip +{ + #region Keys Required Event Args + + /// + /// Arguments used with KeysRequiredEvent + /// + public class KeysRequiredEventArgs : EventArgs + { + #region Constructors + + /// + /// Initialise a new instance of + /// + /// The name of the file for which keys are required. + public KeysRequiredEventArgs(string name) + { + fileName = name; + } + + /// + /// Initialise a new instance of + /// + /// The name of the file for which keys are required. + /// The current key value. + public KeysRequiredEventArgs(string name, byte[] keyValue) + { + fileName = name; + key = keyValue; + } + + #endregion Constructors + + #region Properties + + /// + /// Gets the name of the file for which keys are required. + /// + public string FileName + { + get { return fileName; } + } + + /// + /// Gets or sets the key value + /// + public byte[] Key + { + get { return key; } + set { key = value; } + } + + #endregion Properties + + #region Instance Fields + + private readonly string fileName; + private byte[] key; + + #endregion Instance Fields + } + + #endregion Keys Required Event Args + + #region Test Definitions + + /// + /// The strategy to apply to testing. + /// + public enum TestStrategy + { + /// + /// Find the first error only. + /// + FindFirstError, + + /// + /// Find all possible errors. + /// + FindAllErrors, + } + + /// + /// The operation in progress reported by a during testing. + /// + /// TestArchive + public enum TestOperation + { + /// + /// Setting up testing. + /// + Initialising, + + /// + /// Testing an individual entries header + /// + EntryHeader, + + /// + /// Testing an individual entries data + /// + EntryData, + + /// + /// Testing an individual entry has completed. + /// + EntryComplete, + + /// + /// Running miscellaneous tests + /// + MiscellaneousTests, + + /// + /// Testing is complete + /// + Complete, + } + + /// + /// Status returned by during testing. + /// + /// TestArchive + public class TestStatus + { + #region Constructors + + /// + /// Initialise a new instance of + /// + /// The this status applies to. + public TestStatus(ZipFile file) + { + file_ = file; + } + + #endregion Constructors + + #region Properties + + /// + /// Get the current in progress. + /// + public TestOperation Operation + { + get { return operation_; } + } + + /// + /// Get the this status is applicable to. + /// + public ZipFile File + { + get { return file_; } + } + + /// + /// Get the current/last entry tested. + /// + public ZipEntry Entry + { + get { return entry_; } + } + + /// + /// Get the number of errors detected so far. + /// + public int ErrorCount + { + get { return errorCount_; } + } + + /// + /// Get the number of bytes tested so far for the current entry. + /// + public long BytesTested + { + get { return bytesTested_; } + } + + /// + /// Get a value indicating whether the last entry test was valid. + /// + public bool EntryValid + { + get { return entryValid_; } + } + + #endregion Properties + + #region Internal API + + internal void AddError() + { + errorCount_++; + entryValid_ = false; + } + + internal void SetOperation(TestOperation operation) + { + operation_ = operation; + } + + internal void SetEntry(ZipEntry entry) + { + entry_ = entry; + entryValid_ = true; + bytesTested_ = 0; + } + + internal void SetBytesTested(long value) + { + bytesTested_ = value; + } + + #endregion Internal API + + #region Instance Fields + + private readonly ZipFile file_; + private ZipEntry entry_; + private bool entryValid_; + private int errorCount_; + private long bytesTested_; + private TestOperation operation_; + + #endregion Instance Fields + } + + /// + /// Delegate invoked during testing if supplied indicating current progress and status. + /// + /// If the message is non-null an error has occured. If the message is null + /// the operation as found in status has started. + public delegate void ZipTestResultHandler(TestStatus status, string message); + + #endregion Test Definitions + + #region Update Definitions + + /// + /// The possible ways of applying updates to an archive. + /// + public enum FileUpdateMode + { + /// + /// Perform all updates on temporary files ensuring that the original file is saved. + /// + Safe, + + /// + /// Update the archive directly, which is faster but less safe. + /// + Direct, + } + + #endregion Update Definitions + + #region ZipFile Class + + /// + /// This class represents a Zip archive. You can ask for the contained + /// entries, or get an input stream for a file entry. The entry is + /// automatically decompressed. + /// + /// You can also update the archive adding or deleting entries. + /// + /// This class is thread safe for input: You can open input streams for arbitrary + /// entries in different threads. + ///
+ ///
Author of the original java version : Jochen Hoenicke + ///
+ /// + /// + /// using System; + /// using System.Text; + /// using System.Collections; + /// using System.IO; + /// + /// using ICSharpCode.SharpZipLib.Zip; + /// + /// class MainClass + /// { + /// static public void Main(string[] args) + /// { + /// using (ZipFile zFile = new ZipFile(args[0])) { + /// Console.WriteLine("Listing of : " + zFile.Name); + /// Console.WriteLine(""); + /// Console.WriteLine("Raw Size Size Date Time Name"); + /// Console.WriteLine("-------- -------- -------- ------ ---------"); + /// foreach (ZipEntry e in zFile) { + /// if ( e.IsFile ) { + /// DateTime d = e.DateTime; + /// Console.WriteLine("{0, -10}{1, -10}{2} {3} {4}", e.Size, e.CompressedSize, + /// d.ToString("dd-MM-yy"), d.ToString("HH:mm"), + /// e.Name); + /// } + /// } + /// } + /// } + /// } + /// + /// + public class ZipFile : IEnumerable, IDisposable + { + #region KeyHandling + + /// + /// Delegate for handling keys/password setting during compression/decompression. + /// + public delegate void KeysRequiredEventHandler( + object sender, + KeysRequiredEventArgs e + ); + + /// + /// Event handler for handling encryption keys. + /// + public KeysRequiredEventHandler KeysRequired; + + /// + /// Handles getting of encryption keys when required. + /// + /// The file for which encryption keys are required. + private void OnKeysRequired(string fileName) + { + if (KeysRequired != null) + { + var krea = new KeysRequiredEventArgs(fileName, key); + KeysRequired(this, krea); + key = krea.Key; + } + } + + /// + /// Get/set the encryption key value. + /// + private byte[] Key + { + get { return key; } + set { key = value; } + } + + /// + /// Password to be used for encrypting/decrypting files. + /// + /// Set to null if no password is required. + public string Password + { + set + { + if (string.IsNullOrEmpty(value)) + { + key = null; + } + else + { + key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(value)); + } + + rawPassword_ = value; + } + } + + /// + /// Get a value indicating whether encryption keys are currently available. + /// + private bool HaveKeys + { + get { return key != null; } + } + + #endregion KeyHandling + + #region Constructors + + /// + /// Opens a Zip file with the given name for reading. + /// + /// The name of the file to open. + /// The argument supplied is null. + /// + /// An i/o error occurs + /// + /// + /// The file doesn't contain a valid zip archive. + /// + public ZipFile(string name) + { + name_ = name ?? throw new ArgumentNullException(nameof(name)); + + baseStream_ = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); + isStreamOwner = true; + + try + { + ReadEntries(); + } + catch + { + DisposeInternal(true); + throw; + } + } + + /// + /// Opens a Zip file reading the given . + /// + /// The to read archive data from. + /// The supplied argument is null. + /// + /// An i/o error occurs. + /// + /// + /// The file doesn't contain a valid zip archive. + /// + public ZipFile(FileStream file) : + this(file, false) + { + + } + + /// + /// Opens a Zip file reading the given . + /// + /// The to read archive data from. + /// true to leave the file open when the ZipFile is disposed, false to dispose of it + /// The supplied argument is null. + /// + /// An i/o error occurs. + /// + /// + /// The file doesn't contain a valid zip archive. + /// + public ZipFile(FileStream file, bool leaveOpen) + { + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + + if (!file.CanSeek) + { + throw new ArgumentException("Stream is not seekable", nameof(file)); + } + + baseStream_ = file; + name_ = file.Name; + isStreamOwner = !leaveOpen; + + try + { + ReadEntries(); + } + catch + { + DisposeInternal(true); + throw; + } + } + + /// + /// Opens a Zip file reading the given . + /// + /// The to read archive data from. + /// + /// An i/o error occurs + /// + /// + /// The stream doesn't contain a valid zip archive.
+ ///
+ /// + /// The stream doesnt support seeking. + /// + /// + /// The stream argument is null. + /// + public ZipFile(Stream stream) : + this(stream, false) + { + + } + + /// + /// Opens a Zip file reading the given . + /// + /// The to read archive data from. + /// true to leave the stream open when the ZipFile is disposed, false to dispose of it + /// + /// An i/o error occurs + /// + /// + /// The stream doesn't contain a valid zip archive.
+ ///
+ /// + /// The stream doesnt support seeking. + /// + /// + /// The stream argument is null. + /// + public ZipFile(Stream stream, bool leaveOpen) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!stream.CanSeek) + { + throw new ArgumentException("Stream is not seekable", nameof(stream)); + } + + baseStream_ = stream; + isStreamOwner = !leaveOpen; + + if (baseStream_.Length > 0) + { + try + { + ReadEntries(); + } + catch + { + DisposeInternal(true); + throw; + } + } + else + { + entries_ = Empty.Array(); + isNewArchive_ = true; + } + } + + /// + /// Initialises a default instance with no entries and no file storage. + /// + internal ZipFile() + { + entries_ = Empty.Array(); + isNewArchive_ = true; + } + + #endregion Constructors + + #region Destructors and Closing + + /// + /// Finalize this instance. + /// + ~ZipFile() + { + Dispose(false); + } + + /// + /// Closes the ZipFile. If the stream is owned then this also closes the underlying input stream. + /// Once closed, no further instance methods should be called. + /// + /// + /// An i/o error occurs. + /// + public void Close() + { + DisposeInternal(true); + GC.SuppressFinalize(this); + } + + #endregion Destructors and Closing + + #region Creators + + /// + /// Create a new whose data will be stored in a file. + /// + /// The name of the archive to create. + /// Returns the newly created + /// is null + public static ZipFile Create(string fileName) + { + if (fileName == null) + { + throw new ArgumentNullException(nameof(fileName)); + } + + FileStream fs = File.Create(fileName); + + return new ZipFile + { + name_ = fileName, + baseStream_ = fs, + isStreamOwner = true + }; + } + + /// + /// Create a new whose data will be stored on a stream. + /// + /// The stream providing data storage. + /// Returns the newly created + /// is null + /// doesnt support writing. + public static ZipFile Create(Stream outStream) + { + if (outStream == null) + { + throw new ArgumentNullException(nameof(outStream)); + } + + if (!outStream.CanWrite) + { + throw new ArgumentException("Stream is not writeable", nameof(outStream)); + } + + if (!outStream.CanSeek) + { + throw new ArgumentException("Stream is not seekable", nameof(outStream)); + } + + var result = new ZipFile + { + baseStream_ = outStream + }; + return result; + } + + #endregion Creators + + #region Properties + + /// + /// Get/set a flag indicating if the underlying stream is owned by the ZipFile instance. + /// If the flag is true then the stream will be closed when Close is called. + /// + /// + /// The default value is true in all cases. + /// + public bool IsStreamOwner + { + get { return isStreamOwner; } + set { isStreamOwner = value; } + } + + /// + /// Get a value indicating whether + /// this archive is embedded in another file or not. + /// + public bool IsEmbeddedArchive + { + // Not strictly correct in all circumstances currently + get { return offsetOfFirstEntry > 0; } + } + + /// + /// Get a value indicating that this archive is a new one. + /// + public bool IsNewArchive + { + get { return isNewArchive_; } + } + + /// + /// Gets the comment for the zip file. + /// + public string ZipFileComment + { + get { return comment_; } + } + + /// + /// Gets the name of this zip file. + /// + public string Name + { + get { return name_; } + } + + /// + /// Gets the number of entries in this zip file. + /// + /// + /// The Zip file has been closed. + /// + [Obsolete("Use the Count property instead")] + public int Size + { + get + { + return entries_.Length; + } + } + + /// + /// Get the number of entries contained in this . + /// + public long Count + { + get + { + return entries_.Length; + } + } + + /// + /// Indexer property for ZipEntries + /// + [System.Runtime.CompilerServices.IndexerNameAttribute("EntryByIndex")] + public ZipEntry this[int index] + { + get + { + return (ZipEntry)entries_[index].Clone(); + } + } + + #endregion Properties + + #region Input Handling + + /// + /// Gets an enumerator for the Zip entries in this Zip file. + /// + /// Returns an for this archive. + /// + /// The Zip file has been closed. + /// + public IEnumerator GetEnumerator() + { + if (isDisposed_) + { + throw new ObjectDisposedException("ZipFile"); + } + + return new ZipEntryEnumerator(entries_); + } + + /// + /// Return the index of the entry with a matching name + /// + /// Entry name to find + /// If true the comparison is case insensitive + /// The index position of the matching entry or -1 if not found + /// + /// The Zip file has been closed. + /// + public int FindEntry(string name, bool ignoreCase) + { + if (isDisposed_) + { + throw new ObjectDisposedException("ZipFile"); + } + + // TODO: This will be slow as the next ice age for huge archives! + for (int i = 0; i < entries_.Length; i++) + { + if (string.Compare(name, entries_[i].Name, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) == 0) + { + return i; + } + } + return -1; + } + + /// + /// Searches for a zip entry in this archive with the given name. + /// String comparisons are case insensitive + /// + /// + /// The name to find. May contain directory components separated by slashes ('/'). + /// + /// + /// A clone of the zip entry, or null if no entry with that name exists. + /// + /// + /// The Zip file has been closed. + /// + public ZipEntry GetEntry(string name) + { + if (isDisposed_) + { + throw new ObjectDisposedException("ZipFile"); + } + + int index = FindEntry(name, true); + return (index >= 0) ? (ZipEntry)entries_[index].Clone() : null; + } + + /// + /// Gets an input stream for reading the given zip entry data in an uncompressed form. + /// Normally the should be an entry returned by GetEntry(). + /// + /// The to obtain a data for + /// An input containing data for this + /// + /// The ZipFile has already been closed + /// + /// + /// The compression method for the entry is unknown + /// + /// + /// The entry is not found in the ZipFile + /// + public Stream GetInputStream(ZipEntry entry) + { + if (entry == null) + { + throw new ArgumentNullException(nameof(entry)); + } + + if (isDisposed_) + { + throw new ObjectDisposedException("ZipFile"); + } + + long index = entry.ZipFileIndex; + if ((index < 0) || (index >= entries_.Length) || (entries_[index].Name != entry.Name)) + { + index = FindEntry(entry.Name, true); + if (index < 0) + { + throw new ZipException("Entry cannot be found"); + } + } + return GetInputStream(index); + } + + /// + /// Creates an input stream reading a zip entry + /// + /// The index of the entry to obtain an input stream for. + /// + /// An input containing data for this + /// + /// + /// The ZipFile has already been closed + /// + /// + /// The compression method for the entry is unknown + /// + /// + /// The entry is not found in the ZipFile + /// + public Stream GetInputStream(long entryIndex) + { + if (isDisposed_) + { + throw new ObjectDisposedException("ZipFile"); + } + + long start = LocateEntry(entries_[entryIndex]); + CompressionMethod method = entries_[entryIndex].CompressionMethod; + Stream result = new PartialInputStream(this, start, entries_[entryIndex].CompressedSize); + + if (entries_[entryIndex].IsCrypted == true) + { + result = CreateAndInitDecryptionStream(result, entries_[entryIndex]); + if (result == null) + { + throw new ZipException("Unable to decrypt this entry"); + } + } + + switch (method) + { + case CompressionMethod.Stored: + // read as is. + break; + + case CompressionMethod.Deflated: + // No need to worry about ownership and closing as underlying stream close does nothing. + result = new InflaterInputStream(result, new Inflater(true)); + break; + + case CompressionMethod.BZip2: + result = new BZip2.BZip2InputStream(result); + break; + + default: + throw new ZipException("Unsupported compression method " + method); + } + + return result; + } + + #endregion Input Handling + + #region Archive Testing + + /// + /// Test an archive for integrity/validity + /// + /// Perform low level data Crc check + /// true if all tests pass, false otherwise + /// Testing will terminate on the first error found. + public bool TestArchive(bool testData) + { + return TestArchive(testData, TestStrategy.FindFirstError, null); + } + + /// + /// Test an archive for integrity/validity + /// + /// Perform low level data Crc check + /// The to apply. + /// The handler to call during testing. + /// true if all tests pass, false otherwise + /// The object has already been closed. + public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandler resultHandler) + { + if (isDisposed_) + { + throw new ObjectDisposedException("ZipFile"); + } + + var status = new TestStatus(this); + + resultHandler?.Invoke(status, null); + + HeaderTest test = testData ? (HeaderTest.Header | HeaderTest.Extract) : HeaderTest.Header; + + bool testing = true; + + try + { + int entryIndex = 0; + + while (testing && (entryIndex < Count)) + { + if (resultHandler != null) + { + status.SetEntry(this[entryIndex]); + status.SetOperation(TestOperation.EntryHeader); + resultHandler(status, null); + } + + try + { + TestLocalHeader(this[entryIndex], test); + } + catch (ZipException ex) + { + status.AddError(); + + resultHandler?.Invoke(status, $"Exception during test - '{ex.Message}'"); + + testing &= strategy != TestStrategy.FindFirstError; + } + + if (testing && testData && this[entryIndex].IsFile) + { + // Don't check CRC for AES encrypted archives + var checkCRC = this[entryIndex].AESKeySize == 0; + + if (resultHandler != null) + { + status.SetOperation(TestOperation.EntryData); + resultHandler(status, null); + } + + var crc = new Crc32(); + + using (Stream entryStream = this.GetInputStream(this[entryIndex])) + { + byte[] buffer = new byte[4096]; + long totalBytes = 0; + int bytesRead; + while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0) + { + if (checkCRC) + { + crc.Update(new ArraySegment(buffer, 0, bytesRead)); + } + + if (resultHandler != null) + { + totalBytes += bytesRead; + status.SetBytesTested(totalBytes); + resultHandler(status, null); + } + } + } + + if (checkCRC && this[entryIndex].Crc != crc.Value) + { + status.AddError(); + + resultHandler?.Invoke(status, "CRC mismatch"); + + testing &= strategy != TestStrategy.FindFirstError; + } + + if ((this[entryIndex].Flags & (int)GeneralBitFlags.Descriptor) != 0) + { + var helper = new ZipHelperStream(baseStream_); + var data = new DescriptorData(); + helper.ReadDataDescriptor(this[entryIndex].LocalHeaderRequiresZip64, data); + + if (checkCRC && this[entryIndex].Crc != data.Crc) + { + status.AddError(); + resultHandler?.Invoke(status, "Descriptor CRC mismatch"); + } + + if (this[entryIndex].CompressedSize != data.CompressedSize) + { + status.AddError(); + resultHandler?.Invoke(status, "Descriptor compressed size mismatch"); + } + + if (this[entryIndex].Size != data.Size) + { + status.AddError(); + resultHandler?.Invoke(status, "Descriptor size mismatch"); + } + } + } + + if (resultHandler != null) + { + status.SetOperation(TestOperation.EntryComplete); + resultHandler(status, null); + } + + entryIndex += 1; + } + + if (resultHandler != null) + { + status.SetOperation(TestOperation.MiscellaneousTests); + resultHandler(status, null); + } + + // TODO: the 'Corrina Johns' test where local headers are missing from + // the central directory. They are therefore invisible to many archivers. + } + catch (Exception ex) + { + status.AddError(); + + resultHandler?.Invoke(status, $"Exception during test - '{ex.Message}'"); + } + + if (resultHandler != null) + { + status.SetOperation(TestOperation.Complete); + status.SetEntry(null); + resultHandler(status, null); + } + + return (status.ErrorCount == 0); + } + + [Flags] + private enum HeaderTest + { + Extract = 0x01, // Check that this header represents an entry whose data can be extracted + Header = 0x02, // Check that this header contents are valid + } + + /// + /// Test a local header against that provided from the central directory + /// + /// + /// The entry to test against + /// + /// The type of tests to carry out. + /// The offset of the entries data in the file + private long TestLocalHeader(ZipEntry entry, HeaderTest tests) + { + lock (baseStream_) + { + bool testHeader = (tests & HeaderTest.Header) != 0; + bool testData = (tests & HeaderTest.Extract) != 0; + + var entryAbsOffset = offsetOfFirstEntry + entry.Offset; + + baseStream_.Seek(entryAbsOffset, SeekOrigin.Begin); + var signature = (int)ReadLEUint(); + + if (signature != ZipConstants.LocalHeaderSignature) + { + throw new ZipException(string.Format("Wrong local header signature at 0x{0:x}, expected 0x{1:x8}, actual 0x{2:x8}", + entryAbsOffset, ZipConstants.LocalHeaderSignature, signature)); + } + + var extractVersion = (short)(ReadLEUshort() & 0x00ff); + var localFlags = (short)ReadLEUshort(); + var compressionMethod = (short)ReadLEUshort(); + var fileTime = (short)ReadLEUshort(); + var fileDate = (short)ReadLEUshort(); + uint crcValue = ReadLEUint(); + long compressedSize = ReadLEUint(); + long size = ReadLEUint(); + int storedNameLength = ReadLEUshort(); + int extraDataLength = ReadLEUshort(); + + byte[] nameData = new byte[storedNameLength]; + StreamUtils.ReadFully(baseStream_, nameData); + + byte[] extraData = new byte[extraDataLength]; + StreamUtils.ReadFully(baseStream_, extraData); + + var localExtraData = new ZipExtraData(extraData); + + // Extra data / zip64 checks + if (localExtraData.Find(1)) + { + // 2010-03-04 Forum 10512: removed checks for version >= ZipConstants.VersionZip64 + // and size or compressedSize = MaxValue, due to rogue creators. + + size = localExtraData.ReadLong(); + compressedSize = localExtraData.ReadLong(); + + if ((localFlags & (int)GeneralBitFlags.Descriptor) != 0) + { + // These may be valid if patched later + if ((size != -1) && (size != entry.Size)) + { + throw new ZipException("Size invalid for descriptor"); + } + + if ((compressedSize != -1) && (compressedSize != entry.CompressedSize)) + { + throw new ZipException("Compressed size invalid for descriptor"); + } + } + } + else + { + // No zip64 extra data but entry requires it. + if ((extractVersion >= ZipConstants.VersionZip64) && + (((uint)size == uint.MaxValue) || ((uint)compressedSize == uint.MaxValue))) + { + throw new ZipException("Required Zip64 extended information missing"); + } + } + + if (testData) + { + if (entry.IsFile) + { + if (!entry.IsCompressionMethodSupported()) + { + throw new ZipException("Compression method not supported"); + } + + if ((extractVersion > ZipConstants.VersionMadeBy) + || ((extractVersion > 20) && (extractVersion < ZipConstants.VersionZip64))) + { + throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extractVersion)); + } + + if ((localFlags & (int)(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.EnhancedCompress | GeneralBitFlags.HeaderMasked)) != 0) + { + throw new ZipException("The library does not support the zip version required to extract this entry"); + } + } + } + + if (testHeader) + { + if ((extractVersion <= 63) && // Ignore later versions as we dont know about them.. + (extractVersion != 10) && + (extractVersion != 11) && + (extractVersion != 20) && + (extractVersion != 21) && + (extractVersion != 25) && + (extractVersion != 27) && + (extractVersion != 45) && + (extractVersion != 46) && + (extractVersion != 50) && + (extractVersion != 51) && + (extractVersion != 52) && + (extractVersion != 61) && + (extractVersion != 62) && + (extractVersion != 63) + ) + { + throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})", extractVersion)); + } + + // Local entry flags dont have reserved bit set on. + if ((localFlags & (int)(GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.ReservedPkware15)) != 0) + { + throw new ZipException("Reserved bit flags cannot be set."); + } + + // Encryption requires extract version >= 20 + if (((localFlags & (int)GeneralBitFlags.Encrypted) != 0) && (extractVersion < 20)) + { + throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion)); + } + + // Strong encryption requires encryption flag to be set and extract version >= 50. + if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) + { + if ((localFlags & (int)GeneralBitFlags.Encrypted) == 0) + { + throw new ZipException("Strong encryption flag set but encryption flag is not set"); + } + + if (extractVersion < 50) + { + throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion)); + } + } + + // Patched entries require extract version >= 27 + if (((localFlags & (int)GeneralBitFlags.Patched) != 0) && (extractVersion < 27)) + { + throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion)); + } + + // Central header flags match local entry flags. + if (localFlags != entry.Flags) + { + throw new ZipException("Central header/local header flags mismatch"); + } + + // Central header compression method matches local entry + if (entry.CompressionMethodForHeader != (CompressionMethod)compressionMethod) + { + throw new ZipException("Central header/local header compression method mismatch"); + } + + if (entry.Version != extractVersion) + { + throw new ZipException("Extract version mismatch"); + } + + // Strong encryption and extract version match + if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) + { + if (extractVersion < 62) + { + throw new ZipException("Strong encryption flag set but version not high enough"); + } + } + + if ((localFlags & (int)GeneralBitFlags.HeaderMasked) != 0) + { + if ((fileTime != 0) || (fileDate != 0)) + { + throw new ZipException("Header masked set but date/time values non-zero"); + } + } + + if ((localFlags & (int)GeneralBitFlags.Descriptor) == 0) + { + if (crcValue != (uint)entry.Crc) + { + throw new ZipException("Central header/local header crc mismatch"); + } + } + + // Crc valid for empty entry. + // This will also apply to streamed entries where size isnt known and the header cant be patched + if ((size == 0) && (compressedSize == 0)) + { + if (crcValue != 0) + { + throw new ZipException("Invalid CRC for empty entry"); + } + } + + // TODO: make test more correct... can't compare lengths as was done originally as this can fail for MBCS strings + // Assuming a code page at this point is not valid? Best is to store the name length in the ZipEntry probably + if (entry.Name.Length > storedNameLength) + { + throw new ZipException("File name length mismatch"); + } + + // Name data has already been read convert it and compare. + string localName = ZipStrings.ConvertToStringExt(localFlags, nameData); + + // Central directory and local entry name match + if (localName != entry.Name) + { + throw new ZipException("Central header and local header file name mismatch"); + } + + // Directories have zero actual size but can have compressed size + if (entry.IsDirectory) + { + if (size > 0) + { + throw new ZipException("Directory cannot have size"); + } + + // There may be other cases where the compressed size can be greater than this? + // If so until details are known we will be strict. + if (entry.IsCrypted) + { + if (compressedSize > entry.EncryptionOverheadSize + 2) + { + throw new ZipException("Directory compressed size invalid"); + } + } + else if (compressedSize > 2) + { + // When not compressed the directory size can validly be 2 bytes + // if the true size wasn't known when data was originally being written. + // NOTE: Versions of the library 0.85.4 and earlier always added 2 bytes + throw new ZipException("Directory compressed size invalid"); + } + } + + if (!ZipNameTransform.IsValidName(localName, true)) + { + throw new ZipException("Name is invalid"); + } + } + + // Tests that apply to both data and header. + + // Size can be verified only if it is known in the local header. + // it will always be known in the central header. + if (((localFlags & (int)GeneralBitFlags.Descriptor) == 0) || + ((size > 0 || compressedSize > 0) && entry.Size > 0)) + { + if ((size != 0) + && (size != entry.Size)) + { + throw new ZipException( + string.Format("Size mismatch between central header({0}) and local header({1})", + entry.Size, size)); + } + + if ((compressedSize != 0) + && (compressedSize != entry.CompressedSize && compressedSize != 0xFFFFFFFF && compressedSize != -1)) + { + throw new ZipException( + string.Format("Compressed size mismatch between central header({0}) and local header({1})", + entry.CompressedSize, compressedSize)); + } + } + + int extraLength = storedNameLength + extraDataLength; + return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength; + } + } + + #endregion Archive Testing + + #region Updating + + private const int DefaultBufferSize = 4096; + + /// + /// The kind of update to apply. + /// + private enum UpdateCommand + { + Copy, // Copy original file contents. + Modify, // Change encryption, compression, attributes, name, time etc, of an existing file. + Add, // Add a new file to the archive. + } + + #region Properties + + /// + /// Get / set the to apply to names when updating. + /// + public INameTransform NameTransform + { + get + { + return updateEntryFactory_.NameTransform; + } + + set + { + updateEntryFactory_.NameTransform = value; + } + } + + /// + /// Get/set the used to generate values + /// during updates. + /// + public IEntryFactory EntryFactory + { + get + { + return updateEntryFactory_; + } + + set + { + if (value == null) + { + updateEntryFactory_ = new ZipEntryFactory(); + } + else + { + updateEntryFactory_ = value; + } + } + } + + /// + /// Get /set the buffer size to be used when updating this zip file. + /// + public int BufferSize + { + get { return bufferSize_; } + set + { + if (value < 1024) + { + throw new ArgumentOutOfRangeException(nameof(value), "cannot be below 1024"); + } + + if (bufferSize_ != value) + { + bufferSize_ = value; + copyBuffer_ = null; + } + } + } + + /// + /// Get a value indicating an update has been started. + /// + public bool IsUpdating + { + get { return updates_ != null; } + } + + /// + /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries. + /// + public UseZip64 UseZip64 + { + get { return useZip64_; } + set { useZip64_ = value; } + } + + #endregion Properties + + #region Immediate updating + + // TBD: Direct form of updating + // + // public void Update(IEntryMatcher deleteMatcher) + // { + // } + // + // public void Update(IScanner addScanner) + // { + // } + + #endregion Immediate updating + + #region Deferred Updating + + /// + /// Begin updating this archive. + /// + /// The archive storage for use during the update. + /// The data source to utilise during updating. + /// ZipFile has been closed. + /// One of the arguments provided is null + /// ZipFile has been closed. + public void BeginUpdate(IArchiveStorage archiveStorage, IDynamicDataSource dataSource) + { + if (isDisposed_) + { + throw new ObjectDisposedException("ZipFile"); + } + + if (IsEmbeddedArchive) + { + throw new ZipException("Cannot update embedded/SFX archives"); + } + + archiveStorage_ = archiveStorage ?? throw new ArgumentNullException(nameof(archiveStorage)); + updateDataSource_ = dataSource ?? throw new ArgumentNullException(nameof(dataSource)); + + // NOTE: the baseStream_ may not currently support writing or seeking. + + updateIndex_ = new Dictionary(); + + updates_ = new List(entries_.Length); + foreach (ZipEntry entry in entries_) + { + int index = updates_.Count; + updates_.Add(new ZipUpdate(entry)); + updateIndex_.Add(entry.Name, index); + } + + // We must sort by offset before using offset's calculated sizes + updates_.Sort(new UpdateComparer()); + + int idx = 0; + foreach (ZipUpdate update in updates_) + { + //If last entry, there is no next entry offset to use + if (idx == updates_.Count - 1) + break; + + update.OffsetBasedSize = ((ZipUpdate)updates_[idx + 1]).Entry.Offset - update.Entry.Offset; + idx++; + } + updateCount_ = updates_.Count; + + contentsEdited_ = false; + commentEdited_ = false; + newComment_ = null; + } + + /// + /// Begin updating to this archive. + /// + /// The storage to use during the update. + public void BeginUpdate(IArchiveStorage archiveStorage) + { + BeginUpdate(archiveStorage, new DynamicDiskDataSource()); + } + + /// + /// Begin updating this archive. + /// + /// + /// + /// + public void BeginUpdate() + { + if (Name == null) + { + BeginUpdate(new MemoryArchiveStorage(), new DynamicDiskDataSource()); + } + else + { + BeginUpdate(new DiskArchiveStorage(this), new DynamicDiskDataSource()); + } + } + + /// + /// Commit current updates, updating this archive. + /// + /// + /// + /// ZipFile has been closed. + public void CommitUpdate() + { + if (isDisposed_) + { + throw new ObjectDisposedException("ZipFile"); + } + + CheckUpdating(); + + try + { + updateIndex_.Clear(); + updateIndex_ = null; + + if (contentsEdited_) + { + RunUpdates(); + } + else if (commentEdited_) + { + UpdateCommentOnly(); + } + else + { + // Create an empty archive if none existed originally. + if (entries_.Length == 0) + { + byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipStrings.ConvertToArray(comment_); + using (ZipHelperStream zhs = new ZipHelperStream(baseStream_)) + { + zhs.WriteEndOfCentralDirectory(0, 0, 0, theComment); + } + } + } + } + finally + { + PostUpdateCleanup(); + } + } + + /// + /// Abort updating leaving the archive unchanged. + /// + /// + /// + public void AbortUpdate() + { + PostUpdateCleanup(); + } + + /// + /// Set the file comment to be recorded when the current update is commited. + /// + /// The comment to record. + /// ZipFile has been closed. + public void SetComment(string comment) + { + if (isDisposed_) + { + throw new ObjectDisposedException("ZipFile"); + } + + CheckUpdating(); + + newComment_ = new ZipString(comment); + + if (newComment_.RawLength > 0xffff) + { + newComment_ = null; + throw new ZipException("Comment length exceeds maximum - 65535"); + } + + // We dont take account of the original and current comment appearing to be the same + // as encoding may be different. + commentEdited_ = true; + } + + #endregion Deferred Updating + + #region Adding Entries + + private void AddUpdate(ZipUpdate update) + { + contentsEdited_ = true; + + int index = FindExistingUpdate(update.Entry.Name, isEntryName: true); + + if (index >= 0) + { + if (updates_[index] == null) + { + updateCount_ += 1; + } + + // Direct replacement is faster than delete and add. + updates_[index] = update; + } + else + { + index = updates_.Count; + updates_.Add(update); + updateCount_ += 1; + updateIndex_.Add(update.Entry.Name, index); + } + } + + /// + /// Add a new entry to the archive. + /// + /// The name of the file to add. + /// The compression method to use. + /// Ensure Unicode text is used for name and comment for this entry. + /// Argument supplied is null. + /// ZipFile has been closed. + /// Compression method is not supported for creating entries. + public void Add(string fileName, CompressionMethod compressionMethod, bool useUnicodeText) + { + if (fileName == null) + { + throw new ArgumentNullException(nameof(fileName)); + } + + if (isDisposed_) + { + throw new ObjectDisposedException("ZipFile"); + } + + CheckSupportedCompressionMethod(compressionMethod); + CheckUpdating(); + contentsEdited_ = true; + + ZipEntry entry = EntryFactory.MakeFileEntry(fileName); + entry.IsUnicodeText = useUnicodeText; + entry.CompressionMethod = compressionMethod; + + AddUpdate(new ZipUpdate(fileName, entry)); + } + + /// + /// Add a new entry to the archive. + /// + /// The name of the file to add. + /// The compression method to use. + /// ZipFile has been closed. + /// Compression method is not supported for creating entries. + public void Add(string fileName, CompressionMethod compressionMethod) + { + if (fileName == null) + { + throw new ArgumentNullException(nameof(fileName)); + } + + CheckSupportedCompressionMethod(compressionMethod); + CheckUpdating(); + contentsEdited_ = true; + + ZipEntry entry = EntryFactory.MakeFileEntry(fileName); + entry.CompressionMethod = compressionMethod; + AddUpdate(new ZipUpdate(fileName, entry)); + } + + /// + /// Add a file to the archive. + /// + /// The name of the file to add. + /// Argument supplied is null. + public void Add(string fileName) + { + if (fileName == null) + { + throw new ArgumentNullException(nameof(fileName)); + } + + CheckUpdating(); + AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName))); + } + + /// + /// Add a file to the archive. + /// + /// The name of the file to add. + /// The name to use for the on the Zip file created. + /// Argument supplied is null. + public void Add(string fileName, string entryName) + { + if (fileName == null) + { + throw new ArgumentNullException(nameof(fileName)); + } + + if (entryName == null) + { + throw new ArgumentNullException(nameof(entryName)); + } + + CheckUpdating(); + AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName, entryName, true))); + } + + /// + /// Add a file entry with data. + /// + /// The source of the data for this entry. + /// The name to give to the entry. + public void Add(IStaticDataSource dataSource, string entryName) + { + if (dataSource == null) + { + throw new ArgumentNullException(nameof(dataSource)); + } + + if (entryName == null) + { + throw new ArgumentNullException(nameof(entryName)); + } + + CheckUpdating(); + AddUpdate(new ZipUpdate(dataSource, EntryFactory.MakeFileEntry(entryName, false))); + } + + /// + /// Add a file entry with data. + /// + /// The source of the data for this entry. + /// The name to give to the entry. + /// The compression method to use. + /// Compression method is not supported for creating entries. + public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) + { + if (dataSource == null) + { + throw new ArgumentNullException(nameof(dataSource)); + } + + if (entryName == null) + { + throw new ArgumentNullException(nameof(entryName)); + } + + CheckSupportedCompressionMethod(compressionMethod); + CheckUpdating(); + + ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); + entry.CompressionMethod = compressionMethod; + + AddUpdate(new ZipUpdate(dataSource, entry)); + } + + /// + /// Add a file entry with data. + /// + /// The source of the data for this entry. + /// The name to give to the entry. + /// The compression method to use. + /// Ensure Unicode text is used for name and comments for this entry. + /// Compression method is not supported for creating entries. + public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod, bool useUnicodeText) + { + if (dataSource == null) + { + throw new ArgumentNullException(nameof(dataSource)); + } + + if (entryName == null) + { + throw new ArgumentNullException(nameof(entryName)); + } + + CheckSupportedCompressionMethod(compressionMethod); + CheckUpdating(); + + ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); + entry.IsUnicodeText = useUnicodeText; + entry.CompressionMethod = compressionMethod; + + AddUpdate(new ZipUpdate(dataSource, entry)); + } + + /// + /// Add a that contains no data. + /// + /// The entry to add. + /// This can be used to add directories, volume labels, or empty file entries. + public void Add(ZipEntry entry) + { + if (entry == null) + { + throw new ArgumentNullException(nameof(entry)); + } + + CheckUpdating(); + + if ((entry.Size != 0) || (entry.CompressedSize != 0)) + { + throw new ZipException("Entry cannot have any data"); + } + + AddUpdate(new ZipUpdate(UpdateCommand.Add, entry)); + } + + /// + /// Add a with data. + /// + /// The source of the data for this entry. + /// The entry to add. + /// This can be used to add file entries with a custom data source. + /// + /// The encryption method specified in is unsupported. + /// + /// Compression method is not supported for creating entries. + public void Add(IStaticDataSource dataSource, ZipEntry entry) + { + if (entry == null) + { + throw new ArgumentNullException(nameof(entry)); + } + + if (dataSource == null) + { + throw new ArgumentNullException(nameof(dataSource)); + } + + // We don't currently support adding entries with AES encryption, so throw + // up front instead of failing or falling back to ZipCrypto later on + if (entry.AESKeySize > 0) + { + throw new NotSupportedException("Creation of AES encrypted entries is not supported"); + } + + CheckSupportedCompressionMethod(entry.CompressionMethod); + CheckUpdating(); + + AddUpdate(new ZipUpdate(dataSource, entry)); + } + + /// + /// Add a directory entry to the archive. + /// + /// The directory to add. + public void AddDirectory(string directoryName) + { + if (directoryName == null) + { + throw new ArgumentNullException(nameof(directoryName)); + } + + CheckUpdating(); + + ZipEntry dirEntry = EntryFactory.MakeDirectoryEntry(directoryName); + AddUpdate(new ZipUpdate(UpdateCommand.Add, dirEntry)); + } + + /// + /// Check if the specified compression method is supported for adding a new entry. + /// + /// The compression method for the new entry. + private static void CheckSupportedCompressionMethod(CompressionMethod compressionMethod) + { + if (compressionMethod != CompressionMethod.Deflated && compressionMethod != CompressionMethod.Stored && compressionMethod != CompressionMethod.BZip2) + { + throw new NotImplementedException("Compression method not supported"); + } + } + + #endregion Adding Entries + + #region Modifying Entries + + /* Modify not yet ready for public consumption. + Direct modification of an entry should not overwrite original data before its read. + Safe mode is trivial in this sense. + public void Modify(ZipEntry original, ZipEntry updated) + { + if ( original == null ) { + throw new ArgumentNullException("original"); + } + if ( updated == null ) { + throw new ArgumentNullException("updated"); + } + CheckUpdating(); + contentsEdited_ = true; + updates_.Add(new ZipUpdate(original, updated)); + } + */ + + #endregion Modifying Entries + + #region Deleting Entries + + /// + /// Delete an entry by name + /// + /// The filename to delete + /// True if the entry was found and deleted; false otherwise. + public bool Delete(string fileName) + { + if (fileName == null) + { + throw new ArgumentNullException(nameof(fileName)); + } + + CheckUpdating(); + + bool result = false; + int index = FindExistingUpdate(fileName); + if ((index >= 0) && (updates_[index] != null)) + { + result = true; + contentsEdited_ = true; + updates_[index] = null; + updateCount_ -= 1; + } + else + { + throw new ZipException("Cannot find entry to delete"); + } + return result; + } + + /// + /// Delete a from the archive. + /// + /// The entry to delete. + public void Delete(ZipEntry entry) + { + if (entry == null) + { + throw new ArgumentNullException(nameof(entry)); + } + + CheckUpdating(); + + int index = FindExistingUpdate(entry); + if (index >= 0) + { + contentsEdited_ = true; + updates_[index] = null; + updateCount_ -= 1; + } + else + { + throw new ZipException("Cannot find entry to delete"); + } + } + + #endregion Deleting Entries + + #region Update Support + + #region Writing Values/Headers + + private void WriteLEShort(int value) + { + baseStream_.WriteByte((byte)(value & 0xff)); + baseStream_.WriteByte((byte)((value >> 8) & 0xff)); + } + + /// + /// Write an unsigned short in little endian byte order. + /// + private void WriteLEUshort(ushort value) + { + baseStream_.WriteByte((byte)(value & 0xff)); + baseStream_.WriteByte((byte)(value >> 8)); + } + + /// + /// Write an int in little endian byte order. + /// + private void WriteLEInt(int value) + { + WriteLEShort(value & 0xffff); + WriteLEShort(value >> 16); + } + + /// + /// Write an unsigned int in little endian byte order. + /// + private void WriteLEUint(uint value) + { + WriteLEUshort((ushort)(value & 0xffff)); + WriteLEUshort((ushort)(value >> 16)); + } + + /// + /// Write a long in little endian byte order. + /// + private void WriteLeLong(long value) + { + WriteLEInt((int)(value & 0xffffffff)); + WriteLEInt((int)(value >> 32)); + } + + private void WriteLEUlong(ulong value) + { + WriteLEUint((uint)(value & 0xffffffff)); + WriteLEUint((uint)(value >> 32)); + } + + private void WriteLocalEntryHeader(ZipUpdate update) + { + ZipEntry entry = update.OutEntry; + + // TODO: Local offset will require adjusting for multi-disk zip files. + entry.Offset = baseStream_.Position; + + // TODO: Need to clear any entry flags that dont make sense or throw an exception here. + if (update.Command != UpdateCommand.Copy) + { + if (entry.CompressionMethod == CompressionMethod.Deflated) + { + if (entry.Size == 0) + { + // No need to compress - no data. + entry.CompressedSize = entry.Size; + entry.Crc = 0; + entry.CompressionMethod = CompressionMethod.Stored; + } + } + else if (entry.CompressionMethod == CompressionMethod.Stored) + { + entry.Flags &= ~(int)GeneralBitFlags.Descriptor; + } + + if (HaveKeys) + { + entry.IsCrypted = true; + if (entry.Crc < 0) + { + entry.Flags |= (int)GeneralBitFlags.Descriptor; + } + } + else + { + entry.IsCrypted = false; + } + + switch (useZip64_) + { + case UseZip64.Dynamic: + if (entry.Size < 0) + { + entry.ForceZip64(); + } + break; + + case UseZip64.On: + entry.ForceZip64(); + break; + + case UseZip64.Off: + // Do nothing. The entry itself may be using Zip64 independently. + break; + } + } + + // Write the local file header + WriteLEInt(ZipConstants.LocalHeaderSignature); + + WriteLEShort(entry.Version); + WriteLEShort(entry.Flags); + + WriteLEShort((byte)entry.CompressionMethodForHeader); + WriteLEInt((int)entry.DosTime); + + if (!entry.HasCrc) + { + // Note patch address for updating CRC later. + update.CrcPatchOffset = baseStream_.Position; + WriteLEInt((int)0); + } + else + { + WriteLEInt(unchecked((int)entry.Crc)); + } + + if (entry.LocalHeaderRequiresZip64) + { + WriteLEInt(-1); + WriteLEInt(-1); + } + else + { + if ((entry.CompressedSize < 0) || (entry.Size < 0)) + { + update.SizePatchOffset = baseStream_.Position; + } + + WriteLEInt((int)entry.CompressedSize); + WriteLEInt((int)entry.Size); + } + + byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name); + + if (name.Length > 0xFFFF) + { + throw new ZipException("Entry name too long."); + } + + var ed = new ZipExtraData(entry.ExtraData); + + if (entry.LocalHeaderRequiresZip64) + { + ed.StartNewEntry(); + + // Local entry header always includes size and compressed size. + // NOTE the order of these fields is reversed when compared to the normal headers! + ed.AddLeLong(entry.Size); + ed.AddLeLong(entry.CompressedSize); + ed.AddNewEntry(1); + } + else + { + ed.Delete(1); + } + + entry.ExtraData = ed.GetEntryData(); + + WriteLEShort(name.Length); + WriteLEShort(entry.ExtraData.Length); + + if (name.Length > 0) + { + baseStream_.Write(name, 0, name.Length); + } + + if (entry.LocalHeaderRequiresZip64) + { + if (!ed.Find(1)) + { + throw new ZipException("Internal error cannot find extra data"); + } + + update.SizePatchOffset = baseStream_.Position + ed.CurrentReadIndex; + } + + if (entry.ExtraData.Length > 0) + { + baseStream_.Write(entry.ExtraData, 0, entry.ExtraData.Length); + } + } + + private int WriteCentralDirectoryHeader(ZipEntry entry) + { + if (entry.CompressedSize < 0) + { + throw new ZipException("Attempt to write central directory entry with unknown csize"); + } + + if (entry.Size < 0) + { + throw new ZipException("Attempt to write central directory entry with unknown size"); + } + + if (entry.Crc < 0) + { + throw new ZipException("Attempt to write central directory entry with unknown crc"); + } + + // Write the central file header + WriteLEInt(ZipConstants.CentralHeaderSignature); + + // Version made by + WriteLEShort((entry.HostSystem << 8) | entry.VersionMadeBy); + + // Version required to extract + WriteLEShort(entry.Version); + + WriteLEShort(entry.Flags); + + unchecked + { + WriteLEShort((byte)entry.CompressionMethodForHeader); + WriteLEInt((int)entry.DosTime); + WriteLEInt((int)entry.Crc); + } + + bool useExtraCompressedSize = false; //Do we want to store the compressed size in the extra data? + if ((entry.IsZip64Forced()) || (entry.CompressedSize >= 0xffffffff)) + { + useExtraCompressedSize = true; + WriteLEInt(-1); + } + else + { + WriteLEInt((int)(entry.CompressedSize & 0xffffffff)); + } + + bool useExtraUncompressedSize = false; //Do we want to store the uncompressed size in the extra data? + if ((entry.IsZip64Forced()) || (entry.Size >= 0xffffffff)) + { + useExtraUncompressedSize = true; + WriteLEInt(-1); + } + else + { + WriteLEInt((int)entry.Size); + } + + byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name); + + if (name.Length > 0xFFFF) + { + throw new ZipException("Entry name is too long."); + } + + WriteLEShort(name.Length); + + // Central header extra data is different to local header version so regenerate. + var ed = new ZipExtraData(entry.ExtraData); + + if (entry.CentralHeaderRequiresZip64) + { + ed.StartNewEntry(); + + if (useExtraUncompressedSize) + { + ed.AddLeLong(entry.Size); + } + + if (useExtraCompressedSize) + { + ed.AddLeLong(entry.CompressedSize); + } + + if (entry.Offset >= 0xffffffff) + { + ed.AddLeLong(entry.Offset); + } + + // Number of disk on which this file starts isnt supported and is never written here. + ed.AddNewEntry(1); + } + else + { + // Should have already be done when local header was added. + ed.Delete(1); + } + + byte[] centralExtraData = ed.GetEntryData(); + + WriteLEShort(centralExtraData.Length); + WriteLEShort(entry.Comment != null ? entry.Comment.Length : 0); + + WriteLEShort(0); // disk number + WriteLEShort(0); // internal file attributes + + // External file attributes... + if (entry.ExternalFileAttributes != -1) + { + WriteLEInt(entry.ExternalFileAttributes); + } + else + { + if (entry.IsDirectory) + { + WriteLEUint(16); + } + else + { + WriteLEUint(0); + } + } + + if (entry.Offset >= 0xffffffff) + { + WriteLEUint(0xffffffff); + } + else + { + WriteLEUint((uint)(int)entry.Offset); + } + + if (name.Length > 0) + { + baseStream_.Write(name, 0, name.Length); + } + + if (centralExtraData.Length > 0) + { + baseStream_.Write(centralExtraData, 0, centralExtraData.Length); + } + + byte[] rawComment = (entry.Comment != null) ? Encoding.ASCII.GetBytes(entry.Comment) : Empty.Array(); + + if (rawComment.Length > 0) + { + baseStream_.Write(rawComment, 0, rawComment.Length); + } + + return ZipConstants.CentralHeaderBaseSize + name.Length + centralExtraData.Length + rawComment.Length; + } + + #endregion Writing Values/Headers + + private void PostUpdateCleanup() + { + updateDataSource_ = null; + updates_ = null; + updateIndex_ = null; + + if (archiveStorage_ != null) + { + archiveStorage_.Dispose(); + archiveStorage_ = null; + } + } + + private string GetTransformedFileName(string name) + { + INameTransform transform = NameTransform; + return (transform != null) ? + transform.TransformFile(name) : + name; + } + + private string GetTransformedDirectoryName(string name) + { + INameTransform transform = NameTransform; + return (transform != null) ? + transform.TransformDirectory(name) : + name; + } + + /// + /// Get a raw memory buffer. + /// + /// Returns a raw memory buffer. + private byte[] GetBuffer() + { + if (copyBuffer_ == null) + { + copyBuffer_ = new byte[bufferSize_]; + } + return copyBuffer_; + } + + private void CopyDescriptorBytes(ZipUpdate update, Stream dest, Stream source) + { + // Don't include the signature size to allow copy without seeking + var bytesToCopy = GetDescriptorSize(update, false); + + // Don't touch the source stream if no descriptor is present + if (bytesToCopy == 0) return; + + var buffer = GetBuffer(); + + // Copy the first 4 bytes of the descriptor + source.Read(buffer, 0, sizeof(int)); + dest.Write(buffer, 0, sizeof(int)); + + if (BitConverter.ToUInt32(buffer, 0) != ZipConstants.DataDescriptorSignature) + { + // The initial bytes wasn't the descriptor, reduce the pending byte count + bytesToCopy -= buffer.Length; + } + + while (bytesToCopy > 0) + { + int readSize = Math.Min(buffer.Length, bytesToCopy); + + int bytesRead = source.Read(buffer, 0, readSize); + if (bytesRead > 0) + { + dest.Write(buffer, 0, bytesRead); + bytesToCopy -= bytesRead; + } + else + { + throw new ZipException("Unxpected end of stream"); + } + } + } + + private void CopyBytes(ZipUpdate update, Stream destination, Stream source, + long bytesToCopy, bool updateCrc) + { + if (destination == source) + { + throw new InvalidOperationException("Destination and source are the same"); + } + + // NOTE: Compressed size is updated elsewhere. + var crc = new Crc32(); + byte[] buffer = GetBuffer(); + + long targetBytes = bytesToCopy; + long totalBytesRead = 0; + + int bytesRead; + do + { + int readSize = buffer.Length; + + if (bytesToCopy < readSize) + { + readSize = (int)bytesToCopy; + } + + bytesRead = source.Read(buffer, 0, readSize); + if (bytesRead > 0) + { + if (updateCrc) + { + crc.Update(new ArraySegment(buffer, 0, bytesRead)); + } + destination.Write(buffer, 0, bytesRead); + bytesToCopy -= bytesRead; + totalBytesRead += bytesRead; + } + } + while ((bytesRead > 0) && (bytesToCopy > 0)); + + if (totalBytesRead != targetBytes) + { + throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)); + } + + if (updateCrc) + { + update.OutEntry.Crc = crc.Value; + } + } + + /// + /// Get the size of the source descriptor for a . + /// + /// The update to get the size for. + /// Whether to include the signature size + /// The descriptor size, zero if there isn't one. + private static int GetDescriptorSize(ZipUpdate update, bool includingSignature) + { + if (!((GeneralBitFlags)update.Entry.Flags).HasFlag(GeneralBitFlags.Descriptor)) + return 0; + + var descriptorWithSignature = update.Entry.LocalHeaderRequiresZip64 + ? ZipConstants.Zip64DataDescriptorSize + : ZipConstants.DataDescriptorSize; + + return includingSignature + ? descriptorWithSignature + : descriptorWithSignature - sizeof(int); + } + + private void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long destinationPosition, long sourcePosition) + { + var buffer = GetBuffer(); ; + + stream.Position = sourcePosition; + stream.Read(buffer, 0, sizeof(int)); + var sourceHasSignature = BitConverter.ToUInt32(buffer, 0) == ZipConstants.DataDescriptorSignature; + + var bytesToCopy = GetDescriptorSize(update, sourceHasSignature); + + while (bytesToCopy > 0) + { + stream.Position = sourcePosition; + + var bytesRead = stream.Read(buffer, 0, bytesToCopy); + if (bytesRead > 0) + { + stream.Position = destinationPosition; + stream.Write(buffer, 0, bytesRead); + bytesToCopy -= bytesRead; + destinationPosition += bytesRead; + sourcePosition += bytesRead; + } + else + { + throw new ZipException("Unexpected end of stream"); + } + } + } + + private void CopyEntryDataDirect(ZipUpdate update, Stream stream, bool updateCrc, ref long destinationPosition, ref long sourcePosition) + { + long bytesToCopy = update.Entry.CompressedSize; + + // NOTE: Compressed size is updated elsewhere. + var crc = new Crc32(); + byte[] buffer = GetBuffer(); + + long targetBytes = bytesToCopy; + long totalBytesRead = 0; + + int bytesRead; + do + { + int readSize = buffer.Length; + + if (bytesToCopy < readSize) + { + readSize = (int)bytesToCopy; + } + + stream.Position = sourcePosition; + bytesRead = stream.Read(buffer, 0, readSize); + if (bytesRead > 0) + { + if (updateCrc) + { + crc.Update(new ArraySegment(buffer, 0, bytesRead)); + } + stream.Position = destinationPosition; + stream.Write(buffer, 0, bytesRead); + + destinationPosition += bytesRead; + sourcePosition += bytesRead; + bytesToCopy -= bytesRead; + totalBytesRead += bytesRead; + } + } + while ((bytesRead > 0) && (bytesToCopy > 0)); + + if (totalBytesRead != targetBytes) + { + throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)); + } + + if (updateCrc) + { + update.OutEntry.Crc = crc.Value; + } + } + + private int FindExistingUpdate(ZipEntry entry) + { + int result = -1; + if (updateIndex_.ContainsKey(entry.Name)) + { + result = (int)updateIndex_[entry.Name]; + } + /* + // This is slow like the coming of the next ice age but takes less storage and may be useful + // for CF? + for (int index = 0; index < updates_.Count; ++index) + { + ZipUpdate zu = ( ZipUpdate )updates_[index]; + if ( (zu.Entry.ZipFileIndex == entry.ZipFileIndex) && + (string.Compare(convertedName, zu.Entry.Name, true, CultureInfo.InvariantCulture) == 0) ) { + result = index; + break; + } + } + */ + return result; + } + + private int FindExistingUpdate(string fileName, bool isEntryName = false) + { + int result = -1; + + string convertedName = !isEntryName ? GetTransformedFileName(fileName) : fileName; + + if (updateIndex_.ContainsKey(convertedName)) + { + result = (int)updateIndex_[convertedName]; + } + + /* + // This is slow like the coming of the next ice age but takes less storage and may be useful + // for CF? + for ( int index = 0; index < updates_.Count; ++index ) { + if ( string.Compare(convertedName, (( ZipUpdate )updates_[index]).Entry.Name, + true, CultureInfo.InvariantCulture) == 0 ) { + result = index; + break; + } + } + */ + + return result; + } + + /// + /// Get an output stream for the specified + /// + /// The entry to get an output stream for. + /// The output stream obtained for the entry. + private Stream GetOutputStream(ZipEntry entry) + { + Stream result = baseStream_; + + if (entry.IsCrypted == true) + { + result = CreateAndInitEncryptionStream(result, entry); + } + + switch (entry.CompressionMethod) + { + case CompressionMethod.Stored: + if (!entry.IsCrypted) + { + // If there is an encryption stream in use, that can be returned directly + // otherwise, wrap the base stream in an UncompressedStream instead of returning it directly + result = new UncompressedStream(result); + } + break; + + case CompressionMethod.Deflated: + var dos = new DeflaterOutputStream(result, new Deflater(9, true)) + { + // If there is an encryption stream in use, then we want that to be disposed when the deflator stream is disposed + // If not, then we don't want it to dispose the base stream + IsStreamOwner = entry.IsCrypted + }; + result = dos; + break; + + case CompressionMethod.BZip2: + var bzos = new BZip2.BZip2OutputStream(result) + { + // If there is an encryption stream in use, then we want that to be disposed when the BZip2OutputStream stream is disposed + // If not, then we don't want it to dispose the base stream + IsStreamOwner = entry.IsCrypted + }; + result = bzos; + break; + + default: + throw new ZipException("Unknown compression method " + entry.CompressionMethod); + } + return result; + } + + private void AddEntry(ZipFile workFile, ZipUpdate update) + { + Stream source = null; + + if (update.Entry.IsFile) + { + source = update.GetSource(); + + if (source == null) + { + source = updateDataSource_.GetSource(update.Entry, update.Filename); + } + } + + var useCrc = update.Entry.AESKeySize == 0; + + if (source != null) + { + using (source) + { + long sourceStreamLength = source.Length; + if (update.OutEntry.Size < 0) + { + update.OutEntry.Size = sourceStreamLength; + } + else + { + // Check for errant entries. + if (update.OutEntry.Size != sourceStreamLength) + { + throw new ZipException("Entry size/stream size mismatch"); + } + } + + workFile.WriteLocalEntryHeader(update); + + long dataStart = workFile.baseStream_.Position; + + using (Stream output = workFile.GetOutputStream(update.OutEntry)) + { + CopyBytes(update, output, source, sourceStreamLength, useCrc); + } + + long dataEnd = workFile.baseStream_.Position; + update.OutEntry.CompressedSize = dataEnd - dataStart; + + if ((update.OutEntry.Flags & (int)GeneralBitFlags.Descriptor) == (int)GeneralBitFlags.Descriptor) + { + var helper = new ZipHelperStream(workFile.baseStream_); + helper.WriteDataDescriptor(update.OutEntry); + } + } + } + else + { + workFile.WriteLocalEntryHeader(update); + update.OutEntry.CompressedSize = 0; + } + } + + private void ModifyEntry(ZipFile workFile, ZipUpdate update) + { + workFile.WriteLocalEntryHeader(update); + long dataStart = workFile.baseStream_.Position; + + // TODO: This is slow if the changes don't effect the data!! + if (update.Entry.IsFile && (update.Filename != null)) + { + using (Stream output = workFile.GetOutputStream(update.OutEntry)) + { + using (Stream source = this.GetInputStream(update.Entry)) + { + CopyBytes(update, output, source, source.Length, true); + } + } + } + + long dataEnd = workFile.baseStream_.Position; + update.Entry.CompressedSize = dataEnd - dataStart; + } + + private void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destinationPosition) + { + bool skipOver = false || update.Entry.Offset == destinationPosition; + + if (!skipOver) + { + baseStream_.Position = destinationPosition; + workFile.WriteLocalEntryHeader(update); + destinationPosition = baseStream_.Position; + } + + long sourcePosition = 0; + + const int NameLengthOffset = 26; + + // TODO: Add base for SFX friendly handling + long entryDataOffset = update.Entry.Offset + NameLengthOffset; + + baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); + + // Clumsy way of handling retrieving the original name and extra data length for now. + // TODO: Stop re-reading name and data length in CopyEntryDirect. + + uint nameLength = ReadLEUshort(); + uint extraLength = ReadLEUshort(); + + sourcePosition = baseStream_.Position + nameLength + extraLength; + + if (skipOver) + { + if (update.OffsetBasedSize != -1) + { + destinationPosition += update.OffsetBasedSize; + } + else + { + // Skip entry header + destinationPosition += (sourcePosition - entryDataOffset) + NameLengthOffset; + + // Skip entry compressed data + destinationPosition += update.Entry.CompressedSize; + + // Seek to end of entry to check for descriptor signature + baseStream_.Seek(destinationPosition, SeekOrigin.Begin); + + var descriptorHasSignature = ReadLEUint() == ZipConstants.DataDescriptorSignature; + + // Skip descriptor and it's signature (if present) + destinationPosition += GetDescriptorSize(update, descriptorHasSignature); + } + } + else + { + if (update.Entry.CompressedSize > 0) + { + CopyEntryDataDirect(update, baseStream_, false, ref destinationPosition, ref sourcePosition); + } + CopyDescriptorBytesDirect(update, baseStream_, ref destinationPosition, sourcePosition); + } + } + + private void CopyEntry(ZipFile workFile, ZipUpdate update) + { + workFile.WriteLocalEntryHeader(update); + + if (update.Entry.CompressedSize > 0) + { + const int NameLengthOffset = 26; + + long entryDataOffset = update.Entry.Offset + NameLengthOffset; + + // TODO: This wont work for SFX files! + baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); + + uint nameLength = ReadLEUshort(); + uint extraLength = ReadLEUshort(); + + baseStream_.Seek(nameLength + extraLength, SeekOrigin.Current); + + CopyBytes(update, workFile.baseStream_, baseStream_, update.Entry.CompressedSize, false); + } + CopyDescriptorBytes(update, workFile.baseStream_, baseStream_); + } + + private void Reopen(Stream source) + { + isNewArchive_ = false; + baseStream_ = source ?? throw new ZipException("Failed to reopen archive - no source"); + ReadEntries(); + } + + private void Reopen() + { + if (Name == null) + { + throw new InvalidOperationException("Name is not known cannot Reopen"); + } + + Reopen(File.Open(Name, FileMode.Open, FileAccess.Read, FileShare.Read)); + } + + private void UpdateCommentOnly() + { + long baseLength = baseStream_.Length; + + ZipHelperStream updateFile = null; + + if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) + { + Stream copyStream = archiveStorage_.MakeTemporaryCopy(baseStream_); + updateFile = new ZipHelperStream(copyStream) + { + IsStreamOwner = true + }; + + baseStream_.Dispose(); + baseStream_ = null; + } + else + { + if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) + { + // TODO: archiveStorage wasnt originally intended for this use. + // Need to revisit this to tidy up handling as archive storage currently doesnt + // handle the original stream well. + // The problem is when using an existing zip archive with an in memory archive storage. + // The open stream wont support writing but the memory storage should open the same file not an in memory one. + + // Need to tidy up the archive storage interface and contract basically. + baseStream_ = archiveStorage_.OpenForDirectUpdate(baseStream_); + updateFile = new ZipHelperStream(baseStream_); + } + else + { + baseStream_.Dispose(); + baseStream_ = null; + updateFile = new ZipHelperStream(Name); + } + } + + using (updateFile) + { + long locatedCentralDirOffset = + updateFile.LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, + baseLength, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); + if (locatedCentralDirOffset < 0) + { + throw new ZipException("Cannot find central directory"); + } + + const int CentralHeaderCommentSizeOffset = 16; + updateFile.Position += CentralHeaderCommentSizeOffset; + + byte[] rawComment = newComment_.RawComment; + + updateFile.WriteLEShort(rawComment.Length); + updateFile.Write(rawComment, 0, rawComment.Length); + updateFile.SetLength(updateFile.Position); + } + + if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) + { + Reopen(archiveStorage_.ConvertTemporaryToFinal()); + } + else + { + ReadEntries(); + } + } + + /// + /// Class used to sort updates. + /// + private class UpdateComparer : IComparer + { + /// + /// Compares two objects and returns a value indicating whether one is + /// less than, equal to or greater than the other. + /// + /// First object to compare + /// Second object to compare. + /// Compare result. + public int Compare(ZipUpdate x, ZipUpdate y) + { + int result; + + if (x == null) + { + if (y == null) + { + result = 0; + } + else + { + result = -1; + } + } + else if (y == null) + { + result = 1; + } + else + { + int xCmdValue = ((x.Command == UpdateCommand.Copy) || (x.Command == UpdateCommand.Modify)) ? 0 : 1; + int yCmdValue = ((y.Command == UpdateCommand.Copy) || (y.Command == UpdateCommand.Modify)) ? 0 : 1; + + result = xCmdValue - yCmdValue; + if (result == 0) + { + long offsetDiff = x.Entry.Offset - y.Entry.Offset; + if (offsetDiff < 0) + { + result = -1; + } + else if (offsetDiff == 0) + { + result = 0; + } + else + { + result = 1; + } + } + } + return result; + } + } + + private void RunUpdates() + { + long sizeEntries = 0; + long endOfStream = 0; + bool directUpdate = false; + long destinationPosition = 0; // NOT SFX friendly + + ZipFile workFile; + + if (IsNewArchive) + { + workFile = this; + workFile.baseStream_.Position = 0; + directUpdate = true; + } + else if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) + { + workFile = this; + workFile.baseStream_.Position = 0; + directUpdate = true; + + // Sort the updates by offset within copies/modifies, then adds. + // This ensures that data required by copies will not be overwritten. + updates_.Sort(new UpdateComparer()); + } + else + { + workFile = ZipFile.Create(archiveStorage_.GetTemporaryOutput()); + workFile.UseZip64 = UseZip64; + + if (key != null) + { + workFile.key = (byte[])key.Clone(); + } + } + + try + { + foreach (ZipUpdate update in updates_) + { + if (update != null) + { + switch (update.Command) + { + case UpdateCommand.Copy: + if (directUpdate) + { + CopyEntryDirect(workFile, update, ref destinationPosition); + } + else + { + CopyEntry(workFile, update); + } + break; + + case UpdateCommand.Modify: + // TODO: Direct modifying of an entry will take some legwork. + ModifyEntry(workFile, update); + break; + + case UpdateCommand.Add: + if (!IsNewArchive && directUpdate) + { + workFile.baseStream_.Position = destinationPosition; + } + + AddEntry(workFile, update); + + if (directUpdate) + { + destinationPosition = workFile.baseStream_.Position; + } + break; + } + } + } + + if (!IsNewArchive && directUpdate) + { + workFile.baseStream_.Position = destinationPosition; + } + + long centralDirOffset = workFile.baseStream_.Position; + + foreach (ZipUpdate update in updates_) + { + if (update != null) + { + sizeEntries += workFile.WriteCentralDirectoryHeader(update.OutEntry); + } + } + + byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipStrings.ConvertToArray(comment_); + using (ZipHelperStream zhs = new ZipHelperStream(workFile.baseStream_)) + { + zhs.WriteEndOfCentralDirectory(updateCount_, sizeEntries, centralDirOffset, theComment); + } + + endOfStream = workFile.baseStream_.Position; + + // And now patch entries... + foreach (ZipUpdate update in updates_) + { + if (update != null) + { + // If the size of the entry is zero leave the crc as 0 as well. + // The calculated crc will be all bits on... + if ((update.CrcPatchOffset > 0) && (update.OutEntry.CompressedSize > 0)) + { + workFile.baseStream_.Position = update.CrcPatchOffset; + workFile.WriteLEInt((int)update.OutEntry.Crc); + } + + if (update.SizePatchOffset > 0) + { + workFile.baseStream_.Position = update.SizePatchOffset; + if (update.OutEntry.LocalHeaderRequiresZip64) + { + workFile.WriteLeLong(update.OutEntry.Size); + workFile.WriteLeLong(update.OutEntry.CompressedSize); + } + else + { + workFile.WriteLEInt((int)update.OutEntry.CompressedSize); + workFile.WriteLEInt((int)update.OutEntry.Size); + } + } + } + } + } + catch + { + workFile.Close(); + if (!directUpdate && (workFile.Name != null)) + { + File.Delete(workFile.Name); + } + throw; + } + + if (directUpdate) + { + workFile.baseStream_.SetLength(endOfStream); + workFile.baseStream_.Flush(); + isNewArchive_ = false; + ReadEntries(); + } + else + { + baseStream_.Dispose(); + Reopen(archiveStorage_.ConvertTemporaryToFinal()); + } + } + + private void CheckUpdating() + { + if (updates_ == null) + { + throw new InvalidOperationException("BeginUpdate has not been called"); + } + } + + #endregion Update Support + + #region ZipUpdate class + + /// + /// Represents a pending update to a Zip file. + /// + private class ZipUpdate + { + #region Constructors + + public ZipUpdate(string fileName, ZipEntry entry) + { + command_ = UpdateCommand.Add; + entry_ = entry; + filename_ = fileName; + } + + [Obsolete] + public ZipUpdate(string fileName, string entryName, CompressionMethod compressionMethod) + { + command_ = UpdateCommand.Add; + entry_ = new ZipEntry(entryName) + { + CompressionMethod = compressionMethod + }; + filename_ = fileName; + } + + [Obsolete] + public ZipUpdate(string fileName, string entryName) + : this(fileName, entryName, CompressionMethod.Deflated) + { + // Do nothing. + } + + [Obsolete] + public ZipUpdate(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) + { + command_ = UpdateCommand.Add; + entry_ = new ZipEntry(entryName) + { + CompressionMethod = compressionMethod + }; + dataSource_ = dataSource; + } + + public ZipUpdate(IStaticDataSource dataSource, ZipEntry entry) + { + command_ = UpdateCommand.Add; + entry_ = entry; + dataSource_ = dataSource; + } + + public ZipUpdate(ZipEntry original, ZipEntry updated) + { + throw new ZipException("Modify not currently supported"); + /* + command_ = UpdateCommand.Modify; + entry_ = ( ZipEntry )original.Clone(); + outEntry_ = ( ZipEntry )updated.Clone(); + */ + } + + public ZipUpdate(UpdateCommand command, ZipEntry entry) + { + command_ = command; + entry_ = (ZipEntry)entry.Clone(); + } + + /// + /// Copy an existing entry. + /// + /// The existing entry to copy. + public ZipUpdate(ZipEntry entry) + : this(UpdateCommand.Copy, entry) + { + // Do nothing. + } + + #endregion Constructors + + /// + /// Get the for this update. + /// + /// This is the source or original entry. + public ZipEntry Entry + { + get { return entry_; } + } + + /// + /// Get the that will be written to the updated/new file. + /// + public ZipEntry OutEntry + { + get + { + if (outEntry_ == null) + { + outEntry_ = (ZipEntry)entry_.Clone(); + } + + return outEntry_; + } + } + + /// + /// Get the command for this update. + /// + public UpdateCommand Command + { + get { return command_; } + } + + /// + /// Get the filename if any for this update. Null if none exists. + /// + public string Filename + { + get { return filename_; } + } + + /// + /// Get/set the location of the size patch for this update. + /// + public long SizePatchOffset + { + get { return sizePatchOffset_; } + set { sizePatchOffset_ = value; } + } + + /// + /// Get /set the location of the crc patch for this update. + /// + public long CrcPatchOffset + { + get { return crcPatchOffset_; } + set { crcPatchOffset_ = value; } + } + + /// + /// Get/set the size calculated by offset. + /// Specifically, the difference between this and next entry's starting offset. + /// + public long OffsetBasedSize + { + get { return _offsetBasedSize; } + set { _offsetBasedSize = value; } + } + + public Stream GetSource() + { + Stream result = null; + if (dataSource_ != null) + { + result = dataSource_.GetSource(); + } + + return result; + } + + #region Instance Fields + + private ZipEntry entry_; + private ZipEntry outEntry_; + private readonly UpdateCommand command_; + private IStaticDataSource dataSource_; + private readonly string filename_; + private long sizePatchOffset_ = -1; + private long crcPatchOffset_ = -1; + private long _offsetBasedSize = -1; + + #endregion Instance Fields + } + + #endregion ZipUpdate class + + #endregion Updating + + #region Disposing + + #region IDisposable Members + + void IDisposable.Dispose() + { + Close(); + } + + #endregion IDisposable Members + + private void DisposeInternal(bool disposing) + { + if (!isDisposed_) + { + isDisposed_ = true; + entries_ = Empty.Array(); + + if (IsStreamOwner && (baseStream_ != null)) + { + lock (baseStream_) + { + baseStream_.Dispose(); + } + } + + PostUpdateCleanup(); + } + } + + /// + /// Releases the unmanaged resources used by the this instance and optionally releases the managed resources. + /// + /// true to release both managed and unmanaged resources; + /// false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + DisposeInternal(disposing); + } + + #endregion Disposing + + #region Internal routines + + #region Reading + + /// + /// Read an unsigned short in little endian byte order. + /// + /// Returns the value read. + /// + /// The stream ends prematurely + /// + private ushort ReadLEUshort() + { + int data1 = baseStream_.ReadByte(); + + if (data1 < 0) + { + throw new EndOfStreamException("End of stream"); + } + + int data2 = baseStream_.ReadByte(); + + if (data2 < 0) + { + throw new EndOfStreamException("End of stream"); + } + + return unchecked((ushort)((ushort)data1 | (ushort)(data2 << 8))); + } + + /// + /// Read a uint in little endian byte order. + /// + /// Returns the value read. + /// + /// An i/o error occurs. + /// + /// + /// The file ends prematurely + /// + private uint ReadLEUint() + { + return (uint)(ReadLEUshort() | (ReadLEUshort() << 16)); + } + + private ulong ReadLEUlong() + { + return ReadLEUint() | ((ulong)ReadLEUint() << 32); + } + + #endregion Reading + + // NOTE this returns the offset of the first byte after the signature. + private long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) + { + using (ZipHelperStream les = new ZipHelperStream(baseStream_)) + { + return les.LocateBlockWithSignature(signature, endLocation, minimumBlockSize, maximumVariableData); + } + } + + /// + /// Search for and read the central directory of a zip file filling the entries array. + /// + /// + /// An i/o error occurs. + /// + /// + /// The central directory is malformed or cannot be found + /// + private void ReadEntries() + { + // Search for the End Of Central Directory. When a zip comment is + // present the directory will start earlier + // + // The search is limited to 64K which is the maximum size of a trailing comment field to aid speed. + // This should be compatible with both SFX and ZIP files but has only been tested for Zip files + // If a SFX file has the Zip data attached as a resource and there are other resources occurring later then + // this could be invalid. + // Could also speed this up by reading memory in larger blocks. + + if (baseStream_.CanSeek == false) + { + throw new ZipException("ZipFile stream must be seekable"); + } + + long locatedEndOfCentralDir = LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, + baseStream_.Length, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); + + if (locatedEndOfCentralDir < 0) + { + throw new ZipException("Cannot find central directory"); + } + + // Read end of central directory record + ushort thisDiskNumber = ReadLEUshort(); + ushort startCentralDirDisk = ReadLEUshort(); + ulong entriesForThisDisk = ReadLEUshort(); + ulong entriesForWholeCentralDir = ReadLEUshort(); + ulong centralDirSize = ReadLEUint(); + long offsetOfCentralDir = ReadLEUint(); + uint commentSize = ReadLEUshort(); + + if (commentSize > 0) + { + byte[] comment = new byte[commentSize]; + + StreamUtils.ReadFully(baseStream_, comment); + comment_ = ZipStrings.ConvertToString(comment); + } + else + { + comment_ = string.Empty; + } + + bool isZip64 = false; + bool requireZip64 = false; + + // Check if zip64 header information is required. + if ((thisDiskNumber == 0xffff) || + (startCentralDirDisk == 0xffff) || + (entriesForThisDisk == 0xffff) || + (entriesForWholeCentralDir == 0xffff) || + (centralDirSize == 0xffffffff) || + (offsetOfCentralDir == 0xffffffff)) + { + requireZip64 = true; + } + + // #357 - always check for the existance of the Zip64 central directory. + // #403 - Take account of the fixed size of the locator when searching. + // Subtract from locatedEndOfCentralDir so that the endLocation is the location of EndOfCentralDirectorySignature, + // rather than the data following the signature. + long locatedZip64EndOfCentralDirLocator = LocateBlockWithSignature( + ZipConstants.Zip64CentralDirLocatorSignature, + locatedEndOfCentralDir - 4, + ZipConstants.Zip64EndOfCentralDirectoryLocatorSize, + 0); + + if (locatedZip64EndOfCentralDirLocator < 0) + { + if (requireZip64) + { + // This is only an error in cases where the Zip64 directory is required. + throw new ZipException("Cannot find Zip64 locator"); + } + } + else + { + isZip64 = true; + + // number of the disk with the start of the zip64 end of central directory 4 bytes + // relative offset of the zip64 end of central directory record 8 bytes + // total number of disks 4 bytes + ReadLEUint(); // startDisk64 is not currently used + ulong offset64 = ReadLEUlong(); + uint totalDisks = ReadLEUint(); + + baseStream_.Position = (long)offset64; + long sig64 = ReadLEUint(); + + if (sig64 != ZipConstants.Zip64CentralFileHeaderSignature) + { + throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64)); + } + + // NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12. + ulong recordSize = ReadLEUlong(); + int versionMadeBy = ReadLEUshort(); + int versionToExtract = ReadLEUshort(); + uint thisDisk = ReadLEUint(); + uint centralDirDisk = ReadLEUint(); + entriesForThisDisk = ReadLEUlong(); + entriesForWholeCentralDir = ReadLEUlong(); + centralDirSize = ReadLEUlong(); + offsetOfCentralDir = (long)ReadLEUlong(); + + // NOTE: zip64 extensible data sector (variable size) is ignored. + } + + entries_ = new ZipEntry[entriesForThisDisk]; + + // SFX/embedded support, find the offset of the first entry vis the start of the stream + // This applies to Zip files that are appended to the end of an SFX stub. + // Or are appended as a resource to an executable. + // Zip files created by some archivers have the offsets altered to reflect the true offsets + // and so dont require any adjustment here... + // TODO: Difficulty with Zip64 and SFX offset handling needs resolution - maths? + if (!isZip64 && (offsetOfCentralDir < locatedEndOfCentralDir - (4 + (long)centralDirSize))) + { + offsetOfFirstEntry = locatedEndOfCentralDir - (4 + (long)centralDirSize + offsetOfCentralDir); + if (offsetOfFirstEntry <= 0) + { + throw new ZipException("Invalid embedded zip archive"); + } + } + + baseStream_.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin); + + for (ulong i = 0; i < entriesForThisDisk; i++) + { + if (ReadLEUint() != ZipConstants.CentralHeaderSignature) + { + throw new ZipException("Wrong Central Directory signature"); + } + + int versionMadeBy = ReadLEUshort(); + int versionToExtract = ReadLEUshort(); + int bitFlags = ReadLEUshort(); + int method = ReadLEUshort(); + uint dostime = ReadLEUint(); + uint crc = ReadLEUint(); + var csize = (long)ReadLEUint(); + var size = (long)ReadLEUint(); + int nameLen = ReadLEUshort(); + int extraLen = ReadLEUshort(); + int commentLen = ReadLEUshort(); + + int diskStartNo = ReadLEUshort(); // Not currently used + int internalAttributes = ReadLEUshort(); // Not currently used + + uint externalAttributes = ReadLEUint(); + long offset = ReadLEUint(); + + byte[] buffer = new byte[Math.Max(nameLen, commentLen)]; + + StreamUtils.ReadFully(baseStream_, buffer, 0, nameLen); + string name = ZipStrings.ConvertToStringExt(bitFlags, buffer, nameLen); + + var entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod)method) + { + Crc = crc & 0xffffffffL, + Size = size & 0xffffffffL, + CompressedSize = csize & 0xffffffffL, + Flags = bitFlags, + DosTime = dostime, + ZipFileIndex = (long)i, + Offset = offset, + ExternalFileAttributes = (int)externalAttributes + }; + + if ((bitFlags & 8) == 0) + { + entry.CryptoCheckValue = (byte)(crc >> 24); + } + else + { + entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff); + } + + if (extraLen > 0) + { + byte[] extra = new byte[extraLen]; + StreamUtils.ReadFully(baseStream_, extra); + entry.ExtraData = extra; + } + + entry.ProcessExtraData(false); + + if (commentLen > 0) + { + StreamUtils.ReadFully(baseStream_, buffer, 0, commentLen); + entry.Comment = ZipStrings.ConvertToStringExt(bitFlags, buffer, commentLen); + } + + entries_[i] = entry; + } + } + + /// + /// Locate the data for a given entry. + /// + /// + /// The start offset of the data. + /// + /// + /// The stream ends prematurely + /// + /// + /// The local header signature is invalid, the entry and central header file name lengths are different + /// or the local and entry compression methods dont match + /// + private long LocateEntry(ZipEntry entry) + { + return TestLocalHeader(entry, HeaderTest.Extract); + } + + private Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry) + { + CryptoStream result = null; + + if (entry.CompressionMethodForHeader == CompressionMethod.WinZipAES) + { + if (entry.Version >= ZipConstants.VERSION_AES) + { + // Issue #471 - accept an empty string as a password, but reject null. + OnKeysRequired(entry.Name); + if (rawPassword_ == null) + { + throw new ZipException("No password available for AES encrypted stream"); + } + int saltLen = entry.AESSaltLen; + byte[] saltBytes = new byte[saltLen]; + int saltIn = StreamUtils.ReadRequestedBytes(baseStream, saltBytes, 0, saltLen); + if (saltIn != saltLen) + throw new ZipException("AES Salt expected " + saltLen + " got " + saltIn); + // + byte[] pwdVerifyRead = new byte[2]; + StreamUtils.ReadFully(baseStream, pwdVerifyRead); + int blockSize = entry.AESKeySize / 8; // bits to bytes + + var decryptor = new ZipAESTransform(rawPassword_, saltBytes, blockSize, false); + byte[] pwdVerifyCalc = decryptor.PwdVerifier; + if (pwdVerifyCalc[0] != pwdVerifyRead[0] || pwdVerifyCalc[1] != pwdVerifyRead[1]) + throw new ZipException("Invalid password for AES"); + result = new ZipAESStream(baseStream, decryptor, CryptoStreamMode.Read); + } + else + { + throw new ZipException("Decryption method not supported"); + } + } + else + { + if ((entry.Version < ZipConstants.VersionStrongEncryption) + || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) + { + var classicManaged = new PkzipClassicManaged(); + + OnKeysRequired(entry.Name); + if (HaveKeys == false) + { + throw new ZipException("No password available for encrypted stream"); + } + + result = new CryptoStream(baseStream, classicManaged.CreateDecryptor(key, null), CryptoStreamMode.Read); + CheckClassicPassword(result, entry); + } + else + { + // We don't support PKWare strong encryption + throw new ZipException("Decryption method not supported"); + } + } + + return result; + } + + private Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry) + { + CryptoStream result = null; + if ((entry.Version < ZipConstants.VersionStrongEncryption) + || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) + { + var classicManaged = new PkzipClassicManaged(); + + OnKeysRequired(entry.Name); + if (HaveKeys == false) + { + throw new ZipException("No password available for encrypted stream"); + } + + // Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream + // which doesnt do this. + result = new CryptoStream(new UncompressedStream(baseStream), + classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write); + + if ((entry.Crc < 0) || (entry.Flags & 8) != 0) + { + WriteEncryptionHeader(result, entry.DosTime << 16); + } + else + { + WriteEncryptionHeader(result, entry.Crc); + } + } + return result; + } + + private static void CheckClassicPassword(CryptoStream classicCryptoStream, ZipEntry entry) + { + byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize]; + StreamUtils.ReadFully(classicCryptoStream, cryptbuffer); + if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) + { + throw new ZipException("Invalid password"); + } + } + + private static void WriteEncryptionHeader(Stream stream, long crcValue) + { + byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize]; + using (var rng = new RNGCryptoServiceProvider()) + { + rng.GetBytes(cryptBuffer); + } + cryptBuffer[11] = (byte)(crcValue >> 24); + stream.Write(cryptBuffer, 0, cryptBuffer.Length); + } + + #endregion Internal routines + + #region Instance Fields + + private bool isDisposed_; + private string name_; + private string comment_; + private string rawPassword_; + private Stream baseStream_; + private bool isStreamOwner; + private long offsetOfFirstEntry; + private ZipEntry[] entries_; + private byte[] key; + private bool isNewArchive_; + + // Default is dynamic which is not backwards compatible and can cause problems + // with XP's built in compression which cant read Zip64 archives. + // However it does avoid the situation were a large file is added and cannot be completed correctly. + // Hint: Set always ZipEntry size before they are added to an archive and this setting isnt needed. + private UseZip64 useZip64_ = UseZip64.Dynamic; + + #region Zip Update Instance Fields + + private List updates_; + private long updateCount_; // Count is managed manually as updates_ can contain nulls! + private Dictionary updateIndex_; + private IArchiveStorage archiveStorage_; + private IDynamicDataSource updateDataSource_; + private bool contentsEdited_; + private int bufferSize_ = DefaultBufferSize; + private byte[] copyBuffer_; + private ZipString newComment_; + private bool commentEdited_; + private IEntryFactory updateEntryFactory_ = new ZipEntryFactory(); + + #endregion Zip Update Instance Fields + + #endregion Instance Fields + + #region Support Classes + + /// + /// Represents a string from a which is stored as an array of bytes. + /// + private class ZipString + { + #region Constructors + + /// + /// Initialise a with a string. + /// + /// The textual string form. + public ZipString(string comment) + { + comment_ = comment; + isSourceString_ = true; + } + + /// + /// Initialise a using a string in its binary 'raw' form. + /// + /// + public ZipString(byte[] rawString) + { + rawComment_ = rawString; + } + + #endregion Constructors + + /// + /// Get a value indicating the original source of data for this instance. + /// True if the source was a string; false if the source was binary data. + /// + public bool IsSourceString + { + get { return isSourceString_; } + } + + /// + /// Get the length of the comment when represented as raw bytes. + /// + public int RawLength + { + get + { + MakeBytesAvailable(); + return rawComment_.Length; + } + } + + /// + /// Get the comment in its 'raw' form as plain bytes. + /// + public byte[] RawComment + { + get + { + MakeBytesAvailable(); + return (byte[])rawComment_.Clone(); + } + } + + /// + /// Reset the comment to its initial state. + /// + public void Reset() + { + if (isSourceString_) + { + rawComment_ = null; + } + else + { + comment_ = null; + } + } + + private void MakeTextAvailable() + { + if (comment_ == null) + { + comment_ = ZipStrings.ConvertToString(rawComment_); + } + } + + private void MakeBytesAvailable() + { + if (rawComment_ == null) + { + rawComment_ = ZipStrings.ConvertToArray(comment_); + } + } + + /// + /// Implicit conversion of comment to a string. + /// + /// The to convert to a string. + /// The textual equivalent for the input value. + static public implicit operator string(ZipString zipString) + { + zipString.MakeTextAvailable(); + return zipString.comment_; + } + + #region Instance Fields + + private string comment_; + private byte[] rawComment_; + private readonly bool isSourceString_; + + #endregion Instance Fields + } + + /// + /// An enumerator for Zip entries + /// + private class ZipEntryEnumerator : IEnumerator + { + #region Constructors + + public ZipEntryEnumerator(ZipEntry[] entries) + { + array = entries; + } + + #endregion Constructors + + #region IEnumerator Members + + public object Current + { + get + { + return array[index]; + } + } + + public void Reset() + { + index = -1; + } + + public bool MoveNext() + { + return (++index < array.Length); + } + + #endregion IEnumerator Members + + #region Instance Fields + + private ZipEntry[] array; + private int index = -1; + + #endregion Instance Fields + } + + /// + /// An is a stream that you can write uncompressed data + /// to and flush, but cannot read, seek or do anything else to. + /// + private class UncompressedStream : Stream + { + #region Constructors + + public UncompressedStream(Stream baseStream) + { + baseStream_ = baseStream; + } + + #endregion Constructors + + /// + /// Gets a value indicating whether the current stream supports reading. + /// + public override bool CanRead + { + get + { + return false; + } + } + + /// + /// Write any buffered data to underlying storage. + /// + public override void Flush() + { + baseStream_.Flush(); + } + + /// + /// Gets a value indicating whether the current stream supports writing. + /// + public override bool CanWrite + { + get + { + return baseStream_.CanWrite; + } + } + + /// + /// Gets a value indicating whether the current stream supports seeking. + /// + public override bool CanSeek + { + get + { + return false; + } + } + + /// + /// Get the length in bytes of the stream. + /// + public override long Length + { + get + { + return 0; + } + } + + /// + /// Gets or sets the position within the current stream. + /// + public override long Position + { + get + { + return baseStream_.Position; + } + set + { + throw new NotImplementedException(); + } + } + + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source. + /// The zero-based byte offset in buffer at which to begin storing the data read from the current stream. + /// The maximum number of bytes to be read from the current stream. + /// + /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. + /// + /// The sum of offset and count is larger than the buffer length. + /// Methods were called after the stream was closed. + /// The stream does not support reading. + /// buffer is null. + /// An I/O error occurs. + /// offset or count is negative. + public override int Read(byte[] buffer, int offset, int count) + { + return 0; + } + + /// + /// Sets the position within the current stream. + /// + /// A byte offset relative to the origin parameter. + /// A value of type indicating the reference point used to obtain the new position. + /// + /// The new position within the current stream. + /// + /// An I/O error occurs. + /// The stream does not support seeking, such as if the stream is constructed from a pipe or console output. + /// Methods were called after the stream was closed. + public override long Seek(long offset, SeekOrigin origin) + { + return 0; + } + + /// + /// Sets the length of the current stream. + /// + /// The desired length of the current stream in bytes. + /// The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output. + /// An I/O error occurs. + /// Methods were called after the stream was closed. + public override void SetLength(long value) + { + } + + /// + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// An array of bytes. This method copies count bytes from buffer to the current stream. + /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream. + /// The number of bytes to be written to the current stream. + /// An I/O error occurs. + /// The stream does not support writing. + /// Methods were called after the stream was closed. + /// buffer is null. + /// The sum of offset and count is greater than the buffer length. + /// offset or count is negative. + public override void Write(byte[] buffer, int offset, int count) + { + baseStream_.Write(buffer, offset, count); + } + + private readonly + + #region Instance Fields + + Stream baseStream_; + + #endregion Instance Fields + } + + /// + /// A is an + /// whose data is only a part or subsection of a file. + /// + private class PartialInputStream : Stream + { + #region Constructors + + /// + /// Initialise a new instance of the class. + /// + /// The containing the underlying stream to use for IO. + /// The start of the partial data. + /// The length of the partial data. + public PartialInputStream(ZipFile zipFile, long start, long length) + { + start_ = start; + length_ = length; + + // Although this is the only time the zipfile is used + // keeping a reference here prevents premature closure of + // this zip file and thus the baseStream_. + + // Code like this will cause apparently random failures depending + // on the size of the files and when garbage is collected. + // + // ZipFile z = new ZipFile (stream); + // Stream reader = z.GetInputStream(0); + // uses reader here.... + zipFile_ = zipFile; + baseStream_ = zipFile_.baseStream_; + readPos_ = start; + end_ = start + length; + } + + #endregion Constructors + + /// + /// Read a byte from this stream. + /// + /// Returns the byte read or -1 on end of stream. + public override int ReadByte() + { + if (readPos_ >= end_) + { + // -1 is the correct value at end of stream. + return -1; + } + + lock (baseStream_) + { + baseStream_.Seek(readPos_++, SeekOrigin.Begin); + return baseStream_.ReadByte(); + } + } + + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source. + /// The zero-based byte offset in buffer at which to begin storing the data read from the current stream. + /// The maximum number of bytes to be read from the current stream. + /// + /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. + /// + /// The sum of offset and count is larger than the buffer length. + /// Methods were called after the stream was closed. + /// The stream does not support reading. + /// buffer is null. + /// An I/O error occurs. + /// offset or count is negative. + public override int Read(byte[] buffer, int offset, int count) + { + lock (baseStream_) + { + if (count > end_ - readPos_) + { + count = (int)(end_ - readPos_); + if (count == 0) + { + return 0; + } + } + // Protect against Stream implementations that throw away their buffer on every Seek + // (for example, Mono FileStream) + if (baseStream_.Position != readPos_) + { + baseStream_.Seek(readPos_, SeekOrigin.Begin); + } + int readCount = baseStream_.Read(buffer, offset, count); + if (readCount > 0) + { + readPos_ += readCount; + } + return readCount; + } + } + + /// + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// An array of bytes. This method copies count bytes from buffer to the current stream. + /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream. + /// The number of bytes to be written to the current stream. + /// An I/O error occurs. + /// The stream does not support writing. + /// Methods were called after the stream was closed. + /// buffer is null. + /// The sum of offset and count is greater than the buffer length. + /// offset or count is negative. + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + /// + /// When overridden in a derived class, sets the length of the current stream. + /// + /// The desired length of the current stream in bytes. + /// The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output. + /// An I/O error occurs. + /// Methods were called after the stream was closed. + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + /// + /// When overridden in a derived class, sets the position within the current stream. + /// + /// A byte offset relative to the origin parameter. + /// A value of type indicating the reference point used to obtain the new position. + /// + /// The new position within the current stream. + /// + /// An I/O error occurs. + /// The stream does not support seeking, such as if the stream is constructed from a pipe or console output. + /// Methods were called after the stream was closed. + public override long Seek(long offset, SeekOrigin origin) + { + long newPos = readPos_; + + switch (origin) + { + case SeekOrigin.Begin: + newPos = start_ + offset; + break; + + case SeekOrigin.Current: + newPos = readPos_ + offset; + break; + + case SeekOrigin.End: + newPos = end_ + offset; + break; + } + + if (newPos < start_) + { + throw new ArgumentException("Negative position is invalid"); + } + + if (newPos > end_) + { + throw new IOException("Cannot seek past end"); + } + readPos_ = newPos; + return readPos_; + } + + /// + /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. + /// + /// An I/O error occurs. + public override void Flush() + { + // Nothing to do. + } + + /// + /// Gets or sets the position within the current stream. + /// + /// + /// The current position within the stream. + /// An I/O error occurs. + /// The stream does not support seeking. + /// Methods were called after the stream was closed. + public override long Position + { + get { return readPos_ - start_; } + set + { + long newPos = start_ + value; + + if (newPos < start_) + { + throw new ArgumentException("Negative position is invalid"); + } + + if (newPos > end_) + { + throw new InvalidOperationException("Cannot seek past end"); + } + readPos_ = newPos; + } + } + + /// + /// Gets the length in bytes of the stream. + /// + /// + /// A long value representing the length of the stream in bytes. + /// A class derived from Stream does not support seeking. + /// Methods were called after the stream was closed. + public override long Length + { + get { return length_; } + } + + /// + /// Gets a value indicating whether the current stream supports writing. + /// + /// false + /// true if the stream supports writing; otherwise, false. + public override bool CanWrite + { + get { return false; } + } + + /// + /// Gets a value indicating whether the current stream supports seeking. + /// + /// true + /// true if the stream supports seeking; otherwise, false. + public override bool CanSeek + { + get { return true; } + } + + /// + /// Gets a value indicating whether the current stream supports reading. + /// + /// true. + /// true if the stream supports reading; otherwise, false. + public override bool CanRead + { + get { return true; } + } + + /// + /// Gets a value that determines whether the current stream can time out. + /// + /// + /// A value that determines whether the current stream can time out. + public override bool CanTimeout + { + get { return baseStream_.CanTimeout; } + } + + #region Instance Fields + + private ZipFile zipFile_; + private Stream baseStream_; + private readonly long start_; + private readonly long length_; + private long readPos_; + private readonly long end_; + + #endregion Instance Fields + } + + #endregion Support Classes + } + + #endregion ZipFile Class + + #region DataSources + + /// + /// Provides a static way to obtain a source of data for an entry. + /// + public interface IStaticDataSource + { + /// + /// Get a source of data by creating a new stream. + /// + /// Returns a to use for compression input. + /// Ideally a new stream is created and opened to achieve this, to avoid locking problems. + Stream GetSource(); + } + + /// + /// Represents a source of data that can dynamically provide + /// multiple data sources based on the parameters passed. + /// + public interface IDynamicDataSource + { + /// + /// Get a data source. + /// + /// The to get a source for. + /// The name for data if known. + /// Returns a to use for compression input. + /// Ideally a new stream is created and opened to achieve this, to avoid locking problems. + Stream GetSource(ZipEntry entry, string name); + } + + /// + /// Default implementation of a for use with files stored on disk. + /// + public class StaticDiskDataSource : IStaticDataSource + { + /// + /// Initialise a new instance of + /// + /// The name of the file to obtain data from. + public StaticDiskDataSource(string fileName) + { + fileName_ = fileName; + } + + #region IDataSource Members + + /// + /// Get a providing data. + /// + /// Returns a providing data. + public Stream GetSource() + { + return File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); + } + + private readonly + + #endregion IDataSource Members + + #region Instance Fields + + string fileName_; + + #endregion Instance Fields + } + + /// + /// Default implementation of for files stored on disk. + /// + public class DynamicDiskDataSource : IDynamicDataSource + { + #region IDataSource Members + + /// + /// Get a providing data for an entry. + /// + /// The entry to provide data for. + /// The file name for data if known. + /// Returns a stream providing data; or null if not available + public Stream GetSource(ZipEntry entry, string name) + { + Stream result = null; + + if (name != null) + { + result = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); + } + + return result; + } + + #endregion IDataSource Members + } + + #endregion DataSources + + #region Archive Storage + + /// + /// Defines facilities for data storage when updating Zip Archives. + /// + public interface IArchiveStorage + { + /// + /// Get the to apply during updates. + /// + FileUpdateMode UpdateMode { get; } + + /// + /// Get an empty that can be used for temporary output. + /// + /// Returns a temporary output + /// + Stream GetTemporaryOutput(); + + /// + /// Convert a temporary output stream to a final stream. + /// + /// The resulting final + /// + Stream ConvertTemporaryToFinal(); + + /// + /// Make a temporary copy of the original stream. + /// + /// The to copy. + /// Returns a temporary output that is a copy of the input. + Stream MakeTemporaryCopy(Stream stream); + + /// + /// Return a stream suitable for performing direct updates on the original source. + /// + /// The current stream. + /// Returns a stream suitable for direct updating. + /// This may be the current stream passed. + Stream OpenForDirectUpdate(Stream stream); + + /// + /// Dispose of this instance. + /// + void Dispose(); + } + + /// + /// An abstract suitable for extension by inheritance. + /// + abstract public class BaseArchiveStorage : IArchiveStorage + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The update mode. + protected BaseArchiveStorage(FileUpdateMode updateMode) + { + updateMode_ = updateMode; + } + + #endregion Constructors + + #region IArchiveStorage Members + + /// + /// Gets a temporary output + /// + /// Returns the temporary output stream. + /// + public abstract Stream GetTemporaryOutput(); + + /// + /// Converts the temporary to its final form. + /// + /// Returns a that can be used to read + /// the final storage for the archive. + /// + public abstract Stream ConvertTemporaryToFinal(); + + /// + /// Make a temporary copy of a . + /// + /// The to make a copy of. + /// Returns a temporary output that is a copy of the input. + public abstract Stream MakeTemporaryCopy(Stream stream); + + /// + /// Return a stream suitable for performing direct updates on the original source. + /// + /// The to open for direct update. + /// Returns a stream suitable for direct updating. + public abstract Stream OpenForDirectUpdate(Stream stream); + + /// + /// Disposes this instance. + /// + public abstract void Dispose(); + + /// + /// Gets the update mode applicable. + /// + /// The update mode. + public FileUpdateMode UpdateMode + { + get + { + return updateMode_; + } + } + + #endregion IArchiveStorage Members + + #region Instance Fields + + private readonly FileUpdateMode updateMode_; + + #endregion Instance Fields + } + + /// + /// An implementation suitable for hard disks. + /// + public class DiskArchiveStorage : BaseArchiveStorage + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The file. + /// The update mode. + public DiskArchiveStorage(ZipFile file, FileUpdateMode updateMode) + : base(updateMode) + { + if (file.Name == null) + { + throw new ZipException("Cant handle non file archives"); + } + + fileName_ = file.Name; + } + + /// + /// Initializes a new instance of the class. + /// + /// The file. + public DiskArchiveStorage(ZipFile file) + : this(file, FileUpdateMode.Safe) + { + } + + #endregion Constructors + + #region IArchiveStorage Members + + /// + /// Gets a temporary output for performing updates on. + /// + /// Returns the temporary output stream. + public override Stream GetTemporaryOutput() + { + temporaryName_ = PathUtils.GetTempFileName(temporaryName_); + temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); + + return temporaryStream_; + } + + /// + /// Converts a temporary to its final form. + /// + /// Returns a that can be used to read + /// the final storage for the archive. + public override Stream ConvertTemporaryToFinal() + { + if (temporaryStream_ == null) + { + throw new ZipException("No temporary stream has been created"); + } + + Stream result = null; + + string moveTempName = PathUtils.GetTempFileName(fileName_); + bool newFileCreated = false; + + try + { + temporaryStream_.Dispose(); + File.Move(fileName_, moveTempName); + File.Move(temporaryName_, fileName_); + newFileCreated = true; + File.Delete(moveTempName); + + result = File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); + } + catch (Exception) + { + result = null; + + // Try to roll back changes... + if (!newFileCreated) + { + File.Move(moveTempName, fileName_); + File.Delete(temporaryName_); + } + + throw; + } + + return result; + } + + /// + /// Make a temporary copy of a stream. + /// + /// The to copy. + /// Returns a temporary output that is a copy of the input. + public override Stream MakeTemporaryCopy(Stream stream) + { + stream.Dispose(); + + temporaryName_ = PathUtils.GetTempFileName(fileName_); + File.Copy(fileName_, temporaryName_, true); + + temporaryStream_ = new FileStream(temporaryName_, + FileMode.Open, + FileAccess.ReadWrite); + return temporaryStream_; + } + + /// + /// Return a stream suitable for performing direct updates on the original source. + /// + /// The current stream. + /// Returns a stream suitable for direct updating. + /// If the is not null this is used as is. + public override Stream OpenForDirectUpdate(Stream stream) + { + Stream result; + if ((stream == null) || !stream.CanWrite) + { + if (stream != null) + { + stream.Dispose(); + } + + result = new FileStream(fileName_, + FileMode.Open, + FileAccess.ReadWrite); + } + else + { + result = stream; + } + + return result; + } + + /// + /// Disposes this instance. + /// + public override void Dispose() + { + if (temporaryStream_ != null) + { + temporaryStream_.Dispose(); + } + } + + #endregion IArchiveStorage Members + + #region Instance Fields + + private Stream temporaryStream_; + private readonly string fileName_; + private string temporaryName_; + + #endregion Instance Fields + } + + /// + /// An implementation suitable for in memory streams. + /// + public class MemoryArchiveStorage : BaseArchiveStorage + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public MemoryArchiveStorage() + : base(FileUpdateMode.Direct) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The to use + /// This constructor is for testing as memory streams dont really require safe mode. + public MemoryArchiveStorage(FileUpdateMode updateMode) + : base(updateMode) + { + } + + #endregion Constructors + + #region Properties + + /// + /// Get the stream returned by if this was in fact called. + /// + public MemoryStream FinalStream + { + get { return finalStream_; } + } + + #endregion Properties + + #region IArchiveStorage Members + + /// + /// Gets the temporary output + /// + /// Returns the temporary output stream. + public override Stream GetTemporaryOutput() + { + temporaryStream_ = new MemoryStream(); + return temporaryStream_; + } + + /// + /// Converts the temporary to its final form. + /// + /// Returns a that can be used to read + /// the final storage for the archive. + public override Stream ConvertTemporaryToFinal() + { + if (temporaryStream_ == null) + { + throw new ZipException("No temporary stream has been created"); + } + + finalStream_ = new MemoryStream(temporaryStream_.ToArray()); + return finalStream_; + } + + /// + /// Make a temporary copy of the original stream. + /// + /// The to copy. + /// Returns a temporary output that is a copy of the input. + public override Stream MakeTemporaryCopy(Stream stream) + { + temporaryStream_ = new MemoryStream(); + stream.Position = 0; + StreamUtils.Copy(stream, temporaryStream_, new byte[4096]); + return temporaryStream_; + } + + /// + /// Return a stream suitable for performing direct updates on the original source. + /// + /// The original source stream + /// Returns a stream suitable for direct updating. + /// If the passed is not null this is used; + /// otherwise a new is returned. + public override Stream OpenForDirectUpdate(Stream stream) + { + Stream result; + if ((stream == null) || !stream.CanWrite) + { + result = new MemoryStream(); + + if (stream != null) + { + stream.Position = 0; + StreamUtils.Copy(stream, result, new byte[4096]); + + stream.Dispose(); + } + } + else + { + result = stream; + } + + return result; + } + + /// + /// Disposes this instance. + /// + public override void Dispose() + { + if (temporaryStream_ != null) + { + temporaryStream_.Dispose(); + } + } + + #endregion IArchiveStorage Members + + #region Instance Fields + + private MemoryStream temporaryStream_; + private MemoryStream finalStream_; + + #endregion Instance Fields + } + + #endregion Archive Storage +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipHelperStream.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipHelperStream.cs new file mode 100644 index 0000000..da65630 --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipHelperStream.cs @@ -0,0 +1,629 @@ +using System; +using System.IO; + +namespace ICSharpCode.SharpZipLib.Zip +{ + /// + /// Holds data pertinent to a data descriptor. + /// + public class DescriptorData + { + /// + /// Get /set the compressed size of data. + /// + public long CompressedSize + { + get { return compressedSize; } + set { compressedSize = value; } + } + + /// + /// Get / set the uncompressed size of data + /// + public long Size + { + get { return size; } + set { size = value; } + } + + /// + /// Get /set the crc value. + /// + public long Crc + { + get { return crc; } + set { crc = (value & 0xffffffff); } + } + + #region Instance Fields + + private long size; + private long compressedSize; + private long crc; + + #endregion Instance Fields + } + + internal class EntryPatchData + { + public long SizePatchOffset + { + get { return sizePatchOffset_; } + set { sizePatchOffset_ = value; } + } + + public long CrcPatchOffset + { + get { return crcPatchOffset_; } + set { crcPatchOffset_ = value; } + } + + #region Instance Fields + + private long sizePatchOffset_; + private long crcPatchOffset_; + + #endregion Instance Fields + } + + /// + /// This class assists with writing/reading from Zip files. + /// + internal class ZipHelperStream : Stream + { + #region Constructors + + /// + /// Initialise an instance of this class. + /// + /// The name of the file to open. + public ZipHelperStream(string name) + { + stream_ = new FileStream(name, FileMode.Open, FileAccess.ReadWrite); + isOwner_ = true; + } + + /// + /// Initialise a new instance of . + /// + /// The stream to use. + public ZipHelperStream(Stream stream) + { + stream_ = stream; + } + + #endregion Constructors + + /// + /// Get / set a value indicating whether the underlying stream is owned or not. + /// + /// If the stream is owned it is closed when this instance is closed. + public bool IsStreamOwner + { + get { return isOwner_; } + set { isOwner_ = value; } + } + + #region Base Stream Methods + + public override bool CanRead + { + get { return stream_.CanRead; } + } + + public override bool CanSeek + { + get { return stream_.CanSeek; } + } + + public override bool CanTimeout + { + get { return stream_.CanTimeout; } + } + + public override long Length + { + get { return stream_.Length; } + } + + public override long Position + { + get { return stream_.Position; } + set { stream_.Position = value; } + } + + public override bool CanWrite + { + get { return stream_.CanWrite; } + } + + public override void Flush() + { + stream_.Flush(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + return stream_.Seek(offset, origin); + } + + public override void SetLength(long value) + { + stream_.SetLength(value); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return stream_.Read(buffer, offset, count); + } + + public override void Write(byte[] buffer, int offset, int count) + { + stream_.Write(buffer, offset, count); + } + + /// + /// Close the stream. + /// + /// + /// The underlying stream is closed only if is true. + /// + protected override void Dispose(bool disposing) + { + Stream toClose = stream_; + stream_ = null; + if (isOwner_ && (toClose != null)) + { + isOwner_ = false; + toClose.Dispose(); + } + } + + #endregion Base Stream Methods + + // Write the local file header + // TODO: ZipHelperStream.WriteLocalHeader is not yet used and needs checking for ZipFile and ZipOuptutStream usage + private void WriteLocalHeader(ZipEntry entry, EntryPatchData patchData) + { + CompressionMethod method = entry.CompressionMethod; + bool headerInfoAvailable = true; // How to get this? + bool patchEntryHeader = false; + + WriteLEInt(ZipConstants.LocalHeaderSignature); + + WriteLEShort(entry.Version); + WriteLEShort(entry.Flags); + WriteLEShort((byte)method); + WriteLEInt((int)entry.DosTime); + + if (headerInfoAvailable == true) + { + WriteLEInt((int)entry.Crc); + if (entry.LocalHeaderRequiresZip64) + { + WriteLEInt(-1); + WriteLEInt(-1); + } + else + { + WriteLEInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CryptoHeaderSize : (int)entry.CompressedSize); + WriteLEInt((int)entry.Size); + } + } + else + { + if (patchData != null) + { + patchData.CrcPatchOffset = stream_.Position; + } + WriteLEInt(0); // Crc + + if (patchData != null) + { + patchData.SizePatchOffset = stream_.Position; + } + + // For local header both sizes appear in Zip64 Extended Information + if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) + { + WriteLEInt(-1); + WriteLEInt(-1); + } + else + { + WriteLEInt(0); // Compressed size + WriteLEInt(0); // Uncompressed size + } + } + + byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name); + + if (name.Length > 0xFFFF) + { + throw new ZipException("Entry name too long."); + } + + var ed = new ZipExtraData(entry.ExtraData); + + if (entry.LocalHeaderRequiresZip64 && (headerInfoAvailable || patchEntryHeader)) + { + ed.StartNewEntry(); + if (headerInfoAvailable) + { + ed.AddLeLong(entry.Size); + ed.AddLeLong(entry.CompressedSize); + } + else + { + ed.AddLeLong(-1); + ed.AddLeLong(-1); + } + ed.AddNewEntry(1); + + if (!ed.Find(1)) + { + throw new ZipException("Internal error cant find extra data"); + } + + if (patchData != null) + { + patchData.SizePatchOffset = ed.CurrentReadIndex; + } + } + else + { + ed.Delete(1); + } + + byte[] extra = ed.GetEntryData(); + + WriteLEShort(name.Length); + WriteLEShort(extra.Length); + + if (name.Length > 0) + { + stream_.Write(name, 0, name.Length); + } + + if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) + { + patchData.SizePatchOffset += stream_.Position; + } + + if (extra.Length > 0) + { + stream_.Write(extra, 0, extra.Length); + } + } + + /// + /// Locates a block with the desired . + /// + /// The signature to find. + /// Location, marking the end of block. + /// Minimum size of the block. + /// The maximum variable data. + /// Returns the offset of the first byte after the signature; -1 if not found + public long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) + { + long pos = endLocation - minimumBlockSize; + if (pos < 0) + { + return -1; + } + + long giveUpMarker = Math.Max(pos - maximumVariableData, 0); + + // TODO: This loop could be optimised for speed. + do + { + if (pos < giveUpMarker) + { + return -1; + } + Seek(pos--, SeekOrigin.Begin); + } while (ReadLEInt() != signature); + + return Position; + } + + /// + /// Write Zip64 end of central directory records (File header and locator). + /// + /// The number of entries in the central directory. + /// The size of entries in the central directory. + /// The offset of the central directory. + public void WriteZip64EndOfCentralDirectory(long noOfEntries, long sizeEntries, long centralDirOffset) + { + long centralSignatureOffset = centralDirOffset + sizeEntries; + WriteLEInt(ZipConstants.Zip64CentralFileHeaderSignature); + WriteLELong(44); // Size of this record (total size of remaining fields in header or full size - 12) + WriteLEShort(ZipConstants.VersionMadeBy); // Version made by + WriteLEShort(ZipConstants.VersionZip64); // Version to extract + WriteLEInt(0); // Number of this disk + WriteLEInt(0); // number of the disk with the start of the central directory + WriteLELong(noOfEntries); // No of entries on this disk + WriteLELong(noOfEntries); // Total No of entries in central directory + WriteLELong(sizeEntries); // Size of the central directory + WriteLELong(centralDirOffset); // offset of start of central directory + // zip64 extensible data sector not catered for here (variable size) + + // Write the Zip64 end of central directory locator + WriteLEInt(ZipConstants.Zip64CentralDirLocatorSignature); + + // no of the disk with the start of the zip64 end of central directory + WriteLEInt(0); + + // relative offset of the zip64 end of central directory record + WriteLELong(centralSignatureOffset); + + // total number of disks + WriteLEInt(1); + } + + /// + /// Write the required records to end the central directory. + /// + /// The number of entries in the directory. + /// The size of the entries in the directory. + /// The start of the central directory. + /// The archive comment. (This can be null). + public void WriteEndOfCentralDirectory(long noOfEntries, long sizeEntries, + long startOfCentralDirectory, byte[] comment) + { + if ((noOfEntries >= 0xffff) || + (startOfCentralDirectory >= 0xffffffff) || + (sizeEntries >= 0xffffffff)) + { + WriteZip64EndOfCentralDirectory(noOfEntries, sizeEntries, startOfCentralDirectory); + } + + WriteLEInt(ZipConstants.EndOfCentralDirectorySignature); + + // TODO: ZipFile Multi disk handling not done + WriteLEShort(0); // number of this disk + WriteLEShort(0); // no of disk with start of central dir + + // Number of entries + if (noOfEntries >= 0xffff) + { + WriteLEUshort(0xffff); // Zip64 marker + WriteLEUshort(0xffff); + } + else + { + WriteLEShort((short)noOfEntries); // entries in central dir for this disk + WriteLEShort((short)noOfEntries); // total entries in central directory + } + + // Size of the central directory + if (sizeEntries >= 0xffffffff) + { + WriteLEUint(0xffffffff); // Zip64 marker + } + else + { + WriteLEInt((int)sizeEntries); + } + + // offset of start of central directory + if (startOfCentralDirectory >= 0xffffffff) + { + WriteLEUint(0xffffffff); // Zip64 marker + } + else + { + WriteLEInt((int)startOfCentralDirectory); + } + + int commentLength = (comment != null) ? comment.Length : 0; + + if (commentLength > 0xffff) + { + throw new ZipException(string.Format("Comment length({0}) is too long can only be 64K", commentLength)); + } + + WriteLEShort(commentLength); + + if (commentLength > 0) + { + Write(comment, 0, comment.Length); + } + } + + #region LE value reading/writing + + /// + /// Read an unsigned short in little endian byte order. + /// + /// Returns the value read. + /// + /// An i/o error occurs. + /// + /// + /// The file ends prematurely + /// + public int ReadLEShort() + { + int byteValue1 = stream_.ReadByte(); + + if (byteValue1 < 0) + { + throw new EndOfStreamException(); + } + + int byteValue2 = stream_.ReadByte(); + if (byteValue2 < 0) + { + throw new EndOfStreamException(); + } + + return byteValue1 | (byteValue2 << 8); + } + + /// + /// Read an int in little endian byte order. + /// + /// Returns the value read. + /// + /// An i/o error occurs. + /// + /// + /// The file ends prematurely + /// + public int ReadLEInt() + { + return ReadLEShort() | (ReadLEShort() << 16); + } + + /// + /// Read a long in little endian byte order. + /// + /// The value read. + public long ReadLELong() + { + return (uint)ReadLEInt() | ((long)ReadLEInt() << 32); + } + + /// + /// Write an unsigned short in little endian byte order. + /// + /// The value to write. + public void WriteLEShort(int value) + { + stream_.WriteByte((byte)(value & 0xff)); + stream_.WriteByte((byte)((value >> 8) & 0xff)); + } + + /// + /// Write a ushort in little endian byte order. + /// + /// The value to write. + public void WriteLEUshort(ushort value) + { + stream_.WriteByte((byte)(value & 0xff)); + stream_.WriteByte((byte)(value >> 8)); + } + + /// + /// Write an int in little endian byte order. + /// + /// The value to write. + public void WriteLEInt(int value) + { + WriteLEShort(value); + WriteLEShort(value >> 16); + } + + /// + /// Write a uint in little endian byte order. + /// + /// The value to write. + public void WriteLEUint(uint value) + { + WriteLEUshort((ushort)(value & 0xffff)); + WriteLEUshort((ushort)(value >> 16)); + } + + /// + /// Write a long in little endian byte order. + /// + /// The value to write. + public void WriteLELong(long value) + { + WriteLEInt((int)value); + WriteLEInt((int)(value >> 32)); + } + + /// + /// Write a ulong in little endian byte order. + /// + /// The value to write. + public void WriteLEUlong(ulong value) + { + WriteLEUint((uint)(value & 0xffffffff)); + WriteLEUint((uint)(value >> 32)); + } + + #endregion LE value reading/writing + + /// + /// Write a data descriptor. + /// + /// The entry to write a descriptor for. + /// Returns the number of descriptor bytes written. + public int WriteDataDescriptor(ZipEntry entry) + { + if (entry == null) + { + throw new ArgumentNullException(nameof(entry)); + } + + int result = 0; + + // Add data descriptor if flagged as required + if ((entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) + { + // The signature is not PKZIP originally but is now described as optional + // in the PKZIP Appnote documenting the format. + WriteLEInt(ZipConstants.DataDescriptorSignature); + WriteLEInt(unchecked((int)(entry.Crc))); + + result += 8; + + if (entry.LocalHeaderRequiresZip64) + { + WriteLELong(entry.CompressedSize); + WriteLELong(entry.Size); + result += 16; + } + else + { + WriteLEInt((int)entry.CompressedSize); + WriteLEInt((int)entry.Size); + result += 8; + } + } + + return result; + } + + /// + /// Read data descriptor at the end of compressed data. + /// + /// if set to true [zip64]. + /// The data to fill in. + /// Returns the number of bytes read in the descriptor. + public void ReadDataDescriptor(bool zip64, DescriptorData data) + { + int intValue = ReadLEInt(); + + // In theory this may not be a descriptor according to PKZIP appnote. + // In practice its always there. + if (intValue != ZipConstants.DataDescriptorSignature) + { + throw new ZipException("Data descriptor signature not found"); + } + + data.Crc = ReadLEInt(); + + if (zip64) + { + data.CompressedSize = ReadLELong(); + data.Size = ReadLELong(); + } + else + { + data.CompressedSize = ReadLEInt(); + data.Size = ReadLEInt(); + } + } + + #region Instance Fields + + private bool isOwner_; + private Stream stream_; + + #endregion Instance Fields + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs new file mode 100644 index 0000000..cccac66 --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs @@ -0,0 +1,727 @@ +using ICSharpCode.SharpZipLib.Checksum; +using ICSharpCode.SharpZipLib.Encryption; +using ICSharpCode.SharpZipLib.Zip.Compression; +using ICSharpCode.SharpZipLib.Zip.Compression.Streams; +using System; +using System.IO; + +namespace ICSharpCode.SharpZipLib.Zip +{ + /// + /// This is an InflaterInputStream that reads the files baseInputStream an zip archive + /// one after another. It has a special method to get the zip entry of + /// the next file. The zip entry contains information about the file name + /// size, compressed size, Crc, etc. + /// It includes support for Stored and Deflated entries. + ///
+ ///
Author of the original java version : Jochen Hoenicke + ///
+ /// + /// This sample shows how to read a zip file + /// + /// using System; + /// using System.Text; + /// using System.IO; + /// + /// using ICSharpCode.SharpZipLib.Zip; + /// + /// class MainClass + /// { + /// public static void Main(string[] args) + /// { + /// using ( ZipInputStream s = new ZipInputStream(File.OpenRead(args[0]))) { + /// + /// ZipEntry theEntry; + /// const int size = 2048; + /// byte[] data = new byte[2048]; + /// + /// while ((theEntry = s.GetNextEntry()) != null) { + /// if ( entry.IsFile ) { + /// Console.Write("Show contents (y/n) ?"); + /// if (Console.ReadLine() == "y") { + /// while (true) { + /// size = s.Read(data, 0, data.Length); + /// if (size > 0) { + /// Console.Write(new ASCIIEncoding().GetString(data, 0, size)); + /// } else { + /// break; + /// } + /// } + /// } + /// } + /// } + /// } + /// } + /// } + /// + /// + public class ZipInputStream : InflaterInputStream + { + #region Instance Fields + + /// + /// Delegate for reading bytes from a stream. + /// + private delegate int ReadDataHandler(byte[] b, int offset, int length); + + /// + /// The current reader this instance. + /// + private ReadDataHandler internalReader; + + private Crc32 crc = new Crc32(); + private ZipEntry entry; + + private long size; + private CompressionMethod method; + private int flags; + private string password; + + #endregion Instance Fields + + #region Constructors + + /// + /// Creates a new Zip input stream, for reading a zip archive. + /// + /// The underlying providing data. + public ZipInputStream(Stream baseInputStream) + : base(baseInputStream, new Inflater(true)) + { + internalReader = new ReadDataHandler(ReadingNotAvailable); + } + + /// + /// Creates a new Zip input stream, for reading a zip archive. + /// + /// The underlying providing data. + /// Size of the buffer. + public ZipInputStream(Stream baseInputStream, int bufferSize) + : base(baseInputStream, new Inflater(true), bufferSize) + { + internalReader = new ReadDataHandler(ReadingNotAvailable); + } + + #endregion Constructors + + /// + /// Optional password used for encryption when non-null + /// + /// A password for all encrypted entries in this + public string Password + { + get + { + return password; + } + set + { + password = value; + } + } + + /// + /// Gets a value indicating if there is a current entry and it can be decompressed + /// + /// + /// The entry can only be decompressed if the library supports the zip features required to extract it. + /// See the ZipEntry Version property for more details. + /// + /// Since uses the local headers for extraction, entries with no compression combined with the + /// flag set, cannot be extracted as the end of the entry data cannot be deduced. + /// + public bool CanDecompressEntry + => entry != null + && IsEntryCompressionMethodSupported(entry) + && entry.CanDecompress + && (!entry.HasFlag(GeneralBitFlags.Descriptor) || entry.CompressionMethod != CompressionMethod.Stored || entry.IsCrypted); + + /// + /// Is the compression method for the specified entry supported? + /// + /// + /// Uses entry.CompressionMethodForHeader so that entries of type WinZipAES will be rejected. + /// + /// the entry to check. + /// true if the compression method is supported, false if not. + private static bool IsEntryCompressionMethodSupported(ZipEntry entry) + { + var entryCompressionMethod = entry.CompressionMethodForHeader; + + return entryCompressionMethod == CompressionMethod.Deflated || + entryCompressionMethod == CompressionMethod.Stored; + } + + /// + /// Advances to the next entry in the archive + /// + /// + /// The next entry in the archive or null if there are no more entries. + /// + /// + /// If the previous entry is still open CloseEntry is called. + /// + /// + /// Input stream is closed + /// + /// + /// Password is not set, password is invalid, compression method is invalid, + /// version required to extract is not supported + /// + public ZipEntry GetNextEntry() + { + if (crc == null) + { + throw new InvalidOperationException("Closed."); + } + + if (entry != null) + { + CloseEntry(); + } + + int header = inputBuffer.ReadLeInt(); + + if (header == ZipConstants.CentralHeaderSignature || + header == ZipConstants.EndOfCentralDirectorySignature || + header == ZipConstants.CentralHeaderDigitalSignature || + header == ZipConstants.ArchiveExtraDataSignature || + header == ZipConstants.Zip64CentralFileHeaderSignature) + { + // No more individual entries exist + Dispose(); + return null; + } + + // -jr- 07-Dec-2003 Ignore spanning temporary signatures if found + // Spanning signature is same as descriptor signature and is untested as yet. + if ((header == ZipConstants.SpanningTempSignature) || (header == ZipConstants.SpanningSignature)) + { + header = inputBuffer.ReadLeInt(); + } + + if (header != ZipConstants.LocalHeaderSignature) + { + throw new ZipException("Wrong Local header signature: 0x" + String.Format("{0:X}", header)); + } + + var versionRequiredToExtract = (short)inputBuffer.ReadLeShort(); + + flags = inputBuffer.ReadLeShort(); + method = (CompressionMethod)inputBuffer.ReadLeShort(); + var dostime = (uint)inputBuffer.ReadLeInt(); + int crc2 = inputBuffer.ReadLeInt(); + csize = inputBuffer.ReadLeInt(); + size = inputBuffer.ReadLeInt(); + int nameLen = inputBuffer.ReadLeShort(); + int extraLen = inputBuffer.ReadLeShort(); + + bool isCrypted = (flags & 1) == 1; + + byte[] buffer = new byte[nameLen]; + inputBuffer.ReadRawBuffer(buffer); + + string name = ZipStrings.ConvertToStringExt(flags, buffer); + + entry = new ZipEntry(name, versionRequiredToExtract, ZipConstants.VersionMadeBy, method) + { + Flags = flags, + }; + + if ((flags & 8) == 0) + { + entry.Crc = crc2 & 0xFFFFFFFFL; + entry.Size = size & 0xFFFFFFFFL; + entry.CompressedSize = csize & 0xFFFFFFFFL; + + entry.CryptoCheckValue = (byte)((crc2 >> 24) & 0xff); + } + else + { + // This allows for GNU, WinZip and possibly other archives, the PKZIP spec + // says these values are zero under these circumstances. + if (crc2 != 0) + { + entry.Crc = crc2 & 0xFFFFFFFFL; + } + + if (size != 0) + { + entry.Size = size & 0xFFFFFFFFL; + } + + if (csize != 0) + { + entry.CompressedSize = csize & 0xFFFFFFFFL; + } + + entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff); + } + + entry.DosTime = dostime; + + // If local header requires Zip64 is true then the extended header should contain + // both values. + + // Handle extra data if present. This can set/alter some fields of the entry. + if (extraLen > 0) + { + byte[] extra = new byte[extraLen]; + inputBuffer.ReadRawBuffer(extra); + entry.ExtraData = extra; + } + + entry.ProcessExtraData(true); + if (entry.CompressedSize >= 0) + { + csize = entry.CompressedSize; + } + + if (entry.Size >= 0) + { + size = entry.Size; + } + + if (method == CompressionMethod.Stored && (!isCrypted && csize != size || (isCrypted && csize - ZipConstants.CryptoHeaderSize != size))) + { + throw new ZipException("Stored, but compressed != uncompressed"); + } + + // Determine how to handle reading of data if this is attempted. + if (IsEntryCompressionMethodSupported(entry)) + { + internalReader = new ReadDataHandler(InitialRead); + } + else + { + internalReader = new ReadDataHandler(ReadingNotSupported); + } + + return entry; + } + + /// + /// Read data descriptor at the end of compressed data. + /// + private void ReadDataDescriptor() + { + if (inputBuffer.ReadLeInt() != ZipConstants.DataDescriptorSignature) + { + throw new ZipException("Data descriptor signature not found"); + } + + entry.Crc = inputBuffer.ReadLeInt() & 0xFFFFFFFFL; + + if (entry.LocalHeaderRequiresZip64) + { + csize = inputBuffer.ReadLeLong(); + size = inputBuffer.ReadLeLong(); + } + else + { + csize = inputBuffer.ReadLeInt(); + size = inputBuffer.ReadLeInt(); + } + entry.CompressedSize = csize; + entry.Size = size; + } + + /// + /// Complete cleanup as the final part of closing. + /// + /// True if the crc value should be tested + private void CompleteCloseEntry(bool testCrc) + { + StopDecrypting(); + + if ((flags & 8) != 0) + { + ReadDataDescriptor(); + } + + size = 0; + + if (testCrc && + ((crc.Value & 0xFFFFFFFFL) != entry.Crc) && (entry.Crc != -1)) + { + throw new ZipException("CRC mismatch"); + } + + crc.Reset(); + + if (method == CompressionMethod.Deflated) + { + inf.Reset(); + } + entry = null; + } + + /// + /// Closes the current zip entry and moves to the next one. + /// + /// + /// The stream is closed + /// + /// + /// The Zip stream ends early + /// + public void CloseEntry() + { + if (crc == null) + { + throw new InvalidOperationException("Closed"); + } + + if (entry == null) + { + return; + } + + if (method == CompressionMethod.Deflated) + { + if ((flags & 8) != 0) + { + // We don't know how much we must skip, read until end. + byte[] tmp = new byte[4096]; + + // Read will close this entry + while (Read(tmp, 0, tmp.Length) > 0) + { + } + return; + } + + csize -= inf.TotalIn; + inputBuffer.Available += inf.RemainingInput; + } + + if ((inputBuffer.Available > csize) && (csize >= 0)) + { + inputBuffer.Available = (int)((long)inputBuffer.Available - csize); + } + else + { + csize -= inputBuffer.Available; + inputBuffer.Available = 0; + while (csize != 0) + { + long skipped = Skip(csize); + + if (skipped <= 0) + { + throw new ZipException("Zip archive ends early."); + } + + csize -= skipped; + } + } + + CompleteCloseEntry(false); + } + + /// + /// Returns 1 if there is an entry available + /// Otherwise returns 0. + /// + public override int Available + { + get + { + return entry != null ? 1 : 0; + } + } + + /// + /// Returns the current size that can be read from the current entry if available + /// + /// Thrown if the entry size is not known. + /// Thrown if no entry is currently available. + public override long Length + { + get + { + if (entry != null) + { + if (entry.Size >= 0) + { + return entry.Size; + } + else + { + throw new ZipException("Length not available for the current entry"); + } + } + else + { + throw new InvalidOperationException("No current entry"); + } + } + } + + /// + /// Reads a byte from the current zip entry. + /// + /// + /// The byte or -1 if end of stream is reached. + /// + public override int ReadByte() + { + byte[] b = new byte[1]; + if (Read(b, 0, 1) <= 0) + { + return -1; + } + return b[0] & 0xff; + } + + /// + /// Handle attempts to read by throwing an . + /// + /// The destination array to store data in. + /// The offset at which data read should be stored. + /// The maximum number of bytes to read. + /// Returns the number of bytes actually read. + private int ReadingNotAvailable(byte[] destination, int offset, int count) + { + throw new InvalidOperationException("Unable to read from this stream"); + } + + /// + /// Handle attempts to read from this entry by throwing an exception + /// + private int ReadingNotSupported(byte[] destination, int offset, int count) + { + throw new ZipException("The compression method for this entry is not supported"); + } + + /// + /// Handle attempts to read from this entry by throwing an exception + /// + private int StoredDescriptorEntry(byte[] destination, int offset, int count) => + throw new StreamUnsupportedException( + "The combination of Stored compression method and Descriptor flag is not possible to read using ZipInputStream"); + + + /// + /// Perform the initial read on an entry which may include + /// reading encryption headers and setting up inflation. + /// + /// The destination to fill with data read. + /// The offset to start reading at. + /// The maximum number of bytes to read. + /// The actual number of bytes read. + private int InitialRead(byte[] destination, int offset, int count) + { + var usesDescriptor = (entry.Flags & (int)GeneralBitFlags.Descriptor) != 0; + + // Handle encryption if required. + if (entry.IsCrypted) + { + if (password == null) + { + throw new ZipException("No password set."); + } + + // Generate and set crypto transform... + var managed = new PkzipClassicManaged(); + byte[] key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(password)); + + inputBuffer.CryptoTransform = managed.CreateDecryptor(key, null); + + byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize]; + inputBuffer.ReadClearTextBuffer(cryptbuffer, 0, ZipConstants.CryptoHeaderSize); + + if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) + { + throw new ZipException("Invalid password"); + } + + if (csize >= ZipConstants.CryptoHeaderSize) + { + csize -= ZipConstants.CryptoHeaderSize; + } + else if (!usesDescriptor) + { + throw new ZipException($"Entry compressed size {csize} too small for encryption"); + } + } + else + { + inputBuffer.CryptoTransform = null; + } + + if (csize > 0 || usesDescriptor) + { + if (method == CompressionMethod.Deflated && inputBuffer.Available > 0) + { + inputBuffer.SetInflaterInput(inf); + } + + // It's not possible to know how many bytes to read when using "Stored" compression (unless using encryption) + if (!entry.IsCrypted && method == CompressionMethod.Stored && usesDescriptor) + { + internalReader = StoredDescriptorEntry; + return StoredDescriptorEntry(destination, offset, count); + } + + if (!CanDecompressEntry) + { + internalReader = ReadingNotSupported; + return ReadingNotSupported(destination, offset, count); + } + + internalReader = BodyRead; + return BodyRead(destination, offset, count); + } + + + internalReader = ReadingNotAvailable; + return 0; + } + + /// + /// Read a block of bytes from the stream. + /// + /// The destination for the bytes. + /// The index to start storing data. + /// The number of bytes to attempt to read. + /// Returns the number of bytes read. + /// Zero bytes read means end of stream. + public override int Read(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative"); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative"); + } + + if ((buffer.Length - offset) < count) + { + throw new ArgumentException("Invalid offset/count combination"); + } + + return internalReader(buffer, offset, count); + } + + /// + /// Reads a block of bytes from the current zip entry. + /// + /// + /// The number of bytes read (this may be less than the length requested, even before the end of stream), or 0 on end of stream. + /// + /// + /// An i/o error occurred. + /// + /// + /// The deflated stream is corrupted. + /// + /// + /// The stream is not open. + /// + private int BodyRead(byte[] buffer, int offset, int count) + { + if (crc == null) + { + throw new InvalidOperationException("Closed"); + } + + if ((entry == null) || (count <= 0)) + { + return 0; + } + + if (offset + count > buffer.Length) + { + throw new ArgumentException("Offset + count exceeds buffer size"); + } + + bool finished = false; + + switch (method) + { + case CompressionMethod.Deflated: + count = base.Read(buffer, offset, count); + if (count <= 0) + { + if (!inf.IsFinished) + { + throw new ZipException("Inflater not finished!"); + } + inputBuffer.Available = inf.RemainingInput; + + // A csize of -1 is from an unpatched local header + if ((flags & 8) == 0 && + (inf.TotalIn != csize && csize != 0xFFFFFFFF && csize != -1 || inf.TotalOut != size)) + { + throw new ZipException("Size mismatch: " + csize + ";" + size + " <-> " + inf.TotalIn + ";" + inf.TotalOut); + } + inf.Reset(); + finished = true; + } + break; + + case CompressionMethod.Stored: + if ((count > csize) && (csize >= 0)) + { + count = (int)csize; + } + + if (count > 0) + { + count = inputBuffer.ReadClearTextBuffer(buffer, offset, count); + if (count > 0) + { + csize -= count; + size -= count; + } + } + + if (csize == 0) + { + finished = true; + } + else + { + if (count < 0) + { + throw new ZipException("EOF in stored block"); + } + } + break; + } + + if (count > 0) + { + crc.Update(new ArraySegment(buffer, offset, count)); + } + + if (finished) + { + CompleteCloseEntry(true); + } + + return count; + } + + /// + /// Closes the zip input stream + /// + protected override void Dispose(bool disposing) + { + internalReader = new ReadDataHandler(ReadingNotAvailable); + crc = null; + entry = null; + + base.Dispose(disposing); + } + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipNameTransform.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipNameTransform.cs new file mode 100644 index 0000000..f91b20c --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipNameTransform.cs @@ -0,0 +1,313 @@ +using ICSharpCode.SharpZipLib.Core; +using System; +using System.IO; +using System.Text; + +namespace ICSharpCode.SharpZipLib.Zip +{ + /// + /// ZipNameTransform transforms names as per the Zip file naming convention. + /// + /// The use of absolute names is supported although its use is not valid + /// according to Zip naming conventions, and should not be used if maximum compatability is desired. + public class ZipNameTransform : INameTransform + { + #region Constructors + + /// + /// Initialize a new instance of + /// + public ZipNameTransform() + { + } + + /// + /// Initialize a new instance of + /// + /// The string to trim from the front of paths if found. + public ZipNameTransform(string trimPrefix) + { + TrimPrefix = trimPrefix; + } + + #endregion Constructors + + /// + /// Static constructor. + /// + static ZipNameTransform() + { + char[] invalidPathChars; + invalidPathChars = Path.GetInvalidPathChars(); + int howMany = invalidPathChars.Length + 2; + + InvalidEntryCharsRelaxed = new char[howMany]; + Array.Copy(invalidPathChars, 0, InvalidEntryCharsRelaxed, 0, invalidPathChars.Length); + InvalidEntryCharsRelaxed[howMany - 1] = '*'; + InvalidEntryCharsRelaxed[howMany - 2] = '?'; + + howMany = invalidPathChars.Length + 4; + InvalidEntryChars = new char[howMany]; + Array.Copy(invalidPathChars, 0, InvalidEntryChars, 0, invalidPathChars.Length); + InvalidEntryChars[howMany - 1] = ':'; + InvalidEntryChars[howMany - 2] = '\\'; + InvalidEntryChars[howMany - 3] = '*'; + InvalidEntryChars[howMany - 4] = '?'; + } + + /// + /// Transform a windows directory name according to the Zip file naming conventions. + /// + /// The directory name to transform. + /// The transformed name. + public string TransformDirectory(string name) + { + name = TransformFile(name); + if (name.Length > 0) + { + if (!name.EndsWith("/", StringComparison.Ordinal)) + { + name += "/"; + } + } + else + { + throw new ZipException("Cannot have an empty directory name"); + } + return name; + } + + /// + /// Transform a windows file name according to the Zip file naming conventions. + /// + /// The file name to transform. + /// The transformed name. + public string TransformFile(string name) + { + if (name != null) + { + string lowerName = name.ToLower(); + if ((trimPrefix_ != null) && (lowerName.IndexOf(trimPrefix_, StringComparison.Ordinal) == 0)) + { + name = name.Substring(trimPrefix_.Length); + } + + name = name.Replace(@"\", "/"); + name = PathUtils.DropPathRoot(name); + + // Drop any leading and trailing slashes. + name = name.Trim('/'); + + // Convert consecutive // characters to / + int index = name.IndexOf("//", StringComparison.Ordinal); + while (index >= 0) + { + name = name.Remove(index, 1); + index = name.IndexOf("//", StringComparison.Ordinal); + } + + name = MakeValidName(name, '_'); + } + else + { + name = string.Empty; + } + return name; + } + + /// + /// Get/set the path prefix to be trimmed from paths if present. + /// + /// The prefix is trimmed before any conversion from + /// a windows path is done. + public string TrimPrefix + { + get { return trimPrefix_; } + set + { + trimPrefix_ = value; + if (trimPrefix_ != null) + { + trimPrefix_ = trimPrefix_.ToLower(); + } + } + } + + /// + /// Force a name to be valid by replacing invalid characters with a fixed value + /// + /// The name to force valid + /// The replacement character to use. + /// Returns a valid name + private static string MakeValidName(string name, char replacement) + { + int index = name.IndexOfAny(InvalidEntryChars); + if (index >= 0) + { + var builder = new StringBuilder(name); + + while (index >= 0) + { + builder[index] = replacement; + + if (index >= name.Length) + { + index = -1; + } + else + { + index = name.IndexOfAny(InvalidEntryChars, index + 1); + } + } + name = builder.ToString(); + } + + if (name.Length > 0xffff) + { + throw new PathTooLongException(); + } + + return name; + } + + /// + /// Test a name to see if it is a valid name for a zip entry. + /// + /// The name to test. + /// If true checking is relaxed about windows file names and absolute paths. + /// Returns true if the name is a valid zip name; false otherwise. + /// Zip path names are actually in Unix format, and should only contain relative paths. + /// This means that any path stored should not contain a drive or + /// device letter, or a leading slash. All slashes should forward slashes '/'. + /// An empty name is valid for a file where the input comes from standard input. + /// A null name is not considered valid. + /// + public static bool IsValidName(string name, bool relaxed) + { + bool result = (name != null); + + if (result) + { + if (relaxed) + { + result = name.IndexOfAny(InvalidEntryCharsRelaxed) < 0; + } + else + { + result = + (name.IndexOfAny(InvalidEntryChars) < 0) && + (name.IndexOf('/') != 0); + } + } + + return result; + } + + /// + /// Test a name to see if it is a valid name for a zip entry. + /// + /// The name to test. + /// Returns true if the name is a valid zip name; false otherwise. + /// Zip path names are actually in unix format, + /// and should only contain relative paths if a path is present. + /// This means that the path stored should not contain a drive or + /// device letter, or a leading slash. All slashes should forward slashes '/'. + /// An empty name is valid where the input comes from standard input. + /// A null name is not considered valid. + /// + public static bool IsValidName(string name) + { + bool result = + (name != null) && + (name.IndexOfAny(InvalidEntryChars) < 0) && + (name.IndexOf('/') != 0) + ; + return result; + } + + #region Instance Fields + + private string trimPrefix_; + + #endregion Instance Fields + + #region Class Fields + + private static readonly char[] InvalidEntryChars; + private static readonly char[] InvalidEntryCharsRelaxed; + + #endregion Class Fields + } + + /// + /// An implementation of INameTransform that transforms entry paths as per the Zip file naming convention. + /// Strips path roots and puts directory separators in the correct format ('/') + /// + public class PathTransformer : INameTransform + { + /// + /// Initialize a new instance of + /// + public PathTransformer() + { + } + + /// + /// Transform a windows directory name according to the Zip file naming conventions. + /// + /// The directory name to transform. + /// The transformed name. + public string TransformDirectory(string name) + { + name = TransformFile(name); + + if (name.Length > 0) + { + if (!name.EndsWith("/", StringComparison.Ordinal)) + { + name += "/"; + } + } + else + { + throw new ZipException("Cannot have an empty directory name"); + } + + return name; + } + + /// + /// Transform a windows file name according to the Zip file naming conventions. + /// + /// The file name to transform. + /// The transformed name. + public string TransformFile(string name) + { + if (name != null) + { + // Put separators in the expected format. + name = name.Replace(@"\", "/"); + + // Remove the path root. + name = PathUtils.DropPathRoot(name); + + // Drop any leading and trailing slashes. + name = name.Trim('/'); + + // Convert consecutive // characters to / + int index = name.IndexOf("//", StringComparison.Ordinal); + while (index >= 0) + { + name = name.Remove(index, 1); + index = name.IndexOf("//", StringComparison.Ordinal); + } + } + else + { + name = string.Empty; + } + + return name; + } + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs new file mode 100644 index 0000000..79d65f5 --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs @@ -0,0 +1,1079 @@ +using ICSharpCode.SharpZipLib.Checksum; +using ICSharpCode.SharpZipLib.Core; +using ICSharpCode.SharpZipLib.Encryption; +using ICSharpCode.SharpZipLib.Zip.Compression; +using ICSharpCode.SharpZipLib.Zip.Compression.Streams; +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; + +namespace ICSharpCode.SharpZipLib.Zip +{ + /// + /// This is a DeflaterOutputStream that writes the files into a zip + /// archive one after another. It has a special method to start a new + /// zip entry. The zip entries contains information about the file name + /// size, compressed size, CRC, etc. + /// + /// It includes support for Stored and Deflated entries. + /// This class is not thread safe. + ///
+ ///
Author of the original java version : Jochen Hoenicke + ///
+ /// This sample shows how to create a zip file + /// + /// using System; + /// using System.IO; + /// + /// using ICSharpCode.SharpZipLib.Core; + /// using ICSharpCode.SharpZipLib.Zip; + /// + /// class MainClass + /// { + /// public static void Main(string[] args) + /// { + /// string[] filenames = Directory.GetFiles(args[0]); + /// byte[] buffer = new byte[4096]; + /// + /// using ( ZipOutputStream s = new ZipOutputStream(File.Create(args[1])) ) { + /// + /// s.SetLevel(9); // 0 - store only to 9 - means best compression + /// + /// foreach (string file in filenames) { + /// ZipEntry entry = new ZipEntry(file); + /// s.PutNextEntry(entry); + /// + /// using (FileStream fs = File.OpenRead(file)) { + /// StreamUtils.Copy(fs, s, buffer); + /// } + /// } + /// } + /// } + /// } + /// + /// + public class ZipOutputStream : DeflaterOutputStream + { + #region Constructors + + /// + /// Creates a new Zip output stream, writing a zip archive. + /// + /// + /// The output stream to which the archive contents are written. + /// + public ZipOutputStream(Stream baseOutputStream) + : base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true)) + { + } + + /// + /// Creates a new Zip output stream, writing a zip archive. + /// + /// The output stream to which the archive contents are written. + /// Size of the buffer to use. + public ZipOutputStream(Stream baseOutputStream, int bufferSize) + : base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true), bufferSize) + { + } + + #endregion Constructors + + /// + /// Gets a flag value of true if the central header has been added for this archive; false if it has not been added. + /// + /// No further entries can be added once this has been done. + public bool IsFinished + { + get + { + return entries == null; + } + } + + /// + /// Set the zip file comment. + /// + /// + /// The comment text for the entire archive. + /// + /// + /// The converted comment is longer than 0xffff bytes. + /// + public void SetComment(string comment) + { + // TODO: Its not yet clear how to handle unicode comments here. + byte[] commentBytes = ZipStrings.ConvertToArray(comment); + if (commentBytes.Length > 0xffff) + { + throw new ArgumentOutOfRangeException(nameof(comment)); + } + zipComment = commentBytes; + } + + /// + /// Sets the compression level. The new level will be activated + /// immediately. + /// + /// The new compression level (1 to 9). + /// + /// Level specified is not supported. + /// + /// + public void SetLevel(int level) + { + deflater_.SetLevel(level); + defaultCompressionLevel = level; + } + + /// + /// Get the current deflater compression level + /// + /// The current compression level + public int GetLevel() + { + return deflater_.GetLevel(); + } + + /// + /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries. + /// + /// Older archivers may not understand Zip64 extensions. + /// If backwards compatability is an issue be careful when adding entries to an archive. + /// Setting this property to off is workable but less desirable as in those circumstances adding a file + /// larger then 4GB will fail. + public UseZip64 UseZip64 + { + get { return useZip64_; } + set { useZip64_ = value; } + } + + /// + /// Used for transforming the names of entries added by . + /// Defaults to , set to null to disable transforms and use names as supplied. + /// + public INameTransform NameTransform { get; set; } = new PathTransformer(); + + /// + /// Get/set the password used for encryption. + /// + /// When set to null or if the password is empty no encryption is performed + public string Password + { + get + { + return password; + } + set + { + if ((value != null) && (value.Length == 0)) + { + password = null; + } + else + { + password = value; + } + } + } + + /// + /// Write an unsigned short in little endian byte order. + /// + private void WriteLeShort(int value) + { + unchecked + { + baseOutputStream_.WriteByte((byte)(value & 0xff)); + baseOutputStream_.WriteByte((byte)((value >> 8) & 0xff)); + } + } + + /// + /// Write an int in little endian byte order. + /// + private void WriteLeInt(int value) + { + unchecked + { + WriteLeShort(value); + WriteLeShort(value >> 16); + } + } + + /// + /// Write an int in little endian byte order. + /// + private void WriteLeLong(long value) + { + unchecked + { + WriteLeInt((int)value); + WriteLeInt((int)(value >> 32)); + } + } + + // Apply any configured transforms/cleaning to the name of the supplied entry. + private void TransformEntryName(ZipEntry entry) + { + if (this.NameTransform != null) + { + if (entry.IsDirectory) + { + entry.Name = this.NameTransform.TransformDirectory(entry.Name); + } + else + { + entry.Name = this.NameTransform.TransformFile(entry.Name); + } + } + } + + /// + /// Starts a new Zip entry. It automatically closes the previous + /// entry if present. + /// All entry elements bar name are optional, but must be correct if present. + /// If the compression method is stored and the output is not patchable + /// the compression for that entry is automatically changed to deflate level 0 + /// + /// + /// the entry. + /// + /// + /// if entry passed is null. + /// + /// + /// if an I/O error occured. + /// + /// + /// if stream was finished + /// + /// + /// Too many entries in the Zip file
+ /// Entry name is too long
+ /// Finish has already been called
+ ///
+ /// + /// The Compression method specified for the entry is unsupported. + /// + public void PutNextEntry(ZipEntry entry) + { + if (entry == null) + { + throw new ArgumentNullException(nameof(entry)); + } + + if (entries == null) + { + throw new InvalidOperationException("ZipOutputStream was finished"); + } + + if (curEntry != null) + { + CloseEntry(); + } + + if (entries.Count == int.MaxValue) + { + throw new ZipException("Too many entries for Zip file"); + } + + CompressionMethod method = entry.CompressionMethod; + + // Check that the compression is one that we support + if (method != CompressionMethod.Deflated && method != CompressionMethod.Stored) + { + throw new NotImplementedException("Compression method not supported"); + } + + // A password must have been set in order to add AES encrypted entries + if (entry.AESKeySize > 0 && string.IsNullOrEmpty(this.Password)) + { + throw new InvalidOperationException("The Password property must be set before AES encrypted entries can be added"); + } + + int compressionLevel = defaultCompressionLevel; + + // Clear flags that the library manages internally + entry.Flags &= (int)GeneralBitFlags.UnicodeText; + patchEntryHeader = false; + + bool headerInfoAvailable; + + // No need to compress - definitely no data. + if (entry.Size == 0) + { + entry.CompressedSize = entry.Size; + entry.Crc = 0; + method = CompressionMethod.Stored; + headerInfoAvailable = true; + } + else + { + headerInfoAvailable = (entry.Size >= 0) && entry.HasCrc && entry.CompressedSize >= 0; + + // Switch to deflation if storing isnt possible. + if (method == CompressionMethod.Stored) + { + if (!headerInfoAvailable) + { + if (!CanPatchEntries) + { + // Can't patch entries so storing is not possible. + method = CompressionMethod.Deflated; + compressionLevel = 0; + } + } + else // entry.size must be > 0 + { + entry.CompressedSize = entry.Size; + headerInfoAvailable = entry.HasCrc; + } + } + } + + if (headerInfoAvailable == false) + { + if (CanPatchEntries == false) + { + // Only way to record size and compressed size is to append a data descriptor + // after compressed data. + + // Stored entries of this form have already been converted to deflating. + entry.Flags |= 8; + } + else + { + patchEntryHeader = true; + } + } + + if (Password != null) + { + entry.IsCrypted = true; + if (entry.Crc < 0) + { + // Need to append a data descriptor as the crc isnt available for use + // with encryption, the date is used instead. Setting the flag + // indicates this to the decompressor. + entry.Flags |= 8; + } + } + + entry.Offset = offset; + entry.CompressionMethod = (CompressionMethod)method; + + curMethod = method; + sizePatchPos = -1; + + if ((useZip64_ == UseZip64.On) || ((entry.Size < 0) && (useZip64_ == UseZip64.Dynamic))) + { + entry.ForceZip64(); + } + + // Write the local file header + WriteLeInt(ZipConstants.LocalHeaderSignature); + + WriteLeShort(entry.Version); + WriteLeShort(entry.Flags); + WriteLeShort((byte)entry.CompressionMethodForHeader); + WriteLeInt((int)entry.DosTime); + + // TODO: Refactor header writing. Its done in several places. + if (headerInfoAvailable) + { + WriteLeInt((int)entry.Crc); + if (entry.LocalHeaderRequiresZip64) + { + WriteLeInt(-1); + WriteLeInt(-1); + } + else + { + WriteLeInt((int)entry.CompressedSize + entry.EncryptionOverheadSize); + WriteLeInt((int)entry.Size); + } + } + else + { + if (patchEntryHeader) + { + crcPatchPos = baseOutputStream_.Position; + } + WriteLeInt(0); // Crc + + if (patchEntryHeader) + { + sizePatchPos = baseOutputStream_.Position; + } + + // For local header both sizes appear in Zip64 Extended Information + if (entry.LocalHeaderRequiresZip64 || patchEntryHeader) + { + WriteLeInt(-1); + WriteLeInt(-1); + } + else + { + WriteLeInt(0); // Compressed size + WriteLeInt(0); // Uncompressed size + } + } + + // Apply any required transforms to the entry name, and then convert to byte array format. + TransformEntryName(entry); + byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name); + + if (name.Length > 0xFFFF) + { + throw new ZipException("Entry name too long."); + } + + var ed = new ZipExtraData(entry.ExtraData); + + if (entry.LocalHeaderRequiresZip64) + { + ed.StartNewEntry(); + if (headerInfoAvailable) + { + ed.AddLeLong(entry.Size); + ed.AddLeLong(entry.CompressedSize + entry.EncryptionOverheadSize); + } + else + { + ed.AddLeLong(-1); + ed.AddLeLong(-1); + } + ed.AddNewEntry(1); + + if (!ed.Find(1)) + { + throw new ZipException("Internal error cant find extra data"); + } + + if (patchEntryHeader) + { + sizePatchPos = ed.CurrentReadIndex; + } + } + else + { + ed.Delete(1); + } + + if (entry.AESKeySize > 0) + { + AddExtraDataAES(entry, ed); + } + byte[] extra = ed.GetEntryData(); + + WriteLeShort(name.Length); + WriteLeShort(extra.Length); + + if (name.Length > 0) + { + baseOutputStream_.Write(name, 0, name.Length); + } + + if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) + { + sizePatchPos += baseOutputStream_.Position; + } + + if (extra.Length > 0) + { + baseOutputStream_.Write(extra, 0, extra.Length); + } + + offset += ZipConstants.LocalHeaderBaseSize + name.Length + extra.Length; + // Fix offsetOfCentraldir for AES + if (entry.AESKeySize > 0) + offset += entry.AESOverheadSize; + + // Activate the entry. + curEntry = entry; + crc.Reset(); + if (method == CompressionMethod.Deflated) + { + deflater_.Reset(); + deflater_.SetLevel(compressionLevel); + } + size = 0; + + if (entry.IsCrypted) + { + if (entry.AESKeySize > 0) + { + WriteAESHeader(entry); + } + else + { + if (entry.Crc < 0) + { // so testing Zip will says its ok + WriteEncryptionHeader(entry.DosTime << 16); + } + else + { + WriteEncryptionHeader(entry.Crc); + } + } + } + } + + /// + /// Closes the current entry, updating header and footer information as required + /// + /// + /// Invalid entry field values. + /// + /// + /// An I/O error occurs. + /// + /// + /// No entry is active. + /// + public void CloseEntry() + { + if (curEntry == null) + { + throw new InvalidOperationException("No open entry"); + } + + long csize = size; + + // First finish the deflater, if appropriate + if (curMethod == CompressionMethod.Deflated) + { + if (size >= 0) + { + base.Finish(); + csize = deflater_.TotalOut; + } + else + { + deflater_.Reset(); + } + } + else if (curMethod == CompressionMethod.Stored) + { + // This is done by Finish() for Deflated entries, but we need to do it + // ourselves for Stored ones + base.GetAuthCodeIfAES(); + } + + // Write the AES Authentication Code (a hash of the compressed and encrypted data) + if (curEntry.AESKeySize > 0) + { + baseOutputStream_.Write(AESAuthCode, 0, 10); + // Always use 0 as CRC for AE-2 format + curEntry.Crc = 0; + } + else + { + if (curEntry.Crc < 0) + { + curEntry.Crc = crc.Value; + } + else if (curEntry.Crc != crc.Value) + { + throw new ZipException($"crc was {crc.Value}, but {curEntry.Crc} was expected"); + } + } + + if (curEntry.Size < 0) + { + curEntry.Size = size; + } + else if (curEntry.Size != size) + { + throw new ZipException($"size was {size}, but {curEntry.Size} was expected"); + } + + if (curEntry.CompressedSize < 0) + { + curEntry.CompressedSize = csize; + } + else if (curEntry.CompressedSize != csize) + { + throw new ZipException($"compressed size was {csize}, but {curEntry.CompressedSize} expected"); + } + + offset += csize; + + if (curEntry.IsCrypted) + { + curEntry.CompressedSize += curEntry.EncryptionOverheadSize; + } + + // Patch the header if possible + if (patchEntryHeader) + { + patchEntryHeader = false; + + long curPos = baseOutputStream_.Position; + baseOutputStream_.Seek(crcPatchPos, SeekOrigin.Begin); + WriteLeInt((int)curEntry.Crc); + + if (curEntry.LocalHeaderRequiresZip64) + { + if (sizePatchPos == -1) + { + throw new ZipException("Entry requires zip64 but this has been turned off"); + } + + baseOutputStream_.Seek(sizePatchPos, SeekOrigin.Begin); + WriteLeLong(curEntry.Size); + WriteLeLong(curEntry.CompressedSize); + } + else + { + WriteLeInt((int)curEntry.CompressedSize); + WriteLeInt((int)curEntry.Size); + } + baseOutputStream_.Seek(curPos, SeekOrigin.Begin); + } + + // Add data descriptor if flagged as required + if ((curEntry.Flags & 8) != 0) + { + WriteLeInt(ZipConstants.DataDescriptorSignature); + WriteLeInt(unchecked((int)curEntry.Crc)); + + if (curEntry.LocalHeaderRequiresZip64) + { + WriteLeLong(curEntry.CompressedSize); + WriteLeLong(curEntry.Size); + offset += ZipConstants.Zip64DataDescriptorSize; + } + else + { + WriteLeInt((int)curEntry.CompressedSize); + WriteLeInt((int)curEntry.Size); + offset += ZipConstants.DataDescriptorSize; + } + } + + entries.Add(curEntry); + curEntry = null; + } + + /// + /// Initializes encryption keys based on given . + /// + /// The password. + private void InitializePassword(string password) + { + var pkManaged = new PkzipClassicManaged(); + byte[] key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(password)); + cryptoTransform_ = pkManaged.CreateEncryptor(key, null); + } + + /// + /// Initializes encryption keys based on given password. + /// + private void InitializeAESPassword(ZipEntry entry, string rawPassword, + out byte[] salt, out byte[] pwdVerifier) + { + salt = new byte[entry.AESSaltLen]; + + // Salt needs to be cryptographically random, and unique per file + _aesRnd.GetBytes(salt); + + int blockSize = entry.AESKeySize / 8; // bits to bytes + + cryptoTransform_ = new ZipAESTransform(rawPassword, salt, blockSize, true); + pwdVerifier = ((ZipAESTransform)cryptoTransform_).PwdVerifier; + } + + private void WriteEncryptionHeader(long crcValue) + { + offset += ZipConstants.CryptoHeaderSize; + + InitializePassword(Password); + + byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize]; + using (var rng = new RNGCryptoServiceProvider()) + { + rng.GetBytes(cryptBuffer); + } + + cryptBuffer[11] = (byte)(crcValue >> 24); + + EncryptBlock(cryptBuffer, 0, cryptBuffer.Length); + baseOutputStream_.Write(cryptBuffer, 0, cryptBuffer.Length); + } + + private static void AddExtraDataAES(ZipEntry entry, ZipExtraData extraData) + { + // Vendor Version: AE-1 IS 1. AE-2 is 2. With AE-2 no CRC is required and 0 is stored. + const int VENDOR_VERSION = 2; + // Vendor ID is the two ASCII characters "AE". + const int VENDOR_ID = 0x4541; //not 6965; + extraData.StartNewEntry(); + // Pack AES extra data field see http://www.winzip.com/aes_info.htm + //extraData.AddLeShort(7); // Data size (currently 7) + extraData.AddLeShort(VENDOR_VERSION); // 2 = AE-2 + extraData.AddLeShort(VENDOR_ID); // "AE" + extraData.AddData(entry.AESEncryptionStrength); // 1 = 128, 2 = 192, 3 = 256 + extraData.AddLeShort((int)entry.CompressionMethod); // The actual compression method used to compress the file + extraData.AddNewEntry(0x9901); + } + + // Replaces WriteEncryptionHeader for AES + // + private void WriteAESHeader(ZipEntry entry) + { + byte[] salt; + byte[] pwdVerifier; + InitializeAESPassword(entry, Password, out salt, out pwdVerifier); + // File format for AES: + // Size (bytes) Content + // ------------ ------- + // Variable Salt value + // 2 Password verification value + // Variable Encrypted file data + // 10 Authentication code + // + // Value in the "compressed size" fields of the local file header and the central directory entry + // is the total size of all the items listed above. In other words, it is the total size of the + // salt value, password verification value, encrypted data, and authentication code. + baseOutputStream_.Write(salt, 0, salt.Length); + baseOutputStream_.Write(pwdVerifier, 0, pwdVerifier.Length); + } + + /// + /// Writes the given buffer to the current entry. + /// + /// The buffer containing data to write. + /// The offset of the first byte to write. + /// The number of bytes to write. + /// Archive size is invalid + /// No entry is active. + public override void Write(byte[] buffer, int offset, int count) + { + if (curEntry == null) + { + throw new InvalidOperationException("No open entry."); + } + + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative"); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative"); + } + + if ((buffer.Length - offset) < count) + { + throw new ArgumentException("Invalid offset/count combination"); + } + + if (curEntry.AESKeySize == 0) + { + // Only update CRC if AES is not enabled + crc.Update(new ArraySegment(buffer, offset, count)); + } + + size += count; + + switch (curMethod) + { + case CompressionMethod.Deflated: + base.Write(buffer, offset, count); + break; + + case CompressionMethod.Stored: + if (Password != null) + { + CopyAndEncrypt(buffer, offset, count); + } + else + { + baseOutputStream_.Write(buffer, offset, count); + } + break; + } + } + + private void CopyAndEncrypt(byte[] buffer, int offset, int count) + { + const int CopyBufferSize = 4096; + byte[] localBuffer = new byte[CopyBufferSize]; + while (count > 0) + { + int bufferCount = (count < CopyBufferSize) ? count : CopyBufferSize; + + Array.Copy(buffer, offset, localBuffer, 0, bufferCount); + EncryptBlock(localBuffer, 0, bufferCount); + baseOutputStream_.Write(localBuffer, 0, bufferCount); + count -= bufferCount; + offset += bufferCount; + } + } + + /// + /// Finishes the stream. This will write the central directory at the + /// end of the zip file and flush the stream. + /// + /// + /// This is automatically called when the stream is closed. + /// + /// + /// An I/O error occurs. + /// + /// + /// Comment exceeds the maximum length
+ /// Entry name exceeds the maximum length + ///
+ public override void Finish() + { + if (entries == null) + { + return; + } + + if (curEntry != null) + { + CloseEntry(); + } + + long numEntries = entries.Count; + long sizeEntries = 0; + + foreach (ZipEntry entry in entries) + { + WriteLeInt(ZipConstants.CentralHeaderSignature); + WriteLeShort((entry.HostSystem << 8) | entry.VersionMadeBy); + WriteLeShort(entry.Version); + WriteLeShort(entry.Flags); + WriteLeShort((short)entry.CompressionMethodForHeader); + WriteLeInt((int)entry.DosTime); + WriteLeInt((int)entry.Crc); + + if (entry.IsZip64Forced() || + (entry.CompressedSize >= uint.MaxValue)) + { + WriteLeInt(-1); + } + else + { + WriteLeInt((int)entry.CompressedSize); + } + + if (entry.IsZip64Forced() || + (entry.Size >= uint.MaxValue)) + { + WriteLeInt(-1); + } + else + { + WriteLeInt((int)entry.Size); + } + + byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name); + + if (name.Length > 0xffff) + { + throw new ZipException("Name too long."); + } + + var ed = new ZipExtraData(entry.ExtraData); + + if (entry.CentralHeaderRequiresZip64) + { + ed.StartNewEntry(); + if (entry.IsZip64Forced() || + (entry.Size >= 0xffffffff)) + { + ed.AddLeLong(entry.Size); + } + + if (entry.IsZip64Forced() || + (entry.CompressedSize >= 0xffffffff)) + { + ed.AddLeLong(entry.CompressedSize); + } + + if (entry.Offset >= 0xffffffff) + { + ed.AddLeLong(entry.Offset); + } + + ed.AddNewEntry(1); + } + else + { + ed.Delete(1); + } + + if (entry.AESKeySize > 0) + { + AddExtraDataAES(entry, ed); + } + byte[] extra = ed.GetEntryData(); + + byte[] entryComment = + (entry.Comment != null) ? + ZipStrings.ConvertToArray(entry.Flags, entry.Comment) : + Empty.Array(); + + if (entryComment.Length > 0xffff) + { + throw new ZipException("Comment too long."); + } + + WriteLeShort(name.Length); + WriteLeShort(extra.Length); + WriteLeShort(entryComment.Length); + WriteLeShort(0); // disk number + WriteLeShort(0); // internal file attributes + // external file attributes + + if (entry.ExternalFileAttributes != -1) + { + WriteLeInt(entry.ExternalFileAttributes); + } + else + { + if (entry.IsDirectory) + { // mark entry as directory (from nikolam.AT.perfectinfo.com) + WriteLeInt(16); + } + else + { + WriteLeInt(0); + } + } + + if (entry.Offset >= uint.MaxValue) + { + WriteLeInt(-1); + } + else + { + WriteLeInt((int)entry.Offset); + } + + if (name.Length > 0) + { + baseOutputStream_.Write(name, 0, name.Length); + } + + if (extra.Length > 0) + { + baseOutputStream_.Write(extra, 0, extra.Length); + } + + if (entryComment.Length > 0) + { + baseOutputStream_.Write(entryComment, 0, entryComment.Length); + } + + sizeEntries += ZipConstants.CentralHeaderBaseSize + name.Length + extra.Length + entryComment.Length; + } + + using (ZipHelperStream zhs = new ZipHelperStream(baseOutputStream_)) + { + zhs.WriteEndOfCentralDirectory(numEntries, sizeEntries, offset, zipComment); + } + + entries = null; + } + + /// + /// Flushes the stream by calling Flush on the deflater stream unless + /// the current compression method is . Then it flushes the underlying output stream. + /// + public override void Flush() + { + if(curMethod == CompressionMethod.Stored) + { + baseOutputStream_.Flush(); + } + else + { + base.Flush(); + } + } + + #region Instance Fields + + /// + /// The entries for the archive. + /// + private List entries = new List(); + + /// + /// Used to track the crc of data added to entries. + /// + private Crc32 crc = new Crc32(); + + /// + /// The current entry being added. + /// + private ZipEntry curEntry; + + private int defaultCompressionLevel = Deflater.DEFAULT_COMPRESSION; + + private CompressionMethod curMethod = CompressionMethod.Deflated; + + /// + /// Used to track the size of data for an entry during writing. + /// + private long size; + + /// + /// Offset to be recorded for each entry in the central header. + /// + private long offset; + + /// + /// Comment for the entire archive recorded in central header. + /// + private byte[] zipComment = Empty.Array(); + + /// + /// Flag indicating that header patching is required for the current entry. + /// + private bool patchEntryHeader; + + /// + /// Position to patch crc + /// + private long crcPatchPos = -1; + + /// + /// Position to patch size. + /// + private long sizePatchPos = -1; + + // Default is dynamic which is not backwards compatible and can cause problems + // with XP's built in compression which cant read Zip64 archives. + // However it does avoid the situation were a large file is added and cannot be completed correctly. + // NOTE: Setting the size for entries before they are added is the best solution! + private UseZip64 useZip64_ = UseZip64.Dynamic; + + /// + /// The password to use when encrypting archive entries. + /// + private string password; + + #endregion Instance Fields + + #region Static Fields + + // Static to help ensure that multiple files within a zip will get different random salt + private static RandomNumberGenerator _aesRnd = RandomNumberGenerator.Create(); + + #endregion Static Fields + } +} diff --git a/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipStrings.cs b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipStrings.cs new file mode 100644 index 0000000..2d0c4cf --- /dev/null +++ b/常用工具集/Utility/ICSharpCode.SharpZipLib/Zip/ZipStrings.cs @@ -0,0 +1,194 @@ +using System; +using System.Text; +using ICSharpCode.SharpZipLib.Core; + +namespace ICSharpCode.SharpZipLib.Zip +{ + /// + /// This static class contains functions for encoding and decoding zip file strings + /// + public static class ZipStrings + { + static ZipStrings() + { + try + { + var platformCodepage = Encoding.GetEncoding(0).CodePage; + SystemDefaultCodePage = (platformCodepage == 1 || platformCodepage == 2 || platformCodepage == 3 || platformCodepage == 42) ? FallbackCodePage : platformCodepage; + } + catch + { + SystemDefaultCodePage = FallbackCodePage; + } + } + + /// Code page backing field + /// + /// The original Zip specification (https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT) states + /// that file names should only be encoded with IBM Code Page 437 or UTF-8. + /// In practice, most zip apps use OEM or system encoding (typically cp437 on Windows). + /// Let's be good citizens and default to UTF-8 http://utf8everywhere.org/ + /// + private static int codePage = AutomaticCodePage; + + /// Automatically select codepage while opening archive + /// see https://github.com/icsharpcode/SharpZipLib/pull/280#issuecomment-433608324 + /// + private const int AutomaticCodePage = -1; + + /// + /// Encoding used for string conversion. Setting this to 65001 (UTF-8) will + /// also set the Language encoding flag to indicate UTF-8 encoded file names. + /// + public static int CodePage + { + get + { + return codePage == AutomaticCodePage? Encoding.UTF8.CodePage:codePage; + } + set + { + if ((value < 0) || (value > 65535) || + (value == 1) || (value == 2) || (value == 3) || (value == 42)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + codePage = value; + } + } + + private const int FallbackCodePage = 437; + + /// + /// Attempt to get the operating system default codepage, or failing that, to + /// the fallback code page IBM 437. + /// + public static int SystemDefaultCodePage { get; } + + /// + /// Get whether the default codepage is set to UTF-8. Setting this property to false will + /// set the to + /// + /// + /// Get OEM codepage from NetFX, which parses the NLP file with culture info table etc etc. + /// But sometimes it yields the special value of 1 which is nicknamed CodePageNoOEM in sources (might also mean CP_OEMCP, but Encoding puts it so). + /// This was observed on Ukranian and Hindu systems. + /// Given this value, throws an . + /// So replace it with , (IBM 437 which is the default code page in a default Windows installation console. + /// + public static bool UseUnicode + { + get + { + return codePage == Encoding.UTF8.CodePage; + } + set + { + if (value) + { + codePage = Encoding.UTF8.CodePage; + } + else + { + codePage = SystemDefaultCodePage; + } + } + } + + /// + /// Convert a portion of a byte array to a string using + /// + /// + /// Data to convert to string + /// + /// + /// Number of bytes to convert starting from index 0 + /// + /// + /// data[0]..data[count - 1] converted to a string + /// + public static string ConvertToString(byte[] data, int count) + => data == null + ? string.Empty + : Encoding.GetEncoding(CodePage).GetString(data, 0, count); + + /// + /// Convert a byte array to a string using + /// + /// + /// Byte array to convert + /// + /// + /// dataconverted to a string + /// + public static string ConvertToString(byte[] data) + => ConvertToString(data, data.Length); + + private static Encoding EncodingFromFlag(int flags) + => ((flags & (int)GeneralBitFlags.UnicodeText) != 0) + ? Encoding.UTF8 + : Encoding.GetEncoding( + // if CodePage wasn't set manually and no utf flag present + // then we must use SystemDefault (old behavior) + // otherwise, CodePage should be preferred over SystemDefault + // see https://github.com/icsharpcode/SharpZipLib/issues/274 + codePage == AutomaticCodePage? + SystemDefaultCodePage: + codePage); + + /// + /// Convert a byte array to a string using + /// + /// The applicable general purpose bits flags + /// + /// Byte array to convert + /// + /// The number of bytes to convert. + /// + /// dataconverted to a string + /// + public static string ConvertToStringExt(int flags, byte[] data, int count) + => (data == null) + ? string.Empty + : EncodingFromFlag(flags).GetString(data, 0, count); + + /// + /// Convert a byte array to a string using + /// + /// + /// Byte array to convert + /// + /// The applicable general purpose bits flags + /// + /// dataconverted to a string + /// + public static string ConvertToStringExt(int flags, byte[] data) + => ConvertToStringExt(flags, data, data.Length); + + /// + /// Convert a string to a byte array using + /// + /// + /// String to convert to an array + /// + /// Converted array + public static byte[] ConvertToArray(string str) + => str == null + ? Empty.Array() + : Encoding.GetEncoding(CodePage).GetBytes(str); + + /// + /// Convert a string to a byte array using + /// + /// The applicable general purpose bits flags + /// + /// String to convert to an array + /// + /// Converted array + public static byte[] ConvertToArray(int flags, string str) + => (string.IsNullOrEmpty(str)) + ? Empty.Array() + : EncodingFromFlag(flags).GetBytes(str); + } +} diff --git a/常用工具集/Utility/Network/EIP/CIPHelper.cs b/常用工具集/Utility/Network/EIP/CIPHelper.cs new file mode 100644 index 0000000..a250baf --- /dev/null +++ b/常用工具集/Utility/Network/EIP/CIPHelper.cs @@ -0,0 +1,587 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace 欧姆龙EIP调试.EIP +{ + /// + /// CIP通信工具类 + /// + public class CIPHelper + { + /// + /// IP地址 + /// + public string IpAddress { get; set; } + /// + /// 通信端口号 + /// + public int Port { get; set; } + + public bool IsConnected + { + get + { + if (client == null) + return false; + return client.Connected; + } + } + + + private Socket client; + public int SessionHandle = 0; + + /// + /// 默认无参构造 + /// + public CIPHelper() + { + + } + + + + /// + /// 构造方法,传递通信IP与端口 + /// + public CIPHelper(string ip, int port) + { + this.IpAddress = ip; + this.Port = port; + } + + /// + /// 建立连接 + /// + public void Connect() + { + //连接 + try + { + client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + client.Connect(new IPEndPoint(IPAddress.Parse(IpAddress), Port)); + } + catch (Exception ex) + { + + } + } + + + /// + /// 注册会话 + /// + public bool RegisterSession() + { + //注册会话 + if (client != null && client.Connected) + { + byte[] registerSessionBytes = { (byte)0x65, (byte)0x00, (byte)0x04, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 }; + client.Send(registerSessionBytes); + Thread.Sleep(20); + byte[] rev = new byte[1024]; + try + { + int length = client.Receive(rev); + byte[] data = new byte[length]; + Array.Copy(rev, 0, data, 0, length); + if (data[0] == 0x65) + return GetSessionHandle(data); + } + catch + { + return false; + } + } + return false; + } + + /// + /// 读取标签值 + /// + /// + /// + public object ReadTag(string tag) + { + if (client == null) + return null; + if (!client.Connected) + return null; + if (SessionHandle == 0) + return null; + byte[] d = GetReadTagBytes(tag); + client.Send(d); + Thread.Sleep(20); + byte[] rev = new byte[1024]; + try + { + int length = client.Receive(rev); + byte[] data = new byte[length]; + Array.Copy(rev, 0, data, 0, length); + if (data[0] == 0x6f) + return ReadTagVal(data); + return null; + } + catch + { + return null; + } + } + + public bool WriteTag(string tag, object value) + { + if (client == null) + return false; + if (!client.Connected) + return false; + if (SessionHandle == 0) + return false; + byte[] d = GetWriteTagBytes(tag, value); + client.Send(d); + Thread.Sleep(20); + byte[] rev = new byte[1024]; + try + { + int length = client.Receive(rev); + byte[] data = new byte[length]; + Array.Copy(rev, 0, data, 0, length); + if (data[0] == 0x6f) + return WriteTagVal(data); + return false; + } + catch + { + return false; + } + } + + + + /// + /// 注销会话 + /// + /// + public bool UnRegisterSession() + { + if (client == null) + return false; + if (!client.Connected) + return false; + if (SessionHandle == 0) + return false; + List UnregisterSession = new List(); + UnregisterSession.AddRange(new List { (byte)0x66, (byte)0x00, (byte)0x00, (byte)0x00 }); + UnregisterSession.AddRange(BitConverter.GetBytes(SessionHandle)); + UnregisterSession.AddRange(new List { (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00 }); + try + { + client.Send(UnregisterSession.ToArray()); + return true; + } + catch + { + return false; + } + } + + + + /// + /// 断开连接 + /// + public void DisConnect() + { + + if (client == null) + return; + //断开 + try + { + if (IsConnected) + { + client.Close(); + } + client = null; + SessionHandle = 0; + } + catch (Exception ex) + { + client = null; + SessionHandle = 0; + } + } + + + #region Private + + /// + /// 解析写标签数据 + /// + /// + /// + private bool WriteTagVal(byte[] data) + { + int byte_index = 0; + short var1 = BitConverter.ToInt16(data, 0);//命令码 + short var2 = BitConverter.ToInt16(data, 2);//去除Hearder后的长度 + if ((var2 + 24) != data.Length)//24是Hearder封装头的长度 封装头长度+后面报文长度 应该等于总长度 否则报文错误 + { + return false; + } + int sessionHandle = BitConverter.ToInt32(data, 4); + if (sessionHandle != this.SessionHandle) + { + //如果会话句柄不一致 返回 + return false; + } + int state = BitConverter.ToInt32(data, 8); + if (state != 0) + { + //会话状态不正确 + return false; + } + //00 00 00 00 01 00 02 00 00 00 00 00 B2 00 08 00 CC 00 00 00 C1 00 00 00 + //发送方描述 + byte_index = 12; //8 + //选项 4字节 + byte_index = 20; + //接口句柄 4字节 + byte_index = 24; + //超时 2字节 + byte_index = 28; + //项数 2字节 + byte_index = 30; + //连接的地址项 2字节 + byte_index = 32; + //连接地址项长度 2字节 + byte_index = 34; + //未连接数据项 2字节 + byte_index = 36; + //连接长度 2字节 + byte_index = 38; + //数据开始 + byte_index = 40; + for (int i = byte_index; i < data.Length; i++) + { + if (data[i] != 0xCD) + continue; + //填充字节 1字节 + if (BitConverter.ToInt16(data, i + 2) != 0x00) + { + return true; + } + return false; + } + return false; + } + + /// + /// 解析读标签数据 + /// + /// + /// + private object ReadTagVal(byte[] data) + { + int byte_index = 0; + short var1 = BitConverter.ToInt16(data, 0);//命令码 + short var2 = BitConverter.ToInt16(data, 2);//去除Hearder后的长度 + if ((var2 + 24) != data.Length)//24是Hearder封装头的长度 封装头长度+后面报文长度 应该等于总长度 否则报文错误 + { + return null; + } + int sessionHandle = BitConverter.ToInt32(data, 4); + if (sessionHandle != this.SessionHandle) + { + //如果会话句柄不一致 返回 + return null; + } + int state = BitConverter.ToInt32(data, 8); + if (state != 0) + { + //会话状态不正确 + return null; + } + //发送方描述 + byte_index = 12; //8 + //选项 4字节 + byte_index = 20; + //接口句柄 4字节 + byte_index = 24; + //超时 2字节 + byte_index = 28; + //项数 2字节 + byte_index = 30; + //连接的地址项 2字节 + byte_index = 32; + //连接地址项长度 2字节 + byte_index = 34; + //未连接数据项 2字节 + byte_index = 36; + //连接长度 2字节 + byte_index = 38; + //数据开始 + byte_index = 40; + for (int i = byte_index; i < data.Length; i++) + { + if (data[i] != 0xCC) + continue; + //服务标识 0xCC + //填充字节 1字节 + if (BitConverter.ToInt16(data, i + 2) != 0x00) // data[var4] != 0x00 || data[var4 + 1] != 0x00) + { + return null; + } + //数据类型 + short dataType = BitConverter.ToInt16(data, i + 4); + if (dataType == 0x00C1)//BOOL + { + bool value = Convert.ToBoolean(((short)data[i + 6] & 0x00ff) | ((short)data[i + 7] << 8)); + return value; + } + if (dataType == 0x00D1)//byte + { + byte value = data[i + 6]; + return value; + } + if (dataType == 0x00C3)//int + { + short value = BitConverter.ToInt16(data, i + 6);// ((short)data[i+6] & 0x00ff) | ((short)data[i+7] << 8); + return value; + } + if (dataType == 0x00C4)//long型 + { + int value = BitConverter.ToInt32(data, i + 6); + return value; + } + if (dataType == 0x00CA)//float + { + float value = BitConverter.ToSingle(data, i + 6); + return value; + } + } + return null; + } + + + /// + /// 写标签报文数据 + /// + /// + /// + /// + private byte[] GetWriteTagBytes(string tag, object value) + { + byte[] TagStringToAscii; + byte[] Var4 = Encoding.Default.GetBytes(tag); + if (Var4.Length % 2 == 0) + { + TagStringToAscii = new byte[Var4.Length]; + for (int i = 0; i < Var4.Length; i++) + { + TagStringToAscii[i] = Var4[i]; + } + } + else + { + TagStringToAscii = new byte[Var4.Length + 1]; + for (int i = 0; i < Var4.Length; i++) + { + TagStringToAscii[i] = Var4[i]; + } + TagStringToAscii[Var4.Length] = 0x00; + } + + + //CIP协议数据 ================================================= + List Cip_Msg = new List(); + Cip_Msg.Add((byte)0x4D);//服务标识 + Cip_Msg.Add((byte)((TagStringToAscii.Length + 2) / 2));//CIP长度多少字 从标识开始 大读取标签长度结束 + Cip_Msg.Add((byte)0x91);//固定 + Cip_Msg.Add((byte)TagStringToAscii.Length);//PLC标签长度 多少个字节 + Cip_Msg.AddRange(TagStringToAscii);//添加标签 + //根据类型 + if (value is bool) + { + Cip_Msg.AddRange(new byte[] { 0xC1, 0x00 }); + if ((bool)value) + { + Cip_Msg.AddRange(new byte[] { 0x01, 0x00 }); + } + else + { + Cip_Msg.AddRange(new byte[] { 0x00, 0x00 }); + } + } + else if (value is byte) + { + Cip_Msg.AddRange(new byte[] { 0xD1, 0x00 }); + Cip_Msg.Add((byte)value); + } + else if (value is short) + { + Cip_Msg.AddRange(new byte[] { 0xC3, 0x00 }); + Cip_Msg.AddRange(BitConverter.GetBytes((short)value)); + } + else if (value is int) + { + Cip_Msg.AddRange(new byte[] { 0xC4, 0x00 }); + Cip_Msg.AddRange(BitConverter.GetBytes((int)value)); + } + else if (value is float) + { + Cip_Msg.AddRange(new byte[] { 0xCA, 0x00 }); + Cip_Msg.AddRange(BitConverter.GetBytes((float)value)); + } + List Slot = new List { (byte)0x01, (byte)0x00, (byte)0x01, (byte)0x00 }; //槽号 + List Cip_Msg0 = new List(); + Cip_Msg0.Add((byte)0x52);//命令 + Cip_Msg0.Add((byte)0X02);//请求路径长度 + Cip_Msg0.AddRange(new byte[] { 0x20, 0x06, 0x24, 0x01 });//默认请求路径 + Cip_Msg0.AddRange(new byte[] { 0x0A, 0xF0 });//默认超时 + Cip_Msg0.AddRange(BitConverter.GetBytes((short)Cip_Msg.Count)); //后面报文长度 不包含PLC槽号 + Cip_Msg0.AddRange(Cip_Msg);//添加CIP报文 + + List C_S_D = new List(); + C_S_D.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00 });//接口句柄 CIP + C_S_D.AddRange(new byte[] { 0x01, 0x00 });//超时时间 默认 + C_S_D.AddRange(new byte[] { 0x02, 0x00 });//项数 默认 + C_S_D.AddRange(new byte[] { 0x00, 0x00 });//空地址项 默认 + C_S_D.AddRange(new byte[] { 0x00, 0x00 });//空地址长度 默认 + C_S_D.AddRange(new byte[] { 0xB2, 0x00 });//未连接项 默认 + C_S_D.AddRange(BitConverter.GetBytes((short)(Cip_Msg0.Count + Slot.Count)));//CIP报文包的长度 包括槽号 + + + List Header = new List(); + Header.AddRange(new byte[] { 0x6F, 0x00 });//命令码 + Header.AddRange(BitConverter.GetBytes((short)(Cip_Msg0.Count + C_S_D.Count + Slot.Count)));//后面报文的长度 特定命令数据 和 CIP报文长度 和PLC槽号 + Header.AddRange(BitConverter.GetBytes(SessionHandle));//添加会话句柄 + Header.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00 });//添加状态 + Header.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); //发送方描述 + Header.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00 });//选项默认 + + + + List EtherNet_IP_CIP_MSG = new List();//单标签读取完整报文 + EtherNet_IP_CIP_MSG.AddRange(Header);//添加Header 封装头 + EtherNet_IP_CIP_MSG.AddRange(C_S_D);//添加特定命令数据 + EtherNet_IP_CIP_MSG.AddRange(Cip_Msg0);//添加CIP报文 + EtherNet_IP_CIP_MSG.AddRange(Slot);//添加槽号 + return EtherNet_IP_CIP_MSG.ToArray(); + + } + /// + /// 读标签报文数据 + /// + /// + /// + private byte[] GetReadTagBytes(string tag) + { + byte[] TagStringToAscii; + byte[] Var4 = Encoding.Default.GetBytes(tag); + if (Var4.Length % 2 == 0) + { + TagStringToAscii = new byte[Var4.Length]; + for (int i = 0; i < Var4.Length; i++) + { + TagStringToAscii[i] = Var4[i]; + } + } + else + { + TagStringToAscii = new byte[Var4.Length + 1]; + for (int i = 0; i < Var4.Length; i++) + { + TagStringToAscii[i] = Var4[i]; + } + TagStringToAscii[Var4.Length] = 0x00; + } + + + //CIP协议数据 ================================================= + List Cip_Msg = new List(); + Cip_Msg.Add((byte)0x4C);//服务标识 + Cip_Msg.Add((byte)((TagStringToAscii.Length + 2) / 2));//CIP长度多少字 从标识开始 大读取标签长度结束 + Cip_Msg.Add((byte)0x91);//固定 + Cip_Msg.Add((byte)TagStringToAscii.Length);//PLC标签长度 多少个字节 + Cip_Msg.AddRange(TagStringToAscii);//添加标签 + Cip_Msg.Add((byte)0x01); + Cip_Msg.Add((byte)0x00); // 读取长度 + + List Slot = new List { (byte)0x01, (byte)0x00, (byte)0x01, (byte)0x00 }; //槽号 + + List Cip_Msg0 = new List(); + Cip_Msg0.Add((byte)0x52);//命令 + Cip_Msg0.Add((byte)0X02);//请求路径长度 + Cip_Msg0.AddRange(new byte[] { 0x20, 0x06, 0x24, 0x01 });//默认请求路径 + Cip_Msg0.AddRange(new byte[] { 0x0A, 0xF0 });//默认超时 + Cip_Msg0.AddRange(BitConverter.GetBytes((short)Cip_Msg.Count)); //后面报文长度 不包含PLC槽号 + Cip_Msg0.AddRange(Cip_Msg);//添加CIP报文 + + List C_S_D = new List(); + C_S_D.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00 });//接口句柄 CIP + C_S_D.AddRange(new byte[] { 0x01, 0x00 });//超时时间 默认 + C_S_D.AddRange(new byte[] { 0x02, 0x00 });//项数 默认 + C_S_D.AddRange(new byte[] { 0x00, 0x00 });//空地址项 默认 + C_S_D.AddRange(new byte[] { 0x00, 0x00 });//空地址长度 默认 + C_S_D.AddRange(new byte[] { 0xB2, 0x00 });//未连接项 默认 + C_S_D.AddRange(BitConverter.GetBytes((short)(Cip_Msg0.Count + Slot.Count)));//CIP报文包的长度 包括槽号 + + + List Header = new List(); + Header.Add((byte)0x6F); Header.Add((byte)0x00);//命令码 + Header.AddRange(BitConverter.GetBytes((short)(Cip_Msg0.Count + C_S_D.Count + Slot.Count)));//后面报文的长度 特定命令数据 和 CIP报文长度 和PLC槽号 + Header.AddRange(BitConverter.GetBytes(SessionHandle));//添加会话句柄 + Header.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00 });//添加状态 + Header.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); //发送方描述 + Header.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00 });//选项默认 + + + + List EtherNet_IP_CIP_MSG = new List();//单标签读取完整报文 + EtherNet_IP_CIP_MSG.AddRange(Header);//添加Header 封装头 + EtherNet_IP_CIP_MSG.AddRange(C_S_D);//添加特定命令数据 + EtherNet_IP_CIP_MSG.AddRange(Cip_Msg0);//添加CIP报文 + EtherNet_IP_CIP_MSG.AddRange(Slot);//添加槽号 + return EtherNet_IP_CIP_MSG.ToArray(); + } + + + /// + /// 注册会话结果解析 + /// + /// + /// + private bool GetSessionHandle(byte[] data) + { + //short sessionLength = BitConverter.ToInt16(data, 2);// (Convert.ToInt16(data[3]) * 256) | Convert.ToInt16(data[2]); + //byte[] var1 = new byte[4]; + //for (int i = 0; i < 4; i++) + //{ + // var1[i] = data[sessionLength + 4 + i]; + //} + int state = BitConverter.ToInt32(data, 8); + if (state == 0)//判断报文状态 + { + SessionHandle = BitConverter.ToInt32(data, 4); + //SessionHandle = new byte[sessionLength]; + //for (int i = 0; i < sessionLength; i++) + //{ + // SessionHandle[i] = data[4 + i]; + //} + return true; + } + else + { + return false; + } + } + #endregion + } +} diff --git a/常用工具集/Utility/Network/FtpHelper.cs b/常用工具集/Utility/Network/FtpHelper.cs new file mode 100644 index 0000000..f94ba01 --- /dev/null +++ b/常用工具集/Utility/Network/FtpHelper.cs @@ -0,0 +1,601 @@ +using System; +using System.Collections.Generic; +using System.Drawing.Printing; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Web; + +namespace 常用工具集.Utility.Network +{ + + public class FtpHelper + { + private string ip; + private int port = 21; + private string ftpUserId; + private string ftpPassword; + private Encoding encoding; + private string FtpUrl + { + get + { + return $"ftp://{ip}:{port}/"; + } + } + + + public FtpHelper(string ip) : this(ip, 21) { } + + public FtpHelper(string ip, int port) : this(ip, 21, Encoding.GetEncoding("GB2312")) { } + /// + /// 匿名 + /// + /// + public FtpHelper(string ip, Encoding encoding) : this(ip, 21, encoding) { } + + public FtpHelper(string ip, int port, Encoding encoding) : this(ip, port, "anonymous", "", encoding) { } + + + public FtpHelper(string ip, string userName, string password) : this(ip, 21, userName, password) { } + + public FtpHelper(string ip, int port, string userName, string password) : this(ip, port, userName, password, Encoding.UTF8) { } + /// + /// 指定用户名密码 + /// + /// + /// + /// + + public FtpHelper(string ip, string userName, string password, Encoding encoding) : this(ip, 21, userName, password, encoding) { } + + /// + /// 指定用户名密码 + /// + /// + /// + /// + + public FtpHelper(string ip, int port, string userName, string password, Encoding encoding) + { + this.ip = ip; + this.port = port; + this.ftpUserId = userName; + this.ftpPassword = password; + this.encoding = encoding; + } + + + + /// + /// 获得文件的最后更新时间 + /// + /// + /// + /// + /// + /// + public DateTime GetFileLastModified(string ftpPath) + { + try + { + string url = Uri.EscapeUriString(FtpUrl + ftpPath); + FtpWebRequest request = null; + request = (FtpWebRequest)WebRequest.Create(new Uri(url)); + request.UseBinary = true; + request.Credentials = new NetworkCredential(ftpUserId, ftpPassword); + request.Method = WebRequestMethods.Ftp.GetDateTimestamp; + FtpWebResponse response = (FtpWebResponse)request.GetResponse(); + DateTime dt = response.LastModified; + response.Close(); + response = null; + request = null; + return dt; + } + catch (Exception ex) + { + Console.WriteLine("FtpUtil.GetFileLastModified 获得文件的最后更新时间 错误"); + throw ex; + } + + } + + /// + /// 列出ftp目录下的所有文件 + /// + /// + /// + /// + /// + public List GetList(string ftpPath) + { + List fileList = GetFileDetail(ftpPath); + List list = new List(); + try + { + string url = Uri.EscapeUriString(FtpUrl + ftpPath); + FtpWebRequest request = null; + request = (FtpWebRequest)WebRequest.Create(new Uri(url)); + request.Credentials = new NetworkCredential(ftpUserId, ftpPassword); + request.Method = WebRequestMethods.Ftp.ListDirectory; + FtpWebResponse response = (FtpWebResponse)request.GetResponse(); + Stream responseStream = response.GetResponseStream(); + MemoryStream stream = new MemoryStream(); + byte[] bArr = new byte[1024 * 1024]; + int size = responseStream.Read(bArr, 0, (int)bArr.Length); + while (size > 0) + { + stream.Write(bArr, 0, size); + size = responseStream.Read(bArr, 0, (int)bArr.Length); + } + stream.Seek(0, SeekOrigin.Begin); + responseStream.Close(); + + string str = encoding.GetString(stream.ToArray()); + foreach (string line in str.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries)) + { + if (line == "." || line == "..") + continue; + string detail = fileList.Where(it => it.EndsWith(line)).FirstOrDefault(); + if (detail == null || detail.Length == 0) + { + continue; + + } + if (detail.ToUpper().Contains("") || detail.ToUpper().Contains("DR")) + { + list.Add(new FtpFileInfo { IsDirectory = true, Name = line }); + } + else + { + list.Add(new FtpFileInfo { IsDirectory = false, Name = line }); + } + } + return list.OrderByDescending(it => it.IsDirectory).ToList(); + } + catch (Exception ex) + { + Console.WriteLine("FtpUtil.GetFile 列出ftp目录下的所有文件 错误"); + throw ex; + } + + } + + public List GetFileDetail(string ftpPath) + { + List list = new List(); + try + { + string url = Uri.EscapeUriString(FtpUrl + ftpPath); + + FtpWebRequest request = null; + request = (FtpWebRequest)WebRequest.Create(new Uri(url)); + request.Credentials = new NetworkCredential(ftpUserId, ftpPassword); + request.Method = WebRequestMethods.Ftp.ListDirectoryDetails; + FtpWebResponse response = (FtpWebResponse)request.GetResponse(); + Stream responseStream = response.GetResponseStream(); + MemoryStream stream = new MemoryStream(); + byte[] bArr = new byte[1024 * 1024]; + int size = responseStream.Read(bArr, 0, (int)bArr.Length); + while (size > 0) + { + stream.Write(bArr, 0, size); + size = responseStream.Read(bArr, 0, (int)bArr.Length); + } + stream.Seek(0, SeekOrigin.Begin); + responseStream.Close(); + + string str = encoding.GetString(stream.ToArray()); + foreach (string line in str.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries)) + { + list.Add(line); + } + return list; + } + catch (Exception ex) + { + Console.WriteLine("FtpUtil.GetFile 列出ftp目录下的所有文件 错误"); + throw ex; + } + } + + /// + /// 判断是否是文件 + /// + /// + /// + /// + /// + /// + public bool IsFileExist(string ftpPath, string fileName) + { + List list = GetList(ftpPath); + if (list.Any(it => !it.IsDirectory && it.Name == fileName)) + { + return true; + } + return false; + } + + + #region 文件夹是否存在 + /// + /// 文件夹是否存在 + /// + /// + /// + /// + public bool FolderExists(string ftpPath, string folderName) + { + List list = GetList(ftpPath); + if (list.Any(it => it.IsDirectory && it.Name == folderName)) + { + return true; + } + return false; + } + #endregion + + #region 创建文件夹 + /// + /// 创建文件夹 + /// + /// ftp路径 + /// 文件夹名称 + public bool CreateFolder(string ftpPath) + { + try + { + string url = Uri.EscapeUriString(FtpUrl + ftpPath); + FtpWebRequest request = null; + request = (FtpWebRequest)WebRequest.Create(new Uri(url)); + request.Credentials = new NetworkCredential(ftpUserId, ftpPassword); + request.Method = "MKD"; + ((FtpWebResponse)request.GetResponse()).Close(); + return true; + } + catch (Exception ex) + { + return false; + } + } + #endregion + + #region 文件是否存在 + /// + /// 文件是否存在 + /// + /// ftp路径 + /// 用户名 + /// 密码 + /// 文件相对路径 + public bool FileExists(string ftpPath) + { + try + { + string url = Uri.EscapeUriString(FtpUrl + ftpPath); + FtpWebRequest request = null; + string folderName = Path.GetDirectoryName(ftpPath); + string fileName = Path.GetFileName(ftpPath); + request = (FtpWebRequest)WebRequest.Create(new Uri(url)); + request.Credentials = new NetworkCredential(ftpUserId, ftpPassword); + request.Method = "LIST"; + FtpWebResponse response = (FtpWebResponse)request.GetResponse(); + Stream responseStream = response.GetResponseStream(); + MemoryStream stream = new MemoryStream(); + byte[] bArr = new byte[1024 * 1024]; + int size = responseStream.Read(bArr, 0, (int)bArr.Length); + while (size > 0) + { + stream.Write(bArr, 0, size); + size = responseStream.Read(bArr, 0, (int)bArr.Length); + } + stream.Seek(0, SeekOrigin.Begin); + responseStream.Close(); + + string str = encoding.GetString(stream.ToArray()); + foreach (string line in str.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries)) + { + if (!line.ToUpper().Contains("DIR") && !line.ToUpper().Contains("DR")) + { + int pos = line.LastIndexOf(" "); + string strFileName = line.Substring(pos).Trim(); + if (strFileName == fileName) + { + return true; + } + } + } + } + catch (Exception ex) + { + Console.WriteLine("FtpUtil.FileExists 判断文件是否存在 错误"); + throw ex; + } + + return false; + } + #endregion + + #region 下载文件 + /// + /// 下载文件 + /// + /// ftp路径 + /// 用户名 + /// 密码 + /// 文件相对路径 + public MemoryStream DownloadFile(string ftpPath) + { + try + { + string url = Uri.EscapeUriString(FtpUrl + ftpPath); + FtpWebRequest request = null; + request = (FtpWebRequest)WebRequest.Create(new Uri(url)); + request.Credentials = new NetworkCredential(ftpUserId, ftpPassword); + request.Method = "RETR"; + FtpWebResponse response = (FtpWebResponse)request.GetResponse(); + Stream responseStream = response.GetResponseStream(); + MemoryStream stream = new MemoryStream(); + byte[] bArr = new byte[1024 * 1024]; + int size = responseStream.Read(bArr, 0, (int)bArr.Length); + while (size > 0) + { + stream.Write(bArr, 0, size); + size = responseStream.Read(bArr, 0, (int)bArr.Length); + } + stream.Seek(0, SeekOrigin.Begin); + responseStream.Close(); + + + return stream; + } + catch (Exception ex) + { + Console.WriteLine("FtpUtil.DownloadFile 下载文件 错误"); + throw ex; + } + } + + + /// + /// 下载文件 + /// + /// ftp路径 + /// 用户名 + /// 密码 + /// 文件相对路径 + public void DownloadFile(string ftpPath, string localFile) + { + try + { + string url = Uri.EscapeUriString(FtpUrl + ftpPath); + FtpWebRequest request = null; + request = (FtpWebRequest)WebRequest.Create(new Uri(url)); + request.Credentials = new NetworkCredential(ftpUserId, ftpPassword); + request.Method = "RETR"; + FtpWebResponse response = (FtpWebResponse)request.GetResponse(); + Stream responseStream = response.GetResponseStream(); + FileStream stream = new FileStream(localFile, FileMode.OpenOrCreate, FileAccess.Write); + byte[] bArr = new byte[1024 * 1024]; + int size = responseStream.Read(bArr, 0, (int)bArr.Length); + while (size > 0) + { + stream.Write(bArr, 0, size); + size = responseStream.Read(bArr, 0, (int)bArr.Length); + } + stream.Seek(0, SeekOrigin.Begin); + responseStream.Close(); + stream.Dispose(); + } + catch (Exception ex) + { + Console.WriteLine("FtpUtil.DownloadFile 下载文件 错误"); + throw ex; + } + } + #endregion + + #region 异步下载文件 + /// + /// 异步下载文件 + /// + /// ftp路径 + public async Task DownloadFileAsync(string ftpPath) + { + try + { + string url = Uri.EscapeUriString(FtpUrl + ftpPath); + FtpWebRequest request = null; + request = (FtpWebRequest)WebRequest.Create(new Uri(url)); + request.Credentials = new NetworkCredential(ftpUserId, ftpPassword); + request.Method = "RETR"; + FtpWebResponse response = (FtpWebResponse)(await request.GetResponseAsync()); + Stream responseStream = response.GetResponseStream(); + MemoryStream stream = new MemoryStream(); + byte[] bArr = new byte[1024 * 1024]; + int size = await responseStream.ReadAsync(bArr, 0, (int)bArr.Length); + while (size > 0) + { + stream.Write(bArr, 0, size); + size = await responseStream.ReadAsync(bArr, 0, (int)bArr.Length); + } + stream.Seek(0, SeekOrigin.Begin); + responseStream.Close(); + + return stream; + } + catch (Exception ex) + { + Console.WriteLine("FtpUtil.DownloadFileAsync 异步下载文件 错误"); + throw ex; + } + } + #endregion + + #region 上传文件 + /// + /// 上传文件 + /// + /// ftp路径 + /// 文件相对路径 + /// 文件内容 + public bool UploadFile(string ftpPath, byte[] bArr) + { + try + { + string url = Uri.EscapeUriString(FtpUrl + ftpPath); + FtpWebRequest request = null; + request = (FtpWebRequest)WebRequest.Create(new Uri(url)); + request.Credentials = new NetworkCredential(ftpUserId, ftpPassword); + request.Method = "STOR"; + Stream postStream = request.GetRequestStream(); + int pageSize = 1024 * 1024; + int index = 0; + while (index < bArr.Length) + { + if (bArr.Length - index > pageSize) + { + postStream.Write(bArr, index, pageSize); + index += pageSize; + } + else + { + postStream.Write(bArr, index, bArr.Length - index); + index = index + (bArr.Length - index); + } + } + postStream.Close(); + FtpWebResponse response = (FtpWebResponse)request.GetResponse(); + Stream responseStream = response.GetResponseStream(); + StreamReader sr = new StreamReader(responseStream, Encoding.UTF8); + string result = sr.ReadToEnd(); + responseStream.Close(); + return true; + } + catch (Exception ex) + { + return false; + } + } + + /// + /// 上传文件 + /// + /// 本地文件 + /// 远端路径 + /// + public bool UploadFile(string localFilePath, string ftpPath) + { + try + { + string url = Uri.EscapeUriString(FtpUrl + ftpPath); + byte[] bytes = new byte[1024 * 1024]; + FtpWebRequest request = null; + request = (FtpWebRequest)WebRequest.Create(new Uri(url)); + request.Credentials = new NetworkCredential(ftpUserId, ftpPassword); + request.Method = "STOR"; + + FileStream stream = new FileStream(localFilePath, FileMode.Open); + + Stream postStream = request.GetRequestStream(); + while (true) + { + int count = stream.Read(bytes, 0, bytes.Length); + if (count == 0) + break; + postStream.Write(bytes, 0, count); + } + postStream.Close(); + FtpWebResponse response = (FtpWebResponse)request.GetResponse(); + Stream responseStream = response.GetResponseStream(); + StreamReader sr = new StreamReader(responseStream, Encoding.UTF8); + string result = sr.ReadToEnd(); + responseStream.Close(); + return true; + } + catch (Exception ex) + { + return false; + } + } + #endregion + + #region 删除文件 + /// + /// 删除文件 + /// + /// ftp路径 + /// 用户名 + /// 密码 + /// 文件相对路径 + public bool DeleteFile(string ftpPath) + { + try + { + string url = Uri.EscapeUriString(FtpUrl + ftpPath); + FtpWebRequest request = null; + request = (FtpWebRequest)WebRequest.Create(new Uri(url)); + request.Credentials = new NetworkCredential(ftpUserId, ftpPassword); + request.Method = WebRequestMethods.Ftp.DeleteFile; + ((FtpWebResponse)request.GetResponse()).Close(); + return true; + } + catch (Exception ex) + { + throw ex; + } + } + + public bool Rename(string ftpPath, string newName) + { + try + { + string url = Uri.EscapeUriString(FtpUrl + ftpPath); + FtpWebRequest request = null; + request = (FtpWebRequest)WebRequest.Create(new Uri(url)); + request.Credentials = new NetworkCredential(ftpUserId, ftpPassword); + request.Method = WebRequestMethods.Ftp.Rename; + request.RenameTo = newName; + ((FtpWebResponse)request.GetResponse()).Close(); + return true; + } + catch (Exception ex) + { + throw ex; + } + + } + + public bool DeleteDirectory(string ftpPath) + { + try + { + string url = Uri.EscapeUriString(FtpUrl + ftpPath); + if (ftpPath.EndsWith("/")) + ftpPath = ftpPath.Substring(0, ftpPath.Length - 1); + FtpWebRequest request = null; + request = (FtpWebRequest)WebRequest.Create(new Uri(url)); + request.Credentials = new NetworkCredential(ftpUserId, ftpPassword); + request.Method = WebRequestMethods.Ftp.RemoveDirectory; + ((FtpWebResponse)request.GetResponse()).Close(); + return true; + } + catch (Exception ex) + { + throw ex; + } + } + #endregion + } + + + public class FtpFileInfo + { + public bool IsDirectory { get; set; } + public string Name { get; set; } + } +} diff --git a/常用工具集/Utility/Network/HttpUtils.cs b/常用工具集/Utility/Network/HttpUtils.cs new file mode 100644 index 0000000..e4827e5 --- /dev/null +++ b/常用工具集/Utility/Network/HttpUtils.cs @@ -0,0 +1,321 @@ +using MES.Utility.Core; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; + +namespace 常用工具集.Utility.Network +{ + public class HttpUtils + { + public static bool DoGetFile(string url, string filePath, int? timeout) + { + var task = DoGetFileAsync(url, filePath); + if (timeout != null) + { + if (!task.Wait(timeout.Value)) + { + return false; + } + } + return task.Result; + } + + public static byte[] DoGetFile(string url, int? timeout) + { + var task = DoGetFileAsync(url); + if (timeout != null) + { + if (!task.Wait(timeout.Value)) + { + return null; + } + } + return task.Result; + } + + public static async Task DoGetFileAsync(string url, string filePath) + { + try + { + var httpClient = new HttpClient(); + var response = await httpClient.GetAsync(url); + var fileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write); + await response.Content.CopyToAsync(fileStream); + fileStream.Close(); + return true; + } + catch + { + return false; + } + } + + public static async Task DoGetFileAsync(string url) + { + try + { + var httpClient = new HttpClient(); + var response = await httpClient.GetAsync(url); + MemoryStream memoryStream = new MemoryStream(); + return await response.Content.ReadAsByteArrayAsync(); + } + catch + { + return null; + } + } + + /// + /// 通过Get请求数据 + /// + /// + /// + /// + /// + public static string DoGet(string url, Dictionary parametersDict, int? timeout) + { + var task = DoGetAsync(url, parametersDict); + if (timeout != null) + { + if (!task.Wait(timeout.Value)) + { + return null; + } + } + return task.Result; + } + + /// + /// 通过Get请求数据 + /// + /// + /// + /// + public static async Task DoGetAsync(string url, Dictionary parametersDict) + { + try + { + if (!url.Contains("?")) + url = url + "?"; + foreach (string key in parametersDict.Keys) + { + url = url + key + "=" + parametersDict[key] + "&"; + } + url = url.Substring(0, url.Length - 1); + var httpClient = new HttpClient(); + var response = await httpClient.GetAsync(url); + string str= await response.Content.ReadAsStringAsync(); + return str; + } + catch + { + return string.Empty; + } + } + + /// + /// 通过Post上传文件:multipart/form-data + /// + /// + /// + /// + /// + /// + public static string DoPostFile(string url, Dictionary parms, Dictionary fileParms, int? timeout) + { + var task = DoPostFileAsync(url, parms, fileParms); + if (timeout != null) + { + if (!task.Wait(timeout.Value)) + { + return null; + } + } + return task.Result; + } + + /// + /// 通过Post上传文件:multipart/form-data + /// + /// + /// + /// + /// + public static async Task DoPostFileAsync(string url, Dictionary parms, Dictionary fileParms) + { + try + { + var httpClient = new HttpClient(); + var content = new MultipartFormDataContent(); + if (parms != null) + { + foreach (KeyValuePair keyValue in parms) + { + content.Add(new StringContent(keyValue.Value), keyValue.Key); + } + } + if (fileParms != null) + { + foreach (KeyValuePair keyValue in fileParms) + { + string filePath = keyValue.Value; + string fileName = Path.GetFileName(filePath); + if (!File.Exists(filePath)) + { + continue; + } + content.Add(new ByteArrayContent(File.ReadAllBytes(filePath)), keyValue.Key, fileName); + } + } + var response = await httpClient.PostAsync(url, content); + return await response.Content.ReadAsStringAsync(); + } + catch (Exception ex) + { + return string.Empty; + } + } + + /// + /// 通过Post请求数据: application/x-www-form-urlencoded + /// + /// + /// + /// + /// + public static string DoPostForm(string url, Dictionary parms, int? timeout) + { + var task = DoPostFormAsync(url, parms); + if (timeout != null) + { + if (!task.Wait(timeout.Value)) + { + return null; + } + } + return task.Result; + } + + /// + /// 通过Post请求数据: application/x-www-form-urlencoded + /// + /// + /// + /// + public static async Task DoPostFormAsync(string url, Dictionary parms) + { + try + { + var httpClient = new HttpClient(); + FormUrlEncodedContent formData; + if (parms != null) + { + formData = new FormUrlEncodedContent(parms); + } + else + { + formData = new FormUrlEncodedContent(new Dictionary()); + } + var response = await httpClient.PostAsync(url, formData); + return await response.Content.ReadAsStringAsync(); + + } + catch (Exception ex) + { + return string.Empty; + } + } + + + /// + /// 通过Post请求数据: application/x-www-form-urlencoded + /// + /// + /// + /// + /// + public static string DoPostJson(string url, string json, int? timeout) + { + var task = DoPostJsonAsync(url, json); + if (timeout != null) + { + if (!task.Wait(timeout.Value)) + { + return null; + } + } + return task.Result; + } + + /// + /// 通过Post请求数据: application/x-www-form-urlencoded + /// + /// + /// + /// + public static async Task DoPostJsonAsync(string url, string json) + { + try + { + var httpClient = new HttpClient(); + StringContent jsonContent = new StringContent(json, Encoding.UTF8, "application/json"); + var response = await httpClient.PostAsync(url, jsonContent); + return await response.Content.ReadAsStringAsync(); + + } + catch (Exception ex) + { + return string.Empty; + } + } + + + /// + /// 通过Post请求数据: application/x-www-form-urlencoded + /// + /// + /// + /// + /// + public static string DoPostData(string url, object data, int? timeout) + { + var task = DoPostDataAsync(url, data); + if (timeout != null) + { + if (!task.Wait(timeout.Value)) + { + return null; + } + } + return task.Result; + } + + /// + /// 通过Post请求数据: application/x-www-form-urlencoded + /// + /// + /// + /// + public static async Task DoPostDataAsync(string url, object obj) + { + try + { + var httpClient = new HttpClient(); + StringContent jsonContent = new StringContent(obj.ToJson(), Encoding.UTF8, "application/json"); + var response = await httpClient.PostAsync(url, jsonContent); + return await response.Content.ReadAsStringAsync(); + + } + catch (Exception ex) + { + return string.Empty; + } + } + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/Mitsubishi/MC3EServer.cs b/常用工具集/Utility/Network/Mitsubishi/MC3EServer.cs new file mode 100644 index 0000000..61c3537 --- /dev/null +++ b/常用工具集/Utility/Network/Mitsubishi/MC3EServer.cs @@ -0,0 +1,234 @@ +using McProtocol.Mitsubishi; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace 常用工具集.Utility.Network.Mitsubishi +{ + internal class MC3EServer + { + public delegate void DataChanged(int address); + public delegate void LogChanged(string message); + public event DataChanged DataChangedEvent; + public event LogChanged LogChangedEvent; + + private TcpListener _listener; + private bool _isRunning; + public static ushort[] _dataStorage = new ushort[65535]; // 初始化数据存储区; // 65535个地址数据,类型为ushort + + + public MC3EServer(int port) + { + _listener = new TcpListener(IPAddress.Any, port); + + } + + public void Start() + { + new Thread(() => + { + _isRunning = true; + _listener.Start(); + LogChangedEvent?.Invoke("MC-3E Protocol Server started..."); + + while (_isRunning) + { + try + { + TcpClient client = _listener.AcceptTcpClient(); + Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClient)); + clientThread.Start(client); + } + catch (SocketException) + { + continue; + } + catch (ThreadInterruptedException) + { + break; + } + } + }).Start(); + + } + public void Stop() + { + _isRunning = false; + _listener.Stop(); + } + + private void HandleClient(object obj) + { + TcpClient client = (TcpClient)obj; + try + { + NetworkStream stream = client.GetStream(); + + byte[] buffer = new byte[1024]; + int bytesRead; + + while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0) + { + LogChangedEvent?.Invoke($"Received {bytesRead} bytes from client."); + // 解析MC-3E协议请求 + byte[] response = ProcessMc3ERequest(buffer, bytesRead, out bool write, out int address); + // 发送响应 + stream.Write(response, 0, response.Length); + if (write) Task.Run(() => DataChangedEvent?.Invoke(address)); + } + + } + catch (Exception ex) + { + LogChangedEvent?.Invoke(ex.Message); + } + client.Close(); + } + private byte[] ProcessMc3ERequest(byte[] request, int length, out bool write, out int address) + { + // MC-3E协议帧格式示例: + // 子头 (2字节) + 网络编号 (1字节) + PLC编号 (1字节) + 请求目标模块IO编号 (2字节) + 请求目标模块站号 (1字节) + // + 请求数据长度 (2字节) + 请求数据 (N字节) + // 解析请求数据 + if (length < 8) + { + LogChangedEvent?.Invoke($"Invalid request length."); + write = false; + address = 0; + return BuildErrorResponse(); + } + McFrame mcFrame = new McFrame(); + mcFrame.Frame = BitConverter.ToUInt16(request, 0); // 假设前两字节为帧类型 0x0050 80 + mcFrame.NetworkNumber = request[2]; // 假设第3字节为网络编号 + mcFrame.PcNumber = request[3]; // 假设第4字节为PLC编号 + mcFrame.IoNumber = BitConverter.ToUInt16(request, 4); // 假设第5-6字节为模块IO编号 + mcFrame.ChannelNumber = request[6]; // 假设第7字节为模块站号 + int dataLength = BitConverter.ToUInt16(request, 7); // 假设第8-9字节为数据长度 + mcFrame.CpuTimer = BitConverter.ToUInt16(request, 9); // 假设第10-11字节为CPU监视定时器 + int mainCommand = BitConverter.ToUInt16(request, 11); // 假设第12-13字节为主命令 + int subCommand = BitConverter.ToUInt16(request, 13); // 假设第14-15字节为子命令 + mcFrame.Address = request[17] << 16 | request[16] << 8 | request[15]; + mcFrame.Type = (PlcDeviceType)request[18]; + mcFrame.Size = BitConverter.ToUInt16(request, 19); + byte[] data = new byte[mcFrame.Size * 2]; + for (int i = 0; i < data.Length; i++) + { + data[i] = request[21 + i]; + } + mcFrame.Data = data; + // 处理读请求 + if (mainCommand == 0x0401) //读命令 + { + LogChangedEvent?.Invoke($"Read request: Address={mcFrame.Address},Type={mcFrame.Type} Count={mcFrame.Size}"); + write = false; + address = 0; + return HandleReadRequest(mcFrame); + } + // 处理写请求 + else if (mainCommand == 0x1401) // 写命令 + { + LogChangedEvent?.Invoke($"Write request: Address={mcFrame.Address},Type={mcFrame.Type} Count={mcFrame.Size}"); + write = true; + address = mcFrame.Address; + return HandleWriteRequest(mcFrame); ; + } + else + { + LogChangedEvent?.Invoke("Unknown command."); + write = false; + address = 0; + return BuildErrorResponse(); + } + } + + class McFrame + { + public ushort Frame { get; set; } // 头部 + public byte NetworkNumber { get; set; } // 网络号码 + public byte PcNumber { get; set; } // 电脑号码 + public ushort IoNumber { get; set; } // 请求单元 I/O 编号/O番号 + public byte ChannelNumber { get; set; } // 请求单位站号 + public ushort CpuTimer { get; set; } // 中央处理器监控计时器 + public int Address { get; set; } // 地址 + public PlcDeviceType Type { get; set; } // 类型 + + public uint Size { get; set; } // 大小 + public byte[] Data { get; set; } + } + + private byte[] HandleReadRequest(McFrame mcFrame) + { + // 读取数据 + List responseData = new List(); // 每个ushort占2字节 + for (int i = 0; i < mcFrame.Size; i++) + { + byte[] valueBytes = BitConverter.GetBytes(_dataStorage[mcFrame.Address + i]); + responseData.AddRange(valueBytes); + } + // 构建响应帧 + return BuildSuccessResponse(mcFrame, responseData.ToArray()); + } + + private byte[] HandleWriteRequest(McFrame mcFrame) + { + for (int i = 0; i < mcFrame.Size; i++) + { + _dataStorage[mcFrame.Address + i] = BitConverter.ToUInt16(mcFrame.Data, i * 2); + } + LogChangedEvent?.Invoke($"Write successful: Address={mcFrame.Address}, Length={mcFrame.Size}"); + return BuildSuccessResponse(mcFrame, new byte[0]); // 写入成功,返回空响应 + } + + private byte[] BuildSuccessResponse(McFrame mcFrame, byte[] responseData) + { + // 构建成功响应帧 + List response = new List(); + mcFrame.Frame = 0x00D0; + response.AddRange(BitConverter.GetBytes(mcFrame.Frame));//0 1 + response.Add(mcFrame.NetworkNumber); //2 + response.Add(mcFrame.PcNumber); //3 + response.AddRange(BitConverter.GetBytes(mcFrame.IoNumber)); //4 5 + response.Add(mcFrame.ChannelNumber);//6 + //response.AddRange(BitConverter.GetBytes(mcFrame.CpuTimer)); + //数据长度 + response.AddRange(BitConverter.GetBytes((short)(responseData.Length + 2)));//7 8 + response.AddRange(BitConverter.GetBytes((short)0));//9 10结束符号 + response.AddRange(responseData); + return response.ToArray(); + } + + private byte[] BuildErrorResponse() + { + // 构建错误响应帧 + byte[] response = new byte[8]; + + // 子头 (2字节) + response[0] = 0xD0; + response[1] = 0x00; + + // 网络编号 (1字节) + response[2] = 0x00; + + // PLC编号 (1字节) + response[3] = 0xFF; + + // 模块IO编号 (2字节) + response[4] = 0x00; + response[5] = 0x00; + + // 模块站号 (1字节) + response[6] = 0x00; + + // 响应数据长度 (2字节) + response[7] = 0x00; + response[8] = 0x00; + + return response; + } + } +} diff --git a/常用工具集/Utility/Network/Mitsubishi/McHelper.cs b/常用工具集/Utility/Network/Mitsubishi/McHelper.cs new file mode 100644 index 0000000..6db3131 --- /dev/null +++ b/常用工具集/Utility/Network/Mitsubishi/McHelper.cs @@ -0,0 +1,512 @@ +using McProtocol.Mitsubishi; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace McProtocol +{ + /// + /// MC协议通信帮助类 + /// + public class McHelper : IDisposable + { + private McProtocolTcp plc; + + /// + /// 构造方法 + /// + /// + /// + public McHelper(string ip, int port) + { + plc = new McProtocolTcp(ip, port, McFrame.MC3E); + } + public McHelper(string ip, int port, McFrame mcFrame) + { + plc = new McProtocolTcp(ip, port, mcFrame); + } + + /// + /// 建立连接 + /// + /// + public bool Connect() + { + try + { + plc.Open(); + return true; + } + catch (Exception ex) + { + return false; + } + } + + /// + /// 退出后销毁 + /// + public void Dispose() + { + plc.Dispose(); + } + + + #region Int16读写操作 + /// + /// 读取Int16数据 + /// + /// 地址:例如 D10 + /// 返回数据 + /// 读取成功还是失败 + public bool ReadInt16(string address, out short value) + { + + short[] values = new short[1]; + bool flag = ReadInts16(address, 1, out values); + if (!flag) + { + value = 0; + return false; + } + value = values[0]; + return true; + + } + + /// + /// 读取Int16数据 + /// + /// 地址:例如 D10 + /// 读取长度 + /// 返回数据 + /// 读取成功还是失败 + public bool ReadInts16(string address, int count, out short[] values) + { + try + { + //地址解析 + values = plc.ReadInt16(address, count); + return true; + } + catch (Exception ex) + { + values = null; + return false; + } + } + + /// + /// 写入数据 + /// + /// 地址D100 + /// 写入数据 + /// 写入成功还是失败 + public bool WriteInt16(string address, short value) + { + short[] values = new short[1]; + values[0] = value; + return WriteInts16(address, values); + + } + + /// + /// 写入数据 + /// + /// 地址D100 + /// 写入数据 + /// 写入成功还是失败 + public bool WriteInts16(string address, short[] values) + { + try + { + plc.WriteInt16(address, values); + return true; + } + catch (Exception ex) + { + return false; + } + } + #endregion + + #region UInt16读写操作 + /// + /// 读取UInt16数据 + /// + /// 例如 D10 + /// 返回数据 + /// 读取成功还是失败 + public bool ReadUInt16(string address, out ushort value) + { + ushort[] values = new ushort[1]; + bool flag = ReadUInts16(address, 1, out values); + if (!flag) + { + value = 0; + return false; + } + value = values[0]; + return true; + } + + + /// + /// 读取UInt16数据 + /// + /// 例如 D10 + /// 读取数量 + /// 返回数据 + /// 读取成功还是失败 + public bool ReadUInts16(string address, int count, out ushort[] values) + { + try + { + values = plc.ReadUInt16(address, count); + return true; + } + catch (Exception ex) + { + values = null; + return false; + } + } + + /// + /// 写入数据 + /// + /// 地址D100 + /// 写入数据 + /// 写入成功还是失败 + public bool WriteUInt16(string address, ushort value) + { + + ushort[] values = new ushort[1]; + values[0] = value; + return WriteUInts16(address, values); + } + + /// + /// 写入数据 + /// + /// 地址D100 + /// 写入数据 + /// 写入成功还是失败 + public bool WriteUInts16(string address, ushort[] values) + { + try + { + plc.WriteUInt16(address, values); + return true; + } + catch (Exception ex) + { + return false; + } + } + #endregion + + #region Int32读写操作 + /// + /// 读取Int32 + /// + /// 例如 D10 + /// 返回数据 + /// 读取成功还是失败 + public bool ReadInt32(string address, out int value) + { + int[] values; + bool flag = ReadInts32(address, 1, out values); + if (!flag) + { + value = 0; + return false; + } + value = values[0]; + return true; + + } + + + /// + /// 读取Int32 + /// + /// 例如 D10 + /// 读取数量 + /// 返回数据 + /// 读取成功还是失败 + public bool ReadInts32(string address, int count, out int[] values) + { + try + { + //地址解析 + values = plc.ReadInt32(address, count); + return true; + } + catch (Exception ex) + { + values = null; + return false; + } + } + + /// + /// 写入数据 + /// + /// 地址D100 + /// 写入数据 + /// 写入成功还是失败 + public bool WriteInt32(string address, int value) + { + int[] values = new int[1]; + values[0] = value; + return WriteInts32(address, values); + } + /// + /// 写入数据 + /// + /// 地址D100 + /// 写入数据 + /// 写入成功还是失败 + public bool WriteInts32(string address, int[] values) + { + try + { + plc.WriteInt32(address, values); + return true; + } + catch (Exception ex) + { + return false; + } + } + #endregion + + #region UInt32读写操作 + /// + /// 读取UInt32 + /// + /// 例如 D10 + /// 返回数据 + /// 读取成功还是失败 + public bool ReadUInt32(string address, out uint value) + { + + uint[] values = new uint[1]; + bool flag = ReadUInts32(address, 1, out values); + if (!flag) + { + value = 0; + return false; + } + value = values[0]; + return true; + + } + /// + /// 读取UInt32 + /// + /// 例如 D10 + /// 读取数量 + /// 返回数据 + /// 读取成功还是失败 + public bool ReadUInts32(string address, int count, out uint[] values) + { + try + { + //地址解析 + values = plc.ReadUInt32(address, count); + return true; + } + catch (Exception ex) + { + values = null; + return false; + } + } + + /// + /// 写入数据 + /// + /// 地址D100 + /// 写入数据 + /// 写入成功还是失败 + public bool WriteUInt32(string address, uint value) + { + + uint[] values = new uint[1]; + values[0] = value; + return WriteUInts32(address, values); + + } + + /// + /// 写入数据 + /// + /// 地址D100 + /// 写入数据 + /// 写入成功还是失败 + public bool WriteUInts32(string address, uint[] values) + { + try + { + plc.WriteUInt32(address, values); + return true; + } + catch (Exception ex) + { + return false; + } + } + #endregion + + #region Float读写操作 + /// + /// 读取Float + /// + /// 例如 D10 + /// 返回数据 + /// 读取成功还是失败 + public bool ReadFloat(string address, out float value) + { + float[] values; + bool flag = ReadFloats(address, 1, out values); + if (!flag) + { + value = 0; + return false; + } + value = values[0]; + return true; + } + /// + /// 读取Float + /// + /// 例如 D10 + /// 读取数量 + /// 返回数据 + /// 读取成功还是失败 + public bool ReadFloats(string address, int count, out float[] values) + { + try + { + //地址解析 + values = plc.ReadFloat(address, count); + return true; + } + catch (Exception ex) + { + values = null; + return false; + } + } + + /// + /// 写入数据 + /// + /// 地址D100 + /// 写入数据 + /// 写入成功还是失败 + public bool WriteFloat(string address, float value) + { + float[] values = new float[1]; + values[0] = value; + return WriteFloats(address, values); + } + + /// + /// 写入数据 + /// + /// 地址D100 + /// 写入数据 + /// 写入成功还是失败 + public bool WriteFloats(string address, float[] values) + { + try + { + plc.WriteFloat(address, values); + return true; + } + catch (Exception ex) + { + return false; + } + } + #endregion + + #region String读写操作 + /// + /// 读取字符串 + /// + /// 地址,例如:D3200 + /// 此处为地址长度,例50,表示字符串长度100 + /// 读到的字符串 + /// 返回读取成功还是失败 + public bool ReadString(string address, int length, out string str) + { + try + { + ushort[] value = new ushort[length]; + byte[] values = plc.ReadDeviceBlock(address, length, value); + List bytes = new List(); + foreach (var item in value) + { + bytes.AddRange(BitConverter.GetBytes(item)); + } + str = Encoding.GetEncoding("GB2312").GetString(bytes.ToArray()); + return true; + } + catch (Exception ex) + { + str = string.Empty; + return false; + } + } + + /// + /// 写入字符串 + /// + /// 地址,例如:D3200 + /// 此处为地址长度,例50,表示字符串长度100 + /// 写入的字符串 + /// 返回写入成功还是失败 + public bool WriteString(string address, int length, string str) + { + try + { + byte[] Bytes = new byte[length * 2]; + byte[] bytes = Encoding.GetEncoding("GB2312").GetBytes(str); + if (bytes.Length > Bytes.Length) + { + for (int i = 0; i < Bytes.Length; i++) + { + Bytes[i] = bytes[i]; + } + } + else + { + for (int i = 0; i < bytes.Length; i++) + { + Bytes[i] = bytes[i]; + } + } + ushort[] value = new ushort[length]; + for (int i = 0; i < length; i++) + { + value[i] = BitConverter.ToUInt16(Bytes, i * 2); + } + plc.WriteDeviceBlock(address, value); + return true; + } + catch (Exception ex) + { + return false; + } + } + #endregion + + + + } +} diff --git a/常用工具集/Utility/Network/Mitsubishi/McProtocolTcp.cs b/常用工具集/Utility/Network/Mitsubishi/McProtocolTcp.cs new file mode 100644 index 0000000..e34c39a --- /dev/null +++ b/常用工具集/Utility/Network/Mitsubishi/McProtocolTcp.cs @@ -0,0 +1,1391 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using static EasyModbus.TCPHandler; + +namespace McProtocol.Mitsubishi +{ + public class McProtocolTcp + { + /// + /// 使用的指令框架 + /// + public McFrame CommandFrame { get; set; } + private McCommand Command { get; set; } + /// + /// 主机名或者IP地址 + /// + public string HostName { get; set; } + /// + /// 端口号 + /// + public int PortNumber { get; set; } + public int Device { private set; get; } + + private const int BlockSize = 0x0010; + private TcpClient Client { get; set; } + private NetworkStream Stream { get; set; } + + // 是否连接 + public bool Connected + { + get + { + return Client.Connected; + } + } + + /// + /// 构造函数 + /// + public McProtocolTcp() : this("", 0, McFrame.MC3E) + { + + } + + /// + /// 构造函数 + /// + /// + /// + /// + public McProtocolTcp(string iHostName, int iPortNumber, McFrame frame) + { + CommandFrame = frame; + Client = new TcpClient(); + + //C70 = MC3E + HostName = iHostName; + PortNumber = iPortNumber; + } + + /// + /// 后处理 + /// + public void Dispose() + { + TcpClient c = Client; + if (c.Connected) + { + c.Close(); + } + } + + public void Open() + { + if (!Client.Connected) + { + // 实现Keep Alive功能 + var ka = new List(sizeof(uint) * 3); + ka.AddRange(BitConverter.GetBytes(1u)); + ka.AddRange(BitConverter.GetBytes(45000u)); + ka.AddRange(BitConverter.GetBytes(5000u)); + Client.Client.IOControl(IOControlCode.KeepAliveValues, ka.ToArray(), null); + + IAsyncResult asyncResult = Client.BeginConnect(HostName, PortNumber, null, null); + if (!asyncResult.AsyncWaitHandle.WaitOne(1000)) + { + throw new Exception("连接超时"); + } + Client.EndConnect(asyncResult); + Stream = Client.GetStream(); + } + Command = new McCommand(CommandFrame); + } + + + public int SetBitDevice(string iDeviceName, int iSize, int[] iData) + { + PlcDeviceType type; + int addr; + GetDeviceCode(iDeviceName, out type, out addr); + return SetBitDevice(type, addr, iSize, iData); + } + + public int SetBitDevice(PlcDeviceType iType, int iAddress, int iSize, int[] iData) + { + var type = iType; + var addr = iAddress; + var data = new List(6) { (byte)addr, (byte)(addr >> 8), (byte)(addr >> 16), (byte)type, (byte)iSize, (byte)(iSize >> 8) }; + var d = (byte)iData[0]; + var i = 0; + while (i < iData.Length) + { + if (i % 2 == 0) + { + d = (byte)iData[i]; + d <<= 4; + } + else + { + d |= (byte)(iData[i] & 0x01); + data.Add(d); + } + ++i; + } + if (i % 2 != 0) + { + data.Add(d); + } + int length = (int)Command.FrameType;// == McFrame.MC3E) ? 11 : 15; + byte[] sdCommand = Command.SetCommandMC3E(0x1401, 0x0001, data.ToArray()); + byte[] rtResponse = TryExecution(sdCommand, length); + int rtCode = Command.SetResponse(rtResponse); + return rtCode; + } + + public int GetBitDevice(string iDeviceName, int iSize, int[] oData) + { + PlcDeviceType type; + int addr; + GetDeviceCode(iDeviceName, out type, out addr); + return GetBitDevice(type, addr, iSize, oData); + } + + public int GetBitDevice(PlcDeviceType iType, int iAddress, int iSize, int[] oData) + { + + PlcDeviceType type = iType; + int addr = iAddress; + var data = new List(6) { (byte)addr, (byte)(addr >> 8), (byte)(addr >> 16), (byte)type, (byte)iSize, (byte)(iSize >> 8) }; + byte[] sdCommand = Command.SetCommandMC3E(0x0401, 0x0001, data.ToArray()); + int length = (Command.FrameType == McFrame.MC3E) ? 11 : 15; + byte[] rtResponse = TryExecution(sdCommand, length); + int rtCode = Command.SetResponse(rtResponse); + byte[] rtData = Command.Response; + for (int i = 0; i < iSize; ++i) + { + if (i % 2 == 0) + { + oData[i] = (rtCode == 0) ? ((rtData[i / 2] >> 4) & 0x01) : 0; + } + else + { + oData[i] = (rtCode == 0) ? (rtData[i / 2] & 0x01) : 0; + } + } + return rtCode; + } + + public int WriteDeviceBlock(string iDeviceName, int iSize, ushort[] iData) + { + PlcDeviceType type; + int addr; + GetDeviceCode(iDeviceName, out type, out addr); + return WriteDeviceBlock(type, addr, iSize, iData); + } + + public int WriteDeviceBlock(PlcDeviceType iType, int iAddress, int iSize, ushort[] iData) + { + PlcDeviceType type = iType; + int addr = iAddress; + List data; + List DeviceData = new List(); + foreach (ushort t in iData) + { + byte[] value = BitConverter.GetBytes(t); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(value); + } + DeviceData.AddRange(value); + } + byte[] sdCommand; + int length; + switch (CommandFrame) + { + case McFrame.MC3E: + data = new List(6) { (byte)addr, (byte)(addr >> 8), (byte)(addr >> 16), (byte)type, (byte)iSize, (byte)(iSize >> 8) }; + data.AddRange(DeviceData.ToArray()); + sdCommand = Command.SetCommandMC3E(0x1401, 0x0000, data.ToArray()); + length = 11; + break; + case McFrame.MC4E: + data = new List(6) { (byte)addr, (byte)(addr >> 8), (byte)(addr >> 16), (byte)type, (byte)iSize, (byte)(iSize >> 8) }; + data.AddRange(DeviceData.ToArray()); + sdCommand = Command.SetCommandMC4E(0x1401, 0x0000, data.ToArray()); + length = 15; + break; + case McFrame.MC1E: + data = new List(6) { (byte)addr, (byte)(addr >> 8), (byte)(addr >> 16), (byte)(addr >> 24), 0x20, 0x44, (byte)iSize, 0x00 }; + data.AddRange(DeviceData.ToArray()); + //Add data + sdCommand = Command.SetCommandMC1E(0x03, data.ToArray()); + length = 2; + break; + default: + throw new Exception("Message frame not supported"); + } + + //TEST take care of the writing + byte[] rtResponse = TryExecution(sdCommand, length); + int rtCode = Command.SetResponse(rtResponse); + return rtCode; + } + + public byte[] ReadDeviceBlock(string iDeviceName, int iSize, ushort[] oData) + { + PlcDeviceType type; + int addr; + GetDeviceCode(iDeviceName, out type, out addr); + return ReadDeviceBlock(type, addr, iSize, oData); + } + + public byte[] ReadDeviceBlock(PlcDeviceType iType, int iAddress, int iSize, ushort[] oData) + { + + PlcDeviceType type = iType; + int addr = iAddress; + List data; + byte[] sdCommand; + int length; + + switch (CommandFrame) + { + case McFrame.MC3E: + data = new List(6) { (byte)addr, (byte)(addr >> 8), (byte)(addr >> 16), (byte)type, (byte)iSize, (byte)(iSize >> 8) }; + sdCommand = Command.SetCommandMC3E(0x0401, 0x0000, data.ToArray()); + length = 11; + break; + case McFrame.MC4E: + data = new List(6) { (byte)addr, (byte)(addr >> 8), (byte)(addr >> 16), (byte)type, (byte)iSize, (byte)(iSize >> 8) }; + sdCommand = Command.SetCommandMC4E(0x0401, 0x0000, data.ToArray()); + length = 15; + break; + case McFrame.MC1E: + data = new List(6) { (byte)addr, (byte)(addr >> 8), (byte)(addr >> 16), (byte)(addr >> 24), 0x20, 0x44, (byte)iSize, 0x00 }; + sdCommand = Command.SetCommandMC1E(0x01, data.ToArray()); + length = 2; + break; + default: + throw new Exception("Message frame not supported"); + } + + byte[] rtResponse = TryExecution(sdCommand, length); + int rtCode = Command.SetResponse(rtResponse); + byte[] rtData = Command.Response; + for (int i = 0; i < iSize; ++i) + { + if (rtCode == 0) + { + oData[i] = (ushort)(rtData[i * 2] << 8 | rtData[i * 2 + 1]); + } + //oData[i] = (rtCode == 0) ? BitConverter.ToUInt16(rtData, i * 2) : 0; + } + return rtData; + } + + public int SetDevice(string iDeviceName, int iData) + { + PlcDeviceType type; + int addr; + GetDeviceCode(iDeviceName, out type, out addr); + return SetDevice(type, addr, iData); + } + + public int SetDevice(PlcDeviceType iType, int iAddress, int iData) + { + PlcDeviceType type = iType; + int addr = iAddress; + var data = new List(6) { (byte)addr, (byte)(addr >> 8), (byte)(addr >> 16), (byte)type, 0x01, 0x00, (byte)iData, (byte)(iData >> 8) }; + byte[] sdCommand = Command.SetCommandMC3E(0x1401, 0x0000, data.ToArray()); + int length = (Command.FrameType == McFrame.MC3E) ? 11 : 15; + byte[] rtResponse = TryExecution(sdCommand, length); + int rtCode = Command.SetResponse(rtResponse); + return rtCode; + } + + public int GetDevice(string iDeviceName) + { + PlcDeviceType type; + int addr; + GetDeviceCode(iDeviceName, out type, out addr); + return GetDevice(type, addr); + } + + public int GetDevice(PlcDeviceType iType, int iAddress) + { + PlcDeviceType type = iType; + int addr = iAddress; + var data = new List(6) { (byte)addr, (byte)(addr >> 8), (byte)(addr >> 16), (byte)type, 0x01, 0x00 }; + byte[] sdCommand = Command.SetCommandMC3E(0x0401, 0x0000, data.ToArray()); + int length = (Command.FrameType == McFrame.MC3E) ? 11 : 15; + byte[] rtResponse = TryExecution(sdCommand, length); + int rtCode = Command.SetResponse(rtResponse); + if (0 < rtCode) + { + this.Device = 0; + } + else + { + byte[] rtData = Command.Response; + this.Device = BitConverter.ToInt16(rtData, 0); + } + return rtCode; + } + + #region IPlcEx + /// + /// 得到字节数据 + /// + /// 如:M1000 + /// 读取长度:>0 + /// 数据 + public int[] GetBitDevice(string iDeviceName, int iSize = 1) + { + int[] oData = new int[iSize]; + GetBitDevice(iDeviceName, iSize, oData); + return oData; + } + /// + /// 得到字节数据 + /// + /// 设备类型,如:M + /// 如:M1000 + /// 读取长度:>0 + /// 数据 + public int[] GetBitDevice(PlcDeviceType iType, int iAddress, int iSize = 1) + { + int[] oData = new int[iSize]; + GetBitDevice(iType, iAddress, iSize, oData); + return oData; + } + /// + /// 设置字节数据 + /// + /// 如:M1000 + /// 设置的数据 + public void SetBitDevice(string iDeviceName, params int[] iData) + { + SetBitDevice(iDeviceName, iData.Length, iData); + } + /// + /// 设置字节数据 + /// + /// 设备类型,如:M + /// 如:M1000 + /// 设置的数据 + public void SetBitDevice(PlcDeviceType iType, int iAddress, params int[] iData) + { + SetBitDevice(iType, iAddress, iData.Length, iData); + } + /// + /// 得到数据块 + /// + /// 如:D1000 + /// 读取长度:>0 + /// 数据 + public ushort[] ReadDeviceBlock(string iDeviceName, int iSize = 1) + { + ushort[] oData = new ushort[iSize]; + ReadDeviceBlock(iDeviceName, iSize, oData); + return oData; + } + /// + /// 得到数据块 + /// + /// 设备类型,如:D + /// 如:D1000 + /// 读取长度:>0 + /// 数据 + public ushort[] ReadDeviceBlock(PlcDeviceType iType, int iAddress, int iSize = 1) + { + ushort[] oData = new ushort[iSize]; + ReadDeviceBlock(iType, iAddress, iSize, oData); + return oData; + } + /// + /// 写入数据块 + /// + /// 如:D1000 + /// 写入10进制数据 + public void WriteDeviceBlock(string iDeviceName, params ushort[] iData) + { + WriteDeviceBlock(iDeviceName, iData.Length, iData); + } + /// + /// 写入数据块 + /// + /// 设备类型,如:D + /// 如:D1000 + /// 写入10进制数据 + public void WriteDeviceBlock(PlcDeviceType iType, int iAddress, params ushort[] iData) + { + WriteDeviceBlock(iType, iAddress, iData.Length, iData); + } + #endregion + + //public int GetCpuType(out string oCpuName, out int oCpuType) + //{ + // int rtCode = Command.Execute(0x0101, 0x0000, new byte[0]); + // oCpuName = "dummy"; + // oCpuType = 0; + // return rtCode; + //} + + public PlcDeviceType GetDeviceType(string s) + { + return (s == "M") ? PlcDeviceType.M : + (s == "SM") ? PlcDeviceType.SM : + (s == "L") ? PlcDeviceType.L : + (s == "F") ? PlcDeviceType.F : + (s == "V") ? PlcDeviceType.V : + (s == "S") ? PlcDeviceType.S : + (s == "X") ? PlcDeviceType.X : + (s == "Y") ? PlcDeviceType.Y : + (s == "B") ? PlcDeviceType.B : + (s == "SB") ? PlcDeviceType.SB : + (s == "DX") ? PlcDeviceType.DX : + (s == "DY") ? PlcDeviceType.DY : + (s == "D") ? PlcDeviceType.D : + (s == "SD") ? PlcDeviceType.SD : + (s == "R") ? PlcDeviceType.R : + (s == "ZR") ? PlcDeviceType.ZR : + (s == "W") ? PlcDeviceType.W : + (s == "SW") ? PlcDeviceType.SW : + (s == "TC") ? PlcDeviceType.TC : + (s == "TS") ? PlcDeviceType.TS : + (s == "TN") ? PlcDeviceType.TN : + (s == "CC") ? PlcDeviceType.CC : + (s == "CS") ? PlcDeviceType.CS : + (s == "CN") ? PlcDeviceType.CN : + (s == "SC") ? PlcDeviceType.SC : + (s == "SS") ? PlcDeviceType.SS : + (s == "SN") ? PlcDeviceType.SN : + (s == "Z") ? PlcDeviceType.Z : + (s == "TT") ? PlcDeviceType.TT : + (s == "TM") ? PlcDeviceType.TM : + (s == "CT") ? PlcDeviceType.CT : + (s == "CM") ? PlcDeviceType.CM : + (s == "A") ? PlcDeviceType.A : + PlcDeviceType.Max; + } + + public static bool IsBitDevice(PlcDeviceType type) + { + return !((type == PlcDeviceType.D) || (type == PlcDeviceType.SD) || (type == PlcDeviceType.Z) || (type == PlcDeviceType.ZR) || (type == PlcDeviceType.R) || (type == PlcDeviceType.W)); + } + + public bool IsHexDevice(PlcDeviceType type) + { + return (type == PlcDeviceType.X) || (type == PlcDeviceType.Y) || (type == PlcDeviceType.B) || (type == PlcDeviceType.W); + } + + public void GetDeviceCode(string iDeviceName, out PlcDeviceType oType, out int oAddress) + { + string s = iDeviceName.ToUpper(); + string strAddress; + + // 1.取出一个字符 + string strType = s.Substring(0, 1); + switch (strType) + { + case "A": + case "B": + case "D": + case "F": + case "L": + case "M": + case "R": + case "V": + case "W": + case "X": + case "Y": + // 2字母后面,它应该是一个数字,所以转换它 + strAddress = s.Substring(1); + break; + case "Z": + // 再拿出一个字符 + strType = s.Substring(0, 2); + // 对于文件寄存器 : 2 + // 对于索引寄存器 : 1 + strAddress = s.Substring(strType.Equals("ZR") ? 2 : 1); + break; + case "C": + // 再拿出一个字符 + strType = s.Substring(0, 2); + switch (strType) + { + case "CC": + case "CM": + case "CN": + case "CS": + case "CT": + strAddress = s.Substring(2); + break; + default: + throw new Exception("Invalid format."); + } + break; + case "S": + // 再拿出一个字符 + strType = s.Substring(0, 2); + switch (strType) + { + case "SD": + case "SM": + strAddress = s.Substring(2); + break; + default: + throw new Exception("Invalid format."); + } + break; + case "T": + // 再拿出一个字符 + strType = s.Substring(0, 2); + switch (strType) + { + case "TC": + case "TM": + case "TN": + case "TS": + case "TT": + strAddress = s.Substring(2); + break; + default: + throw new Exception("Invalid format."); + } + break; + default: + throw new Exception("Invalid format."); + } + oType = GetDeviceType(strType); + oAddress = IsHexDevice(oType) ? Convert.ToInt32(strAddress, BlockSize) : Convert.ToInt32(strAddress); + } + + + private byte[] TryExecution(byte[] iCommand, int minlength) + { + byte[] rtResponse; + int tCount = 10; + do + { + rtResponse = Execute(iCommand); + --tCount; + if (tCount < 0) + { + throw new Exception("无法从 PLC 获取正确的值."); + } + } while (Command.IsIncorrectResponse(rtResponse, minlength)); + return rtResponse; + } + + // 表示用于通信的命令的内部类 + class McCommand + { + public McFrame FrameType { get; private set; } // 框架类型 + private uint SerialNumber { get; set; } // 序号 + private uint NetworkNumber { get; set; } // 网络号码 + private uint PcNumber { get; set; } // 电脑号码 + private uint IoNumber { get; set; } // 请求单元 I/O 编号/O番号 + private uint ChannelNumber { get; set; } // 请求单位站号 + private uint CpuTimer { get; set; } // 中央处理器监控计时器 + private int ResultCode { get; set; } // 退出代码 + public byte[] Response { get; private set; } // 响应数据 + + // 构造 函数 + public McCommand(McFrame iFrame) + { + FrameType = iFrame; + SerialNumber = 0x0001u; + NetworkNumber = 0x0000u; + PcNumber = 0x00FFu; + IoNumber = 0x03FFu; + ChannelNumber = 0x0000u; + CpuTimer = 0x0010u; + } + + public byte[] SetCommandMC1E(byte Subheader, byte[] iData) + { + List ret = new List(iData.Length + 4); + ret.Add(Subheader); + ret.Add((byte)this.PcNumber); + ret.Add((byte)CpuTimer); + ret.Add((byte)(CpuTimer >> 8)); + ret.AddRange(iData); + return ret.ToArray(); + } + public byte[] SetCommandMC3E(uint iMainCommand, uint iSubCommand, byte[] iData) + { + var dataLength = (uint)(iData.Length + 6); + List ret = new List(iData.Length + 20); + uint frame = 0x0050u; + ret.Add((byte)frame); + ret.Add((byte)(frame >> 8)); + ret.Add((byte)NetworkNumber); + ret.Add((byte)PcNumber); + ret.Add((byte)IoNumber); + ret.Add((byte)(IoNumber >> 8)); + ret.Add((byte)ChannelNumber); + ret.Add((byte)dataLength); + ret.Add((byte)(dataLength >> 8)); + ret.Add((byte)CpuTimer); + ret.Add((byte)(CpuTimer >> 8)); + ret.Add((byte)iMainCommand); + ret.Add((byte)(iMainCommand >> 8)); + ret.Add((byte)iSubCommand); + ret.Add((byte)(iSubCommand >> 8)); + + ret.AddRange(iData); + return ret.ToArray(); + } + public byte[] SetCommandMC4E(uint iMainCommand, uint iSubCommand, byte[] iData) + { + var dataLength = (uint)(iData.Length + 6); + var ret = new List(iData.Length + 20); + uint frame = 0x0054u; + ret.Add((byte)frame); + ret.Add((byte)(frame >> 8)); + ret.Add((byte)SerialNumber); + ret.Add((byte)(SerialNumber >> 8)); + ret.Add(0x00); + ret.Add(0x00); + ret.Add((byte)NetworkNumber); + ret.Add((byte)PcNumber); + ret.Add((byte)IoNumber); + ret.Add((byte)(IoNumber >> 8)); + ret.Add((byte)ChannelNumber); + ret.Add((byte)dataLength); + ret.Add((byte)(dataLength >> 8)); + ret.Add((byte)CpuTimer); + ret.Add((byte)(CpuTimer >> 8)); + ret.Add((byte)iMainCommand); + ret.Add((byte)(iMainCommand >> 8)); + ret.Add((byte)iSubCommand); + ret.Add((byte)(iSubCommand >> 8)); + ret.AddRange(iData); + return ret.ToArray(); + } + public int SetResponse(byte[] iResponse) + { + int min; + switch (FrameType) + { + case McFrame.MC1E: + min = 2; + if (min <= iResponse.Length) + { + ResultCode = (int)iResponse[min - 2]; + Response = new byte[iResponse.Length - 2]; + Buffer.BlockCopy(iResponse, min, Response, 0, Response.Length); + } + break; + case McFrame.MC3E: + min = 11; + if (min <= iResponse.Length) + { + var btCount = new[] { iResponse[min - 4], iResponse[min - 3] }; + var btCode = new[] { iResponse[min - 2], iResponse[min - 1] }; + int rsCount = BitConverter.ToUInt16(btCount, 0); + ResultCode = BitConverter.ToUInt16(btCode, 0); + Response = new byte[rsCount - 2]; + Buffer.BlockCopy(iResponse, min, Response, 0, Response.Length); + } + break; + case McFrame.MC4E: + min = 15; + if (min <= iResponse.Length) + { + var btCount = new[] { iResponse[min - 4], iResponse[min - 3] }; + var btCode = new[] { iResponse[min - 2], iResponse[min - 1] }; + int rsCount = BitConverter.ToUInt16(btCount, 0); + ResultCode = BitConverter.ToUInt16(btCode, 0); + Response = new byte[rsCount - 2]; + Buffer.BlockCopy(iResponse, min, Response, 0, Response.Length); + } + break; + default: + throw new Exception("Frame type not supported."); + + } + return ResultCode; + } + public bool IsIncorrectResponse(byte[] iResponse, int minLenght) + { + //TEST add 1E frame + switch (this.FrameType) + { + case McFrame.MC1E: return ((iResponse.Length < minLenght)); + + case McFrame.MC3E: + case McFrame.MC4E: + var btCount = new[] { iResponse[minLenght - 4], iResponse[minLenght - 3] }; + var btCode = new[] { iResponse[minLenght - 2], iResponse[minLenght - 1] }; + var rsCount = BitConverter.ToUInt16(btCount, 0) - 2; + var rsCode = BitConverter.ToUInt16(btCode, 0); + return (rsCode == 0 && rsCount != (iResponse.Length - minLenght)); + + default: throw new Exception("Type Not supported"); + + } + } + } + + private readonly object balanceLock = new object(); + protected byte[] Execute(byte[] iCommand) + { + lock (balanceLock) + { + List list = new List(); + + NetworkStream ns = Stream; + ns.Write(iCommand, 0, iCommand.Length); + ns.Flush(); + + using (var ms = new MemoryStream()) + { + var buff = new byte[256]; + do + { + int sz = ns.Read(buff, 0, buff.Length); + if (sz == 0) + { + throw new Exception("已断开连接"); + } + ms.Write(buff, 0, sz); + } + while (ns.DataAvailable); + + return ms.ToArray(); + } + } + } + + #region INT16 + public short ReadInt16(string address) + { + try + { + return ReadInt16(address, 1)[0]; + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + + public short[] ReadInt16(string address, int count = 1) + { + try + { + //地址解析 + ushort[] value = ReadDeviceBlock(address, count);//一个地址2个字节 + short[] values = new short[count]; + for (int i = 0; i < count; i++) + { + byte[] data = BitConverter.GetBytes(value[i]); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(data); + } + values[i] = BitConverter.ToInt16(data, 0); + } + return values; + + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + public void WriteInt16(string address, short value) + { + try + { + WriteInt16(address, new short[] { value }); + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + + public void WriteInt16(string address, short[] value) + { + try + { + ushort[] values = new ushort[value.Length]; + for (int i = 0; i < value.Length; i++) + { + byte[] data = BitConverter.GetBytes(value[i]); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(data); + } + values[i] = BitConverter.ToUInt16(data, 0); + } + WriteDeviceBlock(address, values); + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + #endregion + + #region UINT16 + public ushort ReadUInt16(string address) + { + try + { + return ReadUInt16(address, 1)[0]; + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + + public ushort[] ReadUInt16(string address, int count = 1) + { + try + { + //地址解析 + ushort[] value = ReadDeviceBlock(address, count);//一个地址2个字节 + ushort[] values = new ushort[count]; + for (int i = 0; i < count; i++) + { + byte[] data = BitConverter.GetBytes(value[i]); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(data); + } + values[i] = BitConverter.ToUInt16(data, 0); + } + return values; + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + public void WriteUInt16(string address, ushort value) + { + try + { + WriteUInt16(address, new ushort[] { value }); + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + + public void WriteUInt16(string address, ushort[] value) + { + try + { + ushort[] values = new ushort[value.Length]; + for (int i = 0; i < value.Length; i++) + { + byte[] data = BitConverter.GetBytes(value[i]); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(data); + } + values[i] = BitConverter.ToUInt16(data, 0); + } + WriteDeviceBlock(address, values); + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + #endregion + + #region INT32 + public int ReadInt32(string address) + { + try + { + return ReadInt32(address, 1)[0]; + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + + public int[] ReadInt32(string address, int count = 1) + { + try + { + //地址解析 + ushort[] value = ReadDeviceBlock(address, count * 2);//一个地址2个字节 + int[] values = new int[count]; + for (int i = 0; i < count; i++) + { + byte[] data1 = BitConverter.GetBytes(value[2 * i + 0]); + byte[] data2 = BitConverter.GetBytes(value[2 * i + 1]); + List list = new List(); + list.AddRange(data1); + list.AddRange(data2); + byte[] data = list.ToArray(); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(data); + } + values[i] = BitConverter.ToInt32(data, 0); + } + return values; + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + public void WriteInt32(string address, int value) + { + try + { + WriteInt32(address, new int[] { value }); + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + + public void WriteInt32(string address, int[] value) + { + try + { + List values = new List(); + for (int i = 0; i < value.Length; i++) + { + byte[] data = BitConverter.GetBytes(value[i]); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(data); + } + ushort value1 = BitConverter.ToUInt16(data, 0); + ushort value2 = BitConverter.ToUInt16(data, 2); + values.Add(value1); + values.Add(value2); + } + WriteDeviceBlock(address, values.ToArray()); + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + #endregion + + #region UINT32 + public uint ReadUInt32(string address) + { + try + { + return ReadUInt32(address, 1)[0]; + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + + public uint[] ReadUInt32(string address, int count = 1) + { + try + { + //地址解析 + ushort[] value = ReadDeviceBlock(address, count * 2);//一个地址2个字节 + uint[] values = new uint[count]; + for (int i = 0; i < count; i++) + { + byte[] data1 = BitConverter.GetBytes(value[2 * i + 0]); + byte[] data2 = BitConverter.GetBytes(value[2 * i + 1]); + List list = new List(); + list.AddRange(data1); + list.AddRange(data2); + byte[] data = list.ToArray(); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(data); + } + values[i] = BitConverter.ToUInt32(data, 0); + } + return values; + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + public void WriteUInt32(string address, uint value) + { + try + { + WriteUInt32(address, new uint[] { value }); + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + + public void WriteUInt32(string address, uint[] value) + { + try + { + List values = new List(); + for (int i = 0; i < value.Length; i++) + { + byte[] data = BitConverter.GetBytes(value[i]); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(data); + } + ushort value1 = BitConverter.ToUInt16(data, 0); + ushort value2 = BitConverter.ToUInt16(data, 2); + values.Add(value1); + values.Add(value2); + } + WriteDeviceBlock(address, values.ToArray()); + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + #endregion + + #region FLOAT + public float ReadFloat(string address) + { + try + { + return ReadFloat(address, 1)[0]; + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + + public float[] ReadFloat(string address, int count = 1) + { + try + { + //地址解析 + ushort[] value = ReadDeviceBlock(address, count * 2);//一个地址2个字节 + float[] values = new float[count]; + for (int i = 0; i < count; i++) + { + byte[] data1 = BitConverter.GetBytes(value[2 * i + 0]); + byte[] data2 = BitConverter.GetBytes(value[2 * i + 1]); + List list = new List(); + list.AddRange(data1); + list.AddRange(data2); + byte[] data = list.ToArray(); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(data); + } + values[i] = BitConverter.ToSingle(data, 0); + } + return values; + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + public void WriteFloat(string address, float value) + { + try + { + WriteFloat(address, new float[] { value }); + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + + public void WriteFloat(string address, float[] value) + { + try + { + List values = new List(); + for (int i = 0; i < value.Length; i++) + { + byte[] data = BitConverter.GetBytes(value[i]); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(data); + } + ushort value1 = BitConverter.ToUInt16(data, 0); + ushort value2 = BitConverter.ToUInt16(data, 2); + values.Add(value1); + values.Add(value2); + } + WriteDeviceBlock(address, values.ToArray()); + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + #endregion + + + #region Double + public double ReadDouble(string address) + { + try + { + return ReadDouble(address, 1)[0]; + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + + public double[] ReadDouble(string address, int count = 1) + { + try + { + //地址解析 + ushort[] value = ReadDeviceBlock(address, count * 4); + double[] values = new double[count]; + for (int i = 0; i < count; i++) + { + byte[] data1 = BitConverter.GetBytes(value[4 * i + 0]); + byte[] data2 = BitConverter.GetBytes(value[4 * i + 1]); + byte[] data3 = BitConverter.GetBytes(value[4 * i + 2]); + byte[] data4 = BitConverter.GetBytes(value[4 * i + 3]); + List list = new List(); + list.AddRange(data1); + list.AddRange(data2); + list.AddRange(data3); + list.AddRange(data4); + byte[] data = list.ToArray(); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(data); + } + values[i] = BitConverter.ToDouble(data, 0); + } + return values; + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + public void WriteDouble(string address, double value) + { + try + { + WriteDouble(address, new double[] { value }); + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + + public void WriteDouble(string address, double[] value) + { + try + { + List values = new List(); + for (int i = 0; i < value.Length; i++) + { + byte[] data = BitConverter.GetBytes(value[i]); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(data); + } + ushort value1 = BitConverter.ToUInt16(data, 0); + ushort value2 = BitConverter.ToUInt16(data, 2); + ushort value3 = BitConverter.ToUInt16(data, 4); + ushort value4 = BitConverter.ToUInt16(data, 6); + values.Add(value1); + values.Add(value2); + values.Add(value3); + values.Add(value4); + } + WriteDeviceBlock(address, values.ToArray()); + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + #endregion + + #region Int64 + public long ReadInt64(string address) + { + try + { + return ReadInt64(address, 1)[0]; + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + + public long[] ReadInt64(string address, int count = 1) + { + try + { + //地址解析 + ushort[] value = ReadDeviceBlock(address, count * 4); + long[] values = new long[count]; + for (int i = 0; i < count; i++) + { + byte[] data1 = BitConverter.GetBytes(value[4 * i + 0]); + byte[] data2 = BitConverter.GetBytes(value[4 * i + 1]); + byte[] data3 = BitConverter.GetBytes(value[4 * i + 2]); + byte[] data4 = BitConverter.GetBytes(value[4 * i + 3]); + List list = new List(); + list.AddRange(data1); + list.AddRange(data2); + list.AddRange(data3); + list.AddRange(data4); + byte[] data = list.ToArray(); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(data); + } + values[i] = BitConverter.ToInt64(data, 0); + } + return values; + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + public void WriteInt64(string address, long value) + { + try + { + WriteInt64(address, new long[] { value }); + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + + public void WriteInt64(string address, long[] value) + { + try + { + List values = new List(); + for (int i = 0; i < value.Length; i++) + { + byte[] data = BitConverter.GetBytes(value[i]); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(data); + } + ushort value1 = BitConverter.ToUInt16(data, 0); + ushort value2 = BitConverter.ToUInt16(data, 2); + ushort value3 = BitConverter.ToUInt16(data, 4); + ushort value4 = BitConverter.ToUInt16(data, 6); + values.Add(value1); + values.Add(value2); + values.Add(value3); + values.Add(value4); + } + WriteDeviceBlock(address, values.ToArray()); + } + catch (Exception e) + { + throw new Exception(e.Message); + } + } + + #endregion + + } + public enum McFrame + { + MC1E = 4, + MC3E = 11, + MC4E = 15 + } + + /// + /// 定义 PLC 设备类型的枚举 + /// + public enum PlcDeviceType + { + // PLC设备 + M = 0x90 + , SM = 0x91 + , L = 0x92 + , F = 0x93 + , V = 0x94 + , S = 0x98 + , X = 0x9C + , Y = 0x9D + , B = 0xA0 + , SB = 0xA1 + , DX = 0xA2 + , DY = 0xA3 + , D = 0xA8 + , SD = 0xA9 + , R = 0xAF + , ZR = 0xB0 + , W = 0xB4 + , SW = 0xB5 + , TC = 0xC0 + , TS = 0xC1 + , TN = 0xC2 + , CC = 0xC3 + , CS = 0xC4 + , CN = 0xC5 + , SC = 0xC6 + , SS = 0xC7 + , SN = 0xC8 + , Z = 0xCC + , TT + , TM + , CT + , CM + , A + , Max + } +} diff --git a/常用工具集/Utility/Network/Modbus/EasyModbus/Exceptions/Exceptions.cs b/常用工具集/Utility/Network/Modbus/EasyModbus/Exceptions/Exceptions.cs new file mode 100644 index 0000000..25f0d9b --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/EasyModbus/Exceptions/Exceptions.cs @@ -0,0 +1,211 @@ +/* +Copyright (c) 2018-2020 Rossmann-Engineering +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software +and associated documentation files (the "Software"), +to deal in the Software without restriction, +including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission +notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Runtime.Serialization; + +namespace EasyModbus.Exceptions +{ + /// + /// Exception to be thrown if serial port is not opened + /// + public class SerialPortNotOpenedException : ModbusException + { + public SerialPortNotOpenedException() + : base() + { + } + + public SerialPortNotOpenedException(string message) + : base(message) + { + } + + public SerialPortNotOpenedException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected SerialPortNotOpenedException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } + + /// + /// Exception to be thrown if Connection to Modbus device failed + /// + public class ConnectionException : ModbusException + { + public ConnectionException() + : base() + { + } + + public ConnectionException(string message) + : base(message) + { + } + + public ConnectionException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected ConnectionException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } + + /// + /// Exception to be thrown if Modbus Server returns error code "Function code not supported" + /// + public class FunctionCodeNotSupportedException : ModbusException + { + public FunctionCodeNotSupportedException() + : base() + { + } + + public FunctionCodeNotSupportedException(string message) + : base(message) + { + } + + public FunctionCodeNotSupportedException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected FunctionCodeNotSupportedException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } + + /// + /// Exception to be thrown if Modbus Server returns error code "quantity invalid" + /// + public class QuantityInvalidException : ModbusException + { + public QuantityInvalidException() + : base() + { + } + + public QuantityInvalidException(string message) + : base(message) + { + } + + public QuantityInvalidException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected QuantityInvalidException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } + + /// + /// Exception to be thrown if Modbus Server returns error code "starting adddress and quantity invalid" + /// + public class StartingAddressInvalidException : ModbusException + { + public StartingAddressInvalidException() + : base() + { + } + + public StartingAddressInvalidException(string message) + : base(message) + { + } + + public StartingAddressInvalidException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected StartingAddressInvalidException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } + + /// + /// Exception to be thrown if Modbus Server returns error code "Function Code not executed (0x04)" + /// + public class ModbusException : Exception + { + public ModbusException() + : base() + { + } + + public ModbusException(string message) + : base(message) + { + } + + public ModbusException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected ModbusException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } + + /// + /// Exception to be thrown if CRC Check failed + /// + public class CRCCheckFailedException : ModbusException + { + public CRCCheckFailedException() + : base() + { + } + + public CRCCheckFailedException(string message) + : base(message) + { + } + + public CRCCheckFailedException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected CRCCheckFailedException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } + +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/Modbus/EasyModbus/ModbusClient.cs b/常用工具集/Utility/Network/Modbus/EasyModbus/ModbusClient.cs new file mode 100644 index 0000000..d3b6963 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/EasyModbus/ModbusClient.cs @@ -0,0 +1,2878 @@ +/* +Copyright (c) 2018-2020 Rossmann-Engineering +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software +and associated documentation files (the "Software"), +to deal in the Software without restriction, +including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission +notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Net.Sockets; +using System.Net; +using System.IO.Ports; +using System.Reflection; +using System.Text; +using System.Collections.Generic; +using System.Threading; +namespace EasyModbus +{ + /// + /// Implements a ModbusClient. + /// + public partial class ModbusClient + { + public enum RegisterOrder { LowHigh = 0, HighLow = 1 }; + private bool debug = false; + private TcpClient tcpClient; + private string ipAddress = "127.0.0.1"; + private int port = 502; + private uint transactionIdentifierInternal = 0; + private byte[] transactionIdentifier = new byte[2]; + private byte[] protocolIdentifier = new byte[2]; + private byte[] crc = new byte[2]; + private byte[] length = new byte[2]; + private byte unitIdentifier = 0x01; + private byte functionCode; + private byte[] startingAddress = new byte[2]; + private byte[] quantity = new byte[2]; + private bool udpFlag = false; + private int portOut; + private int baudRate = 9600; + private int connectTimeout = 1000; + public byte[] receiveData; + public byte[] sendData; + private SerialPort serialport; + private Parity parity = Parity.Even; + private StopBits stopBits = StopBits.One; + private bool connected = false; + public int NumberOfRetries { get; set; } = 3; + private int countRetries = 0; + + public delegate void ReceiveDataChangedHandler(object sender); + public event ReceiveDataChangedHandler ReceiveDataChanged; + + public delegate void SendDataChangedHandler(object sender); + public event SendDataChangedHandler SendDataChanged; + + public delegate void ConnectedChangedHandler(object sender); + public event ConnectedChangedHandler ConnectedChanged; + + NetworkStream stream; + + /// + /// Constructor which determines the Master ip-address and the Master Port. + /// + /// IP-Address of the Master device + /// Listening port of the Master device (should be 502) + public ModbusClient(string ipAddress, int port) + { + if (debug) StoreLogData.Instance.Store("EasyModbus library initialized for Modbus-TCP, IPAddress: " + ipAddress + ", Port: " + port, System.DateTime.Now); +#if (!COMMERCIAL) + Console.WriteLine("EasyModbus Client Library Version: " + Assembly.GetExecutingAssembly().GetName().Version.ToString()); + Console.WriteLine("Copyright (c) Stefan Rossmann Engineering Solutions"); + Console.WriteLine(); +#endif + this.ipAddress = ipAddress; + this.port = port; + } + + /// + /// Constructor which determines the Serial-Port + /// + /// Serial-Port Name e.G. "COM1" + public ModbusClient(string serialPort) + { + if (debug) StoreLogData.Instance.Store("EasyModbus library initialized for Modbus-RTU, COM-Port: " + serialPort, System.DateTime.Now); +#if (!COMMERCIAL) + Console.WriteLine("EasyModbus Client Library Version: " + Assembly.GetExecutingAssembly().GetName().Version.ToString()); + Console.WriteLine("Copyright (c) Stefan Rossmann Engineering Solutions"); + Console.WriteLine(); +#endif + this.serialport = new SerialPort(); + serialport.PortName = serialPort; + serialport.BaudRate = baudRate; + serialport.Parity = parity; + serialport.StopBits = stopBits; + serialport.WriteTimeout = 10000; + serialport.ReadTimeout = connectTimeout; + + serialport.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler); + } + + /// + /// Parameterless constructor + /// + public ModbusClient() + { + if (debug) StoreLogData.Instance.Store("EasyModbus library initialized for Modbus-TCP", System.DateTime.Now); +#if (!COMMERCIAL) + Console.WriteLine("EasyModbus Client Library Version: " + Assembly.GetExecutingAssembly().GetName().Version.ToString()); + Console.WriteLine("Copyright (c) Stefan Rossmann Engineering Solutions"); + Console.WriteLine(); +#endif + } + + /// + /// Establish connection to Master device in case of Modbus TCP. Opens COM-Port in case of Modbus RTU + /// + public void Connect() + { + if (serialport != null) + { + if (!serialport.IsOpen) + { + if (debug) StoreLogData.Instance.Store("Open Serial port " + serialport.PortName, System.DateTime.Now); + serialport.BaudRate = baudRate; + serialport.Parity = parity; + serialport.StopBits = stopBits; + serialport.WriteTimeout = 10000; + serialport.ReadTimeout = connectTimeout; + serialport.Open(); + connected = true; + + + } + if (ConnectedChanged != null) + try + { + ConnectedChanged(this); + } + catch + { + + } + return; + } + if (!udpFlag) + { + if (debug) StoreLogData.Instance.Store("Open TCP-Socket, IP-Address: " + ipAddress + ", Port: " + port, System.DateTime.Now); + tcpClient = new TcpClient(); + var result = tcpClient.BeginConnect(ipAddress, port, null, null); + var success = result.AsyncWaitHandle.WaitOne(connectTimeout); + if (!success) + { + throw new EasyModbus.Exceptions.ConnectionException("connection timed out"); + } + tcpClient.EndConnect(result); + + //tcpClient = new TcpClient(ipAddress, port); + stream = tcpClient.GetStream(); + stream.ReadTimeout = connectTimeout; + connected = true; + } + else + { + tcpClient = new TcpClient(); + connected = true; + } + if (ConnectedChanged != null) + try + { + ConnectedChanged(this); + } + catch + { + + } + } + + /// + /// Establish connection to Master device in case of Modbus TCP. + /// + public void Connect(string ipAddress, int port) + { + if (!udpFlag) + { + if (debug) StoreLogData.Instance.Store("Open TCP-Socket, IP-Address: " + ipAddress + ", Port: " + port, System.DateTime.Now); + tcpClient = new TcpClient(); + var result = tcpClient.BeginConnect(ipAddress, port, null, null); + var success = result.AsyncWaitHandle.WaitOne(connectTimeout); + if (!success) + { + throw new EasyModbus.Exceptions.ConnectionException("connection timed out"); + } + tcpClient.EndConnect(result); + + //tcpClient = new TcpClient(ipAddress, port); + stream = tcpClient.GetStream(); + stream.ReadTimeout = connectTimeout; + connected = true; + } + else + { + tcpClient = new TcpClient(); + connected = true; + } + + if (ConnectedChanged != null) + ConnectedChanged(this); + } + + /// + /// Converts two ModbusRegisters to Float - Example: EasyModbus.ModbusClient.ConvertRegistersToFloat(modbusClient.ReadHoldingRegisters(19,2)) + /// + /// Two Register values received from Modbus + /// Connected float value + public static float ConvertRegistersToFloat(int[] registers) + { + if (registers.Length != 2) + throw new ArgumentException("Input Array length invalid - Array langth must be '2'"); + int highRegister = registers[1]; + int lowRegister = registers[0]; + byte[] highRegisterBytes = BitConverter.GetBytes(highRegister); + byte[] lowRegisterBytes = BitConverter.GetBytes(lowRegister); + byte[] floatBytes = { + lowRegisterBytes[0], + lowRegisterBytes[1], + highRegisterBytes[0], + highRegisterBytes[1] + }; + return BitConverter.ToSingle(floatBytes, 0); + } + + /// + /// Converts two ModbusRegisters to Float, Registers can by swapped + /// + /// Two Register values received from Modbus + /// Desired Word Order (Low Register first or High Register first + /// Connected float value + public static float ConvertRegistersToFloat(int[] registers, RegisterOrder registerOrder) + { + int[] swappedRegisters = { registers[0], registers[1] }; + if (registerOrder == RegisterOrder.HighLow) + swappedRegisters = new int[] { registers[1], registers[0] }; + return ConvertRegistersToFloat(swappedRegisters); + } + + /// + /// Converts two ModbusRegisters to 32 Bit Integer value + /// + /// Two Register values received from Modbus + /// Connected 32 Bit Integer value + public static Int32 ConvertRegistersToInt(int[] registers) + { + if (registers.Length != 2) + throw new ArgumentException("Input Array length invalid - Array langth must be '2'"); + int highRegister = registers[1]; + int lowRegister = registers[0]; + byte[] highRegisterBytes = BitConverter.GetBytes(highRegister); + byte[] lowRegisterBytes = BitConverter.GetBytes(lowRegister); + byte[] doubleBytes = { + lowRegisterBytes[0], + lowRegisterBytes[1], + highRegisterBytes[0], + highRegisterBytes[1] + }; + return BitConverter.ToInt32(doubleBytes, 0); + } + + /// + /// Converts two ModbusRegisters to 32 Bit Integer Value - Registers can be swapped + /// + /// Two Register values received from Modbus + /// Desired Word Order (Low Register first or High Register first + /// Connecteds 32 Bit Integer value + public static Int32 ConvertRegistersToInt(int[] registers, RegisterOrder registerOrder) + { + int[] swappedRegisters = { registers[0], registers[1] }; + if (registerOrder == RegisterOrder.HighLow) + swappedRegisters = new int[] { registers[1], registers[0] }; + return ConvertRegistersToInt(swappedRegisters); + } + + /// + /// Convert four 16 Bit Registers to 64 Bit Integer value Register Order "LowHigh": Reg0: Low Word.....Reg3: High Word, "HighLow": Reg0: High Word.....Reg3: Low Word + /// + /// four Register values received from Modbus + /// 64 bit value + public static Int64 ConvertRegistersToLong(int[] registers) + { + if (registers.Length != 4) + throw new ArgumentException("Input Array length invalid - Array langth must be '4'"); + int highRegister = registers[3]; + int highLowRegister = registers[2]; + int lowHighRegister = registers[1]; + int lowRegister = registers[0]; + byte[] highRegisterBytes = BitConverter.GetBytes(highRegister); + byte[] highLowRegisterBytes = BitConverter.GetBytes(highLowRegister); + byte[] lowHighRegisterBytes = BitConverter.GetBytes(lowHighRegister); + byte[] lowRegisterBytes = BitConverter.GetBytes(lowRegister); + byte[] longBytes = { + lowRegisterBytes[0], + lowRegisterBytes[1], + lowHighRegisterBytes[0], + lowHighRegisterBytes[1], + highLowRegisterBytes[0], + highLowRegisterBytes[1], + highRegisterBytes[0], + highRegisterBytes[1] + }; + return BitConverter.ToInt64(longBytes, 0); + } + + /// + /// Convert four 16 Bit Registers to 64 Bit Integer value - Registers can be swapped + /// + /// four Register values received from Modbus + /// Desired Word Order (Low Register first or High Register first + /// Connected 64 Bit Integer value + public static Int64 ConvertRegistersToLong(int[] registers, RegisterOrder registerOrder) + { + if (registers.Length != 4) + throw new ArgumentException("Input Array length invalid - Array langth must be '4'"); + int[] swappedRegisters = { registers[0], registers[1], registers[2], registers[3] }; + if (registerOrder == RegisterOrder.HighLow) + swappedRegisters = new int[] { registers[3], registers[2], registers[1], registers[0] }; + return ConvertRegistersToLong(swappedRegisters); + } + + /// + /// Convert four 16 Bit Registers to 64 Bit double prec. value Register Order "LowHigh": Reg0: Low Word.....Reg3: High Word, "HighLow": Reg0: High Word.....Reg3: Low Word + /// + /// four Register values received from Modbus + /// 64 bit value + public static double ConvertRegistersToDouble(int[] registers) + { + if (registers.Length != 4) + throw new ArgumentException("Input Array length invalid - Array langth must be '4'"); + int highRegister = registers[3]; + int highLowRegister = registers[2]; + int lowHighRegister = registers[1]; + int lowRegister = registers[0]; + byte[] highRegisterBytes = BitConverter.GetBytes(highRegister); + byte[] highLowRegisterBytes = BitConverter.GetBytes(highLowRegister); + byte[] lowHighRegisterBytes = BitConverter.GetBytes(lowHighRegister); + byte[] lowRegisterBytes = BitConverter.GetBytes(lowRegister); + byte[] longBytes = { + lowRegisterBytes[0], + lowRegisterBytes[1], + lowHighRegisterBytes[0], + lowHighRegisterBytes[1], + highLowRegisterBytes[0], + highLowRegisterBytes[1], + highRegisterBytes[0], + highRegisterBytes[1] + }; + return BitConverter.ToDouble(longBytes, 0); + } + + /// + /// Convert four 16 Bit Registers to 64 Bit double prec. value - Registers can be swapped + /// + /// four Register values received from Modbus + /// Desired Word Order (Low Register first or High Register first + /// Connected double prec. float value + public static double ConvertRegistersToDouble(int[] registers, RegisterOrder registerOrder) + { + if (registers.Length != 4) + throw new ArgumentException("Input Array length invalid - Array langth must be '4'"); + int[] swappedRegisters = { registers[0], registers[1], registers[2], registers[3] }; + if (registerOrder == RegisterOrder.HighLow) + swappedRegisters = new int[] { registers[3], registers[2], registers[1], registers[0] }; + return ConvertRegistersToDouble(swappedRegisters); + } + + /// + /// Converts float to two ModbusRegisters - Example: modbusClient.WriteMultipleRegisters(24, EasyModbus.ModbusClient.ConvertFloatToTwoRegisters((float)1.22)); + /// + /// Float value which has to be converted into two registers + /// Register values + public static int[] ConvertFloatToRegisters(float floatValue) + { + byte[] floatBytes = BitConverter.GetBytes(floatValue); + byte[] highRegisterBytes = + { + floatBytes[2], + floatBytes[3], + 0, + 0 + }; + byte[] lowRegisterBytes = + { + + floatBytes[0], + floatBytes[1], + 0, + 0 + }; + int[] returnValue = + { + BitConverter.ToInt32(lowRegisterBytes,0), + BitConverter.ToInt32(highRegisterBytes,0) + }; + return returnValue; + } + + /// + /// Converts float to two ModbusRegisters Registers - Registers can be swapped + /// + /// Float value which has to be converted into two registers + /// Desired Word Order (Low Register first or High Register first + /// Register values + public static int[] ConvertFloatToRegisters(float floatValue, RegisterOrder registerOrder) + { + int[] registerValues = ConvertFloatToRegisters(floatValue); + int[] returnValue = registerValues; + if (registerOrder == RegisterOrder.HighLow) + returnValue = new Int32[] { registerValues[1], registerValues[0] }; + return returnValue; + } + + /// + /// Converts 32 Bit Value to two ModbusRegisters + /// + /// Int value which has to be converted into two registers + /// Register values + public static int[] ConvertIntToRegisters(Int32 intValue) + { + byte[] doubleBytes = BitConverter.GetBytes(intValue); + byte[] highRegisterBytes = + { + doubleBytes[2], + doubleBytes[3], + 0, + 0 + }; + byte[] lowRegisterBytes = + { + + doubleBytes[0], + doubleBytes[1], + 0, + 0 + }; + int[] returnValue = + { + BitConverter.ToInt32(lowRegisterBytes,0), + BitConverter.ToInt32(highRegisterBytes,0) + }; + return returnValue; + } + + /// + /// Converts 32 Bit Value to two ModbusRegisters Registers - Registers can be swapped + /// + /// Double value which has to be converted into two registers + /// Desired Word Order (Low Register first or High Register first + /// Register values + public static int[] ConvertIntToRegisters(Int32 intValue, RegisterOrder registerOrder) + { + int[] registerValues = ConvertIntToRegisters(intValue); + int[] returnValue = registerValues; + if (registerOrder == RegisterOrder.HighLow) + returnValue = new Int32[] { registerValues[1], registerValues[0] }; + return returnValue; + } + + /// + /// Converts 64 Bit Value to four ModbusRegisters + /// + /// long value which has to be converted into four registers + /// Register values + public static int[] ConvertLongToRegisters(Int64 longValue) + { + byte[] longBytes = BitConverter.GetBytes(longValue); + byte[] highRegisterBytes = + { + longBytes[6], + longBytes[7], + 0, + 0 + }; + byte[] highLowRegisterBytes = + { + longBytes[4], + longBytes[5], + 0, + 0 + }; + byte[] lowHighRegisterBytes = + { + longBytes[2], + longBytes[3], + 0, + 0 + }; + byte[] lowRegisterBytes = + { + + longBytes[0], + longBytes[1], + 0, + 0 + }; + int[] returnValue = + { + BitConverter.ToInt32(lowRegisterBytes,0), + BitConverter.ToInt32(lowHighRegisterBytes,0), + BitConverter.ToInt32(highLowRegisterBytes,0), + BitConverter.ToInt32(highRegisterBytes,0) + }; + return returnValue; + } + + /// + /// Converts 64 Bit Value to four ModbusRegisters - Registers can be swapped + /// + /// long value which has to be converted into four registers + /// Desired Word Order (Low Register first or High Register first + /// Register values + public static int[] ConvertLongToRegisters(Int64 longValue, RegisterOrder registerOrder) + { + int[] registerValues = ConvertLongToRegisters(longValue); + int[] returnValue = registerValues; + if (registerOrder == RegisterOrder.HighLow) + returnValue = new int[] { registerValues[3], registerValues[2], registerValues[1], registerValues[0] }; + return returnValue; + } + + /// + /// Converts 64 Bit double prec Value to four ModbusRegisters + /// + /// double value which has to be converted into four registers + /// Register values + public static int[] ConvertDoubleToRegisters(double doubleValue) + { + byte[] doubleBytes = BitConverter.GetBytes(doubleValue); + byte[] highRegisterBytes = + { + doubleBytes[6], + doubleBytes[7], + 0, + 0 + }; + byte[] highLowRegisterBytes = + { + doubleBytes[4], + doubleBytes[5], + 0, + 0 + }; + byte[] lowHighRegisterBytes = + { + doubleBytes[2], + doubleBytes[3], + 0, + 0 + }; + byte[] lowRegisterBytes = + { + + doubleBytes[0], + doubleBytes[1], + 0, + 0 + }; + int[] returnValue = + { + BitConverter.ToInt32(lowRegisterBytes,0), + BitConverter.ToInt32(lowHighRegisterBytes,0), + BitConverter.ToInt32(highLowRegisterBytes,0), + BitConverter.ToInt32(highRegisterBytes,0) + }; + return returnValue; + } + + /// + /// Converts 64 Bit double prec. Value to four ModbusRegisters - Registers can be swapped + /// + /// double value which has to be converted into four registers + /// Desired Word Order (Low Register first or High Register first + /// Register values + public static int[] ConvertDoubleToRegisters(double doubleValue, RegisterOrder registerOrder) + { + int[] registerValues = ConvertDoubleToRegisters(doubleValue); + int[] returnValue = registerValues; + if (registerOrder == RegisterOrder.HighLow) + returnValue = new int[] { registerValues[3], registerValues[2], registerValues[1], registerValues[0] }; + return returnValue; + } + + /// + /// Converts 16 - Bit Register values to String + /// + /// Register array received via Modbus + /// First Register containing the String to convert + /// number of characters in String (must be even) + /// Converted String + public static string ConvertRegistersToString(int[] registers, int offset, int stringLength) + { + byte[] result = new byte[stringLength]; + byte[] registerResult = new byte[2]; + + for (int i = 0; i < stringLength / 2; i++) + { + registerResult = BitConverter.GetBytes(registers[offset + i]); + result[i * 2] = registerResult[0]; + result[i * 2 + 1] = registerResult[1]; + } + return System.Text.Encoding.Default.GetString(result); + } + + /// + /// Converts a String to 16 - Bit Registers + /// + /// Register array received via Modbus + /// Converted String + public static int[] ConvertStringToRegisters(string stringToConvert) + { + byte[] array = System.Text.Encoding.ASCII.GetBytes(stringToConvert); + int[] returnarray = new int[stringToConvert.Length / 2 + stringToConvert.Length % 2]; + for (int i = 0; i < returnarray.Length; i++) + { + returnarray[i] = array[i * 2]; + if (i * 2 + 1 < array.Length) + { + returnarray[i] = returnarray[i] | ((int)array[i * 2 + 1] << 8); + } + } + return returnarray; + } + + + /// + /// Calculates the CRC16 for Modbus-RTU + /// + /// Byte buffer to send + /// Number of bytes to calculate CRC + /// First byte in buffer to start calculating CRC + public static UInt16 calculateCRC(byte[] data, UInt16 numberOfBytes, int startByte) + { + byte[] auchCRCHi = { + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, + 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, + 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, + 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, + 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, + 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, + 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, + 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, + 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, + 0x40 + }; + + byte[] auchCRCLo = { + 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, + 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, + 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, + 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, + 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, + 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, + 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, + 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, + 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, + 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, + 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, + 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, + 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, + 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, + 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, + 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, + 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, + 0x40 + }; + UInt16 usDataLen = numberOfBytes; + byte uchCRCHi = 0xFF; + byte uchCRCLo = 0xFF; + int i = 0; + int uIndex; + while (usDataLen > 0) + { + usDataLen--; + if ((i + startByte) < data.Length) + { + uIndex = uchCRCLo ^ data[i + startByte]; + uchCRCLo = (byte)(uchCRCHi ^ auchCRCHi[uIndex]); + uchCRCHi = auchCRCLo[uIndex]; + } + i++; + } + return (UInt16)((UInt16)uchCRCHi << 8 | uchCRCLo); + } + + private bool dataReceived = false; + private bool receiveActive = false; + private byte[] readBuffer = new byte[256]; + private int bytesToRead = 0; + private int akjjjctualPositionToRead = 0; + DateTime dateTimeLastRead; + /* + private void DataReceivedHandler(object sender, + SerialDataReceivedEventArgs e) + { + long ticksWait = TimeSpan.TicksPerMillisecond * 2000; + SerialPort sp = (SerialPort)sender; + + if (bytesToRead == 0 || sp.BytesToRead == 0) + { + actualPositionToRead = 0; + sp.DiscardInBuffer(); + dataReceived = false; + receiveActive = false; + return; + } + + if (actualPositionToRead == 0 && !dataReceived) + readBuffer = new byte[256]; + + //if ((DateTime.Now.Ticks - dateTimeLastRead.Ticks) > ticksWait) + //{ + // readBuffer = new byte[256]; + // actualPositionToRead = 0; + //} + int numberOfBytesInBuffer = sp.BytesToRead; + sp.Read(readBuffer, actualPositionToRead, ((numberOfBytesInBuffer + actualPositionToRead) > readBuffer.Length) ? 0 : numberOfBytesInBuffer); + actualPositionToRead = actualPositionToRead + numberOfBytesInBuffer; + //sp.DiscardInBuffer(); + //if (DetectValidModbusFrame(readBuffer, (actualPositionToRead < readBuffer.Length) ? actualPositionToRead : readBuffer.Length) | bytesToRead <= actualPositionToRead) + if (actualPositionToRead >= bytesToRead) + { + + dataReceived = true; + bytesToRead = 0; + actualPositionToRead = 0; + if (debug) StoreLogData.Instance.Store("Received Serial-Data: " + BitConverter.ToString(readBuffer), System.DateTime.Now); + + } + + + //dateTimeLastRead = DateTime.Now; + } + */ + + + private void DataReceivedHandler(object sender, + SerialDataReceivedEventArgs e) + { + serialport.DataReceived -= DataReceivedHandler; + + //while (receiveActive | dataReceived) + // System.Threading.Thread.Sleep(10); + receiveActive = true; + + const long ticksWait = TimeSpan.TicksPerMillisecond * 2000;//((40*10000000) / this.baudRate); + + + SerialPort sp = (SerialPort)sender; + if (bytesToRead == 0) + { + sp.DiscardInBuffer(); + receiveActive = false; + serialport.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler); + return; + } + readBuffer = new byte[256]; + int numbytes = 0; + int actualPositionToRead = 0; + DateTime dateTimeLastRead = DateTime.Now; + do + { + try + { + dateTimeLastRead = DateTime.Now; + while ((sp.BytesToRead) == 0) + { + System.Threading.Thread.Sleep(10); + if ((DateTime.Now.Ticks - dateTimeLastRead.Ticks) > ticksWait) + break; + } + numbytes = sp.BytesToRead; + + + byte[] rxbytearray = new byte[numbytes]; + sp.Read(rxbytearray, 0, numbytes); + Array.Copy(rxbytearray, 0, readBuffer, actualPositionToRead, (actualPositionToRead + rxbytearray.Length) <= bytesToRead ? rxbytearray.Length : bytesToRead - actualPositionToRead); + + actualPositionToRead = actualPositionToRead + rxbytearray.Length; + + } + catch (Exception) + { + + } + + if (bytesToRead <= actualPositionToRead) + break; + + if (DetectValidModbusFrame(readBuffer, (actualPositionToRead < readBuffer.Length) ? actualPositionToRead : readBuffer.Length) | bytesToRead <= actualPositionToRead) + break; + } + while ((DateTime.Now.Ticks - dateTimeLastRead.Ticks) < ticksWait); + + //10.000 Ticks in 1 ms + + receiveData = new byte[actualPositionToRead]; + Array.Copy(readBuffer, 0, receiveData, 0, (actualPositionToRead < readBuffer.Length) ? actualPositionToRead : readBuffer.Length); + if (debug) StoreLogData.Instance.Store("Received Serial-Data: " + BitConverter.ToString(readBuffer), System.DateTime.Now); + bytesToRead = 0; + + + + + dataReceived = true; + receiveActive = false; + serialport.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler); + if (ReceiveDataChanged != null) + { + + ReceiveDataChanged(this); + + } + + //sp.DiscardInBuffer(); + } + + public static bool DetectValidModbusFrame(byte[] readBuffer, int length) + { + // minimum length 6 bytes + if (length < 6) + return false; + //SlaveID correct + if ((readBuffer[0] < 1) | (readBuffer[0] > 247)) + return false; + //CRC correct? + byte[] crc = new byte[2]; + crc = BitConverter.GetBytes(calculateCRC(readBuffer, (ushort)(length - 2), 0)); + if (crc[0] != readBuffer[length - 2] | crc[1] != readBuffer[length - 1]) + return false; + return true; + } + + + + /// + /// Read Discrete Inputs from Server device (FC2). + /// + /// First discrete input to read + /// Number of discrete Inputs to read + /// Boolean Array which contains the discrete Inputs + public bool[] ReadDiscreteInputs(int startingAddress, int quantity) + { + if (debug) StoreLogData.Instance.Store("FC2 (Read Discrete Inputs from Master device), StartingAddress: " + startingAddress + ", Quantity: " + quantity, System.DateTime.Now); + transactionIdentifierInternal++; + if (serialport != null) + if (!serialport.IsOpen) + { + if (debug) StoreLogData.Instance.Store("SerialPortNotOpenedException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.SerialPortNotOpenedException("serial port not opened"); + } + if (tcpClient == null & !udpFlag & serialport == null) + { + if (debug) StoreLogData.Instance.Store("ConnectionException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.ConnectionException("connection error"); + } + if (startingAddress > 65535 | quantity > 2000) + { + if (debug) StoreLogData.Instance.Store("ArgumentException Throwed", System.DateTime.Now); + throw new ArgumentException("Starting address must be 0 - 65535; quantity must be 0 - 2000"); + } + bool[] response; + this.transactionIdentifier = BitConverter.GetBytes((uint)transactionIdentifierInternal); + this.protocolIdentifier = BitConverter.GetBytes((int)0x0000); + this.length = BitConverter.GetBytes((int)0x0006); + this.functionCode = 0x02; + this.startingAddress = BitConverter.GetBytes(startingAddress); + this.quantity = BitConverter.GetBytes(quantity); + Byte[] data = new byte[] + { + this.transactionIdentifier[1], + this.transactionIdentifier[0], + this.protocolIdentifier[1], + this.protocolIdentifier[0], + this.length[1], + this.length[0], + this.unitIdentifier, + this.functionCode, + this.startingAddress[1], + this.startingAddress[0], + this.quantity[1], + this.quantity[0], + this.crc[0], + this.crc[1] + }; + crc = BitConverter.GetBytes(calculateCRC(data, 6, 6)); + data[12] = crc[0]; + data[13] = crc[1]; + + if (serialport != null) + { + dataReceived = false; + if (quantity % 8 == 0) + bytesToRead = 5 + quantity / 8; + else + bytesToRead = 6 + quantity / 8; + // serialport.ReceivedBytesThreshold = bytesToRead; + serialport.Write(data, 6, 8); + if (debug) + { + byte[] debugData = new byte[8]; + Array.Copy(data, 6, debugData, 0, 8); + if (debug) StoreLogData.Instance.Store("Send Serial-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + if (SendDataChanged != null) + { + sendData = new byte[8]; + Array.Copy(data, 6, sendData, 0, 8); + SendDataChanged(this); + + } + data = new byte[2100]; + readBuffer = new byte[256]; + DateTime dateTimeSend = DateTime.Now; + byte receivedUnitIdentifier = 0xFF; + + SpinWait sw_delay = new SpinWait(); + while (receivedUnitIdentifier != this.unitIdentifier & !((DateTime.Now.Ticks - dateTimeSend.Ticks) > TimeSpan.TicksPerMillisecond * this.connectTimeout)) + { + while (dataReceived == false & !((DateTime.Now.Ticks - dateTimeSend.Ticks) > TimeSpan.TicksPerMillisecond * this.connectTimeout)) + sw_delay.SpinOnce(); + + data = new byte[2100]; + Array.Copy(readBuffer, 0, data, 6, readBuffer.Length); + receivedUnitIdentifier = data[6]; + } + if (receivedUnitIdentifier != this.unitIdentifier) + data = new byte[2100]; + else + countRetries = 0; + } + else if (tcpClient.Client.Connected | udpFlag) + { + if (udpFlag) + { + UdpClient udpClient = new UdpClient(); + IPEndPoint endPoint = new IPEndPoint(System.Net.IPAddress.Parse(ipAddress), port); + udpClient.Send(data, data.Length - 2, endPoint); + portOut = ((IPEndPoint)udpClient.Client.LocalEndPoint).Port; + udpClient.Client.ReceiveTimeout = 5000; + endPoint = new IPEndPoint(System.Net.IPAddress.Parse(ipAddress), portOut); + data = udpClient.Receive(ref endPoint); + } + else + { + stream.Write(data, 0, data.Length - 2); + if (debug) + { + byte[] debugData = new byte[data.Length - 2]; + Array.Copy(data, 0, debugData, 0, data.Length - 2); + if (debug) StoreLogData.Instance.Store("Send ModbusTCP-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + if (SendDataChanged != null) + { + sendData = new byte[data.Length - 2]; + Array.Copy(data, 0, sendData, 0, data.Length - 2); + SendDataChanged(this); + } + data = new Byte[2100]; + int NumberOfBytes = stream.Read(data, 0, data.Length); + if (ReceiveDataChanged != null) + { + receiveData = new byte[NumberOfBytes]; + Array.Copy(data, 0, receiveData, 0, NumberOfBytes); + if (debug) StoreLogData.Instance.Store("Receive ModbusTCP-Data: " + BitConverter.ToString(receiveData), System.DateTime.Now); + ReceiveDataChanged(this); + } + } + } + if (data[7] == 0x82 & data[8] == 0x01) + { + if (debug) StoreLogData.Instance.Store("FunctionCodeNotSupportedException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.FunctionCodeNotSupportedException("Function code not supported by master"); + } + if (data[7] == 0x82 & data[8] == 0x02) + { + if (debug) StoreLogData.Instance.Store("StartingAddressInvalidException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.StartingAddressInvalidException("Starting address invalid or starting address + quantity invalid"); + } + if (data[7] == 0x82 & data[8] == 0x03) + { + if (debug) StoreLogData.Instance.Store("QuantityInvalidException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.QuantityInvalidException("quantity invalid"); + } + if (data[7] == 0x82 & data[8] == 0x04) + { + if (debug) StoreLogData.Instance.Store("ModbusException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.ModbusException("error reading"); + } + if (serialport != null) + { + crc = BitConverter.GetBytes(calculateCRC(data, (ushort)(data[8] + 3), 6)); + if ((crc[0] != data[data[8] + 9] | crc[1] != data[data[8] + 10]) & dataReceived) + { + if (debug) StoreLogData.Instance.Store("CRCCheckFailedException Throwed", System.DateTime.Now); + if (NumberOfRetries <= countRetries) + { + countRetries = 0; + throw new EasyModbus.Exceptions.CRCCheckFailedException("Response CRC check failed"); + } + else + { + countRetries++; + return ReadDiscreteInputs(startingAddress, quantity); + } + } + else if (!dataReceived) + { + if (debug) StoreLogData.Instance.Store("TimeoutException Throwed", System.DateTime.Now); + if (NumberOfRetries <= countRetries) + { + countRetries = 0; + throw new TimeoutException("No Response from Modbus Slave"); + } + else + { + countRetries++; + return ReadDiscreteInputs(startingAddress, quantity); + } + } + } + response = new bool[quantity]; + for (int i = 0; i < quantity; i++) + { + int intData = data[9 + i / 8]; + int mask = Convert.ToInt32(Math.Pow(2, (i % 8))); + response[i] = Convert.ToBoolean((intData & mask) / mask); + } + return (response); + } + + + /// + /// Read Coils from Server device (FC1). + /// + /// First coil to read + /// Numer of coils to read + /// Boolean Array which contains the coils + public bool[] ReadCoils(int startingAddress, int quantity) + { + if (debug) StoreLogData.Instance.Store("FC1 (Read Coils from Master device), StartingAddress: " + startingAddress + ", Quantity: " + quantity, System.DateTime.Now); + transactionIdentifierInternal++; + if (serialport != null) + if (!serialport.IsOpen) + { + if (debug) StoreLogData.Instance.Store("SerialPortNotOpenedException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.SerialPortNotOpenedException("serial port not opened"); + } + if (tcpClient == null & !udpFlag & serialport == null) + { + if (debug) StoreLogData.Instance.Store("ConnectionException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.ConnectionException("connection error"); + } + if (startingAddress > 65535 | quantity > 2000) + { + if (debug) StoreLogData.Instance.Store("ArgumentException Throwed", System.DateTime.Now); + throw new ArgumentException("Starting address must be 0 - 65535; quantity must be 0 - 2000"); + } + bool[] response; + this.transactionIdentifier = BitConverter.GetBytes((uint)transactionIdentifierInternal); + this.protocolIdentifier = BitConverter.GetBytes((int)0x0000); + this.length = BitConverter.GetBytes((int)0x0006); + this.functionCode = 0x01; + this.startingAddress = BitConverter.GetBytes(startingAddress); + this.quantity = BitConverter.GetBytes(quantity); + Byte[] data = new byte[]{ + this.transactionIdentifier[1], + this.transactionIdentifier[0], + this.protocolIdentifier[1], + this.protocolIdentifier[0], + this.length[1], + this.length[0], + this.unitIdentifier, + this.functionCode, + this.startingAddress[1], + this.startingAddress[0], + this.quantity[1], + this.quantity[0], + this.crc[0], + this.crc[1] + }; + + crc = BitConverter.GetBytes(calculateCRC(data, 6, 6)); + data[12] = crc[0]; + data[13] = crc[1]; + if (serialport != null) + { + dataReceived = false; + if (quantity % 8 == 0) + bytesToRead = 5 + quantity / 8; + else + bytesToRead = 6 + quantity / 8; + // serialport.ReceivedBytesThreshold = bytesToRead; + serialport.Write(data, 6, 8); + if (debug) + { + byte[] debugData = new byte[8]; + Array.Copy(data, 6, debugData, 0, 8); + if (debug) StoreLogData.Instance.Store("Send Serial-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + if (SendDataChanged != null) + { + sendData = new byte[8]; + Array.Copy(data, 6, sendData, 0, 8); + SendDataChanged(this); + + } + data = new byte[2100]; + readBuffer = new byte[256]; + DateTime dateTimeSend = DateTime.Now; + byte receivedUnitIdentifier = 0xFF; + + SpinWait sw_delay = new SpinWait(); + while (receivedUnitIdentifier != this.unitIdentifier & !((DateTime.Now.Ticks - dateTimeSend.Ticks) > TimeSpan.TicksPerMillisecond * this.connectTimeout)) + { + while (dataReceived == false & !((DateTime.Now.Ticks - dateTimeSend.Ticks) > TimeSpan.TicksPerMillisecond * this.connectTimeout)) + sw_delay.SpinOnce(); + + data = new byte[2100]; + + Array.Copy(readBuffer, 0, data, 6, readBuffer.Length); + receivedUnitIdentifier = data[6]; + } + if (receivedUnitIdentifier != this.unitIdentifier) + data = new byte[2100]; + else + countRetries = 0; + } + else if (tcpClient.Client.Connected | udpFlag) + { + if (udpFlag) + { + UdpClient udpClient = new UdpClient(); + IPEndPoint endPoint = new IPEndPoint(System.Net.IPAddress.Parse(ipAddress), port); + udpClient.Send(data, data.Length - 2, endPoint); + portOut = ((IPEndPoint)udpClient.Client.LocalEndPoint).Port; + udpClient.Client.ReceiveTimeout = 5000; + endPoint = new IPEndPoint(System.Net.IPAddress.Parse(ipAddress), portOut); + data = udpClient.Receive(ref endPoint); + } + else + { + stream.Write(data, 0, data.Length - 2); + if (debug) + { + byte[] debugData = new byte[data.Length - 2]; + Array.Copy(data, 0, debugData, 0, data.Length - 2); + if (debug) StoreLogData.Instance.Store("Send MocbusTCP-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + if (SendDataChanged != null) + { + sendData = new byte[data.Length - 2]; + Array.Copy(data, 0, sendData, 0, data.Length - 2); + SendDataChanged(this); + + } + data = new Byte[2100]; + int NumberOfBytes = stream.Read(data, 0, data.Length); + if (ReceiveDataChanged != null) + { + receiveData = new byte[NumberOfBytes]; + Array.Copy(data, 0, receiveData, 0, NumberOfBytes); + if (debug) StoreLogData.Instance.Store("Receive ModbusTCP-Data: " + BitConverter.ToString(receiveData), System.DateTime.Now); + ReceiveDataChanged(this); + } + } + } + if (data[7] == 0x81 & data[8] == 0x01) + { + if (debug) StoreLogData.Instance.Store("FunctionCodeNotSupportedException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.FunctionCodeNotSupportedException("Function code not supported by master"); + } + if (data[7] == 0x81 & data[8] == 0x02) + { + if (debug) StoreLogData.Instance.Store("StartingAddressInvalidException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.StartingAddressInvalidException("Starting address invalid or starting address + quantity invalid"); + } + if (data[7] == 0x81 & data[8] == 0x03) + { + if (debug) StoreLogData.Instance.Store("QuantityInvalidException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.QuantityInvalidException("quantity invalid"); + } + if (data[7] == 0x81 & data[8] == 0x04) + { + if (debug) StoreLogData.Instance.Store("ModbusException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.ModbusException("error reading"); + } + if (serialport != null) + { + crc = BitConverter.GetBytes(calculateCRC(data, (ushort)(data[8] + 3), 6)); + if ((crc[0] != data[data[8] + 9] | crc[1] != data[data[8] + 10]) & dataReceived) + { + if (debug) StoreLogData.Instance.Store("CRCCheckFailedException Throwed", System.DateTime.Now); + if (NumberOfRetries <= countRetries) + { + countRetries = 0; + throw new EasyModbus.Exceptions.CRCCheckFailedException("Response CRC check failed"); + } + else + { + countRetries++; + return ReadCoils(startingAddress, quantity); + } + } + else if (!dataReceived) + { + if (debug) StoreLogData.Instance.Store("TimeoutException Throwed", System.DateTime.Now); + if (NumberOfRetries <= countRetries) + { + countRetries = 0; + throw new TimeoutException("No Response from Modbus Slave"); + } + else + { + countRetries++; + return ReadCoils(startingAddress, quantity); + } + } + } + response = new bool[quantity]; + for (int i = 0; i < quantity; i++) + { + int intData = data[9 + i / 8]; + int mask = Convert.ToInt32(Math.Pow(2, (i % 8))); + response[i] = Convert.ToBoolean((intData & mask) / mask); + } + return (response); + } + + + /// + /// Read Holding Registers from Master device (FC3). + /// + /// First holding register to be read + /// Number of holding registers to be read + /// Int Array which contains the holding registers + public int[] ReadHoldingRegisters(int startingAddress, int quantity) + { + if (debug) StoreLogData.Instance.Store("FC3 (Read Holding Registers from Master device), StartingAddress: " + startingAddress + ", Quantity: " + quantity, System.DateTime.Now); + transactionIdentifierInternal++; + if (serialport != null) + if (!serialport.IsOpen) + { + if (debug) StoreLogData.Instance.Store("SerialPortNotOpenedException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.SerialPortNotOpenedException("serial port not opened"); + } + if (tcpClient == null & !udpFlag & serialport == null) + { + if (debug) StoreLogData.Instance.Store("ConnectionException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.ConnectionException("connection error"); + } + if (startingAddress > 65535 | quantity > 125) + { + if (debug) StoreLogData.Instance.Store("ArgumentException Throwed", System.DateTime.Now); + throw new ArgumentException("Starting address must be 0 - 65535; quantity must be 0 - 125"); + } + int[] response; + this.transactionIdentifier = BitConverter.GetBytes((uint)transactionIdentifierInternal); + this.protocolIdentifier = BitConverter.GetBytes((int)0x0000); + this.length = BitConverter.GetBytes((int)0x0006); + this.functionCode = 0x03; + this.startingAddress = BitConverter.GetBytes(startingAddress); + this.quantity = BitConverter.GetBytes(quantity); + Byte[] data = new byte[]{ this.transactionIdentifier[1], + this.transactionIdentifier[0], + this.protocolIdentifier[1], + this.protocolIdentifier[0], + this.length[1], + this.length[0], + this.unitIdentifier, + this.functionCode, + this.startingAddress[1], + this.startingAddress[0], + this.quantity[1], + this.quantity[0], + this.crc[0], + this.crc[1] + }; + crc = BitConverter.GetBytes(calculateCRC(data, 6, 6)); + data[12] = crc[0]; + data[13] = crc[1]; + if (serialport != null) + { + dataReceived = false; + bytesToRead = 5 + 2 * quantity; + // serialport.ReceivedBytesThreshold = bytesToRead; + serialport.Write(data, 6, 8); + if (debug) + { + byte[] debugData = new byte[8]; + Array.Copy(data, 6, debugData, 0, 8); + if (debug) StoreLogData.Instance.Store("Send Serial-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + if (SendDataChanged != null) + { + sendData = new byte[8]; + Array.Copy(data, 6, sendData, 0, 8); + SendDataChanged(this); + + } + data = new byte[2100]; + readBuffer = new byte[256]; + + DateTime dateTimeSend = DateTime.Now; + byte receivedUnitIdentifier = 0xFF; + + SpinWait sw_delay = new SpinWait(); + while (receivedUnitIdentifier != this.unitIdentifier & !((DateTime.Now.Ticks - dateTimeSend.Ticks) > TimeSpan.TicksPerMillisecond * this.connectTimeout)) + { + while (dataReceived == false & !((DateTime.Now.Ticks - dateTimeSend.Ticks) > TimeSpan.TicksPerMillisecond * this.connectTimeout)) + sw_delay.SpinOnce(); + + data = new byte[2100]; + Array.Copy(readBuffer, 0, data, 6, readBuffer.Length); + + receivedUnitIdentifier = data[6]; + } + if (receivedUnitIdentifier != this.unitIdentifier) + data = new byte[2100]; + else + countRetries = 0; + } + else if (tcpClient.Client.Connected | udpFlag) + { + if (udpFlag) + { + UdpClient udpClient = new UdpClient(); + IPEndPoint endPoint = new IPEndPoint(System.Net.IPAddress.Parse(ipAddress), port); + udpClient.Send(data, data.Length - 2, endPoint); + portOut = ((IPEndPoint)udpClient.Client.LocalEndPoint).Port; + udpClient.Client.ReceiveTimeout = 5000; + endPoint = new IPEndPoint(System.Net.IPAddress.Parse(ipAddress), portOut); + data = udpClient.Receive(ref endPoint); + } + else + { + stream.Write(data, 0, data.Length - 2); + if (debug) + { + byte[] debugData = new byte[data.Length - 2]; + Array.Copy(data, 0, debugData, 0, data.Length - 2); + if (debug) StoreLogData.Instance.Store("Send ModbusTCP-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + if (SendDataChanged != null) + { + sendData = new byte[data.Length - 2]; + Array.Copy(data, 0, sendData, 0, data.Length - 2); + SendDataChanged(this); + + } + data = new Byte[256]; + int NumberOfBytes = stream.Read(data, 0, data.Length); + if (ReceiveDataChanged != null) + { + receiveData = new byte[NumberOfBytes]; + Array.Copy(data, 0, receiveData, 0, NumberOfBytes); + if (debug) StoreLogData.Instance.Store("Receive ModbusTCP-Data: " + BitConverter.ToString(receiveData), System.DateTime.Now); + ReceiveDataChanged(this); + } + } + } + if (data[7] == 0x83 & data[8] == 0x01) + { + if (debug) StoreLogData.Instance.Store("FunctionCodeNotSupportedException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.FunctionCodeNotSupportedException("Function code not supported by master"); + } + if (data[7] == 0x83 & data[8] == 0x02) + { + if (debug) StoreLogData.Instance.Store("StartingAddressInvalidException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.StartingAddressInvalidException("Starting address invalid or starting address + quantity invalid"); + } + if (data[7] == 0x83 & data[8] == 0x03) + { + if (debug) StoreLogData.Instance.Store("QuantityInvalidException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.QuantityInvalidException("quantity invalid"); + } + if (data[7] == 0x83 & data[8] == 0x04) + { + if (debug) StoreLogData.Instance.Store("ModbusException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.ModbusException("error reading"); + } + if (serialport != null) + { + crc = BitConverter.GetBytes(calculateCRC(data, (ushort)(data[8] + 3), 6)); + if ((crc[0] != data[data[8] + 9] | crc[1] != data[data[8] + 10]) & dataReceived) + { + if (debug) StoreLogData.Instance.Store("CRCCheckFailedException Throwed", System.DateTime.Now); + if (NumberOfRetries <= countRetries) + { + countRetries = 0; + throw new EasyModbus.Exceptions.CRCCheckFailedException("Response CRC check failed"); + } + else + { + countRetries++; + return ReadHoldingRegisters(startingAddress, quantity); + } + } + else if (!dataReceived) + { + if (debug) StoreLogData.Instance.Store("TimeoutException Throwed", System.DateTime.Now); + if (NumberOfRetries <= countRetries) + { + countRetries = 0; + throw new TimeoutException("No Response from Modbus Slave"); + } + else + { + countRetries++; + return ReadHoldingRegisters(startingAddress, quantity); + } + + + } + } + response = new int[quantity]; + for (int i = 0; i < quantity; i++) + { + byte lowByte; + byte highByte; + highByte = data[9 + i * 2]; + lowByte = data[9 + i * 2 + 1]; + + data[9 + i * 2] = lowByte; + data[9 + i * 2 + 1] = highByte; + + response[i] = BitConverter.ToInt16(data, (9 + i * 2)); + } + return (response); + } + + + + /// + /// Read Input Registers from Master device (FC4). + /// + /// First input register to be read + /// Number of input registers to be read + /// Int Array which contains the input registers + public int[] ReadInputRegisters(int startingAddress, int quantity) + { + + if (debug) StoreLogData.Instance.Store("FC4 (Read Input Registers from Master device), StartingAddress: " + startingAddress + ", Quantity: " + quantity, System.DateTime.Now); + transactionIdentifierInternal++; + if (serialport != null) + if (!serialport.IsOpen) + { + if (debug) StoreLogData.Instance.Store("SerialPortNotOpenedException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.SerialPortNotOpenedException("serial port not opened"); + } + if (tcpClient == null & !udpFlag & serialport == null) + { + if (debug) StoreLogData.Instance.Store("ConnectionException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.ConnectionException("connection error"); + } + if (startingAddress > 65535 | quantity > 125) + { + if (debug) StoreLogData.Instance.Store("ArgumentException Throwed", System.DateTime.Now); + throw new ArgumentException("Starting address must be 0 - 65535; quantity must be 0 - 125"); + } + int[] response; + this.transactionIdentifier = BitConverter.GetBytes((uint)transactionIdentifierInternal); + this.protocolIdentifier = BitConverter.GetBytes((int)0x0000); + this.length = BitConverter.GetBytes((int)0x0006); + this.functionCode = 0x04; + this.startingAddress = BitConverter.GetBytes(startingAddress); + this.quantity = BitConverter.GetBytes(quantity); + Byte[] data = new byte[]{ this.transactionIdentifier[1], + this.transactionIdentifier[0], + this.protocolIdentifier[1], + this.protocolIdentifier[0], + this.length[1], + this.length[0], + this.unitIdentifier, + this.functionCode, + this.startingAddress[1], + this.startingAddress[0], + this.quantity[1], + this.quantity[0], + this.crc[0], + this.crc[1] + }; + crc = BitConverter.GetBytes(calculateCRC(data, 6, 6)); + data[12] = crc[0]; + data[13] = crc[1]; + if (serialport != null) + { + dataReceived = false; + bytesToRead = 5 + 2 * quantity; + + + // serialport.ReceivedBytesThreshold = bytesToRead; + serialport.Write(data, 6, 8); + if (debug) + { + byte[] debugData = new byte[8]; + Array.Copy(data, 6, debugData, 0, 8); + if (debug) StoreLogData.Instance.Store("Send Serial-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + if (SendDataChanged != null) + { + sendData = new byte[8]; + Array.Copy(data, 6, sendData, 0, 8); + SendDataChanged(this); + + } + data = new byte[2100]; + readBuffer = new byte[256]; + DateTime dateTimeSend = DateTime.Now; + byte receivedUnitIdentifier = 0xFF; + + SpinWait sw_delay = new SpinWait(); + while (receivedUnitIdentifier != this.unitIdentifier & !((DateTime.Now.Ticks - dateTimeSend.Ticks) > TimeSpan.TicksPerMillisecond * this.connectTimeout)) + { + while (dataReceived == false & !((DateTime.Now.Ticks - dateTimeSend.Ticks) > TimeSpan.TicksPerMillisecond * this.connectTimeout)) + sw_delay.SpinOnce(); + + data = new byte[2100]; + Array.Copy(readBuffer, 0, data, 6, readBuffer.Length); + receivedUnitIdentifier = data[6]; + } + + if (receivedUnitIdentifier != this.unitIdentifier) + data = new byte[2100]; + else + countRetries = 0; + } + else if (tcpClient.Client.Connected | udpFlag) + { + if (udpFlag) + { + UdpClient udpClient = new UdpClient(); + IPEndPoint endPoint = new IPEndPoint(System.Net.IPAddress.Parse(ipAddress), port); + udpClient.Send(data, data.Length - 2, endPoint); + portOut = ((IPEndPoint)udpClient.Client.LocalEndPoint).Port; + udpClient.Client.ReceiveTimeout = 5000; + endPoint = new IPEndPoint(System.Net.IPAddress.Parse(ipAddress), portOut); + data = udpClient.Receive(ref endPoint); + } + else + { + stream.Write(data, 0, data.Length - 2); + if (debug) + { + byte[] debugData = new byte[data.Length - 2]; + Array.Copy(data, 0, debugData, 0, data.Length - 2); + if (debug) StoreLogData.Instance.Store("Send ModbusTCP-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + if (SendDataChanged != null) + { + sendData = new byte[data.Length - 2]; + Array.Copy(data, 0, sendData, 0, data.Length - 2); + SendDataChanged(this); + } + data = new Byte[2100]; + int NumberOfBytes = stream.Read(data, 0, data.Length); + if (ReceiveDataChanged != null) + { + receiveData = new byte[NumberOfBytes]; + Array.Copy(data, 0, receiveData, 0, NumberOfBytes); + if (debug) StoreLogData.Instance.Store("Receive ModbusTCP-Data: " + BitConverter.ToString(receiveData), System.DateTime.Now); + ReceiveDataChanged(this); + } + + } + } + if (data[7] == 0x84 & data[8] == 0x01) + { + if (debug) StoreLogData.Instance.Store("FunctionCodeNotSupportedException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.FunctionCodeNotSupportedException("Function code not supported by master"); + } + if (data[7] == 0x84 & data[8] == 0x02) + { + if (debug) StoreLogData.Instance.Store("StartingAddressInvalidException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.StartingAddressInvalidException("Starting address invalid or starting address + quantity invalid"); + } + if (data[7] == 0x84 & data[8] == 0x03) + { + if (debug) StoreLogData.Instance.Store("QuantityInvalidException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.QuantityInvalidException("quantity invalid"); + } + if (data[7] == 0x84 & data[8] == 0x04) + { + if (debug) StoreLogData.Instance.Store("ModbusException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.ModbusException("error reading"); + } + if (serialport != null) + { + crc = BitConverter.GetBytes(calculateCRC(data, (ushort)(data[8] + 3), 6)); + if ((crc[0] != data[data[8] + 9] | crc[1] != data[data[8] + 10]) & dataReceived) + { + if (debug) StoreLogData.Instance.Store("CRCCheckFailedException Throwed", System.DateTime.Now); + if (NumberOfRetries <= countRetries) + { + countRetries = 0; + throw new EasyModbus.Exceptions.CRCCheckFailedException("Response CRC check failed"); + } + else + { + countRetries++; + return ReadInputRegisters(startingAddress, quantity); + } + } + else if (!dataReceived) + { + if (debug) StoreLogData.Instance.Store("TimeoutException Throwed", System.DateTime.Now); + if (NumberOfRetries <= countRetries) + { + countRetries = 0; + throw new TimeoutException("No Response from Modbus Slave"); + + } + else + { + countRetries++; + return ReadInputRegisters(startingAddress, quantity); + } + + } + } + response = new int[quantity]; + for (int i = 0; i < quantity; i++) + { + byte lowByte; + byte highByte; + highByte = data[9 + i * 2]; + lowByte = data[9 + i * 2 + 1]; + + data[9 + i * 2] = lowByte; + data[9 + i * 2 + 1] = highByte; + + response[i] = BitConverter.ToInt16(data, (9 + i * 2)); + } + return (response); + } + + + /// + /// Write single Coil to Master device (FC5). + /// + /// Coil to be written + /// Coil Value to be written + public void WriteSingleCoil(int startingAddress, bool value) + { + + if (debug) StoreLogData.Instance.Store("FC5 (Write single coil to Master device), StartingAddress: " + startingAddress + ", Value: " + value, System.DateTime.Now); + transactionIdentifierInternal++; + if (serialport != null) + if (!serialport.IsOpen) + { + if (debug) StoreLogData.Instance.Store("SerialPortNotOpenedException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.SerialPortNotOpenedException("serial port not opened"); + } + if (tcpClient == null & !udpFlag & serialport == null) + { + if (debug) StoreLogData.Instance.Store("ConnectionException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.ConnectionException("connection error"); + } + byte[] coilValue = new byte[2]; + this.transactionIdentifier = BitConverter.GetBytes((uint)transactionIdentifierInternal); + this.protocolIdentifier = BitConverter.GetBytes((int)0x0000); + this.length = BitConverter.GetBytes((int)0x0006); + this.functionCode = 0x05; + this.startingAddress = BitConverter.GetBytes(startingAddress); + if (value == true) + { + coilValue = BitConverter.GetBytes((int)0xFF00); + } + else + { + coilValue = BitConverter.GetBytes((int)0x0000); + } + Byte[] data = new byte[]{ this.transactionIdentifier[1], + this.transactionIdentifier[0], + this.protocolIdentifier[1], + this.protocolIdentifier[0], + this.length[1], + this.length[0], + this.unitIdentifier, + this.functionCode, + this.startingAddress[1], + this.startingAddress[0], + coilValue[1], + coilValue[0], + this.crc[0], + this.crc[1] + }; + crc = BitConverter.GetBytes(calculateCRC(data, 6, 6)); + data[12] = crc[0]; + data[13] = crc[1]; + if (serialport != null) + { + dataReceived = false; + bytesToRead = 8; + // serialport.ReceivedBytesThreshold = bytesToRead; + serialport.Write(data, 6, 8); + if (debug) + { + byte[] debugData = new byte[8]; + Array.Copy(data, 6, debugData, 0, 8); + if (debug) StoreLogData.Instance.Store("Send Serial-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + if (SendDataChanged != null) + { + sendData = new byte[8]; + Array.Copy(data, 6, sendData, 0, 8); + SendDataChanged(this); + + } + data = new byte[2100]; + readBuffer = new byte[256]; + DateTime dateTimeSend = DateTime.Now; + byte receivedUnitIdentifier = 0xFF; + + SpinWait sw_delay = new SpinWait(); + while (receivedUnitIdentifier != this.unitIdentifier & !((DateTime.Now.Ticks - dateTimeSend.Ticks) > TimeSpan.TicksPerMillisecond * this.connectTimeout)) + { + while (dataReceived == false & !((DateTime.Now.Ticks - dateTimeSend.Ticks) > TimeSpan.TicksPerMillisecond * this.connectTimeout)) + sw_delay.SpinOnce(); + + data = new byte[2100]; + Array.Copy(readBuffer, 0, data, 6, readBuffer.Length); + receivedUnitIdentifier = data[6]; + } + + if (receivedUnitIdentifier != this.unitIdentifier) + data = new byte[2100]; + else + countRetries = 0; + + } + else if (tcpClient.Client.Connected | udpFlag) + { + if (udpFlag) + { + UdpClient udpClient = new UdpClient(); + IPEndPoint endPoint = new IPEndPoint(System.Net.IPAddress.Parse(ipAddress), port); + udpClient.Send(data, data.Length - 2, endPoint); + portOut = ((IPEndPoint)udpClient.Client.LocalEndPoint).Port; + udpClient.Client.ReceiveTimeout = 5000; + endPoint = new IPEndPoint(System.Net.IPAddress.Parse(ipAddress), portOut); + data = udpClient.Receive(ref endPoint); + } + else + { + stream.Write(data, 0, data.Length - 2); + if (debug) + { + byte[] debugData = new byte[data.Length - 2]; + Array.Copy(data, 0, debugData, 0, data.Length - 2); + if (debug) StoreLogData.Instance.Store("Send ModbusTCP-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + if (SendDataChanged != null) + { + sendData = new byte[data.Length - 2]; + Array.Copy(data, 0, sendData, 0, data.Length - 2); + SendDataChanged(this); + + } + data = new Byte[2100]; + int NumberOfBytes = stream.Read(data, 0, data.Length); + if (ReceiveDataChanged != null) + { + receiveData = new byte[NumberOfBytes]; + Array.Copy(data, 0, receiveData, 0, NumberOfBytes); + if (debug) StoreLogData.Instance.Store("Receive ModbusTCP-Data: " + BitConverter.ToString(receiveData), System.DateTime.Now); + ReceiveDataChanged(this); + } + } + } + if (data[7] == 0x85 & data[8] == 0x01) + { + if (debug) StoreLogData.Instance.Store("FunctionCodeNotSupportedException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.FunctionCodeNotSupportedException("Function code not supported by master"); + } + if (data[7] == 0x85 & data[8] == 0x02) + { + if (debug) StoreLogData.Instance.Store("StartingAddressInvalidException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.StartingAddressInvalidException("Starting address invalid or starting address + quantity invalid"); + } + if (data[7] == 0x85 & data[8] == 0x03) + { + if (debug) StoreLogData.Instance.Store("QuantityInvalidException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.QuantityInvalidException("quantity invalid"); + } + if (data[7] == 0x85 & data[8] == 0x04) + { + if (debug) StoreLogData.Instance.Store("ModbusException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.ModbusException("error reading"); + } + if (serialport != null) + { + crc = BitConverter.GetBytes(calculateCRC(data, 6, 6)); + if ((crc[0] != data[12] | crc[1] != data[13]) & dataReceived) + { + if (debug) StoreLogData.Instance.Store("CRCCheckFailedException Throwed", System.DateTime.Now); + if (NumberOfRetries <= countRetries) + { + countRetries = 0; + throw new EasyModbus.Exceptions.CRCCheckFailedException("Response CRC check failed"); + } + else + { + countRetries++; + WriteSingleCoil(startingAddress, value); + } + } + else if (!dataReceived) + { + if (debug) StoreLogData.Instance.Store("TimeoutException Throwed", System.DateTime.Now); + if (NumberOfRetries <= countRetries) + { + countRetries = 0; + throw new TimeoutException("No Response from Modbus Slave"); + + } + else + { + countRetries++; + WriteSingleCoil(startingAddress, value); + } + } + } + } + + + /// + /// Write single Register to Master device (FC6). + /// + /// Register to be written + /// Register Value to be written + public void WriteSingleRegister(int startingAddress, int value) + { + if (debug) StoreLogData.Instance.Store("FC6 (Write single register to Master device), StartingAddress: " + startingAddress + ", Value: " + value, System.DateTime.Now); + transactionIdentifierInternal++; + if (serialport != null) + if (!serialport.IsOpen) + { + if (debug) StoreLogData.Instance.Store("SerialPortNotOpenedException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.SerialPortNotOpenedException("serial port not opened"); + } + if (tcpClient == null & !udpFlag & serialport == null) + { + if (debug) StoreLogData.Instance.Store("ConnectionException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.ConnectionException("connection error"); + } + byte[] registerValue = new byte[2]; + this.transactionIdentifier = BitConverter.GetBytes((uint)transactionIdentifierInternal); + this.protocolIdentifier = BitConverter.GetBytes((int)0x0000); + this.length = BitConverter.GetBytes((int)0x0006); + this.functionCode = 0x06; + this.startingAddress = BitConverter.GetBytes(startingAddress); + registerValue = BitConverter.GetBytes((int)value); + + Byte[] data = new byte[]{ this.transactionIdentifier[1], + this.transactionIdentifier[0], + this.protocolIdentifier[1], + this.protocolIdentifier[0], + this.length[1], + this.length[0], + this.unitIdentifier, + this.functionCode, + this.startingAddress[1], + this.startingAddress[0], + registerValue[1], + registerValue[0], + this.crc[0], + this.crc[1] + }; + crc = BitConverter.GetBytes(calculateCRC(data, 6, 6)); + data[12] = crc[0]; + data[13] = crc[1]; + if (serialport != null) + { + dataReceived = false; + bytesToRead = 8; + // serialport.ReceivedBytesThreshold = bytesToRead; + serialport.Write(data, 6, 8); + if (debug) + { + byte[] debugData = new byte[8]; + Array.Copy(data, 6, debugData, 0, 8); + if (debug) StoreLogData.Instance.Store("Send Serial-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + if (SendDataChanged != null) + { + sendData = new byte[8]; + Array.Copy(data, 6, sendData, 0, 8); + SendDataChanged(this); + + } + data = new byte[2100]; + readBuffer = new byte[256]; + DateTime dateTimeSend = DateTime.Now; + byte receivedUnitIdentifier = 0xFF; + + SpinWait sw_delay = new SpinWait(); + while (receivedUnitIdentifier != this.unitIdentifier & !((DateTime.Now.Ticks - dateTimeSend.Ticks) > TimeSpan.TicksPerMillisecond * this.connectTimeout)) + { + while (dataReceived == false & !((DateTime.Now.Ticks - dateTimeSend.Ticks) > TimeSpan.TicksPerMillisecond * this.connectTimeout)) + sw_delay.SpinOnce(); + + data = new byte[2100]; + Array.Copy(readBuffer, 0, data, 6, readBuffer.Length); + receivedUnitIdentifier = data[6]; + } + if (receivedUnitIdentifier != this.unitIdentifier) + data = new byte[2100]; + else + countRetries = 0; + } + else if (tcpClient.Client.Connected | udpFlag) + { + if (udpFlag) + { + UdpClient udpClient = new UdpClient(); + IPEndPoint endPoint = new IPEndPoint(System.Net.IPAddress.Parse(ipAddress), port); + udpClient.Send(data, data.Length - 2, endPoint); + portOut = ((IPEndPoint)udpClient.Client.LocalEndPoint).Port; + udpClient.Client.ReceiveTimeout = 5000; + endPoint = new IPEndPoint(System.Net.IPAddress.Parse(ipAddress), portOut); + data = udpClient.Receive(ref endPoint); + } + else + { + stream.Write(data, 0, data.Length - 2); + if (debug) + { + byte[] debugData = new byte[data.Length - 2]; + Array.Copy(data, 0, debugData, 0, data.Length - 2); + if (debug) StoreLogData.Instance.Store("Send ModbusTCP-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + if (SendDataChanged != null) + { + sendData = new byte[data.Length - 2]; + Array.Copy(data, 0, sendData, 0, data.Length - 2); + SendDataChanged(this); + + } + data = new Byte[2100]; + int NumberOfBytes = stream.Read(data, 0, data.Length); + if (ReceiveDataChanged != null) + { + receiveData = new byte[NumberOfBytes]; + Array.Copy(data, 0, receiveData, 0, NumberOfBytes); + if (debug) StoreLogData.Instance.Store("Receive ModbusTCP-Data: " + BitConverter.ToString(receiveData), System.DateTime.Now); + ReceiveDataChanged(this); + } + } + } + if (data[7] == 0x86 & data[8] == 0x01) + { + if (debug) StoreLogData.Instance.Store("FunctionCodeNotSupportedException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.FunctionCodeNotSupportedException("Function code not supported by master"); + } + if (data[7] == 0x86 & data[8] == 0x02) + { + if (debug) StoreLogData.Instance.Store("StartingAddressInvalidException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.StartingAddressInvalidException("Starting address invalid or starting address + quantity invalid"); + } + if (data[7] == 0x86 & data[8] == 0x03) + { + if (debug) StoreLogData.Instance.Store("QuantityInvalidException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.QuantityInvalidException("quantity invalid"); + } + if (data[7] == 0x86 & data[8] == 0x04) + { + if (debug) StoreLogData.Instance.Store("ModbusException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.ModbusException("error reading"); + } + if (serialport != null) + { + crc = BitConverter.GetBytes(calculateCRC(data, 6, 6)); + if ((crc[0] != data[12] | crc[1] != data[13]) & dataReceived) + { + if (debug) StoreLogData.Instance.Store("CRCCheckFailedException Throwed", System.DateTime.Now); + if (NumberOfRetries <= countRetries) + { + countRetries = 0; + throw new EasyModbus.Exceptions.CRCCheckFailedException("Response CRC check failed"); + } + else + { + countRetries++; + WriteSingleRegister(startingAddress, value); + } + } + else if (!dataReceived) + { + if (debug) StoreLogData.Instance.Store("TimeoutException Throwed", System.DateTime.Now); + if (NumberOfRetries <= countRetries) + { + countRetries = 0; + throw new TimeoutException("No Response from Modbus Slave"); + + } + else + { + countRetries++; + WriteSingleRegister(startingAddress, value); + } + } + } + } + + /// + /// Write multiple coils to Master device (FC15). + /// + /// First coil to be written + /// Coil Values to be written + public void WriteMultipleCoils(int startingAddress, bool[] values) + { + string debugString = ""; + for (int i = 0; i < values.Length; i++) + debugString = debugString + values[i] + " "; + if (debug) StoreLogData.Instance.Store("FC15 (Write multiple coils to Master device), StartingAddress: " + startingAddress + ", Values: " + debugString, System.DateTime.Now); + transactionIdentifierInternal++; + byte byteCount = (byte)((values.Length % 8 != 0 ? values.Length / 8 + 1 : (values.Length / 8))); + byte[] quantityOfOutputs = BitConverter.GetBytes((int)values.Length); + byte singleCoilValue = 0; + if (serialport != null) + if (!serialport.IsOpen) + { + if (debug) StoreLogData.Instance.Store("SerialPortNotOpenedException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.SerialPortNotOpenedException("serial port not opened"); + } + if (tcpClient == null & !udpFlag & serialport == null) + { + if (debug) StoreLogData.Instance.Store("ConnectionException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.ConnectionException("connection error"); + } + this.transactionIdentifier = BitConverter.GetBytes((uint)transactionIdentifierInternal); + this.protocolIdentifier = BitConverter.GetBytes((int)0x0000); + this.length = BitConverter.GetBytes((int)(7 + (byteCount))); + this.functionCode = 0x0F; + this.startingAddress = BitConverter.GetBytes(startingAddress); + + + + Byte[] data = new byte[14 + 2 + (values.Length % 8 != 0 ? values.Length / 8 : (values.Length / 8) - 1)]; + data[0] = this.transactionIdentifier[1]; + data[1] = this.transactionIdentifier[0]; + data[2] = this.protocolIdentifier[1]; + data[3] = this.protocolIdentifier[0]; + data[4] = this.length[1]; + data[5] = this.length[0]; + data[6] = this.unitIdentifier; + data[7] = this.functionCode; + data[8] = this.startingAddress[1]; + data[9] = this.startingAddress[0]; + data[10] = quantityOfOutputs[1]; + data[11] = quantityOfOutputs[0]; + data[12] = byteCount; + for (int i = 0; i < values.Length; i++) + { + if ((i % 8) == 0) + singleCoilValue = 0; + byte CoilValue; + if (values[i] == true) + CoilValue = 1; + else + CoilValue = 0; + + + singleCoilValue = (byte)((int)CoilValue << (i % 8) | (int)singleCoilValue); + + data[13 + (i / 8)] = singleCoilValue; + } + crc = BitConverter.GetBytes(calculateCRC(data, (ushort)(data.Length - 8), 6)); + data[data.Length - 2] = crc[0]; + data[data.Length - 1] = crc[1]; + if (serialport != null) + { + dataReceived = false; + bytesToRead = 8; + // serialport.ReceivedBytesThreshold = bytesToRead; + serialport.Write(data, 6, data.Length - 6); + if (debug) + { + byte[] debugData = new byte[data.Length - 6]; + Array.Copy(data, 6, debugData, 0, data.Length - 6); + if (debug) StoreLogData.Instance.Store("Send Serial-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + if (SendDataChanged != null) + { + sendData = new byte[data.Length - 6]; + Array.Copy(data, 6, sendData, 0, data.Length - 6); + SendDataChanged(this); + + } + data = new byte[2100]; + readBuffer = new byte[256]; + DateTime dateTimeSend = DateTime.Now; + byte receivedUnitIdentifier = 0xFF; + + SpinWait sw_delay = new SpinWait(); + while (receivedUnitIdentifier != this.unitIdentifier & !((DateTime.Now.Ticks - dateTimeSend.Ticks) > TimeSpan.TicksPerMillisecond * this.connectTimeout)) + { + while (dataReceived == false & !((DateTime.Now.Ticks - dateTimeSend.Ticks) > TimeSpan.TicksPerMillisecond * this.connectTimeout)) + sw_delay.SpinOnce(); + + data = new byte[2100]; + Array.Copy(readBuffer, 0, data, 6, readBuffer.Length); + receivedUnitIdentifier = data[6]; + } + if (receivedUnitIdentifier != this.unitIdentifier) + data = new byte[2100]; + else + countRetries = 0; + } + else if (tcpClient.Client.Connected | udpFlag) + { + if (udpFlag) + { + UdpClient udpClient = new UdpClient(); + IPEndPoint endPoint = new IPEndPoint(System.Net.IPAddress.Parse(ipAddress), port); + udpClient.Send(data, data.Length - 2, endPoint); + portOut = ((IPEndPoint)udpClient.Client.LocalEndPoint).Port; + udpClient.Client.ReceiveTimeout = 5000; + endPoint = new IPEndPoint(System.Net.IPAddress.Parse(ipAddress), portOut); + data = udpClient.Receive(ref endPoint); + } + else + { + stream.Write(data, 0, data.Length - 2); + if (debug) + { + byte[] debugData = new byte[data.Length - 2]; + Array.Copy(data, 0, debugData, 0, data.Length - 2); + if (debug) StoreLogData.Instance.Store("Send ModbusTCP-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + if (SendDataChanged != null) + { + sendData = new byte[data.Length - 2]; + Array.Copy(data, 0, sendData, 0, data.Length - 2); + SendDataChanged(this); + + } + data = new Byte[2100]; + int NumberOfBytes = stream.Read(data, 0, data.Length); + if (ReceiveDataChanged != null) + { + receiveData = new byte[NumberOfBytes]; + Array.Copy(data, 0, receiveData, 0, NumberOfBytes); + if (debug) StoreLogData.Instance.Store("Receive ModbusTCP-Data: " + BitConverter.ToString(receiveData), System.DateTime.Now); + ReceiveDataChanged(this); + } + } + } + if (data[7] == 0x8F & data[8] == 0x01) + { + if (debug) StoreLogData.Instance.Store("FunctionCodeNotSupportedException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.FunctionCodeNotSupportedException("Function code not supported by master"); + } + if (data[7] == 0x8F & data[8] == 0x02) + { + if (debug) StoreLogData.Instance.Store("StartingAddressInvalidException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.StartingAddressInvalidException("Starting address invalid or starting address + quantity invalid"); + } + if (data[7] == 0x8F & data[8] == 0x03) + { + if (debug) StoreLogData.Instance.Store("QuantityInvalidException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.QuantityInvalidException("quantity invalid"); + } + if (data[7] == 0x8F & data[8] == 0x04) + { + if (debug) StoreLogData.Instance.Store("ModbusException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.ModbusException("error reading"); + } + if (serialport != null) + { + crc = BitConverter.GetBytes(calculateCRC(data, 6, 6)); + if ((crc[0] != data[12] | crc[1] != data[13]) & dataReceived) + { + if (debug) StoreLogData.Instance.Store("CRCCheckFailedException Throwed", System.DateTime.Now); + if (NumberOfRetries <= countRetries) + { + countRetries = 0; + throw new EasyModbus.Exceptions.CRCCheckFailedException("Response CRC check failed"); + } + else + { + countRetries++; + WriteMultipleCoils(startingAddress, values); + } + } + else if (!dataReceived) + { + if (debug) StoreLogData.Instance.Store("TimeoutException Throwed", System.DateTime.Now); + if (NumberOfRetries <= countRetries) + { + countRetries = 0; + throw new TimeoutException("No Response from Modbus Slave"); + + } + else + { + countRetries++; + WriteMultipleCoils(startingAddress, values); + } + } + } + } + + /// + /// Write multiple registers to Master device (FC16). + /// + /// First register to be written + /// register Values to be written + public void WriteMultipleRegisters(int startingAddress, int[] values) + { + string debugString = ""; + for (int i = 0; i < values.Length; i++) + debugString = debugString + values[i] + " "; + if (debug) StoreLogData.Instance.Store("FC16 (Write multiple Registers to Server device), StartingAddress: " + startingAddress + ", Values: " + debugString, System.DateTime.Now); + transactionIdentifierInternal++; + byte byteCount = (byte)(values.Length * 2); + byte[] quantityOfOutputs = BitConverter.GetBytes((int)values.Length); + if (serialport != null) + if (!serialport.IsOpen) + { + if (debug) StoreLogData.Instance.Store("SerialPortNotOpenedException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.SerialPortNotOpenedException("serial port not opened"); + } + if (tcpClient == null & !udpFlag & serialport == null) + { + if (debug) StoreLogData.Instance.Store("ConnectionException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.ConnectionException("connection error"); + } + this.transactionIdentifier = BitConverter.GetBytes((uint)transactionIdentifierInternal); + this.protocolIdentifier = BitConverter.GetBytes((int)0x0000); + this.length = BitConverter.GetBytes((int)(7 + values.Length * 2)); + this.functionCode = 0x10; + this.startingAddress = BitConverter.GetBytes(startingAddress); + + Byte[] data = new byte[13 + 2 + values.Length * 2]; + data[0] = this.transactionIdentifier[1]; + data[1] = this.transactionIdentifier[0]; + data[2] = this.protocolIdentifier[1]; + data[3] = this.protocolIdentifier[0]; + data[4] = this.length[1]; + data[5] = this.length[0]; + data[6] = this.unitIdentifier; + data[7] = this.functionCode; + data[8] = this.startingAddress[1]; + data[9] = this.startingAddress[0]; + data[10] = quantityOfOutputs[1]; + data[11] = quantityOfOutputs[0]; + data[12] = byteCount; + for (int i = 0; i < values.Length; i++) + { + byte[] singleRegisterValue = BitConverter.GetBytes((int)values[i]); + data[13 + i * 2] = singleRegisterValue[1]; + data[14 + i * 2] = singleRegisterValue[0]; + } + crc = BitConverter.GetBytes(calculateCRC(data, (ushort)(data.Length - 8), 6)); + data[data.Length - 2] = crc[0]; + data[data.Length - 1] = crc[1]; + if (serialport != null) + { + dataReceived = false; + bytesToRead = 8; + // serialport.ReceivedBytesThreshold = bytesToRead; + serialport.Write(data, 6, data.Length - 6); + + if (debug) + { + byte[] debugData = new byte[data.Length - 6]; + Array.Copy(data, 6, debugData, 0, data.Length - 6); + if (debug) StoreLogData.Instance.Store("Send Serial-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + if (SendDataChanged != null) + { + sendData = new byte[data.Length - 6]; + Array.Copy(data, 6, sendData, 0, data.Length - 6); + SendDataChanged(this); + + } + data = new byte[2100]; + readBuffer = new byte[256]; + DateTime dateTimeSend = DateTime.Now; + byte receivedUnitIdentifier = 0xFF; + + SpinWait sw_delay = new SpinWait(); + while (receivedUnitIdentifier != this.unitIdentifier & !((DateTime.Now.Ticks - dateTimeSend.Ticks) > TimeSpan.TicksPerMillisecond * this.connectTimeout)) + { + while (dataReceived == false & !((DateTime.Now.Ticks - dateTimeSend.Ticks) > TimeSpan.TicksPerMillisecond * this.connectTimeout)) + sw_delay.SpinOnce(); + + data = new byte[2100]; + Array.Copy(readBuffer, 0, data, 6, readBuffer.Length); + receivedUnitIdentifier = data[6]; + } + if (receivedUnitIdentifier != this.unitIdentifier) + data = new byte[2100]; + else + countRetries = 0; + } + else if (tcpClient.Client.Connected | udpFlag) + { + if (udpFlag) + { + UdpClient udpClient = new UdpClient(); + IPEndPoint endPoint = new IPEndPoint(System.Net.IPAddress.Parse(ipAddress), port); + udpClient.Send(data, data.Length - 2, endPoint); + portOut = ((IPEndPoint)udpClient.Client.LocalEndPoint).Port; + udpClient.Client.ReceiveTimeout = 5000; + endPoint = new IPEndPoint(System.Net.IPAddress.Parse(ipAddress), portOut); + data = udpClient.Receive(ref endPoint); + } + else + { + stream.Write(data, 0, data.Length - 2); + if (debug) + { + byte[] debugData = new byte[data.Length - 2]; + Array.Copy(data, 0, debugData, 0, data.Length - 2); + if (debug) StoreLogData.Instance.Store("Send ModbusTCP-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + if (SendDataChanged != null) + { + sendData = new byte[data.Length - 2]; + Array.Copy(data, 0, sendData, 0, data.Length - 2); + SendDataChanged(this); + } + data = new Byte[2100]; + int NumberOfBytes = stream.Read(data, 0, data.Length); + if (ReceiveDataChanged != null) + { + receiveData = new byte[NumberOfBytes]; + Array.Copy(data, 0, receiveData, 0, NumberOfBytes); + if (debug) StoreLogData.Instance.Store("Receive ModbusTCP-Data: " + BitConverter.ToString(receiveData), System.DateTime.Now); + ReceiveDataChanged(this); + } + } + } + if (data[7] == 0x90 & data[8] == 0x01) + { + if (debug) StoreLogData.Instance.Store("FunctionCodeNotSupportedException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.FunctionCodeNotSupportedException("Function code not supported by master"); + } + if (data[7] == 0x90 & data[8] == 0x02) + { + if (debug) StoreLogData.Instance.Store("StartingAddressInvalidException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.StartingAddressInvalidException("Starting address invalid or starting address + quantity invalid"); + } + if (data[7] == 0x90 & data[8] == 0x03) + { + if (debug) StoreLogData.Instance.Store("QuantityInvalidException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.QuantityInvalidException("quantity invalid"); + } + if (data[7] == 0x90 & data[8] == 0x04) + { + if (debug) StoreLogData.Instance.Store("ModbusException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.ModbusException("error reading"); + } + if (serialport != null) + { + crc = BitConverter.GetBytes(calculateCRC(data, 6, 6)); + if ((crc[0] != data[12] | crc[1] != data[13]) & dataReceived) + { + if (debug) StoreLogData.Instance.Store("CRCCheckFailedException Throwed", System.DateTime.Now); + if (NumberOfRetries <= countRetries) + { + countRetries = 0; + throw new EasyModbus.Exceptions.CRCCheckFailedException("Response CRC check failed"); + } + else + { + countRetries++; + WriteMultipleRegisters(startingAddress, values); + } + } + else if (!dataReceived) + { + if (debug) StoreLogData.Instance.Store("TimeoutException Throwed", System.DateTime.Now); + if (NumberOfRetries <= countRetries) + { + countRetries = 0; + throw new TimeoutException("No Response from Modbus Slave"); + + } + else + { + countRetries++; + WriteMultipleRegisters(startingAddress, values); + } + } + } + } + + /// + /// Read/Write Multiple Registers (FC23). + /// + /// First input register to read + /// Number of input registers to read + /// First input register to write + /// Values to write + /// Int Array which contains the Holding registers + public int[] ReadWriteMultipleRegisters(int startingAddressRead, int quantityRead, int startingAddressWrite, int[] values) + { + + string debugString = ""; + for (int i = 0; i < values.Length; i++) + debugString = debugString + values[i] + " "; + if (debug) StoreLogData.Instance.Store("FC23 (Read and Write multiple Registers to Server device), StartingAddress Read: " + startingAddressRead + ", Quantity Read: " + quantityRead + ", startingAddressWrite: " + startingAddressWrite + ", Values: " + debugString, System.DateTime.Now); + transactionIdentifierInternal++; + byte[] startingAddressReadLocal = new byte[2]; + byte[] quantityReadLocal = new byte[2]; + byte[] startingAddressWriteLocal = new byte[2]; + byte[] quantityWriteLocal = new byte[2]; + byte writeByteCountLocal = 0; + if (serialport != null) + if (!serialport.IsOpen) + { + if (debug) StoreLogData.Instance.Store("SerialPortNotOpenedException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.SerialPortNotOpenedException("serial port not opened"); + } + if (tcpClient == null & !udpFlag & serialport == null) + { + if (debug) StoreLogData.Instance.Store("ConnectionException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.ConnectionException("connection error"); + } + if (startingAddressRead > 65535 | quantityRead > 125 | startingAddressWrite > 65535 | values.Length > 121) + { + if (debug) StoreLogData.Instance.Store("ArgumentException Throwed", System.DateTime.Now); + throw new ArgumentException("Starting address must be 0 - 65535; quantity must be 0 - 2000"); + } + int[] response; + this.transactionIdentifier = BitConverter.GetBytes((uint)transactionIdentifierInternal); + this.protocolIdentifier = BitConverter.GetBytes((int)0x0000); + this.length = BitConverter.GetBytes((int)11 + values.Length * 2); + this.functionCode = 0x17; + startingAddressReadLocal = BitConverter.GetBytes(startingAddressRead); + quantityReadLocal = BitConverter.GetBytes(quantityRead); + startingAddressWriteLocal = BitConverter.GetBytes(startingAddressWrite); + quantityWriteLocal = BitConverter.GetBytes(values.Length); + writeByteCountLocal = Convert.ToByte(values.Length * 2); + Byte[] data = new byte[17 + 2 + values.Length * 2]; + data[0] = this.transactionIdentifier[1]; + data[1] = this.transactionIdentifier[0]; + data[2] = this.protocolIdentifier[1]; + data[3] = this.protocolIdentifier[0]; + data[4] = this.length[1]; + data[5] = this.length[0]; + data[6] = this.unitIdentifier; + data[7] = this.functionCode; + data[8] = startingAddressReadLocal[1]; + data[9] = startingAddressReadLocal[0]; + data[10] = quantityReadLocal[1]; + data[11] = quantityReadLocal[0]; + data[12] = startingAddressWriteLocal[1]; + data[13] = startingAddressWriteLocal[0]; + data[14] = quantityWriteLocal[1]; + data[15] = quantityWriteLocal[0]; + data[16] = writeByteCountLocal; + + for (int i = 0; i < values.Length; i++) + { + byte[] singleRegisterValue = BitConverter.GetBytes((int)values[i]); + data[17 + i * 2] = singleRegisterValue[1]; + data[18 + i * 2] = singleRegisterValue[0]; + } + crc = BitConverter.GetBytes(calculateCRC(data, (ushort)(data.Length - 8), 6)); + data[data.Length - 2] = crc[0]; + data[data.Length - 1] = crc[1]; + if (serialport != null) + { + dataReceived = false; + bytesToRead = 5 + 2 * quantityRead; + // serialport.ReceivedBytesThreshold = bytesToRead; + serialport.Write(data, 6, data.Length - 6); + if (debug) + { + byte[] debugData = new byte[data.Length - 6]; + Array.Copy(data, 6, debugData, 0, data.Length - 6); + if (debug) StoreLogData.Instance.Store("Send Serial-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + if (SendDataChanged != null) + { + sendData = new byte[data.Length - 6]; + Array.Copy(data, 6, sendData, 0, data.Length - 6); + SendDataChanged(this); + } + data = new byte[2100]; + readBuffer = new byte[256]; + DateTime dateTimeSend = DateTime.Now; + byte receivedUnitIdentifier = 0xFF; + + SpinWait sw_delay = new SpinWait(); + while (receivedUnitIdentifier != this.unitIdentifier & !((DateTime.Now.Ticks - dateTimeSend.Ticks) > TimeSpan.TicksPerMillisecond * this.connectTimeout)) + { + while (dataReceived == false & !((DateTime.Now.Ticks - dateTimeSend.Ticks) > TimeSpan.TicksPerMillisecond * this.connectTimeout)) + sw_delay.SpinOnce(); + + data = new byte[2100]; + Array.Copy(readBuffer, 0, data, 6, readBuffer.Length); + receivedUnitIdentifier = data[6]; + } + if (receivedUnitIdentifier != this.unitIdentifier) + data = new byte[2100]; + else + countRetries = 0; + } + else if (tcpClient.Client.Connected | udpFlag) + { + if (udpFlag) + { + UdpClient udpClient = new UdpClient(); + IPEndPoint endPoint = new IPEndPoint(System.Net.IPAddress.Parse(ipAddress), port); + udpClient.Send(data, data.Length - 2, endPoint); + portOut = ((IPEndPoint)udpClient.Client.LocalEndPoint).Port; + udpClient.Client.ReceiveTimeout = 5000; + endPoint = new IPEndPoint(System.Net.IPAddress.Parse(ipAddress), portOut); + data = udpClient.Receive(ref endPoint); + } + else + { + stream.Write(data, 0, data.Length - 2); + if (debug) + { + byte[] debugData = new byte[data.Length - 2]; + Array.Copy(data, 0, debugData, 0, data.Length - 2); + if (debug) StoreLogData.Instance.Store("Send ModbusTCP-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + if (SendDataChanged != null) + { + sendData = new byte[data.Length - 2]; + Array.Copy(data, 0, sendData, 0, data.Length - 2); + SendDataChanged(this); + + } + data = new Byte[2100]; + int NumberOfBytes = stream.Read(data, 0, data.Length); + if (ReceiveDataChanged != null) + { + receiveData = new byte[NumberOfBytes]; + Array.Copy(data, 0, receiveData, 0, NumberOfBytes); + if (debug) StoreLogData.Instance.Store("Receive ModbusTCP-Data: " + BitConverter.ToString(receiveData), System.DateTime.Now); + ReceiveDataChanged(this); + } + } + } + if (data[7] == 0x97 & data[8] == 0x01) + { + if (debug) StoreLogData.Instance.Store("FunctionCodeNotSupportedException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.FunctionCodeNotSupportedException("Function code not supported by master"); + } + if (data[7] == 0x97 & data[8] == 0x02) + { + if (debug) StoreLogData.Instance.Store("StartingAddressInvalidException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.StartingAddressInvalidException("Starting address invalid or starting address + quantity invalid"); + } + if (data[7] == 0x97 & data[8] == 0x03) + { + if (debug) StoreLogData.Instance.Store("QuantityInvalidException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.QuantityInvalidException("quantity invalid"); + } + if (data[7] == 0x97 & data[8] == 0x04) + { + if (debug) StoreLogData.Instance.Store("ModbusException Throwed", System.DateTime.Now); + throw new EasyModbus.Exceptions.ModbusException("error reading"); + } + response = new int[quantityRead]; + for (int i = 0; i < quantityRead; i++) + { + byte lowByte; + byte highByte; + highByte = data[9 + i * 2]; + lowByte = data[9 + i * 2 + 1]; + + data[9 + i * 2] = lowByte; + data[9 + i * 2 + 1] = highByte; + + response[i] = BitConverter.ToInt16(data, (9 + i * 2)); + } + return (response); + } + + /// + /// Close connection to Master Device. + /// + public void Disconnect() + { + if (debug) StoreLogData.Instance.Store("Disconnect", System.DateTime.Now); + if (serialport != null) + { + if (serialport.IsOpen & !this.receiveActive) + serialport.Close(); + if (ConnectedChanged != null) + ConnectedChanged(this); + return; + } + if (stream != null) + stream.Close(); + if (tcpClient != null) + tcpClient.Close(); + connected = false; + if (ConnectedChanged != null) + ConnectedChanged(this); + + } + + /// + /// Destructor - Close connection to Master Device. + /// + ~ModbusClient() + { + if (debug) StoreLogData.Instance.Store("Destructor called - automatically disconnect", System.DateTime.Now); + if (serialport != null) + { + if (serialport.IsOpen) + serialport.Close(); + return; + } + if (tcpClient != null & !udpFlag) + { + if (stream != null) + stream.Close(); + tcpClient.Close(); + } + } + + /// + /// Returns "TRUE" if Client is connected to Server and "FALSE" if not. In case of Modbus RTU returns if COM-Port is opened + /// + public bool Connected + { + get + { + if (serialport != null) + { + return (serialport.IsOpen); + } + + if (udpFlag & tcpClient != null) + return true; + if (tcpClient == null) + return false; + else + { + return connected; + + } + + } + } + + public bool Available(int timeout) + { + // Ping's the local machine. + System.Net.NetworkInformation.Ping pingSender = new System.Net.NetworkInformation.Ping(); + IPAddress address = System.Net.IPAddress.Parse(ipAddress); + + // Create a buffer of 32 bytes of data to be transmitted. + string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + byte[] buffer = System.Text.Encoding.ASCII.GetBytes(data); + + // Wait 10 seconds for a reply. + System.Net.NetworkInformation.PingReply reply = pingSender.Send(address, timeout, buffer); + + if (reply.Status == System.Net.NetworkInformation.IPStatus.Success) + return true; + else + return false; + } + + /// + /// Gets or Sets the IP-Address of the Server. + /// + public string IPAddress + { + get + { + return ipAddress; + } + set + { + ipAddress = value; + } + } + + /// + /// Gets or Sets the Port were the Modbus-TCP Server is reachable (Standard is 502). + /// + public int Port + { + get + { + return port; + } + set + { + port = value; + } + } + + /// + /// Gets or Sets the UDP-Flag to activate Modbus UDP. + /// + public bool UDPFlag + { + get + { + return udpFlag; + } + set + { + udpFlag = value; + } + } + + /// + /// Gets or Sets the Unit identifier in case of serial connection (Default = 0) + /// + public byte UnitIdentifier + { + get + { + return unitIdentifier; + } + set + { + unitIdentifier = value; + } + } + + + /// + /// Gets or Sets the Baudrate for serial connection (Default = 9600) + /// + public int Baudrate + { + get + { + return baudRate; + } + set + { + baudRate = value; + } + } + + /// + /// Gets or Sets the of Parity in case of serial connection + /// + public Parity Parity + { + get + { + if (serialport != null) + return parity; + else + return Parity.Even; + } + set + { + if (serialport != null) + parity = value; + } + } + + + /// + /// Gets or Sets the number of stopbits in case of serial connection + /// + public StopBits StopBits + { + get + { + if (serialport != null) + return stopBits; + else + return StopBits.One; + } + set + { + if (serialport != null) + stopBits = value; + } + } + + /// + /// Gets or Sets the connection Timeout in case of ModbusTCP connection + /// + public int ConnectionTimeout + { + get + { + return connectTimeout; + } + set + { + connectTimeout = value; + } + } + + /// + /// Gets or Sets the serial Port + /// + public string SerialPort + { + get + { + + return serialport.PortName; + } + set + { + if (value == null) + { + serialport = null; + return; + } + if (serialport != null) + serialport.Close(); + this.serialport = new SerialPort(); + this.serialport.PortName = value; + serialport.BaudRate = baudRate; + serialport.Parity = parity; + serialport.StopBits = stopBits; + serialport.WriteTimeout = 10000; + serialport.ReadTimeout = connectTimeout; + serialport.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler); + } + } + + /// + /// Gets or Sets the Filename for the LogFile + /// + public string LogFileFilename + { + get + { + return StoreLogData.Instance.Filename; + } + set + { + StoreLogData.Instance.Filename = value; + if (StoreLogData.Instance.Filename != null) + debug = true; + else + debug = false; + } + } + + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/Modbus/EasyModbus/ModbusServer.cs b/常用工具集/Utility/Network/Modbus/EasyModbus/ModbusServer.cs new file mode 100644 index 0000000..19ebe37 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/EasyModbus/ModbusServer.cs @@ -0,0 +1,2277 @@ +/* +Copyright (c) 2018-2020 Rossmann-Engineering +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software +and associated documentation files (the "Software"), +to deal in the Software without restriction, +including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission +notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Net.Sockets; +using System.Net; +using System.Threading; +using System.Net.NetworkInformation; +using System.IO.Ports; + +namespace EasyModbus +{ + #region class ModbusProtocol + /// + /// Modbus Protocol informations. + /// + public class ModbusProtocol + { + public enum ProtocolType { ModbusTCP = 0, ModbusUDP = 1, ModbusRTU = 2 }; + public DateTime timeStamp; + public bool request; + public bool response; + public UInt16 transactionIdentifier; + public UInt16 protocolIdentifier; + public UInt16 length; + public byte unitIdentifier; + public byte functionCode; + public UInt16 startingAdress; + public UInt16 startingAddressRead; + public UInt16 startingAddressWrite; + public UInt16 quantity; + public UInt16 quantityRead; + public UInt16 quantityWrite; + public byte byteCount; + public byte exceptionCode; + public byte errorCode; + public UInt16[] receiveCoilValues; + public UInt16[] receiveRegisterValues; + public Int16[] sendRegisterValues; + public bool[] sendCoilValues; + public UInt16 crc; + } + #endregion + + #region structs + struct NetworkConnectionParameter + { + public NetworkStream stream; //For TCP-Connection only + public Byte[] bytes; + public int portIn; //For UDP-Connection only + public IPAddress ipAddressIn; //For UDP-Connection only + } + #endregion + + #region TCPHandler class + internal class TCPHandler + { + public delegate void DataChanged(object networkConnectionParameter); + public event DataChanged dataChanged; + + public delegate void NumberOfClientsChanged(); + public event NumberOfClientsChanged numberOfClientsChanged; + + TcpListener server = null; + + + private List tcpClientLastRequestList = new List(); + + public int NumberOfConnectedClients { get; set; } + + public string ipAddress = null; + + /// When making a server TCP listen socket, will listen to this IP address. + public IPAddress LocalIPAddress + { + get { return localIPAddress; } + } + private IPAddress localIPAddress = IPAddress.Any; + + /// + /// Listen to all network interfaces. + /// + /// TCP port to listen + public TCPHandler(int port) + { + server = new TcpListener(LocalIPAddress, port); + server.Start(); + server.BeginAcceptTcpClient(AcceptTcpClientCallback, null); + } + + /// + /// Listen to a specific network interface. + /// + /// IP address of network interface to listen + /// TCP port to listen + public TCPHandler(IPAddress localIPAddress, int port) + { + this.localIPAddress = localIPAddress; + server = new TcpListener(LocalIPAddress, port); + server.Start(); + server.BeginAcceptTcpClient(AcceptTcpClientCallback, null); + } + + + private void AcceptTcpClientCallback(IAsyncResult asyncResult) + { + TcpClient tcpClient = new TcpClient(); + try + { + tcpClient = server.EndAcceptTcpClient(asyncResult); + tcpClient.ReceiveTimeout = 4000; + if (ipAddress != null) + { + string ipEndpoint = tcpClient.Client.RemoteEndPoint.ToString(); + ipEndpoint = ipEndpoint.Split(':')[0]; + if (ipEndpoint != ipAddress) + { + tcpClient.Client.Disconnect(false); + return; + } + } + } + catch (Exception) { } + try + { + server.BeginAcceptTcpClient(AcceptTcpClientCallback, null); + Client client = new Client(tcpClient); + NetworkStream networkStream = client.NetworkStream; + networkStream.ReadTimeout = 4000; + networkStream.BeginRead(client.Buffer, 0, client.Buffer.Length, ReadCallback, client); + } + catch (Exception) { } + } + + private int GetAndCleanNumberOfConnectedClients(Client client) + { + lock (this) + { + int i = 0; + bool objetExists = false; + foreach (Client clientLoop in tcpClientLastRequestList) + { + if (client.Equals(clientLoop)) + objetExists = true; + } + try + { + tcpClientLastRequestList.RemoveAll(delegate (Client c) + { + return ((DateTime.Now.Ticks - c.Ticks) > 40000000); + } + + ); + } + catch (Exception) { } + if (!objetExists) + tcpClientLastRequestList.Add(client); + + + return tcpClientLastRequestList.Count; + } + } + + private void ReadCallback(IAsyncResult asyncResult) + { + NetworkConnectionParameter networkConnectionParameter = new NetworkConnectionParameter(); + Client client = asyncResult.AsyncState as Client; + client.Ticks = DateTime.Now.Ticks; + NumberOfConnectedClients = GetAndCleanNumberOfConnectedClients(client); + if (numberOfClientsChanged != null) + numberOfClientsChanged(); + if (client != null) + { + int read; + NetworkStream networkStream = null; + try + { + networkStream = client.NetworkStream; + + read = networkStream.EndRead(asyncResult); + } + catch (Exception ex) + { + return; + } + + + if (read == 0) + { + //OnClientDisconnected(client.TcpClient); + //connectedClients.Remove(client); + return; + } + byte[] data = new byte[read]; + Buffer.BlockCopy(client.Buffer, 0, data, 0, read); + networkConnectionParameter.bytes = data; + networkConnectionParameter.stream = networkStream; + if (dataChanged != null) + dataChanged(networkConnectionParameter); + try + { + networkStream.BeginRead(client.Buffer, 0, client.Buffer.Length, ReadCallback, client); + } + catch (Exception) + { + } + } + } + + public void Disconnect() + { + try + { + foreach (Client clientLoop in tcpClientLastRequestList) + { + clientLoop.NetworkStream.Close(00); + } + } + catch (Exception) { } + server.Stop(); + + } + + + internal class Client + { + private readonly TcpClient tcpClient; + private readonly byte[] buffer; + public long Ticks { get; set; } + + public Client(TcpClient tcpClient) + { + this.tcpClient = tcpClient; + int bufferSize = tcpClient.ReceiveBufferSize; + buffer = new byte[bufferSize]; + } + + public TcpClient TcpClient + { + get { return tcpClient; } + } + + public byte[] Buffer + { + get { return buffer; } + } + + public NetworkStream NetworkStream + { + get + { + + return tcpClient.GetStream(); + + } + } + } + } + #endregion + + /// + /// Modbus TCP Server. + /// + public class ModbusServer : IDisposable + { + private bool debug = false; + int port; + ModbusProtocol receiveData; + ModbusProtocol sendData = new ModbusProtocol(); + byte[] bytes = new byte[2100]; + public HoldingRegisters holdingRegisters; + public InputRegisters inputRegisters; + public Coils coils; + public DiscreteInputs discreteInputs; + private int numberOfConnections = 0; + private bool udpFlag; + private bool serialFlag; + private int baudrate = 9600; + private Parity parity = Parity.Even; + private StopBits stopBits = StopBits.One; + private string serialPort = "COM1"; + private SerialPort serialport; + private byte unitIdentifier = 1; + private int portIn; + private IPAddress ipAddressIn; + private UdpClient udpClient; + private IPEndPoint iPEndPoint; + private TCPHandler tcpHandler; + Thread listenerThread; + Thread clientConnectionThread; + private ModbusProtocol[] modbusLogData = new ModbusProtocol[100]; + public bool FunctionCode1Disabled { get; set; } + public bool FunctionCode2Disabled { get; set; } + public bool FunctionCode3Disabled { get; set; } + public bool FunctionCode4Disabled { get; set; } + public bool FunctionCode5Disabled { get; set; } + public bool FunctionCode6Disabled { get; set; } + public bool FunctionCode15Disabled { get; set; } + public bool FunctionCode16Disabled { get; set; } + public bool FunctionCode23Disabled { get; set; } + public bool PortChanged { get; set; } + object lockCoils = new object(); + object lockHoldingRegisters = new object(); + private volatile bool shouldStop; + private bool stop; + + private IPAddress localIPAddress = IPAddress.Any; + + /// + /// When creating a TCP or UDP socket, the local IP address to attach to. + /// + public IPAddress LocalIPAddress + { + get { return localIPAddress; } + set { if (listenerThread == null) localIPAddress = value; } + } + + public ModbusServer(int port = 502) + { + this.port = port; + holdingRegisters = new HoldingRegisters(this); + inputRegisters = new InputRegisters(this); + coils = new Coils(this); + discreteInputs = new DiscreteInputs(this); + } + + #region events + public delegate void CoilsChangedHandler(int coil, int numberOfCoils); + public event CoilsChangedHandler CoilsChanged; + + public delegate void HoldingRegistersChangedHandler(int register, int numberOfRegisters); + public event HoldingRegistersChangedHandler HoldingRegistersChanged; + + public delegate void NumberOfConnectedClientsChangedHandler(); + public event NumberOfConnectedClientsChangedHandler NumberOfConnectedClientsChanged; + + public delegate void LogDataChangedHandler(); + public event LogDataChangedHandler LogDataChanged; + #endregion + + public void Start() + { + listenerThread = new Thread(ListenerThread); + listenerThread.Start(); + } + + + public void Dispose() + { + Stop(); + } + public void Stop() + { + if (SerialFlag & (serialport != null)) + { + if (serialport.IsOpen) + serialport.Close(); + shouldStop = true; + } + try + { + tcpHandler.Disconnect(); + stop = true; + listenerThread.Interrupt(); + + } + catch (Exception) { } + listenerThread.Join(); + try + { + stop = true; + clientConnectionThread.Interrupt(); + } + catch (Exception) { } + } + + private void ListenerThread() + { + if (!udpFlag & !serialFlag) + { + if (udpClient != null) + { + try + { + udpClient.Close(); + } + catch (Exception) { } + } + tcpHandler = new TCPHandler(LocalIPAddress, port); + if (debug) StoreLogData.Instance.Store($"EasyModbus Server listing for incomming data at Port {port}, local IP {LocalIPAddress}", System.DateTime.Now); + tcpHandler.dataChanged += new TCPHandler.DataChanged(ProcessReceivedData); + tcpHandler.numberOfClientsChanged += new TCPHandler.NumberOfClientsChanged(numberOfClientsChanged); + } + else if (serialFlag) + { + if (serialport == null) + { + if (debug) StoreLogData.Instance.Store("EasyModbus RTU-Server listing for incomming data at Serial Port " + serialPort, System.DateTime.Now); + serialport = new SerialPort(); + serialport.PortName = serialPort; + serialport.BaudRate = this.baudrate; + serialport.Parity = this.parity; + serialport.StopBits = stopBits; + serialport.WriteTimeout = 10000; + serialport.ReadTimeout = 1000; + serialport.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler); + serialport.Open(); + } + } + else + while (!shouldStop) + { + if (stop) + break; + if (udpFlag) + { + if (udpClient == null | PortChanged) + { + IPEndPoint localEndoint = new IPEndPoint(LocalIPAddress, port); + udpClient = new UdpClient(localEndoint); + if (debug) StoreLogData.Instance.Store($"EasyModbus Server listing for incomming data at Port {port}, local IP {LocalIPAddress}", System.DateTime.Now); + udpClient.Client.ReceiveTimeout = 1000; + iPEndPoint = new IPEndPoint(IPAddress.Any, port); + PortChanged = false; + } + if (tcpHandler != null) + tcpHandler.Disconnect(); + try + { + bytes = udpClient.Receive(ref iPEndPoint); + portIn = iPEndPoint.Port; + NetworkConnectionParameter networkConnectionParameter = new NetworkConnectionParameter(); + networkConnectionParameter.bytes = bytes; + ipAddressIn = iPEndPoint.Address; + networkConnectionParameter.portIn = portIn; + networkConnectionParameter.ipAddressIn = ipAddressIn; + ParameterizedThreadStart pts = new ParameterizedThreadStart(this.ProcessReceivedData); + Thread processDataThread = new Thread(pts); + processDataThread.Start(networkConnectionParameter); + } + catch (Exception ex) + { + if (ex is ThreadInterruptedException) + break; + } + } + + } + } + + #region SerialHandler + private bool dataReceived = false; + private byte[] readBuffer = new byte[2094]; + private DateTime lastReceive; + private int nextSign = 0; + private void DataReceivedHandler(object sender, + SerialDataReceivedEventArgs e) + { + int silence = 4000 / baudrate; + if ((DateTime.Now.Ticks - lastReceive.Ticks) > TimeSpan.TicksPerMillisecond * silence) + nextSign = 0; + + + SerialPort sp = (SerialPort)sender; + + int numbytes = sp.BytesToRead; + byte[] rxbytearray = new byte[numbytes]; + + sp.Read(rxbytearray, 0, numbytes); + + Array.Copy(rxbytearray, 0, readBuffer, nextSign, rxbytearray.Length); + lastReceive = DateTime.Now; + nextSign = numbytes + nextSign; + if (ModbusClient.DetectValidModbusFrame(readBuffer, nextSign)) + { + + dataReceived = true; + nextSign = 0; + + NetworkConnectionParameter networkConnectionParameter = new NetworkConnectionParameter(); + networkConnectionParameter.bytes = readBuffer; + ParameterizedThreadStart pts = new ParameterizedThreadStart(this.ProcessReceivedData); + Thread processDataThread = new Thread(pts); + processDataThread.Start(networkConnectionParameter); + dataReceived = false; + + } + else + dataReceived = false; + } + #endregion + + #region Method numberOfClientsChanged + private void numberOfClientsChanged() + { + numberOfConnections = tcpHandler.NumberOfConnectedClients; + if (NumberOfConnectedClientsChanged != null) + NumberOfConnectedClientsChanged(); + } + #endregion + + object lockProcessReceivedData = new object(); + #region Method ProcessReceivedData + private void ProcessReceivedData(object networkConnectionParameter) + { + lock (lockProcessReceivedData) + { + Byte[] bytes = new byte[((NetworkConnectionParameter)networkConnectionParameter).bytes.Length]; + if (debug) StoreLogData.Instance.Store("Received Data: " + BitConverter.ToString(bytes), System.DateTime.Now); + NetworkStream stream = ((NetworkConnectionParameter)networkConnectionParameter).stream; + int portIn = ((NetworkConnectionParameter)networkConnectionParameter).portIn; + IPAddress ipAddressIn = ((NetworkConnectionParameter)networkConnectionParameter).ipAddressIn; + + + Array.Copy(((NetworkConnectionParameter)networkConnectionParameter).bytes, 0, bytes, 0, ((NetworkConnectionParameter)networkConnectionParameter).bytes.Length); + + ModbusProtocol receiveDataThread = new ModbusProtocol(); + ModbusProtocol sendDataThread = new ModbusProtocol(); + + try + { + UInt16[] wordData = new UInt16[1]; + byte[] byteData = new byte[2]; + receiveDataThread.timeStamp = DateTime.Now; + receiveDataThread.request = true; + if (!serialFlag) + { + //Lese Transaction identifier + byteData[1] = bytes[0]; + byteData[0] = bytes[1]; + Buffer.BlockCopy(byteData, 0, wordData, 0, 2); + receiveDataThread.transactionIdentifier = wordData[0]; + + //Lese Protocol identifier + byteData[1] = bytes[2]; + byteData[0] = bytes[3]; + Buffer.BlockCopy(byteData, 0, wordData, 0, 2); + receiveDataThread.protocolIdentifier = wordData[0]; + + //Lese length + byteData[1] = bytes[4]; + byteData[0] = bytes[5]; + Buffer.BlockCopy(byteData, 0, wordData, 0, 2); + receiveDataThread.length = wordData[0]; + } + + //Lese unit identifier + receiveDataThread.unitIdentifier = bytes[6 - 6 * Convert.ToInt32(serialFlag)]; + //Check UnitIdentifier + if ((receiveDataThread.unitIdentifier != this.unitIdentifier) & (receiveDataThread.unitIdentifier != 0)) + return; + + // Lese function code + receiveDataThread.functionCode = bytes[7 - 6 * Convert.ToInt32(serialFlag)]; + + // Lese starting address + byteData[1] = bytes[8 - 6 * Convert.ToInt32(serialFlag)]; + byteData[0] = bytes[9 - 6 * Convert.ToInt32(serialFlag)]; + Buffer.BlockCopy(byteData, 0, wordData, 0, 2); + receiveDataThread.startingAdress = wordData[0]; + + if (receiveDataThread.functionCode <= 4) + { + // Lese quantity + byteData[1] = bytes[10 - 6 * Convert.ToInt32(serialFlag)]; + byteData[0] = bytes[11 - 6 * Convert.ToInt32(serialFlag)]; + Buffer.BlockCopy(byteData, 0, wordData, 0, 2); + receiveDataThread.quantity = wordData[0]; + } + if (receiveDataThread.functionCode == 5) + { + receiveDataThread.receiveCoilValues = new ushort[1]; + // Lese Value + byteData[1] = bytes[10 - 6 * Convert.ToInt32(serialFlag)]; + byteData[0] = bytes[11 - 6 * Convert.ToInt32(serialFlag)]; + Buffer.BlockCopy(byteData, 0, receiveDataThread.receiveCoilValues, 0, 2); + } + if (receiveDataThread.functionCode == 6) + { + receiveDataThread.receiveRegisterValues = new ushort[1]; + // Lese Value + byteData[1] = bytes[10 - 6 * Convert.ToInt32(serialFlag)]; + byteData[0] = bytes[11 - 6 * Convert.ToInt32(serialFlag)]; + Buffer.BlockCopy(byteData, 0, receiveDataThread.receiveRegisterValues, 0, 2); + } + if (receiveDataThread.functionCode == 15) + { + // Lese quantity + byteData[1] = bytes[10 - 6 * Convert.ToInt32(serialFlag)]; + byteData[0] = bytes[11 - 6 * Convert.ToInt32(serialFlag)]; + Buffer.BlockCopy(byteData, 0, wordData, 0, 2); + receiveDataThread.quantity = wordData[0]; + + receiveDataThread.byteCount = bytes[12 - 6 * Convert.ToInt32(serialFlag)]; + + if ((receiveDataThread.byteCount % 2) != 0) + receiveDataThread.receiveCoilValues = new ushort[receiveDataThread.byteCount / 2 + 1]; + else + receiveDataThread.receiveCoilValues = new ushort[receiveDataThread.byteCount / 2]; + // Lese Value + Buffer.BlockCopy(bytes, 13 - 6 * Convert.ToInt32(serialFlag), receiveDataThread.receiveCoilValues, 0, receiveDataThread.byteCount); + } + if (receiveDataThread.functionCode == 16) + { + // Lese quantity + byteData[1] = bytes[10 - 6 * Convert.ToInt32(serialFlag)]; + byteData[0] = bytes[11 - 6 * Convert.ToInt32(serialFlag)]; + Buffer.BlockCopy(byteData, 0, wordData, 0, 2); + receiveDataThread.quantity = wordData[0]; + + receiveDataThread.byteCount = bytes[12 - 6 * Convert.ToInt32(serialFlag)]; + receiveDataThread.receiveRegisterValues = new ushort[receiveDataThread.quantity]; + for (int i = 0; i < receiveDataThread.quantity; i++) + { + // Lese Value + byteData[1] = bytes[13 + i * 2 - 6 * Convert.ToInt32(serialFlag)]; + byteData[0] = bytes[14 + i * 2 - 6 * Convert.ToInt32(serialFlag)]; + Buffer.BlockCopy(byteData, 0, receiveDataThread.receiveRegisterValues, i * 2, 2); + } + + } + if (receiveDataThread.functionCode == 23) + { + // Lese starting Address Read + byteData[1] = bytes[8 - 6 * Convert.ToInt32(serialFlag)]; + byteData[0] = bytes[9 - 6 * Convert.ToInt32(serialFlag)]; + Buffer.BlockCopy(byteData, 0, wordData, 0, 2); + receiveDataThread.startingAddressRead = wordData[0]; + // Lese quantity Read + byteData[1] = bytes[10 - 6 * Convert.ToInt32(serialFlag)]; + byteData[0] = bytes[11 - 6 * Convert.ToInt32(serialFlag)]; + Buffer.BlockCopy(byteData, 0, wordData, 0, 2); + receiveDataThread.quantityRead = wordData[0]; + // Lese starting Address Write + byteData[1] = bytes[12 - 6 * Convert.ToInt32(serialFlag)]; + byteData[0] = bytes[13 - 6 * Convert.ToInt32(serialFlag)]; + Buffer.BlockCopy(byteData, 0, wordData, 0, 2); + receiveDataThread.startingAddressWrite = wordData[0]; + // Lese quantity Write + byteData[1] = bytes[14 - 6 * Convert.ToInt32(serialFlag)]; + byteData[0] = bytes[15 - 6 * Convert.ToInt32(serialFlag)]; + Buffer.BlockCopy(byteData, 0, wordData, 0, 2); + receiveDataThread.quantityWrite = wordData[0]; + + receiveDataThread.byteCount = bytes[16 - 6 * Convert.ToInt32(serialFlag)]; + receiveDataThread.receiveRegisterValues = new ushort[receiveDataThread.quantityWrite]; + for (int i = 0; i < receiveDataThread.quantityWrite; i++) + { + // Lese Value + byteData[1] = bytes[17 + i * 2 - 6 * Convert.ToInt32(serialFlag)]; + byteData[0] = bytes[18 + i * 2 - 6 * Convert.ToInt32(serialFlag)]; + Buffer.BlockCopy(byteData, 0, receiveDataThread.receiveRegisterValues, i * 2, 2); + } + } + } + catch (Exception exc) + { } + this.CreateAnswer(receiveDataThread, sendDataThread, stream, portIn, ipAddressIn); + //this.sendAnswer(); + this.CreateLogData(receiveDataThread, sendDataThread); + + if (LogDataChanged != null) + LogDataChanged(); + } + } + #endregion + + #region Method CreateAnswer + private void CreateAnswer(ModbusProtocol receiveData, ModbusProtocol sendData, NetworkStream stream, int portIn, IPAddress ipAddressIn) + { + + switch (receiveData.functionCode) + { + // Read Coils + case 1: + if (!FunctionCode1Disabled) + this.ReadCoils(receiveData, sendData, stream, portIn, ipAddressIn); + else + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 1; + sendException(sendData.errorCode, sendData.exceptionCode, receiveData, sendData, stream, portIn, ipAddressIn); + } + break; + // Read Input Registers + case 2: + if (!FunctionCode2Disabled) + this.ReadDiscreteInputs(receiveData, sendData, stream, portIn, ipAddressIn); + else + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 1; + sendException(sendData.errorCode, sendData.exceptionCode, receiveData, sendData, stream, portIn, ipAddressIn); + } + + break; + // Read Holding Registers + case 3: + if (!FunctionCode3Disabled) + this.ReadHoldingRegisters(receiveData, sendData, stream, portIn, ipAddressIn); + else + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 1; + sendException(sendData.errorCode, sendData.exceptionCode, receiveData, sendData, stream, portIn, ipAddressIn); + } + + break; + // Read Input Registers + case 4: + if (!FunctionCode4Disabled) + this.ReadInputRegisters(receiveData, sendData, stream, portIn, ipAddressIn); + else + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 1; + sendException(sendData.errorCode, sendData.exceptionCode, receiveData, sendData, stream, portIn, ipAddressIn); + } + + break; + // Write single coil + case 5: + if (!FunctionCode5Disabled) + this.WriteSingleCoil(receiveData, sendData, stream, portIn, ipAddressIn); + else + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 1; + sendException(sendData.errorCode, sendData.exceptionCode, receiveData, sendData, stream, portIn, ipAddressIn); + } + + break; + // Write single register + case 6: + if (!FunctionCode6Disabled) + this.WriteSingleRegister(receiveData, sendData, stream, portIn, ipAddressIn); + else + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 1; + sendException(sendData.errorCode, sendData.exceptionCode, receiveData, sendData, stream, portIn, ipAddressIn); + } + + break; + // Write Multiple coils + case 15: + if (!FunctionCode15Disabled) + this.WriteMultipleCoils(receiveData, sendData, stream, portIn, ipAddressIn); + else + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 1; + sendException(sendData.errorCode, sendData.exceptionCode, receiveData, sendData, stream, portIn, ipAddressIn); + } + + break; + // Write Multiple registers + case 16: + if (!FunctionCode16Disabled) + this.WriteMultipleRegisters(receiveData, sendData, stream, portIn, ipAddressIn); + else + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 1; + sendException(sendData.errorCode, sendData.exceptionCode, receiveData, sendData, stream, portIn, ipAddressIn); + } + + break; + // Error: Function Code not supported + case 23: + if (!FunctionCode23Disabled) + this.ReadWriteMultipleRegisters(receiveData, sendData, stream, portIn, ipAddressIn); + else + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 1; + sendException(sendData.errorCode, sendData.exceptionCode, receiveData, sendData, stream, portIn, ipAddressIn); + } + + break; + // Error: Function Code not supported + default: + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 1; + sendException(sendData.errorCode, sendData.exceptionCode, receiveData, sendData, stream, portIn, ipAddressIn); + break; + } + sendData.timeStamp = DateTime.Now; + } + #endregion + + private void ReadCoils(ModbusProtocol receiveData, ModbusProtocol sendData, NetworkStream stream, int portIn, IPAddress ipAddressIn) + { + sendData.response = true; + + sendData.transactionIdentifier = receiveData.transactionIdentifier; + sendData.protocolIdentifier = receiveData.protocolIdentifier; + + sendData.unitIdentifier = this.unitIdentifier; + sendData.functionCode = receiveData.functionCode; + if ((receiveData.quantity < 1) | (receiveData.quantity > 0x07D0)) //Invalid quantity + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 3; + } + if (((receiveData.startingAdress + 1 + receiveData.quantity) > 65535) | (receiveData.startingAdress < 0)) //Invalid Starting adress or Starting address + quantity + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 2; + } + if (sendData.exceptionCode == 0) + { + if ((receiveData.quantity % 8) == 0) + sendData.byteCount = (byte)(receiveData.quantity / 8); + else + sendData.byteCount = (byte)(receiveData.quantity / 8 + 1); + + sendData.sendCoilValues = new bool[receiveData.quantity]; + lock (lockCoils) + Array.Copy(coils.localArray, receiveData.startingAdress + 1, sendData.sendCoilValues, 0, receiveData.quantity); + } + if (true) + { + Byte[] data; + + if (sendData.exceptionCode > 0) + data = new byte[9 + 2 * Convert.ToInt32(serialFlag)]; + else + data = new byte[9 + sendData.byteCount + 2 * Convert.ToInt32(serialFlag)]; + + Byte[] byteData = new byte[2]; + + sendData.length = (byte)(data.Length - 6); + + //Send Transaction identifier + byteData = BitConverter.GetBytes((int)sendData.transactionIdentifier); + data[0] = byteData[1]; + data[1] = byteData[0]; + + //Send Protocol identifier + byteData = BitConverter.GetBytes((int)sendData.protocolIdentifier); + data[2] = byteData[1]; + data[3] = byteData[0]; + + //Send length + byteData = BitConverter.GetBytes((int)sendData.length); + data[4] = byteData[1]; + data[5] = byteData[0]; + //Unit Identifier + data[6] = sendData.unitIdentifier; + + //Function Code + data[7] = sendData.functionCode; + + //ByteCount + data[8] = sendData.byteCount; + + if (sendData.exceptionCode > 0) + { + data[7] = sendData.errorCode; + data[8] = sendData.exceptionCode; + sendData.sendCoilValues = null; + } + + if (sendData.sendCoilValues != null) + for (int i = 0; i < (sendData.byteCount); i++) + { + byteData = new byte[2]; + for (int j = 0; j < 8; j++) + { + + byte boolValue; + if (sendData.sendCoilValues[i * 8 + j] == true) + boolValue = 1; + else + boolValue = 0; + byteData[1] = (byte)((byteData[1]) | (boolValue << j)); + if ((i * 8 + j + 1) >= sendData.sendCoilValues.Length) + break; + } + data[9 + i] = byteData[1]; + } + try + { + if (serialFlag) + { + if (!serialport.IsOpen) + throw new EasyModbus.Exceptions.SerialPortNotOpenedException("serial port not opened"); + //Create CRC + sendData.crc = ModbusClient.calculateCRC(data, Convert.ToUInt16(data.Length - 8), 6); + byteData = BitConverter.GetBytes((int)sendData.crc); + data[data.Length - 2] = byteData[0]; + data[data.Length - 1] = byteData[1]; + serialport.Write(data, 6, data.Length - 6); + if (debug) + { + byte[] debugData = new byte[data.Length - 6]; + Array.Copy(data, 6, debugData, 0, data.Length - 6); + if (debug) StoreLogData.Instance.Store("Send Serial-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + } + else if (udpFlag) + { + //UdpClient udpClient = new UdpClient(); + IPEndPoint endPoint = new IPEndPoint(ipAddressIn, portIn); + if (debug) StoreLogData.Instance.Store("Send Data: " + BitConverter.ToString(data), System.DateTime.Now); + udpClient.Send(data, data.Length, endPoint); + + } + else + { + stream.Write(data, 0, data.Length); + if (debug) StoreLogData.Instance.Store("Send Data: " + BitConverter.ToString(data), System.DateTime.Now); + } + } + catch (Exception) { } + } + } + + private void ReadDiscreteInputs(ModbusProtocol receiveData, ModbusProtocol sendData, NetworkStream stream, int portIn, IPAddress ipAddressIn) + { + sendData.response = true; + + sendData.transactionIdentifier = receiveData.transactionIdentifier; + sendData.protocolIdentifier = receiveData.protocolIdentifier; + + sendData.unitIdentifier = this.unitIdentifier; + sendData.functionCode = receiveData.functionCode; + if ((receiveData.quantity < 1) | (receiveData.quantity > 0x07D0)) //Invalid quantity + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 3; + } + if (((receiveData.startingAdress + 1 + receiveData.quantity) > 65535) | (receiveData.startingAdress < 0)) //Invalid Starting adress or Starting address + quantity + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 2; + } + if (sendData.exceptionCode == 0) + { + if ((receiveData.quantity % 8) == 0) + sendData.byteCount = (byte)(receiveData.quantity / 8); + else + sendData.byteCount = (byte)(receiveData.quantity / 8 + 1); + + sendData.sendCoilValues = new bool[receiveData.quantity]; + Array.Copy(discreteInputs.localArray, receiveData.startingAdress + 1, sendData.sendCoilValues, 0, receiveData.quantity); + } + if (true) + { + Byte[] data; + if (sendData.exceptionCode > 0) + data = new byte[9 + 2 * Convert.ToInt32(serialFlag)]; + else + data = new byte[9 + sendData.byteCount + 2 * Convert.ToInt32(serialFlag)]; + Byte[] byteData = new byte[2]; + sendData.length = (byte)(data.Length - 6); + + //Send Transaction identifier + byteData = BitConverter.GetBytes((int)sendData.transactionIdentifier); + data[0] = byteData[1]; + data[1] = byteData[0]; + + //Send Protocol identifier + byteData = BitConverter.GetBytes((int)sendData.protocolIdentifier); + data[2] = byteData[1]; + data[3] = byteData[0]; + + //Send length + byteData = BitConverter.GetBytes((int)sendData.length); + data[4] = byteData[1]; + data[5] = byteData[0]; + + //Unit Identifier + data[6] = sendData.unitIdentifier; + + //Function Code + data[7] = sendData.functionCode; + + //ByteCount + data[8] = sendData.byteCount; + + + if (sendData.exceptionCode > 0) + { + data[7] = sendData.errorCode; + data[8] = sendData.exceptionCode; + sendData.sendCoilValues = null; + } + + if (sendData.sendCoilValues != null) + for (int i = 0; i < (sendData.byteCount); i++) + { + byteData = new byte[2]; + for (int j = 0; j < 8; j++) + { + + byte boolValue; + if (sendData.sendCoilValues[i * 8 + j] == true) + boolValue = 1; + else + boolValue = 0; + byteData[1] = (byte)((byteData[1]) | (boolValue << j)); + if ((i * 8 + j + 1) >= sendData.sendCoilValues.Length) + break; + } + data[9 + i] = byteData[1]; + } + + try + { + if (serialFlag) + { + if (!serialport.IsOpen) + throw new EasyModbus.Exceptions.SerialPortNotOpenedException("serial port not opened"); + //Create CRC + sendData.crc = ModbusClient.calculateCRC(data, Convert.ToUInt16(data.Length - 8), 6); + byteData = BitConverter.GetBytes((int)sendData.crc); + data[data.Length - 2] = byteData[0]; + data[data.Length - 1] = byteData[1]; + serialport.Write(data, 6, data.Length - 6); + if (debug) + { + byte[] debugData = new byte[data.Length - 6]; + Array.Copy(data, 6, debugData, 0, data.Length - 6); + if (debug) StoreLogData.Instance.Store("Send Serial-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + } + else if (udpFlag) + { + //UdpClient udpClient = new UdpClient(); + IPEndPoint endPoint = new IPEndPoint(ipAddressIn, portIn); + udpClient.Send(data, data.Length, endPoint); + + } + else + { + stream.Write(data, 0, data.Length); + if (debug) StoreLogData.Instance.Store("Send Data: " + BitConverter.ToString(data), System.DateTime.Now); + } + } + catch (Exception) { } + } + } + + private void ReadHoldingRegisters(ModbusProtocol receiveData, ModbusProtocol sendData, NetworkStream stream, int portIn, IPAddress ipAddressIn) + { + sendData.response = true; + + sendData.transactionIdentifier = receiveData.transactionIdentifier; + sendData.protocolIdentifier = receiveData.protocolIdentifier; + + sendData.unitIdentifier = this.unitIdentifier; + sendData.functionCode = receiveData.functionCode; + if ((receiveData.quantity < 1) | (receiveData.quantity > 0x007D)) //Invalid quantity + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 3; + } + if (((receiveData.startingAdress + 1 + receiveData.quantity) > 65535) | (receiveData.startingAdress < 0)) //Invalid Starting adress or Starting address + quantity + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 2; + } + if (sendData.exceptionCode == 0) + { + sendData.byteCount = (byte)(2 * receiveData.quantity); + sendData.sendRegisterValues = new Int16[receiveData.quantity]; + lock (lockHoldingRegisters) + Buffer.BlockCopy(holdingRegisters.localArray, receiveData.startingAdress * 2 + 2, sendData.sendRegisterValues, 0, receiveData.quantity * 2); + } + if (sendData.exceptionCode > 0) + sendData.length = 0x03; + else + sendData.length = (ushort)(0x03 + sendData.byteCount); + + if (true) + { + Byte[] data; + if (sendData.exceptionCode > 0) + data = new byte[9 + 2 * Convert.ToInt32(serialFlag)]; + else + data = new byte[9 + sendData.byteCount + 2 * Convert.ToInt32(serialFlag)]; + Byte[] byteData = new byte[2]; + sendData.length = (byte)(data.Length - 6); + + //Send Transaction identifier + byteData = BitConverter.GetBytes((int)sendData.transactionIdentifier); + data[0] = byteData[1]; + data[1] = byteData[0]; + + //Send Protocol identifier + byteData = BitConverter.GetBytes((int)sendData.protocolIdentifier); + data[2] = byteData[1]; + data[3] = byteData[0]; + + //Send length + byteData = BitConverter.GetBytes((int)sendData.length); + data[4] = byteData[1]; + data[5] = byteData[0]; + + //Unit Identifier + data[6] = sendData.unitIdentifier; + + //Function Code + data[7] = sendData.functionCode; + + //ByteCount + data[8] = sendData.byteCount; + + if (sendData.exceptionCode > 0) + { + data[7] = sendData.errorCode; + data[8] = sendData.exceptionCode; + sendData.sendRegisterValues = null; + } + + + if (sendData.sendRegisterValues != null) + for (int i = 0; i < (sendData.byteCount / 2); i++) + { + byteData = BitConverter.GetBytes((Int16)sendData.sendRegisterValues[i]); + data[9 + i * 2] = byteData[1]; + data[10 + i * 2] = byteData[0]; + } + try + { + if (serialFlag) + { + if (!serialport.IsOpen) + throw new EasyModbus.Exceptions.SerialPortNotOpenedException("serial port not opened"); + //Create CRC + sendData.crc = ModbusClient.calculateCRC(data, Convert.ToUInt16(data.Length - 8), 6); + byteData = BitConverter.GetBytes((int)sendData.crc); + data[data.Length - 2] = byteData[0]; + data[data.Length - 1] = byteData[1]; + serialport.Write(data, 6, data.Length - 6); + if (debug) + { + byte[] debugData = new byte[data.Length - 6]; + Array.Copy(data, 6, debugData, 0, data.Length - 6); + if (debug) StoreLogData.Instance.Store("Send Serial-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + } + else if (udpFlag) + { + //UdpClient udpClient = new UdpClient(); + IPEndPoint endPoint = new IPEndPoint(ipAddressIn, portIn); + udpClient.Send(data, data.Length, endPoint); + + } + else + { + stream.Write(data, 0, data.Length); + if (debug) StoreLogData.Instance.Store("Send Data: " + BitConverter.ToString(data), System.DateTime.Now); + } + } + catch (Exception) { } + } + } + + private void ReadInputRegisters(ModbusProtocol receiveData, ModbusProtocol sendData, NetworkStream stream, int portIn, IPAddress ipAddressIn) + { + sendData.response = true; + + sendData.transactionIdentifier = receiveData.transactionIdentifier; + sendData.protocolIdentifier = receiveData.protocolIdentifier; + + sendData.unitIdentifier = this.unitIdentifier; + sendData.functionCode = receiveData.functionCode; + if ((receiveData.quantity < 1) | (receiveData.quantity > 0x007D)) //Invalid quantity + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 3; + } + if (((receiveData.startingAdress + 1 + receiveData.quantity) > 65535) | (receiveData.startingAdress < 0)) //Invalid Starting adress or Starting address + quantity + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 2; + } + if (sendData.exceptionCode == 0) + { + sendData.byteCount = (byte)(2 * receiveData.quantity); + sendData.sendRegisterValues = new Int16[receiveData.quantity]; + Buffer.BlockCopy(inputRegisters.localArray, receiveData.startingAdress * 2 + 2, sendData.sendRegisterValues, 0, receiveData.quantity * 2); + } + if (sendData.exceptionCode > 0) + sendData.length = 0x03; + else + sendData.length = (ushort)(0x03 + sendData.byteCount); + + if (true) + { + Byte[] data; + if (sendData.exceptionCode > 0) + data = new byte[9 + 2 * Convert.ToInt32(serialFlag)]; + else + data = new byte[9 + sendData.byteCount + 2 * Convert.ToInt32(serialFlag)]; + Byte[] byteData = new byte[2]; + sendData.length = (byte)(data.Length - 6); + + //Send Transaction identifier + byteData = BitConverter.GetBytes((int)sendData.transactionIdentifier); + data[0] = byteData[1]; + data[1] = byteData[0]; + + //Send Protocol identifier + byteData = BitConverter.GetBytes((int)sendData.protocolIdentifier); + data[2] = byteData[1]; + data[3] = byteData[0]; + + //Send length + byteData = BitConverter.GetBytes((int)sendData.length); + data[4] = byteData[1]; + data[5] = byteData[0]; + + //Unit Identifier + data[6] = sendData.unitIdentifier; + + //Function Code + data[7] = sendData.functionCode; + + //ByteCount + data[8] = sendData.byteCount; + + + if (sendData.exceptionCode > 0) + { + data[7] = sendData.errorCode; + data[8] = sendData.exceptionCode; + sendData.sendRegisterValues = null; + } + + + if (sendData.sendRegisterValues != null) + for (int i = 0; i < (sendData.byteCount / 2); i++) + { + byteData = BitConverter.GetBytes((Int16)sendData.sendRegisterValues[i]); + data[9 + i * 2] = byteData[1]; + data[10 + i * 2] = byteData[0]; + } + try + { + if (serialFlag) + { + if (!serialport.IsOpen) + throw new EasyModbus.Exceptions.SerialPortNotOpenedException("serial port not opened"); + //Create CRC + sendData.crc = ModbusClient.calculateCRC(data, Convert.ToUInt16(data.Length - 8), 6); + byteData = BitConverter.GetBytes((int)sendData.crc); + data[data.Length - 2] = byteData[0]; + data[data.Length - 1] = byteData[1]; + serialport.Write(data, 6, data.Length - 6); + if (debug) + { + byte[] debugData = new byte[data.Length - 6]; + Array.Copy(data, 6, debugData, 0, data.Length - 6); + if (debug) StoreLogData.Instance.Store("Send Serial-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + + } + else if (udpFlag) + { + //UdpClient udpClient = new UdpClient(); + IPEndPoint endPoint = new IPEndPoint(ipAddressIn, portIn); + udpClient.Send(data, data.Length, endPoint); + + } + else + { + stream.Write(data, 0, data.Length); + if (debug) StoreLogData.Instance.Store("Send Data: " + BitConverter.ToString(data), System.DateTime.Now); + } + } + catch (Exception) { } + } + } + + private void WriteSingleCoil(ModbusProtocol receiveData, ModbusProtocol sendData, NetworkStream stream, int portIn, IPAddress ipAddressIn) + { + sendData.response = true; + + sendData.transactionIdentifier = receiveData.transactionIdentifier; + sendData.protocolIdentifier = receiveData.protocolIdentifier; + + sendData.unitIdentifier = this.unitIdentifier; + sendData.functionCode = receiveData.functionCode; + sendData.startingAdress = receiveData.startingAdress; + sendData.receiveCoilValues = receiveData.receiveCoilValues; + if ((receiveData.receiveCoilValues[0] != 0x0000) & (receiveData.receiveCoilValues[0] != 0xFF00)) //Invalid Value + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 3; + } + if (((receiveData.startingAdress + 1) > 65535) | (receiveData.startingAdress < 0)) //Invalid Starting adress or Starting address + quantity + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 2; + } + if (sendData.exceptionCode == 0) + { + if (receiveData.receiveCoilValues[0] == 0xFF00) + { + lock (lockCoils) + coils[receiveData.startingAdress + 1] = true; + } + if (receiveData.receiveCoilValues[0] == 0x0000) + { + lock (lockCoils) + coils[receiveData.startingAdress + 1] = false; + } + } + if (sendData.exceptionCode > 0) + sendData.length = 0x03; + else + sendData.length = 0x06; + + if (true) + { + Byte[] data; + if (sendData.exceptionCode > 0) + data = new byte[9 + 2 * Convert.ToInt32(serialFlag)]; + else + data = new byte[12 + 2 * Convert.ToInt32(serialFlag)]; + + Byte[] byteData = new byte[2]; + sendData.length = (byte)(data.Length - 6); + + //Send Transaction identifier + byteData = BitConverter.GetBytes((int)sendData.transactionIdentifier); + data[0] = byteData[1]; + data[1] = byteData[0]; + + //Send Protocol identifier + byteData = BitConverter.GetBytes((int)sendData.protocolIdentifier); + data[2] = byteData[1]; + data[3] = byteData[0]; + + //Send length + byteData = BitConverter.GetBytes((int)sendData.length); + data[4] = byteData[1]; + data[5] = byteData[0]; + + //Unit Identifier + data[6] = sendData.unitIdentifier; + + //Function Code + data[7] = sendData.functionCode; + + + + if (sendData.exceptionCode > 0) + { + data[7] = sendData.errorCode; + data[8] = sendData.exceptionCode; + sendData.sendRegisterValues = null; + } + else + { + byteData = BitConverter.GetBytes((int)receiveData.startingAdress); + data[8] = byteData[1]; + data[9] = byteData[0]; + byteData = BitConverter.GetBytes((int)receiveData.receiveCoilValues[0]); + data[10] = byteData[1]; + data[11] = byteData[0]; + } + + + try + { + if (serialFlag) + { + if (!serialport.IsOpen) + throw new EasyModbus.Exceptions.SerialPortNotOpenedException("serial port not opened"); + //Create CRC + sendData.crc = ModbusClient.calculateCRC(data, Convert.ToUInt16(data.Length - 8), 6); + byteData = BitConverter.GetBytes((int)sendData.crc); + data[data.Length - 2] = byteData[0]; + data[data.Length - 1] = byteData[1]; + serialport.Write(data, 6, data.Length - 6); + if (debug) + { + byte[] debugData = new byte[data.Length - 6]; + Array.Copy(data, 6, debugData, 0, data.Length - 6); + if (debug) StoreLogData.Instance.Store("Send Serial-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + + } + else if (udpFlag) + { + //UdpClient udpClient = new UdpClient(); + IPEndPoint endPoint = new IPEndPoint(ipAddressIn, portIn); + udpClient.Send(data, data.Length, endPoint); + + } + else + { + stream.Write(data, 0, data.Length); + if (debug) StoreLogData.Instance.Store("Send Data: " + BitConverter.ToString(data), System.DateTime.Now); + } + } + catch (Exception) { } + if (CoilsChanged != null) + CoilsChanged(receiveData.startingAdress + 1, 1); + } + } + + private void WriteSingleRegister(ModbusProtocol receiveData, ModbusProtocol sendData, NetworkStream stream, int portIn, IPAddress ipAddressIn) + { + sendData.response = true; + + sendData.transactionIdentifier = receiveData.transactionIdentifier; + sendData.protocolIdentifier = receiveData.protocolIdentifier; + + sendData.unitIdentifier = this.unitIdentifier; + sendData.functionCode = receiveData.functionCode; + sendData.startingAdress = receiveData.startingAdress; + sendData.receiveRegisterValues = receiveData.receiveRegisterValues; + + if ((receiveData.receiveRegisterValues[0] < 0x0000) | (receiveData.receiveRegisterValues[0] > 0xFFFF)) //Invalid Value + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 3; + } + if (((receiveData.startingAdress + 1) > 65535) | (receiveData.startingAdress < 0)) //Invalid Starting adress or Starting address + quantity + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 2; + } + if (sendData.exceptionCode == 0) + { + lock (lockHoldingRegisters) + holdingRegisters[receiveData.startingAdress + 1] = unchecked((ushort)receiveData.receiveRegisterValues[0]); + } + if (sendData.exceptionCode > 0) + sendData.length = 0x03; + else + sendData.length = 0x06; + + if (true) + { + Byte[] data; + if (sendData.exceptionCode > 0) + data = new byte[9 + 2 * Convert.ToInt32(serialFlag)]; + else + data = new byte[12 + 2 * Convert.ToInt32(serialFlag)]; + + Byte[] byteData = new byte[2]; + sendData.length = (byte)(data.Length - 6); + + + //Send Transaction identifier + byteData = BitConverter.GetBytes((int)sendData.transactionIdentifier); + data[0] = byteData[1]; + data[1] = byteData[0]; + + //Send Protocol identifier + byteData = BitConverter.GetBytes((int)sendData.protocolIdentifier); + data[2] = byteData[1]; + data[3] = byteData[0]; + + //Send length + byteData = BitConverter.GetBytes((int)sendData.length); + data[4] = byteData[1]; + data[5] = byteData[0]; + + //Unit Identifier + data[6] = sendData.unitIdentifier; + + //Function Code + data[7] = sendData.functionCode; + + + + if (sendData.exceptionCode > 0) + { + data[7] = sendData.errorCode; + data[8] = sendData.exceptionCode; + sendData.sendRegisterValues = null; + } + else + { + byteData = BitConverter.GetBytes((int)receiveData.startingAdress); + data[8] = byteData[1]; + data[9] = byteData[0]; + byteData = BitConverter.GetBytes((int)receiveData.receiveRegisterValues[0]); + data[10] = byteData[1]; + data[11] = byteData[0]; + } + + + try + { + if (serialFlag) + { + if (!serialport.IsOpen) + throw new EasyModbus.Exceptions.SerialPortNotOpenedException("serial port not opened"); + //Create CRC + sendData.crc = ModbusClient.calculateCRC(data, Convert.ToUInt16(data.Length - 8), 6); + byteData = BitConverter.GetBytes((int)sendData.crc); + data[data.Length - 2] = byteData[0]; + data[data.Length - 1] = byteData[1]; + serialport.Write(data, 6, data.Length - 6); + if (debug) + { + byte[] debugData = new byte[data.Length - 6]; + Array.Copy(data, 6, debugData, 0, data.Length - 6); + if (debug) StoreLogData.Instance.Store("Send Serial-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + + } + else if (udpFlag) + { + //UdpClient udpClient = new UdpClient(); + IPEndPoint endPoint = new IPEndPoint(ipAddressIn, portIn); + udpClient.Send(data, data.Length, endPoint); + + } + else + { + stream.Write(data, 0, data.Length); + if (debug) StoreLogData.Instance.Store("Send Data: " + BitConverter.ToString(data), System.DateTime.Now); + } + } + catch (Exception) { } + if (HoldingRegistersChanged != null) + HoldingRegistersChanged(receiveData.startingAdress + 1, 1); + } + } + + private void WriteMultipleCoils(ModbusProtocol receiveData, ModbusProtocol sendData, NetworkStream stream, int portIn, IPAddress ipAddressIn) + { + sendData.response = true; + + sendData.transactionIdentifier = receiveData.transactionIdentifier; + sendData.protocolIdentifier = receiveData.protocolIdentifier; + + sendData.unitIdentifier = this.unitIdentifier; + sendData.functionCode = receiveData.functionCode; + sendData.startingAdress = receiveData.startingAdress; + sendData.quantity = receiveData.quantity; + + if ((receiveData.quantity == 0x0000) | (receiveData.quantity > 0x07B0)) //Invalid Quantity + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 3; + } + if ((((int)receiveData.startingAdress + 1 + (int)receiveData.quantity) > 65535) | (receiveData.startingAdress < 0)) //Invalid Starting adress or Starting address + quantity + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 2; + } + if (sendData.exceptionCode == 0) + { + lock (lockCoils) + for (int i = 0; i < receiveData.quantity; i++) + { + int shift = i % 16; + /* if ((i == receiveData.quantity - 1) & (receiveData.quantity % 2 != 0)) + { + if (shift < 8) + shift = shift + 8; + else + shift = shift - 8; + }*/ + int mask = 0x1; + mask = mask << (shift); + if ((receiveData.receiveCoilValues[i / 16] & (ushort)mask) == 0) + + coils[receiveData.startingAdress + i + 1] = false; + else + + coils[receiveData.startingAdress + i + 1] = true; + + } + } + if (sendData.exceptionCode > 0) + sendData.length = 0x03; + else + sendData.length = 0x06; + if (true) + { + Byte[] data; + if (sendData.exceptionCode > 0) + data = new byte[9 + 2 * Convert.ToInt32(serialFlag)]; + else + data = new byte[12 + 2 * Convert.ToInt32(serialFlag)]; + + Byte[] byteData = new byte[2]; + sendData.length = (byte)(data.Length - 6); + + //Send Transaction identifier + byteData = BitConverter.GetBytes((int)sendData.transactionIdentifier); + data[0] = byteData[1]; + data[1] = byteData[0]; + + //Send Protocol identifier + byteData = BitConverter.GetBytes((int)sendData.protocolIdentifier); + data[2] = byteData[1]; + data[3] = byteData[0]; + + //Send length + byteData = BitConverter.GetBytes((int)sendData.length); + data[4] = byteData[1]; + data[5] = byteData[0]; + + //Unit Identifier + data[6] = sendData.unitIdentifier; + + //Function Code + data[7] = sendData.functionCode; + + + + if (sendData.exceptionCode > 0) + { + data[7] = sendData.errorCode; + data[8] = sendData.exceptionCode; + sendData.sendRegisterValues = null; + } + else + { + byteData = BitConverter.GetBytes((int)receiveData.startingAdress); + data[8] = byteData[1]; + data[9] = byteData[0]; + byteData = BitConverter.GetBytes((int)receiveData.quantity); + data[10] = byteData[1]; + data[11] = byteData[0]; + } + + + try + { + if (serialFlag) + { + if (!serialport.IsOpen) + throw new EasyModbus.Exceptions.SerialPortNotOpenedException("serial port not opened"); + //Create CRC + sendData.crc = ModbusClient.calculateCRC(data, Convert.ToUInt16(data.Length - 8), 6); + byteData = BitConverter.GetBytes((int)sendData.crc); + data[data.Length - 2] = byteData[0]; + data[data.Length - 1] = byteData[1]; + serialport.Write(data, 6, data.Length - 6); + if (debug) + { + byte[] debugData = new byte[data.Length - 6]; + Array.Copy(data, 6, debugData, 0, data.Length - 6); + if (debug) StoreLogData.Instance.Store("Send Serial-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + + } + else if (udpFlag) + { + //UdpClient udpClient = new UdpClient(); + IPEndPoint endPoint = new IPEndPoint(ipAddressIn, portIn); + udpClient.Send(data, data.Length, endPoint); + + } + else + { + stream.Write(data, 0, data.Length); + if (debug) StoreLogData.Instance.Store("Send Data: " + BitConverter.ToString(data), System.DateTime.Now); + } + } + catch (Exception) { } + if (CoilsChanged != null) + CoilsChanged(receiveData.startingAdress + 1, receiveData.quantity); + } + } + + private void WriteMultipleRegisters(ModbusProtocol receiveData, ModbusProtocol sendData, NetworkStream stream, int portIn, IPAddress ipAddressIn) + { + sendData.response = true; + + sendData.transactionIdentifier = receiveData.transactionIdentifier; + sendData.protocolIdentifier = receiveData.protocolIdentifier; + + sendData.unitIdentifier = this.unitIdentifier; + sendData.functionCode = receiveData.functionCode; + sendData.startingAdress = receiveData.startingAdress; + sendData.quantity = receiveData.quantity; + + if ((receiveData.quantity == 0x0000) | (receiveData.quantity > 0x07B0)) //Invalid Quantity + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 3; + } + if ((((int)receiveData.startingAdress + 1 + (int)receiveData.quantity) > 65535) | (receiveData.startingAdress < 0)) //Invalid Starting adress or Starting address + quantity + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 2; + } + if (sendData.exceptionCode == 0) + { + lock (lockHoldingRegisters) + for (int i = 0; i < receiveData.quantity; i++) + { + holdingRegisters[receiveData.startingAdress + i + 1] = unchecked((ushort)receiveData.receiveRegisterValues[i]); + } + } + if (sendData.exceptionCode > 0) + sendData.length = 0x03; + else + sendData.length = 0x06; + if (true) + { + Byte[] data; + if (sendData.exceptionCode > 0) + data = new byte[9 + 2 * Convert.ToInt32(serialFlag)]; + else + data = new byte[12 + 2 * Convert.ToInt32(serialFlag)]; + + Byte[] byteData = new byte[2]; + sendData.length = (byte)(data.Length - 6); + + //Send Transaction identifier + byteData = BitConverter.GetBytes((int)sendData.transactionIdentifier); + data[0] = byteData[1]; + data[1] = byteData[0]; + + //Send Protocol identifier + byteData = BitConverter.GetBytes((int)sendData.protocolIdentifier); + data[2] = byteData[1]; + data[3] = byteData[0]; + + //Send length + byteData = BitConverter.GetBytes((int)sendData.length); + data[4] = byteData[1]; + data[5] = byteData[0]; + + //Unit Identifier + data[6] = sendData.unitIdentifier; + + //Function Code + data[7] = sendData.functionCode; + + + + if (sendData.exceptionCode > 0) + { + data[7] = sendData.errorCode; + data[8] = sendData.exceptionCode; + sendData.sendRegisterValues = null; + } + else + { + byteData = BitConverter.GetBytes((int)receiveData.startingAdress); + data[8] = byteData[1]; + data[9] = byteData[0]; + byteData = BitConverter.GetBytes((int)receiveData.quantity); + data[10] = byteData[1]; + data[11] = byteData[0]; + } + + + try + { + if (serialFlag) + { + if (!serialport.IsOpen) + throw new EasyModbus.Exceptions.SerialPortNotOpenedException("serial port not opened"); + //Create CRC + sendData.crc = ModbusClient.calculateCRC(data, Convert.ToUInt16(data.Length - 8), 6); + byteData = BitConverter.GetBytes((int)sendData.crc); + data[data.Length - 2] = byteData[0]; + data[data.Length - 1] = byteData[1]; + serialport.Write(data, 6, data.Length - 6); + if (debug) + { + byte[] debugData = new byte[data.Length - 6]; + Array.Copy(data, 6, debugData, 0, data.Length - 6); + if (debug) StoreLogData.Instance.Store("Send Serial-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + + } + else if (udpFlag) + { + //UdpClient udpClient = new UdpClient(); + IPEndPoint endPoint = new IPEndPoint(ipAddressIn, portIn); + udpClient.Send(data, data.Length, endPoint); + + } + else + { + stream.Write(data, 0, data.Length); + if (debug) StoreLogData.Instance.Store("Send Data: " + BitConverter.ToString(data), System.DateTime.Now); + } + } + catch (Exception) { } + if (HoldingRegistersChanged != null) + HoldingRegistersChanged(receiveData.startingAdress + 1, receiveData.quantity); + } + } + + private void ReadWriteMultipleRegisters(ModbusProtocol receiveData, ModbusProtocol sendData, NetworkStream stream, int portIn, IPAddress ipAddressIn) + { + sendData.response = true; + + sendData.transactionIdentifier = receiveData.transactionIdentifier; + sendData.protocolIdentifier = receiveData.protocolIdentifier; + + sendData.unitIdentifier = this.unitIdentifier; + sendData.functionCode = receiveData.functionCode; + + + if ((receiveData.quantityRead < 0x0001) | (receiveData.quantityRead > 0x007D) | (receiveData.quantityWrite < 0x0001) | (receiveData.quantityWrite > 0x0079) | (receiveData.byteCount != (receiveData.quantityWrite * 2))) //Invalid Quantity + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 3; + } + if ((((int)receiveData.startingAddressRead + 1 + (int)receiveData.quantityRead) > 65535) | (((int)receiveData.startingAddressWrite + 1 + (int)receiveData.quantityWrite) > 65535) | (receiveData.quantityWrite < 0) | (receiveData.quantityRead < 0)) //Invalid Starting adress or Starting address + quantity + { + sendData.errorCode = (byte)(receiveData.functionCode + 0x80); + sendData.exceptionCode = 2; + } + if (sendData.exceptionCode == 0) + { + sendData.sendRegisterValues = new Int16[receiveData.quantityRead]; + lock (lockHoldingRegisters) + Buffer.BlockCopy(holdingRegisters.localArray, receiveData.startingAddressRead * 2 + 2, sendData.sendRegisterValues, 0, receiveData.quantityRead * 2); + + lock (holdingRegisters) + for (int i = 0; i < receiveData.quantityWrite; i++) + { + holdingRegisters[receiveData.startingAddressWrite + i + 1] = unchecked((ushort)receiveData.receiveRegisterValues[i]); + } + sendData.byteCount = (byte)(2 * receiveData.quantityRead); + } + if (sendData.exceptionCode > 0) + sendData.length = 0x03; + else + sendData.length = Convert.ToUInt16(3 + 2 * receiveData.quantityRead); + if (true) + { + Byte[] data; + if (sendData.exceptionCode > 0) + data = new byte[9 + 2 * Convert.ToInt32(serialFlag)]; + else + data = new byte[9 + sendData.byteCount + 2 * Convert.ToInt32(serialFlag)]; + + Byte[] byteData = new byte[2]; + + //Send Transaction identifier + byteData = BitConverter.GetBytes((int)sendData.transactionIdentifier); + data[0] = byteData[1]; + data[1] = byteData[0]; + + //Send Protocol identifier + byteData = BitConverter.GetBytes((int)sendData.protocolIdentifier); + data[2] = byteData[1]; + data[3] = byteData[0]; + + //Send length + byteData = BitConverter.GetBytes((int)sendData.length); + data[4] = byteData[1]; + data[5] = byteData[0]; + + //Unit Identifier + data[6] = sendData.unitIdentifier; + + //Function Code + data[7] = sendData.functionCode; + + //ByteCount + data[8] = sendData.byteCount; + + + if (sendData.exceptionCode > 0) + { + data[7] = sendData.errorCode; + data[8] = sendData.exceptionCode; + sendData.sendRegisterValues = null; + } + else + { + if (sendData.sendRegisterValues != null) + for (int i = 0; i < (sendData.byteCount / 2); i++) + { + byteData = BitConverter.GetBytes((Int16)sendData.sendRegisterValues[i]); + data[9 + i * 2] = byteData[1]; + data[10 + i * 2] = byteData[0]; + } + + } + + + try + { + if (serialFlag) + { + if (!serialport.IsOpen) + throw new EasyModbus.Exceptions.SerialPortNotOpenedException("serial port not opened"); + //Create CRC + sendData.crc = ModbusClient.calculateCRC(data, Convert.ToUInt16(data.Length - 8), 6); + byteData = BitConverter.GetBytes((int)sendData.crc); + data[data.Length - 2] = byteData[0]; + data[data.Length - 1] = byteData[1]; + serialport.Write(data, 6, data.Length - 6); + if (debug) + { + byte[] debugData = new byte[data.Length - 6]; + Array.Copy(data, 6, debugData, 0, data.Length - 6); + if (debug) StoreLogData.Instance.Store("Send Serial-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + + } + else if (udpFlag) + { + //UdpClient udpClient = new UdpClient(); + IPEndPoint endPoint = new IPEndPoint(ipAddressIn, portIn); + udpClient.Send(data, data.Length, endPoint); + + } + else + { + stream.Write(data, 0, data.Length); + if (debug) StoreLogData.Instance.Store("Send Data: " + BitConverter.ToString(data), System.DateTime.Now); + } + } + catch (Exception) { } + if (HoldingRegistersChanged != null) + HoldingRegistersChanged(receiveData.startingAddressWrite + 1, receiveData.quantityWrite); + } + } + + private void sendException(int errorCode, int exceptionCode, ModbusProtocol receiveData, ModbusProtocol sendData, NetworkStream stream, int portIn, IPAddress ipAddressIn) + { + sendData.response = true; + + sendData.transactionIdentifier = receiveData.transactionIdentifier; + sendData.protocolIdentifier = receiveData.protocolIdentifier; + + sendData.unitIdentifier = receiveData.unitIdentifier; + sendData.errorCode = (byte)errorCode; + sendData.exceptionCode = (byte)exceptionCode; + + if (sendData.exceptionCode > 0) + sendData.length = 0x03; + else + sendData.length = (ushort)(0x03 + sendData.byteCount); + + if (true) + { + Byte[] data; + if (sendData.exceptionCode > 0) + data = new byte[9 + 2 * Convert.ToInt32(serialFlag)]; + else + data = new byte[9 + sendData.byteCount + 2 * Convert.ToInt32(serialFlag)]; + Byte[] byteData = new byte[2]; + sendData.length = (byte)(data.Length - 6); + + //Send Transaction identifier + byteData = BitConverter.GetBytes((int)sendData.transactionIdentifier); + data[0] = byteData[1]; + data[1] = byteData[0]; + + //Send Protocol identifier + byteData = BitConverter.GetBytes((int)sendData.protocolIdentifier); + data[2] = byteData[1]; + data[3] = byteData[0]; + + //Send length + byteData = BitConverter.GetBytes((int)sendData.length); + data[4] = byteData[1]; + data[5] = byteData[0]; + + //Unit Identifier + data[6] = sendData.unitIdentifier; + + + data[7] = sendData.errorCode; + data[8] = sendData.exceptionCode; + + + try + { + if (serialFlag) + { + if (!serialport.IsOpen) + throw new EasyModbus.Exceptions.SerialPortNotOpenedException("serial port not opened"); + //Create CRC + sendData.crc = ModbusClient.calculateCRC(data, Convert.ToUInt16(data.Length - 8), 6); + byteData = BitConverter.GetBytes((int)sendData.crc); + data[data.Length - 2] = byteData[0]; + data[data.Length - 1] = byteData[1]; + serialport.Write(data, 6, data.Length - 6); + if (debug) + { + byte[] debugData = new byte[data.Length - 6]; + Array.Copy(data, 6, debugData, 0, data.Length - 6); + if (debug) StoreLogData.Instance.Store("Send Serial-Data: " + BitConverter.ToString(debugData), System.DateTime.Now); + } + } + else if (udpFlag) + { + //UdpClient udpClient = new UdpClient(); + IPEndPoint endPoint = new IPEndPoint(ipAddressIn, portIn); + udpClient.Send(data, data.Length, endPoint); + + } + else + { + stream.Write(data, 0, data.Length); + if (debug) StoreLogData.Instance.Store("Send Data: " + BitConverter.ToString(data), System.DateTime.Now); + } + } + catch (Exception) { } + } + } + + private void CreateLogData(ModbusProtocol receiveData, ModbusProtocol sendData) + { + for (int i = 0; i < 98; i++) + { + modbusLogData[99 - i] = modbusLogData[99 - i - 2]; + + } + modbusLogData[0] = receiveData; + modbusLogData[1] = sendData; + + } + + + + public int NumberOfConnections + { + get + { + return numberOfConnections; + } + } + + public ModbusProtocol[] ModbusLogData + { + get + { + return modbusLogData; + } + } + + public int Port + { + get + { + return port; + } + set + { + port = value; + + + } + } + + public bool UDPFlag + { + get + { + return udpFlag; + } + set + { + udpFlag = value; + } + } + + public bool SerialFlag + { + get + { + return serialFlag; + } + set + { + serialFlag = value; + } + } + + public int Baudrate + { + get + { + return baudrate; + } + set + { + baudrate = value; + } + } + + public System.IO.Ports.Parity Parity + { + get + { + return parity; + } + set + { + parity = value; + } + } + + public System.IO.Ports.StopBits StopBits + { + get + { + return stopBits; + } + set + { + stopBits = value; + } + } + + public string SerialPort + { + get + { + return serialPort; + } + set + { + serialPort = value; + if (serialPort != null) + serialFlag = true; + else + serialFlag = false; + } + } + + public byte UnitIdentifier + { + get + { + return unitIdentifier; + } + set + { + unitIdentifier = value; + } + } + + + + + /// + /// Gets or Sets the Filename for the LogFile + /// + public string LogFileFilename + { + get + { + return StoreLogData.Instance.Filename; + } + set + { + StoreLogData.Instance.Filename = value; + if (StoreLogData.Instance.Filename != null) + debug = true; + else + debug = false; + } + } + + + + + public class HoldingRegisters + { + public ushort[] localArray = new ushort[65535]; + ModbusServer modbusServer; + + public HoldingRegisters(EasyModbus.ModbusServer modbusServer) + { + this.modbusServer = modbusServer; + } + + public ushort this[int x] + { + get { return this.localArray[x]; } + set + { + this.localArray[x] = value; + + } + } + } + + public class InputRegisters + { + public ushort[] localArray = new ushort[65535]; + ModbusServer modbusServer; + + public InputRegisters(EasyModbus.ModbusServer modbusServer) + { + this.modbusServer = modbusServer; + } + + public ushort this[int x] + { + get { return this.localArray[x]; } + set + { + this.localArray[x] = value; + + } + } + } + + public class Coils + { + public bool[] localArray = new bool[65535]; + ModbusServer modbusServer; + + public Coils(EasyModbus.ModbusServer modbusServer) + { + this.modbusServer = modbusServer; + } + + public bool this[int x] + { + get { return this.localArray[x]; } + set + { + this.localArray[x] = value; + + } + } + } + + public class DiscreteInputs + { + public bool[] localArray = new bool[65535]; + ModbusServer modbusServer; + + public DiscreteInputs(EasyModbus.ModbusServer modbusServer) + { + this.modbusServer = modbusServer; + } + + public bool this[int x] + { + get { return this.localArray[x]; } + set + { + this.localArray[x] = value; + + } + } + + + } + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/Modbus/EasyModbus/StoreLogData.cs b/常用工具集/Utility/Network/Modbus/EasyModbus/StoreLogData.cs new file mode 100644 index 0000000..3f79992 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/EasyModbus/StoreLogData.cs @@ -0,0 +1,120 @@ +/* +Copyright (c) 2018-2020 Rossmann-Engineering +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software +and associated documentation files (the "Software"), +to deal in the Software without restriction, +including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission +notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace EasyModbus +{ + /// + /// Store Log-Data in a File + /// + public sealed class StoreLogData + { + private String filename = null; + private static volatile StoreLogData instance; + private static object syncObject = new Object(); + + /// + /// Private constructor; Ensures the access of the class only via "instance" + /// + private StoreLogData() + { + } + + /// + /// Returns the instance of the class (singleton) + /// + /// instance (Singleton) + public static StoreLogData Instance + { + get + { + if (instance == null) + { + lock (syncObject) + { + if (instance == null) + instance = new StoreLogData(); + } + } + + return instance; + } + } + + /// + /// Store message in Log-File + /// + /// Message to append to the Log-File + public void Store(String message) + { + if (this.filename == null) + return; + + using (System.IO.StreamWriter file = + new System.IO.StreamWriter(Filename, true)) + { + file.WriteLine(message); + } + } + + /// + /// Store message in Log-File including Timestamp + /// + /// Message to append to the Log-File + /// Timestamp to add to the same Row + public void Store(String message, DateTime timestamp) + { + try + { + using (System.IO.StreamWriter file = + new System.IO.StreamWriter(Filename, true)) + { + file.WriteLine(timestamp.ToString("dd.MM.yyyy H:mm:ss.ff ") + message); + } + } + catch (Exception e) + { + + } + } + + /// + /// Gets or Sets the Filename to Store Strings in a File + /// + public string Filename + { + get + { + return filename; + } + set + { + filename = value; + } + } + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/Modbus/ModbusHelper.cs b/常用工具集/Utility/Network/Modbus/ModbusHelper.cs new file mode 100644 index 0000000..90920fe --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/ModbusHelper.cs @@ -0,0 +1,1263 @@ +using SharpModbus; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MES.Utility.Network +{ + public class ModbusHelper : IDisposable + { + public delegate void OnSendedData(byte[] bytes, string hexString); + public event OnSendedData OnSended; + + public delegate void OnRecivedData(byte[] bytes, string hexString); + public event OnRecivedData OnRecived; + + + private object _lock = new object(); + private ModbusMaster master; + private byte slaveId; + public ModbusHelper(string ip, int port) + { + master = ModbusMaster.TCP(ip, port); + master.OnSended += Master_OnSended; ; + master.OnRecived += Master_OnRecived; ; + slaveId = 1; + } + + + + public ModbusHelper(string ip, int port, byte slaveId) + { + master = ModbusMaster.TCP(ip, port); + master.OnSended += Master_OnSended; + master.OnRecived += Master_OnRecived; + this.slaveId = slaveId; + + } + public ModbusHelper(SerialSettings setting, int timeout, byte slaveId) + { + master = ModbusMaster.RTU(setting, timeout); + master.OnSended += Master_OnSended; + master.OnRecived += Master_OnRecived; + this.slaveId = slaveId; + } + + #region ushort INT16 操作 + + /// + /// 读取一个地址 + /// + /// 地址 + /// 传递:数据值 + /// bool 读取成功还是失败 + public bool ReadHoldRegisterInt16(int address, out ushort value) + { + return ReadHoldRegisterInt16(slaveId, address, out value); + } + + /// + /// 读取一个地址 + /// + /// 从站Id + /// 地址 + /// 传递:数据值 + /// bool 读取成功还是失败 + public bool ReadHoldRegisterInt16(byte slaveId, int address, out ushort value) + { + bool flag = ReadHoldRegistersInt16(slaveId, address, 1, out ushort[] values); + value = flag ? values[0] : (ushort)0; + return flag; + } + + + + /// + /// 读取某个地址连续N个数据 + /// + /// 地址 + /// 读取的数量 + /// 传递:数据值 + /// bool 读取成功还是失败 + public bool ReadHoldRegistersInt16(int address, int count, out ushort[] value) + { + return ReadHoldRegistersInt16(slaveId, address, count, out value); + } + + /// + /// 读取某个地址连续N个数据 + /// + /// + /// + /// + /// + /// + public bool ReadHoldRegistersInt16(byte slaveId, int address, int count, out ushort[] value) + { + lock (_lock) + try + { + value = master.ReadHoldingRegisters(slaveId, (ushort)address, (ushort)count); + return true; + } + catch + { + value = null; + return false; + } + } + + + /// + /// 写入一个地址 + /// + /// 地址 + /// 写入的数据 + /// bool 写入成功还是失败 + public bool WriteHoldRegisterInt16(int address, ushort value) + { + return WriteHoldRegisterInt16(slaveId, address, value); + } + + /// + /// 写入一个地址 + /// + /// + /// + /// + /// + public bool WriteHoldRegisterInt16(byte slaveId, int address, ushort value) + { + return WriteHoldRegistersInt16(slaveId, address, new ushort[] { value }); + } + + /// + /// 写入一个地址 + /// + /// + /// + /// + public bool WriteHoldRegistersInt16(int address, ushort[] value) + { + return WriteHoldRegistersInt16(slaveId, address, value); + } + + /// + /// 写入一个地址 + /// + /// + /// + /// + /// + public bool WriteHoldRegistersInt16(byte slaveId, int address, ushort[] value) + { + lock (_lock) + try + { + master.WriteRegisters(slaveId, (ushort)address, value); + return true; + } + catch + { + return false; + } + } + #endregion + + #region int INT32 操作 + /// + /// 读取一个地址 + /// + /// 地址 + /// 传递:数据值 + /// bool 读取成功还是失败 + public bool ReadHoldRegisterInt32(int address, out int value) + { + return ReadHoldRegisterInt32(slaveId, address, out value); + } + + /// + /// 读取一个地址 + /// + /// 从站Id + /// 地址 + /// 传递:数据值 + /// bool 读取成功还是失败 + public bool ReadHoldRegisterInt32(byte slaveId, int address, out int value) + { + bool flag = ReadHoldRegistersInt32(slaveId, address, 1, out int[] values); + value = flag ? values[0] : 0; + return flag; + } + + + /// + /// 读取一个地址 + /// + /// 地址 + /// int32数据的数量,不是地址数 + /// 传递:数据值 + /// bool 读取成功还是失败 + public bool ReadHoldRegistersInt32(int address, int count, out int[] value) + { + return ReadHoldRegistersInt32(slaveId, address, count, out value); + } + + /// + /// 读取一个地址 + /// + /// 从站Id + /// 地址 + /// int32数据的数量,不是地址数 + /// 传递:数据值 + /// bool 读取成功还是失败 + public bool ReadHoldRegistersInt32(byte slaveId, int address, int count, out int[] value) + { + lock (_lock) + try + { + List valueList = new List(); + ushort[] values = master.ReadHoldingRegisters(slaveId, (ushort)address, (ushort)(2 * count)); + for (int i = 0; i < values.Length; i += 2) + { + valueList.Add(ConvertRegistersToInt(new ushort[] { values[i], values[i + 1] })); + } + value = valueList.ToArray(); + return true; + } + catch + { + value = null; + return false; + } + } + + + // + /// 写入一个地址 + /// + /// + /// + /// + public bool WriteHoldRegisterInt32(int address, int value) + { + return WriteHoldRegisterInt32(slaveId, address, value); + } + + + /// + /// + /// + /// 从站Id + /// 地址 + /// 数据 + /// bool 写入成功还是失败 + public bool WriteHoldRegisterInt32(byte slaveId, int address, int value) + { + return WriteHoldRegistersInt32(slaveId, address, new int[] { value }); + } + + // + /// 写入一个地址 + /// + /// 地址 + /// 数据 + /// + public bool WriteHoldRegistersInt32(int address, int[] values) + { + return WriteHoldRegistersInt32(slaveId, address, values); + } + + /// + /// 写入一个地址 + /// + /// 从站Id + /// 地址 + /// 数据 + /// + public bool WriteHoldRegistersInt32(byte slaveId, int address, int[] values) + { + lock (_lock) + try + { + List list = new List(); + foreach (int value in values) + { + ushort[] ushortValues = ConvertIntToRegisters(value); + list.AddRange(ushortValues); + } + master.WriteRegisters(slaveId, (ushort)address, list.ToArray()); + return true; + } + catch + { + return false; + } + } + + + + #endregion + + #region long INT64 操作 + /// + /// 读取一个地址 + /// + /// 地址 + /// 传递:数据值 + /// bool 读取成功还是失败 + public bool ReadHoldRegisterInt64(int address, out long value) + { + return ReadHoldRegisterInt64(slaveId, address, out value); + } + + /// + /// 读取一个地址 + /// + /// 从站Id + /// 地址 + /// 传递:数据值 + /// bool 读取成功还是失败 + public bool ReadHoldRegisterInt64(byte slaveId, int address, out long value) + { + bool flag = ReadHoldRegistersInt64(slaveId, address, 1, out long[] values); + value = flag ? values[0] : 0; + return flag; + } + + + /// + /// 读取一个地址 + /// + /// 地址 + /// int32数据的数量,不是地址数 + /// 传递:数据值 + /// bool 读取成功还是失败 + public bool ReadHoldRegistersInt64(int address, int count, out long[] value) + { + return ReadHoldRegistersInt64(slaveId, address, count, out value); + } + + /// + /// 读取一个地址 + /// + /// 从站Id + /// 地址 + /// int32数据的数量,不是地址数 + /// 传递:数据值 + /// bool 读取成功还是失败 + public bool ReadHoldRegistersInt64(byte slaveId, int address, int count, out long[] value) + { + lock (_lock) + try + { + List valueList = new List(); + ushort[] values = master.ReadHoldingRegisters(slaveId, (ushort)address, (ushort)(4 * count)); + for (int i = 0; i < values.Length; i += 4) + { + valueList.Add(ConvertRegistersToLong(new ushort[] { values[i], values[i + 1], values[i + 2], values[i + 3] })); + } + value = valueList.ToArray(); + return true; + } + catch + { + value = null; + return false; + } + } + + + // + /// 写入一个地址 + /// + /// + /// + /// + public bool WriteHoldRegisterInt64(int address, long value) + { + return WriteHoldRegisterInt64(slaveId, address, value); + } + + + /// + /// + /// + /// 从站Id + /// 地址 + /// 数据 + /// bool 写入成功还是失败 + public bool WriteHoldRegisterInt64(byte slaveId, int address, long value) + { + return WriteHoldRegistersInt64(slaveId, address, new long[] { value }); + } + + // + /// 写入一个地址 + /// + /// 地址 + /// 数据 + /// + public bool WriteHoldRegistersInt64(int address, long[] values) + { + return WriteHoldRegistersInt64(slaveId, address, values); + } + + /// + /// 写入一个地址 + /// + /// 从站Id + /// 地址 + /// 数据 + /// + public bool WriteHoldRegistersInt64(byte slaveId, int address, long[] values) + { + lock (_lock) + try + { + List list = new List(); + foreach (int value in values) + { + ushort[] ushortValues = ConvertLongToRegisters(value); + list.AddRange(ushortValues); + } + master.WriteRegisters(slaveId, (ushort)address, list.ToArray()); + return true; + } + catch + { + return false; + } + } + + + + + #endregion + + #region Float 32位数据操作 + /// + /// 读取一个地址 + /// + /// 地址 + /// 数据 + /// + public bool ReadHoldRegisterFloat(int address, out float value) + { + return ReadHoldRegisterFloat(slaveId, address, out value); + } + + /// + /// 读取一个地址 + /// + /// 从站Id + /// 地址 + /// + /// + public bool ReadHoldRegisterFloat(byte slaveId, int address, out float value) + { + bool flag = ReadHoldRegistersFloat(slaveId, address, 1, out float[] values); + value = flag ? (float)values[0] : (float)0; + return flag; + } + + + /// + /// 读取一个地址 + /// + /// 地址 + /// float数据的数量,不是地址数 + /// + /// + public bool ReadHoldRegistersFloat(int address, int count, out float[] value) + { + return ReadHoldRegistersFloat(slaveId, address, count, out value); + } + /// + /// 读取一个地址 + /// + /// 从站Id + /// 地址 + /// float数据的数量,不是地址数 + /// + /// + public bool ReadHoldRegistersFloat(byte slaveId, int address, int count, out float[] value) + { + lock (_lock) + try + { + List valueList = new List(); + ushort[] values = master.ReadHoldingRegisters(slaveId, (ushort)address, (ushort)(2 * count)); + for (int i = 0; i < values.Length; i += 2) + { + valueList.Add(ConvertRegistersToFloat(values)); + } + value = valueList.ToArray(); + return true; + } + catch + { + value = null; + return false; + } + + } + + + /// + /// 写入地址 + /// + /// 地址 + /// 数据 + /// + public bool WriteHoldRegisterFloat(int address, float value) + { + return WriteHoldRegisterFloat(slaveId, address, value); + } + /// + /// 写入地址 + /// + /// 从站Id + /// 地址 + /// 数据 + /// + public bool WriteHoldRegisterFloat(byte slaveId, int address, float value) + { + return WriteHoldRegistersFloat(slaveId, address, new float[] { value }); + } + + + /// + /// 写入地址 + /// + /// 地址 + /// 数据 + /// + public bool WriteHoldRegistersFloat(int address, float[] values) + { + return WriteHoldRegistersFloat(slaveId, address, values); + } + /// + /// 写入地址 + /// + /// 从站Id + /// 地址 + /// 数据 + /// + public bool WriteHoldRegistersFloat(byte slaveId, int address, float[] values) + { + lock (_lock) + try + { + List list = new List(); + foreach (float value in values) + { + list.AddRange(ConvertFloatToRegisters(value)); + //byte[] bytes = BitConverter.GetBytes(value); + //ushort value1 = BitConverter.ToUInt16(new byte[] { bytes[0], bytes[1] }, 0); + //ushort value2 = BitConverter.ToUInt16(new byte[] { bytes[2], bytes[3] }, 0); + //list.Add(value1); + //list.Add(value2); + } + master.WriteRegisters(slaveId, (ushort)address, list.ToArray()); + return true; + } + catch + { + return false; + } + } + + #endregion + + #region Double 64位操作 + /// + /// 读取一个地址 + /// + /// 地址 + /// 数据 + /// + public bool ReadHoldRegisterDouble(int address, out double value) + { + return ReadHoldRegisterDouble(slaveId, address, out value); + } + + /// + /// 读取一个地址 + /// + /// 从站Id + /// 地址 + /// + /// + public bool ReadHoldRegisterDouble(byte slaveId, int address, out double value) + { + bool flag = ReadHoldRegistersDouble(slaveId, address, 1, out double[] values); + value = flag ? (double)values[0] : (double)0; + return flag; + } + + + /// + /// 读取一个地址 + /// + /// 地址 + /// float数据的数量,不是地址数 + /// + /// + public bool ReadHoldRegistersDouble(int address, int count, out double[] value) + { + return ReadHoldRegistersDouble(slaveId, address, count, out value); + } + /// + /// 读取一个地址 + /// + /// 从站Id + /// 地址 + /// float数据的数量,不是地址数 + /// + /// + public bool ReadHoldRegistersDouble(byte slaveId, int address, int count, out double[] value) + { + lock (_lock) + try + { + List valueList = new List(); + ushort[] values = master.ReadHoldingRegisters(slaveId, (ushort)address, (ushort)(4 * count)); + for (int i = 0; i < values.Length; i += 4) + { + valueList.Add(ConvertRegistersToDouble(values)); + } + value = valueList.ToArray(); + return true; + } + catch + { + value = null; + return false; + } + + } + + + /// + /// 写入地址 + /// + /// 地址 + /// 数据 + /// + public bool WriteHoldRegisterDouble(int address, double value) + { + return WriteHoldRegisterDouble(slaveId, address, value); + } + /// + /// 写入地址 + /// + /// 从站Id + /// 地址 + /// 数据 + /// + public bool WriteHoldRegisterDouble(byte slaveId, int address, double value) + { + return WriteHoldRegistersDouble(slaveId, address, new double[] { value }); + } + + + /// + /// 写入地址 + /// + /// 地址 + /// 数据 + /// + public bool WriteHoldRegistersDouble(int address, double[] values) + { + return WriteHoldRegistersDouble(slaveId, address, values); + } + /// + /// 写入地址 + /// + /// 从站Id + /// 地址 + /// 数据 + /// + public bool WriteHoldRegistersDouble(byte slaveId, int address, double[] values) + { + lock (_lock) + try + { + List list = new List(); + foreach (double value in values) + { + list.AddRange(ConvertDoubleToRegisters(value)); + } + master.WriteRegisters(slaveId, (ushort)address, list.ToArray()); + return true; + } + catch + { + return false; + } + } + + #endregion + + #region 字符串操作 + + /// + /// 读取字符串 + /// + /// 地址 + /// 字符串总长度(不是地址长度,一个地址两个字符) + /// + /// + public bool ReadHoldRegisterString(int address, int length, out string str) + { + return ReadHoldRegisterString(slaveId, address, length, Encoding.ASCII, out str); + } + + /// + /// 读取字符串 + /// + /// 地址 + /// 字符串总长度(不是地址长度,一个地址两个字符) + /// + /// + public bool ReadHoldRegisterString(int address, int length, Encoding encoding, out string str) + { + return ReadHoldRegisterString(slaveId, address, length, encoding, out str); + } + + /// + /// 读取字符串 + /// + /// 从站Id + /// 地址 + /// 字符串总长度(不是地址长度,一个地址两个字符) + /// + /// + public bool ReadHoldRegisterString(byte slaveId, int address, int length, out string str) + { + return ReadHoldRegisterString(slaveId, address, length, Encoding.ASCII, out str); + } + + + + /// + /// 读取字符串 + /// + /// 从站Id + /// 地址 + /// 字符串总长度(不是地址长度,一个地址两个字符) + /// + /// + public bool ReadHoldRegisterString(byte slaveId, int address, int length, Encoding encoding, out string str) + { + lock (_lock) + try + { + int count = length / 2 + length % 2; + ushort[] values = master.ReadHoldingRegisters(slaveId, (ushort)address, (ushort)(count)); + str = ConvertRegistersToString(values, length, encoding); + return true; + } + catch + { + str = ""; + return false; + } + } + + + + /// + /// 写入字符串 + /// + /// 地址 + /// 字符串总长度(不是地址长度,一个地址两个字符) + /// 字符串内容 + /// + public bool WriteHoldRegisterString(int address, int length, string str) + { + return WriteHoldRegisterString(slaveId, address, length, str, Encoding.ASCII); + } + + public bool WriteHoldRegisterString(int address, int length, string str, Encoding encoding) + { + return WriteHoldRegisterString(slaveId, address, length, str, encoding); + } + + /// + /// 写入字符串 + /// + /// 从站Id + /// 地址 + /// 字符串总长度(不是地址长度,一个地址两个字符) + /// 字符串内容 + /// + public bool WriteHoldRegisterString(byte slaveId, int address, int length, string str) + { + lock (_lock) + try + { + ushort[] value = ConvertStringToRegisters(str, length, Encoding.ASCII); + master.WriteRegisters(slaveId, (ushort)address, value); + return true; + } + catch + { + return false; + } + } + + /// + /// 写入字符串 + /// + /// 从站Id + /// 地址 + /// 字符串总长度(不是地址长度,一个地址两个字符) + /// 字符串内容 + /// + public bool WriteHoldRegisterString(byte slaveId, int address, int length, string str, Encoding encoding) + { + lock (_lock) + try + { + ushort[] value = ConvertStringToRegisters(str, length, encoding); + master.WriteRegisters(slaveId, (ushort)address, value); + return true; + } + catch + { + return false; + } + } + + #endregion + + #region 通用操作,线圈,输入寄存器等操作 + + public bool ReadCoil(ushort address, out bool value) + { + return ReadCoil(slaveId, address, out value); //there is no code for single read + } + + public bool ReadCoil(byte slave, ushort address, out bool value) + { + bool flag = ReadCoils(slave, address, 1, out bool[] values); + value = flag ? values[0] : false; + return flag; + } + public bool ReadCoils(ushort address, ushort count, out bool[] values) + { + return ReadCoils(slaveId, address, count, out values); + } + public bool ReadCoils(byte slave, ushort address, ushort count, out bool[] values) + { + lock (_lock) + { + try + { + values = master.ReadCoils(slave, address, count); + return true; + } + catch + { + values = null; + return false; + } + } + } + + public bool WriteCoil(ushort address, bool value) + { + return WriteCoil(slaveId, address, value); + } + public bool WriteCoil(byte slave, ushort address, bool value) + { + lock (_lock) + try + { + master.WriteCoils(slave, address, value); + return true; + } + catch + { + return false; + } + + } + public bool WriteCoils(ushort address, params bool[] values) + { + return WriteCoils(slaveId, address, values); + } + public bool WriteCoils(byte slave, ushort address, params bool[] values) + { + lock (_lock) + { + try + { + master.WriteCoils(slave, address, values); + return true; + } + catch + { + return false; + } + + } + } + + + public bool ReadInput(ushort address, ushort count, out bool value) + { + return ReadInput(slaveId, address, out value); + } + + public bool ReadInput(byte slave, ushort address, out bool value) + { + bool flag = ReadInputs(slave, address, 1, out bool[] values); //there is no code for single read + value = flag ? values[0] : false; + return flag; + } + public bool ReadInputs(ushort address, ushort count, out bool[] values) + { + return ReadInputs(slaveId, address, count, out values); + } + public bool ReadInputs(byte slave, ushort address, ushort count, out bool[] values) + { + lock (_lock) + { + try + { + values = master.ReadInputs(slave, address, count); + return true; + } + catch + { + values = null; + return false; + } + } + + } + + + public bool ReadInputRegister(ushort address, out ushort value) + { + return ReadInputRegister(slaveId, address, out value); + } + public bool ReadInputRegister(byte slave, ushort address, out ushort value) + { + bool flag = ReadInputRegisters(slave, address, 1, out ushort[] values); + value = flag ? values[0] : (ushort)0; + return flag; + } + public bool ReadInputRegisters(ushort address, ushort count, out ushort[] values) + { + return ReadInputRegisters(slaveId, address, count, out values); + } + public bool ReadInputRegisters(byte slave, ushort address, ushort count, out ushort[] values) + { + lock (_lock) + { + try + { + values = master.ReadInputRegisters(slave, address, count); + return true; + } + catch + { + values = null; + return false; + } + } + + } + + + public bool ReadHoldingRegister(ushort address, out ushort value) + { + return ReadHoldingRegister(slaveId, address, out value); + } + + public bool ReadHoldingRegister(byte slave, ushort address, out ushort value) + { + bool flag = ReadHoldingRegisters(slave, address, 1, out ushort[] values); + value = flag ? values[0] : (ushort)0; + return flag; + } + public bool ReadHoldingRegisters(ushort address, ushort count, out ushort[] values) + { + return ReadHoldingRegisters(slaveId, address, count, out values); + } + + public bool ReadHoldingRegisters(byte slave, ushort address, ushort count, out ushort[] values) + { + lock (_lock) + try + { + values = master.ReadHoldingRegisters(slave, address, count); + return true; + } + catch + { + values = null; + return false; + } + } + + + + public bool WriteRegister(ushort address, ushort value) + { + return WriteRegister(slaveId, address, value); + } + public bool WriteRegister(byte slave, ushort address, ushort value) + { + lock (_lock) + try + { + master.WriteRegister(slave, address, value); + return true; + } + catch + { + return false; + } + } + + + public bool WriteRegisters(ushort address, params ushort[] values) + { + return WriteRegisters(slaveId, address, values); + } + + public bool WriteRegisters(byte slave, ushort address, params ushort[] values) + { + lock (_lock) + try + { + master.WriteRegisters(slave, address, values); + return true; + } + catch + { + return false; + } + } + #endregion + + public void Dispose() + { + master.OnSended -= Master_OnSended; + master.OnRecived -= Master_OnRecived; + master.Dispose(); + + } + #region 数据转换功能 + #region PLC寄存器与字符串数据转换 + public static string ConvertRegistersToString(ushort[] registers, int stringLength, Encoding encoding) + { + byte[] result = new byte[stringLength]; + byte[] registerResult = new byte[2]; + for (int i = 0; i < stringLength / 2; i++) + { + registerResult = BitConverter.GetBytes(registers[i]); + result[i * 2] = registerResult[0]; + result[i * 2 + 1] = registerResult[1]; + } + return encoding.GetString(result).Trim('\0').Trim(); + } + + /// + /// + /// + /// + /// 字符串长度 + /// + public static ushort[] ConvertStringToRegisters(string str, int count, Encoding encoding) + { + //需要多少地址 + int addressCount = count / 2 + count % 2; + //这些地址共有多少字节 + byte[] bytesList = new byte[addressCount * 2]; + //将字符串拆成字节数组 + byte[] bytes = encoding.GetBytes(str); + //依次放入byteList中 + for (int i = 0; i < bytesList.Length; i++) + { + //要考虑数组越界的情况 + if (i >= bytes.Length) + { + bytesList[i] = 0x00; + } + else + { + bytesList[i] = bytes[i]; + } + } + //封装ushort + List shortList = new List(); + for (int i = 0; i < bytesList.Length; i += 2) + { + //将字节数组转为int存入list + shortList.Add(BitConverter.ToUInt16(new byte[] { bytesList[i], bytesList[i + 1] }, 0)); + } + return shortList.ToArray(); + } + #endregion + + #region PLC寄存器与Double数据转换 + public static double ConvertRegistersToDouble(ushort[] registers) + { + if (registers.Length != 4) + throw new ArgumentException("Input Array length invalid - Array langth must be '4'"); + ushort highRegister = registers[3]; + ushort highLowRegister = registers[2]; + ushort lowHighRegister = registers[1]; + ushort lowRegister = registers[0]; + byte[] highRegisterBytes = BitConverter.GetBytes(highRegister); + byte[] highLowRegisterBytes = BitConverter.GetBytes(highLowRegister); + byte[] lowHighRegisterBytes = BitConverter.GetBytes(lowHighRegister); + byte[] lowRegisterBytes = BitConverter.GetBytes(lowRegister); + byte[] longBytes = { + lowRegisterBytes[0], + lowRegisterBytes[1], + lowHighRegisterBytes[0], + lowHighRegisterBytes[1], + highLowRegisterBytes[0], + highLowRegisterBytes[1], + highRegisterBytes[0], + highRegisterBytes[1] + }; + return BitConverter.ToDouble(longBytes, 0); + } + public static ushort[] ConvertDoubleToRegisters(double doubleValue) + { + byte[] doubleBytes = BitConverter.GetBytes(doubleValue); + byte[] highRegisterBytes = { doubleBytes[6], doubleBytes[7] }; + byte[] highLowRegisterBytes = { doubleBytes[4], doubleBytes[5] }; + byte[] lowHighRegisterBytes = { doubleBytes[2], doubleBytes[3] }; + byte[] lowRegisterBytes = { doubleBytes[0], doubleBytes[1] }; + ushort[] returnValue = + { + BitConverter.ToUInt16(lowRegisterBytes,0), + BitConverter.ToUInt16(lowHighRegisterBytes,0), + BitConverter.ToUInt16(highLowRegisterBytes,0), + BitConverter.ToUInt16(highRegisterBytes,0) + }; + return returnValue; + } + #endregion + + #region PLC寄存器与INT64数据转换 + public static float ConvertRegistersToFloat(ushort[] registers) + { + if (registers.Length != 2) + throw new ArgumentException("Input Array length invalid - Array langth must be '2'"); + ushort highRegister = registers[1]; + ushort lowRegister = registers[0]; + byte[] highRegisterBytes = BitConverter.GetBytes(highRegister); + byte[] lowRegisterBytes = BitConverter.GetBytes(lowRegister); + byte[] floatBytes = { + lowRegisterBytes[0], + lowRegisterBytes[1], + highRegisterBytes[0], + highRegisterBytes[1] + }; + return BitConverter.ToSingle(floatBytes, 0); + } + + public static ushort[] ConvertFloatToRegisters(float floatValue) + { + byte[] floatBytes = BitConverter.GetBytes(floatValue); + byte[] highRegisterBytes = { floatBytes[2], floatBytes[3] }; + byte[] lowRegisterBytes = { floatBytes[0], floatBytes[1] }; + ushort[] returnValue = { BitConverter.ToUInt16(lowRegisterBytes, 0), BitConverter.ToUInt16(highRegisterBytes, 0) }; + return returnValue; + } + #endregion + + #region PLC寄存器与INT64数据转换 + public static long ConvertRegistersToLong(ushort[] registers) + { + if (registers.Length != 4) + throw new ArgumentException("Input Array length invalid - Array langth must be '4'"); + ushort highRegister = registers[3]; + ushort highLowRegister = registers[2]; + ushort lowHighRegister = registers[1]; + ushort lowRegister = registers[0]; + byte[] highRegisterBytes = BitConverter.GetBytes(highRegister); + byte[] highLowRegisterBytes = BitConverter.GetBytes(highLowRegister); + byte[] lowHighRegisterBytes = BitConverter.GetBytes(lowHighRegister); + byte[] lowRegisterBytes = BitConverter.GetBytes(lowRegister); + byte[] longBytes = { + lowRegisterBytes[0], + lowRegisterBytes[1], + lowHighRegisterBytes[0], + lowHighRegisterBytes[1], + highLowRegisterBytes[0], + highLowRegisterBytes[1], + highRegisterBytes[0], + highRegisterBytes[1] + }; + return BitConverter.ToInt64(longBytes, 0); + } + public static ushort[] ConvertLongToRegisters(long longValue) + { + byte[] longBytes = BitConverter.GetBytes(longValue); + byte[] highRegisterBytes = { longBytes[6], longBytes[7] }; + byte[] highLowRegisterBytes = { longBytes[4], longBytes[5] }; + byte[] lowHighRegisterBytes = { longBytes[2], longBytes[3] }; + byte[] lowRegisterBytes = { longBytes[0], longBytes[1] }; + ushort[] returnValue = + { + BitConverter.ToUInt16(lowRegisterBytes,0), + BitConverter.ToUInt16(lowHighRegisterBytes,0), + BitConverter.ToUInt16(highLowRegisterBytes,0), + BitConverter.ToUInt16(highRegisterBytes,0) + }; + return returnValue; + } + #endregion + + #region PLC寄存器与INT32数据转换 + public static int ConvertRegistersToInt(ushort[] registers) + { + if (registers.Length != 2) + throw new ArgumentException("Input Array length invalid - Array langth must be '2'"); + ushort highRegister = registers[1]; + ushort lowRegister = registers[0]; + byte[] highRegisterBytes = BitConverter.GetBytes(highRegister); + byte[] lowRegisterBytes = BitConverter.GetBytes(lowRegister); + byte[] doubleBytes = { + lowRegisterBytes[0], + lowRegisterBytes[1], + highRegisterBytes[0], + highRegisterBytes[1] + }; + return BitConverter.ToInt32(doubleBytes, 0); + } + + public static ushort[] ConvertIntToRegisters(int intValue) + { + byte[] doubleBytes = BitConverter.GetBytes(intValue); + byte[] highRegisterBytes = { doubleBytes[2], doubleBytes[3] }; + byte[] lowRegisterBytes = { doubleBytes[0], doubleBytes[1] }; + ushort[] returnValue = { BitConverter.ToUInt16(lowRegisterBytes, 0), BitConverter.ToUInt16(highRegisterBytes, 0) }; + return returnValue; + } + #endregion + #endregion + + + private void Master_OnRecived(byte[] bytes, string hexString) + { + OnRecived?.Invoke(bytes, hexString); + } + + private void Master_OnSended(byte[] bytes, string hexString) + { + OnSended?.Invoke(bytes, hexString); + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF01ReadCoils.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF01ReadCoils.cs new file mode 100644 index 0000000..725b432 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF01ReadCoils.cs @@ -0,0 +1,64 @@ +using System; + +namespace SharpModbus +{ + public class ModbusF01ReadCoils : IModbusCommand + { + private readonly byte slave; + private readonly ushort address; + private readonly ushort count; + + public byte Code { get { return 1; } } + public byte Slave { get { return slave; } } + public ushort Address { get { return address; } } + public ushort Count { get { return count; } } + public int RequestLength { get { return 6; } } + public int ResponseLength { get { return 3 + ModbusUtils.BytesForBools(count); } } + + public ModbusF01ReadCoils(byte slave, ushort address, ushort count) + { + this.slave = slave; + this.address = address; + this.count = count; + } + + public void FillRequest(byte[] request, int offset) + { + request[offset + 0] = slave; + request[offset + 1] = 1; + request[offset + 2] = ModbusUtils.High(address); + request[offset + 3] = ModbusUtils.Low(address); + request[offset + 4] = ModbusUtils.High(count); + request[offset + 5] = ModbusUtils.Low(count); + } + + public object ParseResponse(byte[] response, int offset) + { + var bytes = ModbusUtils.BytesForBools(count); + Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}"); + Tools.AssertEqual(response[offset + 1], 1, "Function mismatch got {0} expected {1}"); + Tools.AssertEqual(response[offset + 2], bytes, "Bytes mismatch got {0} expected {1}"); + return ModbusUtils.DecodeBools(response, offset + 3, count); + } + + public object ApplyTo(IModbusModel model) + { + return model.getDOs(slave, address, count); + } + + public void FillResponse(byte[] response, int offset, object value) + { + var bytes = ModbusUtils.BytesForBools(count); + response[offset + 0] = slave; + response[offset + 1] = 1; + response[offset + 2] = bytes; + var data = ModbusUtils.EncodeBools(value as bool[]); + ModbusUtils.Copy(data, 0, response, offset + 3, bytes); + } + + public override string ToString() + { + return string.Format("[ModbusF01ReadCoils Slave={0}, Address={1}, Count={2}]", slave, address, count); + } + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF02ReadInputs.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF02ReadInputs.cs new file mode 100644 index 0000000..6c3a543 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF02ReadInputs.cs @@ -0,0 +1,64 @@ +using System; + +namespace SharpModbus +{ + public class ModbusF02ReadInputs : IModbusCommand + { + private readonly byte slave; + private readonly ushort address; + private readonly ushort count; + + public byte Code { get { return 2; } } + public byte Slave { get { return slave; } } + public ushort Address { get { return address; } } + public ushort Count { get { return count; } } + public int RequestLength { get { return 6; } } + public int ResponseLength { get { return 3 + ModbusUtils.BytesForBools(count); } } + + public ModbusF02ReadInputs(byte slave, ushort address, ushort count) + { + this.slave = slave; + this.address = address; + this.count = count; + } + + public void FillRequest(byte[] request, int offset) + { + request[offset + 0] = slave; + request[offset + 1] = 2; + request[offset + 2] = ModbusUtils.High(address); + request[offset + 3] = ModbusUtils.Low(address); + request[offset + 4] = ModbusUtils.High(count); + request[offset + 5] = ModbusUtils.Low(count); + } + + public object ParseResponse(byte[] response, int offset) + { + var bytes = ModbusUtils.BytesForBools(count); + Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}"); + Tools.AssertEqual(response[offset + 1], 2, "Function mismatch got {0} expected {1}"); + Tools.AssertEqual(response[offset + 2], bytes, "Bytes mismatch got {0} expected {1}"); + return ModbusUtils.DecodeBools(response, offset + 3, count); + } + + public object ApplyTo(IModbusModel model) + { + return model.getDIs(slave, address, count); + } + + public void FillResponse(byte[] response, int offset, object value) + { + var bytes = ModbusUtils.BytesForBools(count); + response[offset + 0] = slave; + response[offset + 1] = 2; + response[offset + 2] = bytes; + var data = ModbusUtils.EncodeBools(value as bool[]); + ModbusUtils.Copy(data, 0, response, offset + 3, bytes); + } + + public override string ToString() + { + return string.Format("[ModbusF02ReadInputs Slave={0}, Address={1}, Count={2}]", slave, address, count); + } + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF03ReadHoldingRegisters.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF03ReadHoldingRegisters.cs new file mode 100644 index 0000000..ffd9adf --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF03ReadHoldingRegisters.cs @@ -0,0 +1,64 @@ +using System; + +namespace SharpModbus +{ + public class ModbusF03ReadHoldingRegisters : IModbusCommand + { + private readonly byte slave; + private readonly ushort address; + private readonly ushort count; + + public byte Code { get { return 3; } } + public byte Slave { get { return slave; } } + public ushort Address { get { return address; } } + public ushort Count { get { return count; } } + public int RequestLength { get { return 6; } } + public int ResponseLength { get { return 3 + ModbusUtils.BytesForWords(count); } } + + public ModbusF03ReadHoldingRegisters(byte slave, ushort address, ushort count) + { + this.slave = slave; + this.address = address; + this.count = count; + } + + public void FillRequest(byte[] request, int offset) + { + request[offset + 0] = slave; + request[offset + 1] = 3; + request[offset + 2] = ModbusUtils.High(address); + request[offset + 3] = ModbusUtils.Low(address); + request[offset + 4] = ModbusUtils.High(count); + request[offset + 5] = ModbusUtils.Low(count); + } + + public object ParseResponse(byte[] response, int offset) + { + var bytes = ModbusUtils.BytesForWords(count); + Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}"); + Tools.AssertEqual(response[offset + 1], 3, "Function mismatch got {0} expected {1}"); + Tools.AssertEqual(response[offset + 2], bytes, "Bytes mismatch got {0} expected {1}"); + return ModbusUtils.DecodeWords(response, offset + 3, count); + } + + public object ApplyTo(IModbusModel model) + { + return model.getWOs(slave, address, count); + } + + public void FillResponse(byte[] response, int offset, object value) + { + var bytes = ModbusUtils.BytesForWords(count); + response[offset + 0] = slave; + response[offset + 1] = 3; + response[offset + 2] = bytes; + var data = ModbusUtils.EncodeWords((ushort[])value); + ModbusUtils.Copy(data, 0, response, offset + 3, bytes); + } + + public override string ToString() + { + return string.Format("[ModbusF03ReadHoldingRegisters Slave={0}, Address={1}, Count={2}]", slave, address, count); + } + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF04ReadInputRegisters.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF04ReadInputRegisters.cs new file mode 100644 index 0000000..b9235ab --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF04ReadInputRegisters.cs @@ -0,0 +1,64 @@ +using System; + +namespace SharpModbus +{ + public class ModbusF04ReadInputRegisters : IModbusCommand + { + private readonly byte slave; + private readonly ushort address; + private readonly ushort count; + + public byte Code { get { return 4; } } + public byte Slave { get { return slave; } } + public ushort Address { get { return address; } } + public ushort Count { get { return count; } } + public int RequestLength { get { return 6; } } + public int ResponseLength { get { return 3 + ModbusUtils.BytesForWords(count); } } + + public ModbusF04ReadInputRegisters(byte slave, ushort address, ushort count) + { + this.slave = slave; + this.address = address; + this.count = count; + } + + public void FillRequest(byte[] request, int offset) + { + request[offset + 0] = slave; + request[offset + 1] = 4; + request[offset + 2] = ModbusUtils.High(address); + request[offset + 3] = ModbusUtils.Low(address); + request[offset + 4] = ModbusUtils.High(count); + request[offset + 5] = ModbusUtils.Low(count); + } + + public object ParseResponse(byte[] response, int offset) + { + var bytes = ModbusUtils.BytesForWords(count); + Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}"); + Tools.AssertEqual(response[offset + 1], 4, "Function mismatch got {0} expected {1}"); + Tools.AssertEqual(response[offset + 2], bytes, "Bytes mismatch got {0} expected {1}"); + return ModbusUtils.DecodeWords(response, offset + 3, count); + } + + public object ApplyTo(IModbusModel model) + { + return model.getWIs(slave, address, count); + } + + public void FillResponse(byte[] response, int offset, object value) + { + var bytes = ModbusUtils.BytesForWords(count); + response[offset + 0] = slave; + response[offset + 1] = 4; + response[offset + 2] = bytes; + var data = ModbusUtils.EncodeWords(value as ushort[]); + ModbusUtils.Copy(data, 0, response, offset + 3, bytes); + } + + public override string ToString() + { + return string.Format("[ModbusF04ReadInputRegisters Slave={0}, Address={1}, Count={2}]", slave, address, count); + } + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF05WriteCoil.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF05WriteCoil.cs new file mode 100644 index 0000000..abd4336 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF05WriteCoil.cs @@ -0,0 +1,61 @@ +using System; + +namespace SharpModbus +{ + public class ModbusF05WriteCoil : IModbusCommand + { + private readonly byte slave; + private readonly ushort address; + private readonly bool value; + + public byte Code { get { return 5; } } + public byte Slave { get { return slave; } } + public ushort Address { get { return address; } } + public bool Value { get { return value; } } + public int RequestLength { get { return 6; } } + public int ResponseLength { get { return 6; } } + + public ModbusF05WriteCoil(byte slave, ushort address, bool state) + { + this.slave = slave; + this.address = address; + this.value = state; + } + + public void FillRequest(byte[] request, int offset) + { + request[offset + 0] = slave; + request[offset + 1] = 5; + request[offset + 2] = ModbusUtils.High(address); + request[offset + 3] = ModbusUtils.Low(address); + request[offset + 4] = ModbusUtils.EncodeBool(value); + request[offset + 5] = 0; + } + + public object ParseResponse(byte[] response, int offset) + { + Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}"); + Tools.AssertEqual(response[offset + 1], 5, "Function mismatch got {0} expected {1}"); + Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 2), address, "Address mismatch got {0} expected {1}"); + Tools.AssertEqual(response[offset + 4], ModbusUtils.EncodeBool(value), "Value mismatch got {0} expected {1}"); + Tools.AssertEqual(response[offset + 5], 0, "Pad mismatch {0} expected:{1}"); + return null; + } + + public object ApplyTo(IModbusModel model) + { + model.setDO(slave, address, value); + return null; + } + + public void FillResponse(byte[] response, int offset, object value) + { + FillRequest(response, offset); + } + + public override string ToString() + { + return string.Format("[ModbusF05WriteCoil Slave={0}, Address={1}, Value={2}]", slave, address, value); + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF06WriteRegister.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF06WriteRegister.cs new file mode 100644 index 0000000..63495ec --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF06WriteRegister.cs @@ -0,0 +1,60 @@ +using System; + +namespace SharpModbus +{ + public class ModbusF06WriteRegister : IModbusCommand + { + private readonly byte slave; + private readonly ushort address; + private readonly ushort value; + + public byte Code { get { return 6; } } + public byte Slave { get { return slave; } } + public ushort Address { get { return address; } } + public ushort Value { get { return value; } } + public int RequestLength { get { return 6; } } + public int ResponseLength { get { return 6; } } + + public ModbusF06WriteRegister(byte slave, ushort address, ushort value) + { + this.slave = slave; + this.address = address; + this.value = value; + } + + public void FillRequest(byte[] request, int offset) + { + request[offset + 0] = slave; + request[offset + 1] = 6; + request[offset + 2] = ModbusUtils.High(address); + request[offset + 3] = ModbusUtils.Low(address); + request[offset + 4] = ModbusUtils.High(value); + request[offset + 5] = ModbusUtils.Low(value); + } + + public object ParseResponse(byte[] response, int offset) + { + Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}"); + Tools.AssertEqual(response[offset + 1], 6, "Function mismatch got {0} expected {1}"); + Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 2), address, "Address mismatch got {0} expected {1}"); + Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 4), value, "Value mismatch got {0} expected {1}"); + return null; + } + + public object ApplyTo(IModbusModel model) + { + model.setWO(slave, address, value); + return null; + } + + public void FillResponse(byte[] response, int offset, object value) + { + FillRequest(response, offset); + } + + public override string ToString() + { + return string.Format("[ModbusF06WriteRegister Slave={0}, Address={1}, Value={2}]", slave, address, value); + } + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF15WriteCoils.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF15WriteCoils.cs new file mode 100644 index 0000000..947f08a --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF15WriteCoils.cs @@ -0,0 +1,63 @@ +using System; + +namespace SharpModbus +{ + public class ModbusF15WriteCoils : IModbusCommand + { + private readonly byte slave; + private readonly ushort address; + private readonly bool[] values; + + public byte Code { get { return 15; } } + public byte Slave { get { return slave; } } + public ushort Address { get { return address; } } + public bool[] Values { get { return ModbusUtils.Clone(values); } } + public int RequestLength { get { return 7 + ModbusUtils.BytesForBools(values.Length); } } + public int ResponseLength { get { return 6; } } + + public ModbusF15WriteCoils(byte slave, ushort address, bool[] values) + { + this.slave = slave; + this.address = address; + this.values = values; + } + + public void FillRequest(byte[] request, int offset) + { + FillResponse(request, offset, null); + var bytes = ModbusUtils.EncodeBools(values); + request[offset + 6] = (byte)bytes.Length; + ModbusUtils.Copy(bytes, 0, request, offset + 7, bytes.Length); + } + + public object ParseResponse(byte[] response, int offset) + { + Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}"); + Tools.AssertEqual(response[offset + 1], 15, "Function mismatch got {0} expected {1}"); + Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 2), address, "Address mismatch got {0} expected {1}"); + Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 4), values.Length, "Coil count mismatch got {0} expected {1}"); + return null; + } + + public object ApplyTo(IModbusModel model) + { + model.setDOs(slave, address, values); + return null; + } + + public void FillResponse(byte[] response, int offset, object value) + { + response[offset + 0] = slave; + response[offset + 1] = 15; + response[offset + 2] = ModbusUtils.High(address); + response[offset + 3] = ModbusUtils.Low(address); + response[offset + 4] = ModbusUtils.High(values.Length); + response[offset + 5] = ModbusUtils.Low(values.Length); + } + + public override string ToString() + { + return string.Format("[ModbusF15WriteCoils Slave={0}, Address={1}, Values={2}]", slave, address, values); + } + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF16WriteRegisters.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF16WriteRegisters.cs new file mode 100644 index 0000000..8e68000 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/Commands/ModbusF16WriteRegisters.cs @@ -0,0 +1,63 @@ +using System; + +namespace SharpModbus +{ + public class ModbusF16WriteRegisters : IModbusCommand + { + private readonly byte slave; + private readonly ushort address; + private readonly ushort[] values; + + public byte Code { get { return 16; } } + public byte Slave { get { return slave; } } + public ushort Address { get { return address; } } + public ushort[] Values { get { return ModbusUtils.Clone(values); } } + public int RequestLength { get { return 7 + ModbusUtils.BytesForWords(values.Length); } } + public int ResponseLength { get { return 6; } } + + public ModbusF16WriteRegisters(byte slave, ushort address, ushort[] values) + { + this.slave = slave; + this.address = address; + this.values = values; + } + + public void FillRequest(byte[] request, int offset) + { + FillResponse(request, offset, null); + var bytes = ModbusUtils.EncodeWords(values); + request[offset + 6] = (byte)bytes.Length; + ModbusUtils.Copy(bytes, 0, request, offset + 7, bytes.Length); + } + + public object ParseResponse(byte[] response, int offset) + { + Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}"); + Tools.AssertEqual(response[offset + 1], 16, "Function mismatch got {0} expected {1}"); + Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 2), address, "Address mismatch got {0} expected {1}"); + Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 4), values.Length, "Register count mismatch got {0} expected {1}"); + return null; + } + + public object ApplyTo(IModbusModel model) + { + model.setWOs(slave, address, values); + return null; + } + + public void FillResponse(byte[] response, int offset, object value) + { + response[offset + 0] = slave; + response[offset + 1] = 16; + response[offset + 2] = ModbusUtils.High(address); + response[offset + 3] = ModbusUtils.Low(address); + response[offset + 4] = ModbusUtils.High(values.Length); + response[offset + 5] = ModbusUtils.Low(values.Length); + } + + public override string ToString() + { + return string.Format("[ModbusF16WriteRegisters Slave={0}, Address={1}, Values={2}]", slave, address, values); + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/IModbusCommand.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/IModbusCommand.cs new file mode 100644 index 0000000..b426460 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/IModbusCommand.cs @@ -0,0 +1,18 @@ + +using System; + +namespace SharpModbus +{ + public interface IModbusCommand + { + byte Code { get; } + byte Slave { get; } + ushort Address { get; } + int RequestLength { get; } + int ResponseLength { get; } + void FillRequest(byte[] request, int offset); + object ParseResponse(byte[] response, int offset); + object ApplyTo(IModbusModel model); + void FillResponse(byte[] response, int offset, object value); + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/IModbusModel.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/IModbusModel.cs new file mode 100644 index 0000000..003f5ba --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/IModbusModel.cs @@ -0,0 +1,39 @@ +using System; + +namespace SharpModbus +{ + public interface IModbusModel + { + void setDI(byte slave, ushort address, bool value); + + void setDIs(byte slave, ushort address, bool[] values); + + bool getDI(byte slave, ushort address); + + bool[] getDIs(byte slave, ushort address, int count); + + void setDO(byte slave, ushort address, bool value); + + void setDOs(byte slave, ushort address, bool[] values); + + bool getDO(byte slave, ushort address); + + bool[] getDOs(byte slave, ushort address, int count); + + void setWI(byte slave, ushort address, ushort value); + + void setWIs(byte slave, ushort address, ushort[] values); + + ushort getWI(byte slave, ushort address); + + ushort[] getWIs(byte slave, ushort address, int count); + + void setWO(byte slave, ushort address, ushort value); + + void setWOs(byte slave, ushort address, ushort[] values); + + ushort getWO(byte slave, ushort address); + + ushort[] getWOs(byte slave, ushort address, int count); + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/IModbusProtocol.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/IModbusProtocol.cs new file mode 100644 index 0000000..06ab386 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/IModbusProtocol.cs @@ -0,0 +1,10 @@ +using System; + +namespace SharpModbus +{ + public interface IModbusProtocol + { + IModbusWrapper Wrap(IModbusCommand wrapped); + IModbusWrapper Parse(byte[] request, int offset); + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/IModbusScanner.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/IModbusScanner.cs new file mode 100644 index 0000000..3b54620 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/IModbusScanner.cs @@ -0,0 +1,10 @@ +using System; + +namespace SharpModbus +{ + public interface IModbusScanner + { + void Append(byte[] data, int offset, int count); + IModbusWrapper Scan(); + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/IModbusStream.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/IModbusStream.cs new file mode 100644 index 0000000..bd66812 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/IModbusStream.cs @@ -0,0 +1,10 @@ +using System; + +namespace SharpModbus +{ + public interface IModbusStream : IDisposable + { + void Write(byte[] data); + int Read(byte[] data); + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/IModbusWrapper.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/IModbusWrapper.cs new file mode 100644 index 0000000..870f515 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/IModbusWrapper.cs @@ -0,0 +1,11 @@ +using System; + +namespace SharpModbus +{ + public interface IModbusWrapper : IModbusCommand + { + IModbusCommand Wrapped { get; } + byte[] GetException(byte code); + void CheckException(byte[] respose, int count); + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusException.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusException.cs new file mode 100644 index 0000000..afcc2fc --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusException.cs @@ -0,0 +1,17 @@ +using System; + +namespace SharpModbus +{ + public class ModbusException : Exception + { + private readonly byte code; + + public byte Code { get { return code; } } + + public ModbusException(byte code) : + base(string.Format("Modbus exception {0}", code)) + { + this.code = code; + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusIsolatedStream.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusIsolatedStream.cs new file mode 100644 index 0000000..6872240 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusIsolatedStream.cs @@ -0,0 +1,39 @@ +using System; +using SharpSerial; + +namespace SharpModbus +{ + public class ModbusIsolatedStream : IModbusStream + { + private readonly Action monitor; + private readonly SerialProcess serialProcess; + private readonly int timeout; + + public ModbusIsolatedStream(object settings, int timeout, Action monitor = null) + { + this.serialProcess = new SerialProcess(settings); + this.timeout = timeout; + this.monitor = monitor; + } + + public void Dispose() + { + Tools.Dispose(serialProcess); + } + + public void Write(byte[] data) + { + if (monitor != null) monitor('>', data, data.Length); + serialProcess.Write(data); + } + + public int Read(byte[] data) + { + var response = serialProcess.Read(data.Length, -1, timeout); + var count = response.Length; + for (var i = 0; i < count; i++) data[i] = response[i]; + if (monitor != null) monitor('<', data, count); + return count; + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusMaster.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusMaster.cs new file mode 100644 index 0000000..008236b --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusMaster.cs @@ -0,0 +1,132 @@ +using MES.Utility.Core; +using System; +using System.Linq; + +namespace SharpModbus +{ + public class ModbusMaster : IDisposable + { + public delegate void OnSendedData(byte[] bytes, string hexString); + public event OnSendedData OnSended; + + public delegate void OnRecivedData(byte[] bytes, string hexString); + public event OnRecivedData OnRecived; + + public static ModbusMaster RTU(SerialSettings settings, int timeout = 400) + { + var stream = new ModbusSerialStream(settings, timeout); + var protocol = new ModbusRTUProtocol(); + return new ModbusMaster(stream, protocol); + } + + public static ModbusMaster IsolatedRTU(SerialSettings settings, int timeout = 400) + { + var stream = new ModbusIsolatedStream(settings, timeout); + var protocol = new ModbusRTUProtocol(); + return new ModbusMaster(stream, protocol); + } + + public static ModbusMaster TCP(string ip, int port, int timeout = 400) + { + var socket = Tools.ConnectWithTimeout(ip, port, timeout); + var stream = new ModbusSocketStream(socket, timeout); + var protocol = new ModbusTCPProtocol(); + return new ModbusMaster(stream, protocol); + } + + private readonly IModbusProtocol protocol; + private readonly IModbusStream stream; + + public ModbusMaster(IModbusStream stream, IModbusProtocol protocol) + { + this.stream = stream; + this.protocol = protocol; + } + + public void Dispose() + { + Tools.Dispose(stream); + } + + public bool ReadCoil(byte slave, ushort address) + { + return ReadCoils(slave, address, 1)[0]; //there is no code for single read + } + + public bool ReadInput(byte slave, ushort address) + { + return ReadInputs(slave, address, 1)[0]; //there is no code for single read + } + + public ushort ReadInputRegister(byte slave, ushort address) + { + return ReadInputRegisters(slave, address, 1)[0]; //there is no code for single read + } + + public ushort ReadHoldingRegister(byte slave, ushort address) + { + return ReadHoldingRegisters(slave, address, 1)[0]; //there is no code for single read + } + + public bool[] ReadCoils(byte slave, ushort address, ushort count) + { + return Execute(new ModbusF01ReadCoils(slave, address, count)) as bool[]; + } + + public bool[] ReadInputs(byte slave, ushort address, ushort count) + { + return Execute(new ModbusF02ReadInputs(slave, address, count)) as bool[]; + } + + public ushort[] ReadInputRegisters(byte slave, ushort address, ushort count) + { + return Execute(new ModbusF04ReadInputRegisters(slave, address, count)) as ushort[]; + } + + public ushort[] ReadHoldingRegisters(byte slave, ushort address, ushort count) + { + return Execute(new ModbusF03ReadHoldingRegisters(slave, address, count)) as ushort[]; + } + + public void WriteCoil(byte slave, ushort address, bool value) + { + Execute(new ModbusF05WriteCoil(slave, address, value)); + } + + public void WriteRegister(byte slave, ushort address, ushort value) + { + Execute(new ModbusF06WriteRegister(slave, address, value)); + } + + public void WriteCoils(byte slave, ushort address, params bool[] values) + { + Execute(new ModbusF15WriteCoils(slave, address, values)); + } + + public void WriteRegisters(byte slave, ushort address, params ushort[] values) + { + Execute(new ModbusF16WriteRegisters(slave, address, values)); + } + + private object Execute(IModbusCommand cmd) + { + var wrapper = protocol.Wrap(cmd); + var request = new byte[wrapper.RequestLength]; + var response = new byte[wrapper.ResponseLength]; + wrapper.FillRequest(request, 0); + OnSended?.Invoke(request, DataHexString(request)); + stream.Write(request); + var count = stream.Read(response); + OnRecived?.Invoke(response, DataHexString(response)); + if (count < response.Length) wrapper.CheckException(response, count); + return wrapper.ParseResponse(response, 0); + } + + private string DataHexString(byte[] bytes) + { + if (bytes == null) + return string.Empty; + return bytes.Select(it => Convert.ToString(it, 16).PadLeft(2, '0').ToUpper()).ToList().GetStrArray(" "); + } + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusModel.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusModel.cs new file mode 100644 index 0000000..575dace --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusModel.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; + +namespace SharpModbus +{ + public enum ModbusIoType + { + DI, + DO, + WO, + WI + } + + public class ModbusModel : IModbusModel + { + private readonly IDictionary digitals = new Dictionary(); + private readonly IDictionary words = new Dictionary(); + + public void setDI(byte slave, ushort address, bool value) + { + var key = Key(ModbusIoType.DI, slave, address); + digitals[key] = value; + } + + public void setDIs(byte slave, ushort address, bool[] values) + { + for (var i = 0; i < values.Length; i++) + { + var key = Key(ModbusIoType.DI, slave, address + i); + digitals[key] = values[i]; + } + } + + public bool getDI(byte slave, ushort address) + { + var key = Key(ModbusIoType.DI, slave, address); + return digitals[key]; + } + + public bool[] getDIs(byte slave, ushort address, int count) + { + var values = new bool[count]; + for (var i = 0; i < values.Length; i++) + { + var key = Key(ModbusIoType.DI, slave, address + i); + values[i] = digitals[key]; + } + return values; + } + + public void setDO(byte slave, ushort address, bool value) + { + var key = Key(ModbusIoType.DO, slave, address); + digitals[key] = value; + } + + public void setDOs(byte slave, ushort address, bool[] values) + { + for (var i = 0; i < values.Length; i++) + { + var key = Key(ModbusIoType.DO, slave, address + i); + digitals[key] = values[i]; + } + } + + public bool getDO(byte slave, ushort address) + { + var key = Key(ModbusIoType.DO, slave, address); + return digitals[key]; + } + + public bool[] getDOs(byte slave, ushort address, int count) + { + var values = new bool[count]; + for (var i = 0; i < values.Length; i++) + { + var key = Key(ModbusIoType.DO, slave, address + i); + values[i] = digitals[key]; + } + return values; + } + + public void setWI(byte slave, ushort address, ushort value) + { + var key = Key(ModbusIoType.WI, slave, address); + words[key] = value; + } + + public void setWIs(byte slave, ushort address, ushort[] values) + { + for (var i = 0; i < values.Length; i++) + { + var key = Key(ModbusIoType.WI, slave, address + i); + words[key] = values[i]; + } + } + + public ushort getWI(byte slave, ushort address) + { + var key = Key(ModbusIoType.WI, slave, address); + return words[key]; + } + + public ushort[] getWIs(byte slave, ushort address, int count) + { + var values = new ushort[count]; + for (var i = 0; i < values.Length; i++) + { + var key = Key(ModbusIoType.WI, slave, address + i); + values[i] = words[key]; + } + return values; + } + + public void setWO(byte slave, ushort address, ushort value) + { + var key = Key(ModbusIoType.WO, slave, address); + words[key] = value; + } + + public void setWOs(byte slave, ushort address, ushort[] values) + { + for (var i = 0; i < values.Length; i++) + { + var key = Key(ModbusIoType.WO, slave, address + i); + words[key] = values[i]; + } + } + + public ushort getWO(byte slave, ushort address) + { + var key = Key(ModbusIoType.WO, slave, address); + return words[key]; + } + + public ushort[] getWOs(byte slave, ushort address, int count) + { + var values = new ushort[count]; + for (var i = 0; i < values.Length; i++) + { + var key = Key(ModbusIoType.WO, slave, address + i); + values[i] = words[key]; + } + return values; + } + + private string Key(ModbusIoType type, byte slave, int address) + { + return string.Format("{0},{1},{2}", slave, type, address); + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusParser.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusParser.cs new file mode 100644 index 0000000..bb21693 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusParser.cs @@ -0,0 +1,90 @@ +using System; + +namespace SharpModbus +{ + public static class ModbusParser + { + public static IModbusCommand Parse(byte[] request, int offset) + { + var slave = request[offset + 0]; + var code = request[offset + 1]; + var address = ModbusUtils.GetUShort(request, offset + 2); + switch (code) + { + case 1: + return Parse01(slave, code, address, request, offset); + case 2: + return Parse02(slave, code, address, request, offset); + case 3: + return Parse03(slave, code, address, request, offset); + case 4: + return Parse04(slave, code, address, request, offset); + case 5: + return Parse05(slave, code, address, request, offset); + case 6: + return Parse06(slave, code, address, request, offset); + case 15: + return Parse15(slave, code, address, request, offset); + case 16: + return Parse16(slave, code, address, request, offset); + } + throw Tools.Make("Unsupported function code {0}", code); + } + + private static IModbusCommand Parse01(byte slave, byte code, ushort address, byte[] request, int offset) + { + var count = ModbusUtils.GetUShort(request, offset + 4); + return new ModbusF01ReadCoils(slave, address, count); + } + + private static IModbusCommand Parse02(byte slave, byte code, ushort address, byte[] request, int offset) + { + var count = ModbusUtils.GetUShort(request, offset + 4); + return new ModbusF02ReadInputs(slave, address, count); + } + + private static IModbusCommand Parse03(byte slave, byte code, ushort address, byte[] request, int offset) + { + var count = ModbusUtils.GetUShort(request, offset + 4); + return new ModbusF03ReadHoldingRegisters(slave, address, count); + } + + private static IModbusCommand Parse04(byte slave, byte code, ushort address, byte[] request, int offset) + { + var count = ModbusUtils.GetUShort(request, offset + 4); + return new ModbusF04ReadInputRegisters(slave, address, count); + } + + private static IModbusCommand Parse05(byte slave, byte code, ushort address, byte[] request, int offset) + { + var value = ModbusUtils.DecodeBool(request[offset + 4]); + var zero = request[offset + 5]; + Tools.AssertEqual(zero, 0, "Zero mismatch got {0} expected {1}"); + return new ModbusF05WriteCoil(slave, address, value); + } + + private static IModbusCommand Parse06(byte slave, byte code, ushort address, byte[] request, int offset) + { + var value = ModbusUtils.GetUShort(request, offset + 4); + return new ModbusF06WriteRegister(slave, address, value); + } + + private static IModbusCommand Parse15(byte slave, byte code, ushort address, byte[] request, int offset) + { + var count = ModbusUtils.GetUShort(request, offset + 4); + var values = ModbusUtils.DecodeBools(request, offset + 7, count); + var bytes = request[offset + 6]; + Tools.AssertEqual(ModbusUtils.BytesForBools(count), bytes, "Byte count mismatch got {0} expected {1}"); + return new ModbusF15WriteCoils(slave, address, values); + } + + private static IModbusCommand Parse16(byte slave, byte code, ushort address, byte[] request, int offset) + { + var count = ModbusUtils.GetUShort(request, offset + 4); + var values = ModbusUtils.DecodeWords(request, offset + 7, count); + var bytes = request[offset + 6]; + Tools.AssertEqual(ModbusUtils.BytesForWords(count), bytes, "Byte count mismatch got {0} expected {1}"); + return new ModbusF16WriteRegisters(slave, address, values); + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusRTUProtocol.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusRTUProtocol.cs new file mode 100644 index 0000000..5b3879f --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusRTUProtocol.cs @@ -0,0 +1,21 @@ +using System; + +namespace SharpModbus +{ + public class ModbusRTUProtocol : IModbusProtocol + { + public IModbusWrapper Wrap(IModbusCommand wrapped) + { + return new ModbusRTUWrapper(wrapped); + } + + public IModbusWrapper Parse(byte[] request, int offset) + { + var wrapped = ModbusParser.Parse(request, offset); + var crc = ModbusUtils.CRC16(request, offset, wrapped.RequestLength); + Tools.AssertEqual(crc, ModbusUtils.GetUShortLittleEndian(request, offset + wrapped.RequestLength), + "CRC mismatch {0:X4} expected {1:X4}"); + return new ModbusRTUWrapper(wrapped); + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusRTUScanner.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusRTUScanner.cs new file mode 100644 index 0000000..bbe690d --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusRTUScanner.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; + +namespace SharpModbus +{ + public class ModbusRTUScanner : IModbusScanner + { + private readonly ModbusRTUProtocol protocol = new ModbusRTUProtocol(); + private readonly List buffer = new List(); + + public void Append(byte[] data, int offset, int count) + { + for (var i = 0; i < count; i++) buffer.Add(data[offset + i]); + } + + public IModbusWrapper Scan() + { + //01,02,03,04,05,06 have 6 + 2(CRC) + //15,16 have 6 + 1(len) + len + 2(CRC) + if (buffer.Count >= 8) + { + var code = buffer[1]; + CheckCode(code); + var length = 8; + if (HasBytesAt6(code)) length += 1 + buffer[6]; + if (buffer.Count >= length) + { + var request = buffer.GetRange(0, length).ToArray(); + buffer.RemoveRange(0, length); + return protocol.Parse(request, 0); + } + } + return null; //not enough data to parse + } + + bool HasBytesAt6(byte code) + { + return "15,16".Contains(code.ToString("00")); + } + + void CheckCode(byte code) + { + var valid = "01,02,03,04,05,06,15,16".Contains(code.ToString("00")); + if (!valid) Tools.Throw("Unsupported code {0}", code); + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusRTUWrapper.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusRTUWrapper.cs new file mode 100644 index 0000000..65c9a51 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusRTUWrapper.cs @@ -0,0 +1,85 @@ +using System; + +namespace SharpModbus +{ + public class ModbusRTUWrapper : IModbusWrapper + { + private readonly IModbusCommand wrapped; + + public byte Code { get { return wrapped.Code; } } + public byte Slave { get { return wrapped.Slave; } } + public ushort Address { get { return wrapped.Address; } } + public IModbusCommand Wrapped { get { return wrapped; } } + public int RequestLength { get { return wrapped.RequestLength + 2; } } + public int ResponseLength { get { return wrapped.ResponseLength + 2; } } + + public ModbusRTUWrapper(IModbusCommand wrapped) + { + this.wrapped = wrapped; + } + + public void FillRequest(byte[] request, int offset) + { + wrapped.FillRequest(request, offset); + var crc = ModbusUtils.CRC16(request, offset, wrapped.RequestLength); + request[offset + wrapped.RequestLength + 0] = ModbusUtils.Low(crc); + request[offset + wrapped.RequestLength + 1] = ModbusUtils.High(crc); + } + + public object ParseResponse(byte[] response, int offset) + { + var crc1 = ModbusUtils.CRC16(response, offset, wrapped.ResponseLength); + //crc is little endian page 13 http://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + var crc2 = ModbusUtils.GetUShortLittleEndian(response, offset + wrapped.ResponseLength); + Tools.AssertEqual(crc2, crc1, "CRC mismatch got {0:X4} expected {1:X4}"); + return wrapped.ParseResponse(response, offset); + } + + public object ApplyTo(IModbusModel model) + { + return wrapped.ApplyTo(model); + } + + public void FillResponse(byte[] response, int offset, object value) + { + wrapped.FillResponse(response, offset, value); + var crc = ModbusUtils.CRC16(response, offset, wrapped.ResponseLength); + response[offset + wrapped.ResponseLength + 0] = ModbusUtils.Low(crc); + response[offset + wrapped.ResponseLength + 1] = ModbusUtils.High(crc); + } + + public byte[] GetException(byte code) + { + var exception = new byte[5]; + exception[0] = wrapped.Slave; + exception[1] = (byte)(wrapped.Code | 0x80); + exception[2] = code; + var crc = ModbusUtils.CRC16(exception, 0, 3); + exception[3] = ModbusUtils.Low(crc); + exception[4] = ModbusUtils.High(crc); + return exception; + } + + public void CheckException(byte[] response, int count) + { + if (count < 5) Tools.Throw("Partial packet exception got {0} expected >= {1}", count, 5); + var offset = 0; + var code = response[offset + 1]; + if ((code & 0x80) != 0) + { + Tools.AssertEqual(response[offset + 0], wrapped.Slave, "Slave mismatch got {0} expected {1}"); + Tools.AssertEqual(code & 0x7F, wrapped.Code, "Code mismatch got {0} expected {1}"); + var crc1 = ModbusUtils.CRC16(response, offset, 3); + //crc is little endian page 13 http://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + var crc2 = ModbusUtils.GetUShortLittleEndian(response, offset + 3); + Tools.AssertEqual(crc2, crc1, "CRC mismatch got {0:X4} expected {1:X4}"); + throw new ModbusException(response[offset + 2]); + } + } + + public override string ToString() + { + return string.Format("[ModbusRTUWrapper Wrapped={0}]", wrapped); + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusSerialStream.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusSerialStream.cs new file mode 100644 index 0000000..6b5ca94 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusSerialStream.cs @@ -0,0 +1,39 @@ +using System; +using SharpSerial; + +namespace SharpModbus +{ + public class ModbusSerialStream : IModbusStream + { + private readonly Action monitor; + private readonly SerialDevice serialDevice; + private readonly int timeout; + + public ModbusSerialStream(SerialSettings settings, int timeout, Action monitor = null) + { + this.serialDevice = new SerialDevice(settings); + this.timeout = timeout; + this.monitor = monitor; + } + + public void Dispose() + { + Tools.Dispose(serialDevice); + } + + public void Write(byte[] data) + { + if (monitor != null) monitor('>', data, data.Length); + serialDevice.Write(data); + } + + public int Read(byte[] data) + { + var response = serialDevice.Read(data.Length, -1, timeout); + var count = response.Length; + for (var i = 0; i < count; i++) data[i] = response[i]; + if (monitor != null) monitor('<', data, count); + return count; + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusSocketStream.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusSocketStream.cs new file mode 100644 index 0000000..b09ac8d --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusSocketStream.cs @@ -0,0 +1,56 @@ +using System; +using System.Threading; +using System.Net.Sockets; + +namespace SharpModbus +{ + public class ModbusSocketStream : IModbusStream + { + private readonly TcpClient socket; + private readonly Action monitor; + + public ModbusSocketStream(TcpClient socket, int timeout, Action monitor = null) + { + socket.ReceiveTimeout = timeout; + socket.SendTimeout = timeout; + this.monitor = monitor; + this.socket = socket; + } + + public void Dispose() + { + Tools.Dispose(socket); + } + + public void Write(byte[] data) + { + //implicit discard to allow retry after timeout as per #5 + while (socket.Available > 0) socket.GetStream().ReadByte(); + if (monitor != null) monitor('>', data, data.Length); + socket.GetStream().Write(data, 0, data.Length); + } + + public int Read(byte[] data) + { + var count = 0; + var dl = DateTime.Now.AddMilliseconds(socket.ReceiveTimeout); + while (count < data.Length) + { + var available = socket.Available; + if (available == 0) + { + if (DateTime.Now > dl) break; + Thread.Sleep(1); + } + else + { + var size = (int)Math.Min(available, data.Length - count); + count += socket.GetStream().Read(data, count, size); + dl = DateTime.Now; //should come in single packet + } + } + if (monitor != null) monitor('<', data, count); + return count; + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusTCPProtocol.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusTCPProtocol.cs new file mode 100644 index 0000000..39e075e --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusTCPProtocol.cs @@ -0,0 +1,29 @@ +using System; + +namespace SharpModbus +{ + public class ModbusTCPProtocol : IModbusProtocol + { + private ushort transactionId = 0; + + public ushort TransactionId + { + get { return transactionId; } + set { transactionId = value; } + } + + public IModbusWrapper Wrap(IModbusCommand wrapped) + { + return new ModbusTCPWrapper(wrapped, transactionId++); + } + + public IModbusWrapper Parse(byte[] request, int offset) + { + var wrapped = ModbusParser.Parse(request, offset + 6); + Tools.AssertEqual(wrapped.RequestLength, ModbusUtils.GetUShort(request, offset + 4), + "RequestLength mismatch got {0} expected {1}"); + var transaction = ModbusUtils.GetUShort(request, offset); + return new ModbusTCPWrapper(wrapped, transaction); + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusTCPScanner.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusTCPScanner.cs new file mode 100644 index 0000000..b86494c --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusTCPScanner.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; + +namespace SharpModbus +{ + public class ModbusTCPScanner : IModbusScanner + { + private readonly ModbusTCPProtocol protocol = new ModbusTCPProtocol(); + private readonly List buffer = new List(); + + public void Append(byte[] data, int offset, int count) + { + for (var i = 0; i < count; i++) buffer.Add(data[offset + i]); + } + + public IModbusWrapper Scan() + { + if (buffer.Count >= 6) + { + var length = ModbusUtils.GetUShort(buffer[4], buffer[5]); + if (buffer.Count >= 6 + length) + { + var request = buffer.GetRange(0, 6 + length).ToArray(); + buffer.RemoveRange(0, 6 + length); + return protocol.Parse(request, 0); + } + } + return null; //not enough data to parse + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusTCPWrapper.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusTCPWrapper.cs new file mode 100644 index 0000000..9c373a5 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusTCPWrapper.cs @@ -0,0 +1,92 @@ +using System; + +namespace SharpModbus +{ + public class ModbusTCPWrapper : IModbusWrapper + { + private readonly IModbusCommand wrapped; + private readonly ushort transactionId; + + public byte Code { get { return wrapped.Code; } } + public byte Slave { get { return wrapped.Slave; } } + public ushort Address { get { return wrapped.Address; } } + public IModbusCommand Wrapped { get { return wrapped; } } + public ushort TransactionId { get { return transactionId; } } + public int RequestLength { get { return wrapped.RequestLength + 6; } } + public int ResponseLength { get { return wrapped.ResponseLength + 6; } } + + public ModbusTCPWrapper(IModbusCommand wrapped, ushort transactionId) + { + this.wrapped = wrapped; + this.transactionId = transactionId; + } + + public void FillRequest(byte[] request, int offset) + { + request[offset + 0] = ModbusUtils.High(transactionId); + request[offset + 1] = ModbusUtils.Low(transactionId); + request[offset + 2] = 0; + request[offset + 3] = 0; + request[offset + 4] = ModbusUtils.High(wrapped.RequestLength); + request[offset + 5] = ModbusUtils.Low(wrapped.RequestLength); + wrapped.FillRequest(request, offset + 6); + } + + public object ParseResponse(byte[] response, int offset) + { + Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 0), transactionId, "TransactionId mismatch got {0} expected {1}"); + Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 2), 0, "Zero mismatch got {0} expected {1}"); + Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 4), wrapped.ResponseLength, "Length mismatch got {0} expected {1}"); + return wrapped.ParseResponse(response, offset + 6); + } + + public object ApplyTo(IModbusModel model) + { + return wrapped.ApplyTo(model); + } + + public void FillResponse(byte[] response, int offset, object value) + { + response[offset + 0] = ModbusUtils.High(transactionId); + response[offset + 1] = ModbusUtils.Low(transactionId); + response[offset + 2] = 0; + response[offset + 3] = 0; + response[offset + 4] = ModbusUtils.High(wrapped.ResponseLength); + response[offset + 5] = ModbusUtils.Low(wrapped.ResponseLength); + wrapped.FillResponse(response, offset + 6, value); + } + + public byte[] GetException(byte code) + { + var exception = new byte[9]; + exception[0] = ModbusUtils.High(transactionId); + exception[1] = ModbusUtils.Low(transactionId); + exception[2] = 0; + exception[3] = 0; + exception[4] = ModbusUtils.High(3); + exception[5] = ModbusUtils.Low(3); + exception[6 + 0] = wrapped.Slave; + exception[6 + 1] = (byte)(wrapped.Code | 0x80); + exception[6 + 2] = code; + return exception; + } + + public void CheckException(byte[] response, int count) + { + if (count < 9) Tools.Throw("Partial packet exception got {0} expected >= {1}", count, 9); + var offset = 6; + var code = response[offset + 1]; + if ((code & 0x80) != 0) + { + Tools.AssertEqual(response[offset + 0], wrapped.Slave, "Slave mismatch got {0} expected {1}"); + Tools.AssertEqual(code & 0x7F, wrapped.Code, "Code mismatch got {0} expected {1}"); + throw new ModbusException(response[offset + 2]); + } + } + + public override string ToString() + { + return string.Format("[ModbusTCPWrapper Wrapped={0}, TransactionId={1}]", wrapped, transactionId); + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusTools.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusTools.cs new file mode 100644 index 0000000..03b96e8 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusTools.cs @@ -0,0 +1,60 @@ +using System; +using System.Net.Sockets; + +namespace SharpModbus +{ + public class SerialSettings : SharpSerial.SerialSettings { } + + public static class Tools + { + public static void AssertEqual(int a, int b, string format) + { + if (a != b) Tools.Throw(format, a, b); + } + + public static TcpClient ConnectWithTimeout(string host, int port, int timeout) + { + var socket = new TcpClient(); + try + { + var result = socket.BeginConnect(host, port, null, null); + var connected = result.AsyncWaitHandle.WaitOne(timeout, true); + if (!connected) Tools.Throw("Timeout connecting to {0}:{1}", host, port); + socket.EndConnect(result); + return socket; + } + catch (Exception ex) + { + Tools.Dispose(socket); + throw ex; + } + } + + public static void Dispose(IDisposable disposable) + { + try { if (disposable != null) disposable.Dispose(); } + catch (Exception) { } + } + + public static Exception Make(string format, params object[] args) + { + var message = format; + if (args.Length > 0) message = string.Format(format, args); + return new Exception(message); + } + + public static void Throw(string format, params object[] args) + { + var message = format; + if (args.Length > 0) message = string.Format(format, args); + throw new Exception(message); + } + + public static void Throw(Exception inner, string format, params object[] args) + { + var message = format; + if (args.Length > 0) message = string.Format(format, args); + throw new Exception(message, inner); + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusUtils.cs b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusUtils.cs new file mode 100644 index 0000000..7939e73 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpModbus/ModbusUtils.cs @@ -0,0 +1,169 @@ +using System; + +namespace SharpModbus +{ + public static class ModbusUtils + { + public static ushort CRC16(byte[] bytes, int offset, int count) + { + ushort crc = 0xFFFF; + for (int pos = 0; pos < count; pos++) + { + crc ^= (ushort)bytes[pos + offset]; + for (int i = 8; i > 0; i--) + { + if ((crc & 0x0001) != 0) + { + crc >>= 1; + crc ^= 0xA001; + } + else + crc >>= 1; + } + } + return crc; + } + + public static byte EncodeBool(bool value) + { + return (byte)(value ? 0xFF : 0x00); + } + + public static bool DecodeBool(byte value) + { + return (value != 0x00); + } + + public static byte[] EncodeBools(bool[] bools) + { + var count = BytesForBools(bools.Length); + var bytes = new byte[count]; + for (var i = 0; i < count; i++) + { + bytes[i] = 0; + } + for (var i = 0; i < bools.Length; i++) + { + var v = bools[i]; + if (v) + { + var bi = i / 8; + bytes[bi] |= (byte)(1 << (i % 8)); + } + } + return bytes; + } + + public static byte[] EncodeWords(ushort[] words) + { + var count = 2 * words.Length; + var bytes = new byte[count]; + for (var i = 0; i < count; i++) + { + bytes[i] = 0; + } + for (var i = 0; i < words.Length; i++) + { + bytes[2 * i + 0] = (byte)((words[i] >> 8) & 0xff); + bytes[2 * i + 1] = (byte)((words[i] >> 0) & 0xff); + } + return bytes; + } + + public static bool[] DecodeBools(byte[] packet, int offset, ushort count) + { + var bools = new bool[count]; + var bytes = BytesForBools(count); + for (var i = 0; i < bytes; i++) + { + var bits = count >= 8 ? 8 : count % 8; + var b = packet[offset + i]; + ByteToBools(b, bools, bools.Length - count, bits); + count -= (ushort)bits; + } + return bools; + } + + public static ushort[] DecodeWords(byte[] packet, int offset, ushort count) + { + var results = new ushort[count]; + for (int i = 0; i < count; i++) + { + results[i] = (ushort)(packet[offset + 2 * i] << 8 | packet[offset + 2 * i + 1]); + } + return results; + } + + private static void ByteToBools(byte b, bool[] bools, int offset, int count) + { + for (int i = 0; i < count; i++) + bools[offset + i] = ((b >> i) & 0x01) == 1; + } + + public static byte BytesForWords(int count) + { + return (byte)(2 * count); + } + + public static byte BytesForBools(int count) + { + return (byte)(count == 0 ? 0 : (count - 1) / 8 + 1); + } + + public static byte High(int value) + { + return (byte)((value >> 8) & 0xff); + } + + public static byte Low(int value) + { + return (byte)((value >> 0) & 0xff); + } + + public static ushort GetUShort(byte bh, byte bl) + { + return (ushort)( + ((bh << 8) & 0xFF00) + | (bl & 0xff) + ); + } + + public static ushort GetUShort(byte[] bytes, int offset) + { + return (ushort)( + ((bytes[offset + 0] << 8) & 0xFF00) + | (bytes[offset + 1] & 0xff) + ); + } + + public static ushort GetUShortLittleEndian(byte[] bytes, int offset) + { + return (ushort)( + ((bytes[offset + 1] << 8) & 0xFF00) + | (bytes[offset + 0] & 0xff) + ); + } + + public static void Copy(byte[] src, int srcOffset, byte[] dst, int dstOffset, int count) + { + for (var i = 0; i < count; i++) + dst[dstOffset + i] = src[srcOffset + i]; + } + + public static bool[] Clone(bool[] values) + { + var clone = new bool[values.Length]; + for (var i = 0; i < values.Length; i++) + clone[i] = values[i]; + return clone; + } + + public static ushort[] Clone(ushort[] values) + { + var clone = new ushort[values.Length]; + for (var i = 0; i < values.Length; i++) + clone[i] = values[i]; + return clone; + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpSerial/Program.Stdio.cs b/常用工具集/Utility/Network/Modbus/SharpSerial/Program.Stdio.cs new file mode 100644 index 0000000..67e3972 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpSerial/Program.Stdio.cs @@ -0,0 +1,47 @@ +using System; +using System.Diagnostics; + +namespace SharpSerial +{ + public static class Stdio + { + private static bool traceTimed = false; + private static bool traceEnabled = false; + private static readonly object locker = new object(); + + public static string ReadLine() => Console.ReadLine(); + + public static void WriteLine(string format, params object[] args) + { + lock (locker) + { + Console.Out.WriteLine(format, args); + Console.Out.Flush(); + } + } + + [Conditional("DEBUG")] + public static void Trace(string format, params object[] args) + { + if (traceEnabled) + { + lock (locker) + { + if (traceTimed) + { + Console.Error.Write(DateTime.Now.ToString("HH:mm:ss.fff")); + Console.Error.Write(" "); + } + Console.Error.WriteLine(format, args); + Console.Error.Flush(); + } + } + } + + public static void EnableTrace(bool enable, bool timed) + { + traceTimed = timed; + traceEnabled = enable; + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpSerial/Program.Tools.cs b/常用工具集/Utility/Network/Modbus/SharpSerial/Program.Tools.cs new file mode 100644 index 0000000..a4203ad --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpSerial/Program.Tools.cs @@ -0,0 +1,99 @@ +using System; +using System.Text; + +namespace SharpSerial +{ + public static class Tools + { + public static void SetupGlobalCatcher() + { + AppDomain.CurrentDomain.UnhandledException += ExceptionHandler; + } + + public static void ExceptionHandler(object sender, UnhandledExceptionEventArgs args) + { + ExceptionHandler(args.ExceptionObject as Exception); + } + + public static void ExceptionHandler(Exception ex) + { + Try(() => Stdio.Trace("!{0}", ex.ToString())); + Try(() => Stdio.WriteLine("!{0}", ex.ToString())); + Environment.Exit(1); + } + + public static string StringHex(byte[] data) + { + var sb = new StringBuilder(); + sb.Append("<"); + foreach (var b in data) sb.Append(b.ToString("X2")); + return sb.ToString(); + } + + public static byte[] ParseHex(string text) + { + Assert(text.Length % 2 == 1, "Odd length expected for {0}:{1}", text.Length, text); + var bytes = new byte[text.Length / 2]; + for (var i = 0; i < bytes.Length; i++) + { + var b2 = text.Substring(1 + i * 2, 2); + bytes[i] = Convert.ToByte(b2, 16); + } + return bytes; + } + + public static void SetProperty(object target, string line) + { + var parts = line.Split(new char[] { '=' }); + if (parts.Length != 2) throw Make("Expected 2 parts in {0}", Readable(line)); + var propertyName = parts[0]; + var propertyValue = parts[1]; + var property = target.GetType().GetProperty(propertyName); + if (property == null) throw Make("Property not found {0}", Readable(propertyName)); + var value = FromString(property.PropertyType, propertyValue); + property.SetValue(target, value, null); + } + + public static object FromString(Type type, string text) + { + if (type.IsEnum) return Enum.Parse(type, text); + return Convert.ChangeType(text, type); + } + + public static void Try(Action action) + { + try + { + action.Invoke(); + } + catch (Exception) + { + //no clear use case for cleanup exception + } + } + + public static Exception Make(string format, params object[] args) + { + var line = format; + if (args.Length > 0) line = string.Format(format, args); + return new Exception(line); + } + + public static string Readable(string text) + { + var sb = new StringBuilder(); + foreach (var c in text) + { + if (Char.IsControl(c)) sb.Append(((int)c).ToString("X2")); + else if (Char.IsWhiteSpace(c)) sb.Append(((int)c).ToString("X2")); + else sb.Append(c); + } + return sb.ToString(); + } + + public static void Assert(bool condition, string format, params object[] args) + { + if (!condition) throw Make(format, args); + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpSerial/SerialDevice.cs b/常用工具集/Utility/Network/Modbus/SharpSerial/SerialDevice.cs new file mode 100644 index 0000000..6d2aa87 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpSerial/SerialDevice.cs @@ -0,0 +1,97 @@ +using System; +using System.IO; +using System.Threading; +using System.Collections.Generic; +using System.IO.Ports; + +namespace SharpSerial +{ + // com0com BytesToRead is UNRELIABLE + // Solution based on BaseStream and influenced by + // https://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport + // https://www.vgies.com/a-reliable-serial-port-in-c/ + public class SerialDevice : ISerialStream, IDisposable + { + private readonly SerialPort serial; + private readonly List list; + private readonly Queue queue; + private readonly byte[] buffer; + + public SerialDevice(object settings) + { + this.buffer = new byte[256]; + this.list = new List(256); + this.queue = new Queue(256); + this.serial = new SerialPort(); + + //init serial port and launch async reader + SerialSettings.CopyProperties(settings, serial); + serial.Open(); + //DiscardInBuffer not needed by FTDI and ignored by com0com + var stream = serial.BaseStream; + //unavailable after closed so pass it + stream.BeginRead(buffer, 0, buffer.Length, ReadCallback, stream); + } + + public void Dispose() + { + Tools.Try(serial.Close); + Tools.Try(serial.Dispose); + } + + public void Write(byte[] data) + { + var stream = serial.BaseStream; + stream.Write(data, 0, data.Length); + //always flush to allow sync by following read available + stream.Flush(); + } + + public byte[] Read(int size, int eop, int toms) + { + list.Clear(); + var dl = DateTime.Now.AddMilliseconds(toms); + while (true) + { + int b = ReadByte(); + if (b == -1) + { + //toms=0 should return immediately with available + if (DateTime.Now >= dl) break; + Thread.Sleep(1); + continue; + } + list.Add((byte)b); + if (eop >= 0 && b == eop) break; + if (size >= 0 && list.Count >= size) break; + dl = DateTime.Now.AddMilliseconds(toms); + } + return list.ToArray(); + } + + private int ReadByte() + { + lock (queue) + { + if (queue.Count == 0) return -1; + return queue.Dequeue(); + } + } + + private void ReadCallback(IAsyncResult ar) + { + Tools.Try(() => + { + //try needed to avoid triggering the domain unhandled + //exception handler when used as standalone stream + var stream = ar.AsyncState as Stream; + int count = stream.EndRead(ar); + if (count > 0) //0 for closed stream + { + lock (queue) for (var i = 0; i < count; i++) queue.Enqueue(buffer[i]); + stream.BeginRead(buffer, 0, buffer.Length, ReadCallback, stream); + } + }); + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpSerial/SerialException.cs b/常用工具集/Utility/Network/Modbus/SharpSerial/SerialException.cs new file mode 100644 index 0000000..79eb6e8 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpSerial/SerialException.cs @@ -0,0 +1,16 @@ +using System; + +namespace SharpSerial +{ + public class SerialException : Exception + { + private readonly string trace; + + public SerialException(string message, string trace) : base(message) + { + this.trace = trace; + } + + public string Trace { get { return trace; } } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpSerial/SerialProcess.cs b/常用工具集/Utility/Network/Modbus/SharpSerial/SerialProcess.cs new file mode 100644 index 0000000..385bb23 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpSerial/SerialProcess.cs @@ -0,0 +1,126 @@ +using System; +using System.IO; +using System.Text; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace SharpSerial +{ + //this class should not swallow exceptions outside dispose + public class SerialProcess : ISerialStream, IDisposable + { + private readonly Process process; + private readonly int pid; + + public int Pid { get { return pid; } } + + public SerialProcess(object settings) + { + var ss = new SerialSettings(settings); + var args = new StringBuilder(); + foreach (var p in ss.GetType().GetProperties()) + { + if (args.Length > 0) args.Append(" "); + args.AppendFormat("{0}={1}", p.Name, p.GetValue(ss, null).ToString()); + } + process = new Process(); + process.StartInfo = new ProcessStartInfo() + { + FileName = typeof(SerialProcess).Assembly.Location, + Arguments = args.ToString(), + CreateNoWindow = true, + UseShellExecute = false, + RedirectStandardInput = true, + RedirectStandardOutput = true, + RedirectStandardError = false, + }; + EnableStandardError(process.StartInfo); + process.Start(); + pid = process.Id; + ForwardStandardError(process.StandardError); + } + + public void Dispose() + { + Tools.Try(() => + { + process.StandardInput.Close(); + process.WaitForExit(200); + }); + Tools.Try(process.Kill); + Tools.Try(process.Dispose); + } + + public void Write(byte[] data) + { + WriteHex(data); + var line = ReadLine(); + Tools.Assert(line == ""); + foreach (var b in data) sb.Append(b.ToString("X2")); + WriteLine(sb.ToString()); + } + + private byte[] ParseHex(string text) + { + Tools.Assert(text.StartsWith("<"), "First char < expected for {0}:{1}", text.Length, text); + Tools.Assert(text.Length % 2 == 1, "Odd length expected for {0}:{1}", text.Length, text); + var bytes = new byte[text.Length / 2]; + for (var i = 0; i < bytes.Length; i++) + { + var b2 = text.Substring(1 + i * 2, 2); + bytes[i] = Convert.ToByte(b2, 16); + } + return bytes; + } + + [Conditional("DEBUG")] + private void EnableStandardError(ProcessStartInfo psi) + { + psi.RedirectStandardError = true; + } + + [Conditional("DEBUG")] + private void ForwardStandardError(StreamReader reader) + { + Task.Factory.StartNew(() => + { + var line = reader.ReadLine(); + while (line != null) + { + Stdio.Trace(line); + line = reader.ReadLine(); + } + }); + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpSerial/SerialSettings.cs b/常用工具集/Utility/Network/Modbus/SharpSerial/SerialSettings.cs new file mode 100644 index 0000000..3031085 --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpSerial/SerialSettings.cs @@ -0,0 +1,141 @@ +using System; +using System.IO.Ports; +using System.Globalization; +using System.ComponentModel; +using System.Text.RegularExpressions; + +namespace SharpSerial +{ + public class SerialSettings + { + public SerialSettings() => CopyProperties(new SerialPort(), this); + public SerialSettings(string portName) => CopyProperties(new SerialPort(portName), this); + public SerialSettings(object source) => CopyProperties(source, this); + + public void CopyFrom(object source) => CopyProperties(source, this); + public void CopyTo(object target) => CopyProperties(this, target); + + [TypeConverter(typeof(PortNameConverter))] + public string PortName { get; set; } + [TypeConverter(typeof(BaudRateConverter))] + public int BaudRate { get; set; } + public int DataBits { get; set; } + public Parity Parity { get; set; } + public StopBits StopBits { get; set; } + public Handshake Handshake { get; set; } + + public static void CopyProperties(Object source, Object target) + { + CopyProperty(source, target, "PortName"); + CopyProperty(source, target, "BaudRate"); + CopyProperty(source, target, "DataBits"); + CopyProperty(source, target, "Parity"); + CopyProperty(source, target, "StopBits"); + CopyProperty(source, target, "Handshake"); + } + + static void CopyProperty(Object source, Object target, string name) + { + var propertySource = source.GetType().GetProperty(name); + var propertyTarget = target.GetType().GetProperty(name); + propertyTarget.SetValue(target, propertySource.GetValue(source, null), null); + } + } + + public class PortNameConverter : TypeConverter + { + //windows only? + private readonly Regex re = new Regex(@"[^a-zA-Z0-9_]"); + + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + { + return true; + } + + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + return new StandardValuesCollection(SerialPort.GetPortNames()); + } + + public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) + { + return false; + } + + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return (sourceType == typeof(string)); + } + + public override object ConvertFrom(ITypeDescriptorContext context, + CultureInfo culture, object value) + { + if (re.IsMatch(value.ToString())) throw Tools.Make("Invalid chars"); + return value; + } + + public override object ConvertTo(ITypeDescriptorContext context, + CultureInfo culture, object value, Type destinationType) + { + return value; + } + } + + public class BaudRateConverter : TypeConverter + { + public readonly static int[] BaudRates = new int[] { + 110, + 300, + 600, + 1200, + 2400, + 4800, + 9600, + 14400, + 19200, + 28800, + 38400, + 56000, + 57600, + 115200, + 128000, + 153600, + 230400, + 256000, + 460800, + 921600 + }; + + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + { + return true; + } + + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + return new StandardValuesCollection(BaudRates); + } + + public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) + { + return false; + } + + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return (sourceType == typeof(string)); + } + + public override object ConvertFrom(ITypeDescriptorContext context, + CultureInfo culture, object value) + { + return int.Parse(value.ToString()); + } + + public override object ConvertTo(ITypeDescriptorContext context, + CultureInfo culture, object value, Type destinationType) + { + return value.ToString(); + } + } +} diff --git a/常用工具集/Utility/Network/Modbus/SharpSerial/SerialStream.cs b/常用工具集/Utility/Network/Modbus/SharpSerial/SerialStream.cs new file mode 100644 index 0000000..16f4d7b --- /dev/null +++ b/常用工具集/Utility/Network/Modbus/SharpSerial/SerialStream.cs @@ -0,0 +1,10 @@ +using System; + +namespace SharpSerial +{ + public interface ISerialStream : IDisposable + { + void Write(byte[] data); + byte[] Read(int size, int eop, int toms); + } +} diff --git a/常用工具集/Utility/Network/OPCUA/ClientUtils.cs b/常用工具集/Utility/Network/OPCUA/ClientUtils.cs new file mode 100644 index 0000000..57e153b --- /dev/null +++ b/常用工具集/Utility/Network/OPCUA/ClientUtils.cs @@ -0,0 +1,1267 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Opc.Ua; +using Opc.Ua.Client; + +namespace OpcUaHelper +{ + /// + /// Defines numerous re-useable utility functions. + /// + public partial class ClientUtils + { + /// + /// Handles an exception. + /// + public static void HandleException(string caption, Exception e) + { + //ExceptionDlg.Show(caption, e); + } + + /// + /// Returns the application icon. + /// + public static System.Drawing.Icon GetAppIcon() + { + try + { + return new Icon("App.ico"); + } + catch (Exception) + { + return null; + } + } + + #region DisplayText Lookup + /// + /// Gets the display text for the access level attribute. + /// + /// The access level. + /// The access level formatted as a string. + public static string GetAccessLevelDisplayText(byte accessLevel) + { + StringBuilder buffer = new StringBuilder(); + + if (accessLevel == AccessLevels.None) + { + buffer.Append("None"); + } + + if ((accessLevel & AccessLevels.CurrentRead) == AccessLevels.CurrentRead) + { + buffer.Append("Read"); + } + + if ((accessLevel & AccessLevels.CurrentWrite) == AccessLevels.CurrentWrite) + { + if (buffer.Length > 0) + { + buffer.Append(" | "); + } + + buffer.Append("Write"); + } + + if ((accessLevel & AccessLevels.HistoryRead) == AccessLevels.HistoryRead) + { + if (buffer.Length > 0) + { + buffer.Append(" | "); + } + + buffer.Append("HistoryRead"); + } + + if ((accessLevel & AccessLevels.HistoryWrite) == AccessLevels.HistoryWrite) + { + if (buffer.Length > 0) + { + buffer.Append(" | "); + } + + buffer.Append("HistoryWrite"); + } + + if ((accessLevel & AccessLevels.SemanticChange) == AccessLevels.SemanticChange) + { + if (buffer.Length > 0) + { + buffer.Append(" | "); + } + + buffer.Append("SemanticChange"); + } + + return buffer.ToString(); + } + + /// + /// Gets the display text for the event notifier attribute. + /// + /// The event notifier. + /// The event notifier formatted as a string. + public static string GetEventNotifierDisplayText(byte eventNotifier) + { + StringBuilder buffer = new StringBuilder(); + + if (eventNotifier == EventNotifiers.None) + { + buffer.Append("None"); + } + + if ((eventNotifier & EventNotifiers.SubscribeToEvents) == EventNotifiers.SubscribeToEvents) + { + buffer.Append("Subscribe"); + } + + if ((eventNotifier & EventNotifiers.HistoryRead) == EventNotifiers.HistoryRead) + { + if (buffer.Length > 0) + { + buffer.Append(" | "); + } + + buffer.Append("HistoryRead"); + } + + if ((eventNotifier & EventNotifiers.HistoryWrite) == EventNotifiers.HistoryWrite) + { + if (buffer.Length > 0) + { + buffer.Append(" | "); + } + + buffer.Append("HistoryWrite"); + } + + return buffer.ToString(); + } + + /// + /// Gets the display text for the value rank attribute. + /// + /// The value rank. + /// The value rank formatted as a string. + public static string GetValueRankDisplayText(int valueRank) + { + switch (valueRank) + { + case ValueRanks.Any: return "Any"; + case ValueRanks.Scalar: return "Scalar"; + case ValueRanks.ScalarOrOneDimension: return "ScalarOrOneDimension"; + case ValueRanks.OneOrMoreDimensions: return "OneOrMoreDimensions"; + case ValueRanks.OneDimension: return "OneDimension"; + case ValueRanks.TwoDimensions: return "TwoDimensions"; + } + + return valueRank.ToString(); + } + + /// + /// Gets the display text for the specified attribute. + /// + /// The currently active session. + /// The id of the attribute. + /// The value of the attribute. + /// The attribute formatted as a string. + public static string GetAttributeDisplayText(Session session, uint attributeId, Variant value) + { + if (value == Variant.Null) + { + return String.Empty; + } + + switch (attributeId) + { + case Attributes.AccessLevel: + case Attributes.UserAccessLevel: + { + byte? field = value.Value as byte?; + + if (field != null) + { + return GetAccessLevelDisplayText(field.Value); + } + + break; + } + + case Attributes.EventNotifier: + { + byte? field = value.Value as byte?; + + if (field != null) + { + return GetEventNotifierDisplayText(field.Value); + } + + break; + } + + case Attributes.DataType: + { + return session.NodeCache.GetDisplayText(value.Value as NodeId); + } + + case Attributes.ValueRank: + { + int? field = value.Value as int?; + + if (field != null) + { + return GetValueRankDisplayText(field.Value); + } + + break; + } + + case Attributes.NodeClass: + { + int? field = value.Value as int?; + + if (field != null) + { + return ((NodeClass)field.Value).ToString(); + } + + break; + } + + case Attributes.NodeId: + { + NodeId field = value.Value as NodeId; + + if (!NodeId.IsNull(field)) + { + return field.ToString(); + } + + return "Null"; + } + } + + // check for byte strings. + if (value.Value is byte[]) + { + return Utils.ToHexString(value.Value as byte[]); + } + + // use default format. + return value.ToString(); + } + #endregion + + #region Browse + /// + /// Browses the address space and returns the references found. + /// + /// The session. + /// The set of browse operations to perform. + /// if set to true a exception will be thrown on an error. + /// + /// The references found. Null if an error occurred. + /// + public static ReferenceDescriptionCollection Browse(Session session, BrowseDescriptionCollection nodesToBrowse, bool throwOnError) + { + return Browse(session, null, nodesToBrowse, throwOnError); + } + + /// + /// Browses the address space and returns the references found. + /// + public static ReferenceDescriptionCollection Browse(Session session, ViewDescription view, BrowseDescriptionCollection nodesToBrowse, bool throwOnError) + { + try + { + ReferenceDescriptionCollection references = new ReferenceDescriptionCollection(); + BrowseDescriptionCollection unprocessedOperations = new BrowseDescriptionCollection(); + + while (nodesToBrowse.Count > 0) + { + // start the browse operation. + BrowseResultCollection results = null; + DiagnosticInfoCollection diagnosticInfos = null; + + session.Browse( + null, + view, + 0, + nodesToBrowse, + out results, + out diagnosticInfos); + + ClientBase.ValidateResponse(results, nodesToBrowse); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse); + + ByteStringCollection continuationPoints = new ByteStringCollection(); + + for (int ii = 0; ii < nodesToBrowse.Count; ii++) + { + // check for error. + if (StatusCode.IsBad(results[ii].StatusCode)) + { + // this error indicates that the server does not have enough simultaneously active + // continuation points. This request will need to be resent after the other operations + // have been completed and their continuation points released. + if (results[ii].StatusCode == StatusCodes.BadNoContinuationPoints) + { + unprocessedOperations.Add(nodesToBrowse[ii]); + } + + continue; + } + + // check if all references have been fetched. + if (results[ii].References.Count == 0) + { + continue; + } + + // save results. + references.AddRange(results[ii].References); + + // check for continuation point. + if (results[ii].ContinuationPoint != null) + { + continuationPoints.Add(results[ii].ContinuationPoint); + } + } + + // process continuation points. + ByteStringCollection revisedContiuationPoints = new ByteStringCollection(); + + while (continuationPoints.Count > 0) + { + // continue browse operation. + session.BrowseNext( + null, + false, + continuationPoints, + out results, + out diagnosticInfos); + + ClientBase.ValidateResponse(results, continuationPoints); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, continuationPoints); + + for (int ii = 0; ii < continuationPoints.Count; ii++) + { + // check for error. + if (StatusCode.IsBad(results[ii].StatusCode)) + { + continue; + } + + // check if all references have been fetched. + if (results[ii].References.Count == 0) + { + continue; + } + + // save results. + references.AddRange(results[ii].References); + + // check for continuation point. + if (results[ii].ContinuationPoint != null) + { + revisedContiuationPoints.Add(results[ii].ContinuationPoint); + } + } + + // check if browsing must continue; + revisedContiuationPoints = continuationPoints; + } + + // check if unprocessed results exist. + nodesToBrowse = unprocessedOperations; + } + + // return complete list. + return references; + } + catch (Exception exception) + { + if (throwOnError) + { + throw new ServiceResultException(exception, StatusCodes.BadUnexpectedError); + } + + return null; + } + } + + /// + /// Browses the address space and returns the references found. + /// + /// The session. + /// The NodeId for the starting node. + /// if set to true a exception will be thrown on an error. + /// + /// The references found. Null if an error occurred. + /// + public static ReferenceDescriptionCollection Browse(Session session, BrowseDescription nodeToBrowse, bool throwOnError) + { + return Browse(session, null, nodeToBrowse, throwOnError); + } + + /// + /// Browses the address space and returns the references found. + /// + public static ReferenceDescriptionCollection Browse(Session session, ViewDescription view, BrowseDescription nodeToBrowse, bool throwOnError) + { + try + { + ReferenceDescriptionCollection references = new ReferenceDescriptionCollection(); + + // construct browse request. + BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection(); + nodesToBrowse.Add(nodeToBrowse); + + // start the browse operation. + BrowseResultCollection results = null; + DiagnosticInfoCollection diagnosticInfos = null; + + session.Browse( + null, + view, + 0, + nodesToBrowse, + out results, + out diagnosticInfos); + + ClientBase.ValidateResponse(results, nodesToBrowse); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse); + + do + { + // check for error. + if (StatusCode.IsBad(results[0].StatusCode)) + { + throw new ServiceResultException(results[0].StatusCode); + } + + // process results. + for (int ii = 0; ii < results[0].References.Count; ii++) + { + references.Add(results[0].References[ii]); + } + + // check if all references have been fetched. + if (results[0].References.Count == 0 || results[0].ContinuationPoint == null) + { + break; + } + + // continue browse operation. + ByteStringCollection continuationPoints = new ByteStringCollection(); + continuationPoints.Add(results[0].ContinuationPoint); + + session.BrowseNext( + null, + false, + continuationPoints, + out results, + out diagnosticInfos); + + ClientBase.ValidateResponse(results, continuationPoints); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, continuationPoints); + } + while (true); + + //return complete list. + return references; + } + catch (Exception exception) + { + if (throwOnError) + { + throw new ServiceResultException(exception, StatusCodes.BadUnexpectedError); + } + + return null; + } + } + + /// + /// Browses the address space and returns all of the supertypes of the specified type node. + /// + /// The session. + /// The NodeId for a type node in the address space. + /// if set to true a exception will be thrown on an error. + /// + /// The references found. Null if an error occurred. + /// + public static ReferenceDescriptionCollection BrowseSuperTypes(Session session, NodeId typeId, bool throwOnError) + { + ReferenceDescriptionCollection supertypes = new ReferenceDescriptionCollection(); + + try + { + // find all of the children of the field. + BrowseDescription nodeToBrowse = new BrowseDescription(); + + nodeToBrowse.NodeId = typeId; + nodeToBrowse.BrowseDirection = BrowseDirection.Inverse; + nodeToBrowse.ReferenceTypeId = ReferenceTypeIds.HasSubtype; + nodeToBrowse.IncludeSubtypes = false; // more efficient to use IncludeSubtypes=False when possible. + nodeToBrowse.NodeClassMask = 0; // the HasSubtype reference already restricts the targets to Types. + nodeToBrowse.ResultMask = (uint)BrowseResultMask.All; + + ReferenceDescriptionCollection references = Browse(session, nodeToBrowse, throwOnError); + + while (references != null && references.Count > 0) + { + // should never be more than one supertype. + supertypes.Add(references[0]); + + // only follow references within this server. + if (references[0].NodeId.IsAbsolute) + { + break; + } + + // get the references for the next level up. + nodeToBrowse.NodeId = (NodeId)references[0].NodeId; + references = Browse(session, nodeToBrowse, throwOnError); + } + + // return complete list. + return supertypes; + } + catch (Exception exception) + { + if (throwOnError) + { + throw new ServiceResultException(exception, StatusCodes.BadUnexpectedError); + } + + return null; + } + } + + /// + /// Returns the node ids for a set of relative paths. + /// + /// An open session with the server to use. + /// The starting node for the relative paths. + /// The namespace URIs referenced by the relative paths. + /// The relative paths. + /// A collection of local nodes. + public static List TranslateBrowsePaths( + Session session, + NodeId startNodeId, + NamespaceTable namespacesUris, + params string[] relativePaths) + { + // build the list of browse paths to follow by parsing the relative paths. + BrowsePathCollection browsePaths = new BrowsePathCollection(); + + if (relativePaths != null) + { + for (int ii = 0; ii < relativePaths.Length; ii++) + { + BrowsePath browsePath = new BrowsePath(); + + // The relative paths used indexes in the namespacesUris table. These must be + // converted to indexes used by the server. An error occurs if the relative path + // refers to a namespaceUri that the server does not recognize. + + // The relative paths may refer to ReferenceType by their BrowseName. The TypeTree object + // allows the parser to look up the server's NodeId for the ReferenceType. + + browsePath.RelativePath = RelativePath.Parse( + relativePaths[ii], + session.TypeTree, + namespacesUris, + session.NamespaceUris); + + browsePath.StartingNode = startNodeId; + + browsePaths.Add(browsePath); + } + } + + // make the call to the server. + BrowsePathResultCollection results; + DiagnosticInfoCollection diagnosticInfos; + + ResponseHeader responseHeader = session.TranslateBrowsePathsToNodeIds( + null, + browsePaths, + out results, + out diagnosticInfos); + + // ensure that the server returned valid results. + Session.ValidateResponse(results, browsePaths); + Session.ValidateDiagnosticInfos(diagnosticInfos, browsePaths); + + // collect the list of node ids found. + List nodes = new List(); + + for (int ii = 0; ii < results.Count; ii++) + { + // check if the start node actually exists. + if (StatusCode.IsBad(results[ii].StatusCode)) + { + nodes.Add(null); + continue; + } + + // an empty list is returned if no node was found. + if (results[ii].Targets.Count == 0) + { + nodes.Add(null); + continue; + } + + // Multiple matches are possible, however, the node that matches the type model is the + // one we are interested in here. The rest can be ignored. + BrowsePathTarget target = results[ii].Targets[0]; + + if (target.RemainingPathIndex != UInt32.MaxValue) + { + nodes.Add(null); + continue; + } + + // The targetId is an ExpandedNodeId because it could be node in another server. + // The ToNodeId function is used to convert a local NodeId stored in a ExpandedNodeId to a NodeId. + nodes.Add(ExpandedNodeId.ToNodeId(target.TargetId, session.NamespaceUris)); + } + + // return whatever was found. + return nodes; + } + #endregion + + #region Events + /// + /// Finds the type of the event for the notification. + /// + /// The monitored item. + /// The notification. + /// The NodeId of the EventType. + public static NodeId FindEventType(MonitoredItem monitoredItem, EventFieldList notification) + { + EventFilter filter = monitoredItem.Status.Filter as EventFilter; + + if (filter != null) + { + for (int ii = 0; ii < filter.SelectClauses.Count; ii++) + { + SimpleAttributeOperand clause = filter.SelectClauses[ii]; + + if (clause.BrowsePath.Count == 1 && clause.BrowsePath[0] == BrowseNames.EventType) + { + return notification.EventFields[ii].Value as NodeId; + } + } + } + + return null; + } + + /// + /// Constructs an event object from a notification. + /// + /// The session. + /// The monitored item that produced the notification. + /// The notification. + /// The known event types. + /// Mapping between event types and known event types. + /// + /// The event object. Null if the notification is not a valid event type. + /// + public static BaseEventState ConstructEvent( + Session session, + MonitoredItem monitoredItem, + EventFieldList notification, + Dictionary knownEventTypes, + Dictionary eventTypeMappings) + { + // find the event type. + NodeId eventTypeId = FindEventType(monitoredItem, notification); + + if (eventTypeId == null) + { + return null; + } + + // look up the known event type. + Type knownType = null; + NodeId knownTypeId = null; + + if (eventTypeMappings.TryGetValue(eventTypeId, out knownTypeId)) + { + knownType = knownEventTypes[knownTypeId]; + } + + // try again. + if (knownType == null) + { + if (knownEventTypes.TryGetValue(eventTypeId, out knownType)) + { + knownTypeId = eventTypeId; + eventTypeMappings.Add(eventTypeId, eventTypeId); + } + } + + // try mapping it to a known type. + if (knownType == null) + { + // browse for the supertypes of the event type. + ReferenceDescriptionCollection supertypes = ClientUtils.BrowseSuperTypes(session, eventTypeId, false); + + // can't do anything with unknown types. + if (supertypes == null) + { + return null; + } + + // find the first supertype that matches a known event type. + for (int ii = 0; ii < supertypes.Count; ii++) + { + NodeId superTypeId = (NodeId)supertypes[ii].NodeId; + + if (knownEventTypes.TryGetValue(superTypeId, out knownType)) + { + knownTypeId = superTypeId; + eventTypeMappings.Add(eventTypeId, superTypeId); + } + + if (knownTypeId != null) + { + break; + } + } + + // can't do anything with unknown types. + if (knownTypeId == null) + { + return null; + } + } + + // construct the event based on the known event type. + BaseEventState e = (BaseEventState)Activator.CreateInstance(knownType, new object[] { (NodeState)null }); + + // get the filter which defines the contents of the notification. + EventFilter filter = monitoredItem.Status.Filter as EventFilter; + + // initialize the event with the values in the notification. + e.Update(session.SystemContext, filter.SelectClauses, notification); + + // save the orginal notification. + e.Handle = notification; + + return e; + } + #endregion + + #region Type Model Browsing + /// + /// Collects the instance declarations for a type. + /// + public static List CollectInstanceDeclarationsForType(Session session, NodeId typeId) + { + return CollectInstanceDeclarationsForType(session, typeId, true); + } + + /// + /// Collects the instance declarations for a type. + /// + public static List CollectInstanceDeclarationsForType(Session session, NodeId typeId, bool includeSupertypes) + { + // process the types starting from the top of the tree. + List instances = new List(); + Dictionary map = new Dictionary(); + + // get the supertypes. + if (includeSupertypes) + { + ReferenceDescriptionCollection supertypes = ClientUtils.BrowseSuperTypes(session, typeId, false); + + if (supertypes != null) + { + for (int ii = supertypes.Count - 1; ii >= 0; ii--) + { + CollectInstanceDeclarations(session, (NodeId)supertypes[ii].NodeId, null, instances, map); + } + } + } + + // collect the fields for the selected type. + CollectInstanceDeclarations(session, typeId, null, instances, map); + + // return the complete list. + return instances; + } + + /// + /// Collects the fields for the instance node. + /// + private static void CollectInstanceDeclarations( + Session session, + NodeId typeId, + InstanceDeclaration parent, + List instances, + IDictionary map) + { + // find the children. + BrowseDescription nodeToBrowse = new BrowseDescription(); + + if (parent == null) + { + nodeToBrowse.NodeId = typeId; + } + else + { + nodeToBrowse.NodeId = parent.NodeId; + } + + nodeToBrowse.BrowseDirection = BrowseDirection.Forward; + nodeToBrowse.ReferenceTypeId = ReferenceTypeIds.HasChild; + nodeToBrowse.IncludeSubtypes = true; + nodeToBrowse.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable | NodeClass.Method); + nodeToBrowse.ResultMask = (uint)BrowseResultMask.All; + + // ignore any browsing errors. + ReferenceDescriptionCollection references = ClientUtils.Browse(session, nodeToBrowse, false); + + if (references == null) + { + return; + } + + // process the children. + List nodeIds = new List(); + List children = new List(); + + for (int ii = 0; ii < references.Count; ii++) + { + ReferenceDescription reference = references[ii]; + + if (reference.NodeId.IsAbsolute) + { + continue; + } + + // create a new declaration. + InstanceDeclaration child = new InstanceDeclaration(); + + child.RootTypeId = typeId; + child.NodeId = (NodeId)reference.NodeId; + child.BrowseName = reference.BrowseName; + child.NodeClass = reference.NodeClass; + + if (!LocalizedText.IsNullOrEmpty(reference.DisplayName)) + { + child.DisplayName = reference.DisplayName.Text; + } + else + { + child.DisplayName = reference.BrowseName.Name; + } + + if (parent != null) + { + child.BrowsePath = new QualifiedNameCollection(parent.BrowsePath); + child.BrowsePathDisplayText = Utils.Format("{0}/{1}", parent.BrowsePathDisplayText, reference.BrowseName); + child.DisplayPath = Utils.Format("{0}/{1}", parent.DisplayPath, reference.DisplayName); + } + else + { + child.BrowsePath = new QualifiedNameCollection(); + child.BrowsePathDisplayText = Utils.Format("{0}", reference.BrowseName); + child.DisplayPath = Utils.Format("{0}", reference.DisplayName); + } + + child.BrowsePath.Add(reference.BrowseName); + + // check if reading an overridden declaration. + InstanceDeclaration overriden = null; + + if (map.TryGetValue(child.BrowsePathDisplayText, out overriden)) + { + child.OverriddenDeclaration = overriden; + } + + map[child.BrowsePathDisplayText] = child; + + // add to list. + children.Add(child); + nodeIds.Add(child.NodeId); + } + + // check if nothing more to do. + if (children.Count == 0) + { + return; + } + + // find the modelling rules. + List modellingRules = FindTargetOfReference(session, nodeIds, Opc.Ua.ReferenceTypeIds.HasModellingRule, false); + + if (modellingRules != null) + { + for (int ii = 0; ii < nodeIds.Count; ii++) + { + children[ii].ModellingRule = modellingRules[ii]; + + // if the modelling rule is null then the instance is not part of the type declaration. + if (NodeId.IsNull(modellingRules[ii])) + { + map.Remove(children[ii].BrowsePathDisplayText); + } + } + } + + // update the descriptions. + UpdateInstanceDescriptions(session, children, false); + + // recusively collect instance declarations for the tree below. + for (int ii = 0; ii < children.Count; ii++) + { + if (!NodeId.IsNull(children[ii].ModellingRule)) + { + instances.Add(children[ii]); + CollectInstanceDeclarations(session, typeId, children[ii], instances, map); + } + } + } + + /// + /// Finds the targets for the specified reference. + /// + private static List FindTargetOfReference(Session session, List nodeIds, NodeId referenceTypeId, bool throwOnError) + { + try + { + // construct browse request. + BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection(); + + for (int ii = 0; ii < nodeIds.Count; ii++) + { + BrowseDescription nodeToBrowse = new BrowseDescription(); + nodeToBrowse.NodeId = nodeIds[ii]; + nodeToBrowse.BrowseDirection = BrowseDirection.Forward; + nodeToBrowse.ReferenceTypeId = referenceTypeId; + nodeToBrowse.IncludeSubtypes = false; + nodeToBrowse.NodeClassMask = 0; + nodeToBrowse.ResultMask = (uint)BrowseResultMask.None; + nodesToBrowse.Add(nodeToBrowse); + } + + // start the browse operation. + BrowseResultCollection results = null; + DiagnosticInfoCollection diagnosticInfos = null; + + session.Browse( + null, + null, + 1, + nodesToBrowse, + out results, + out diagnosticInfos); + + ClientBase.ValidateResponse(results, nodesToBrowse); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse); + + List targetIds = new List(); + ByteStringCollection continuationPoints = new ByteStringCollection(); + + for (int ii = 0; ii < nodeIds.Count; ii++) + { + targetIds.Add(null); + + // check for error. + if (StatusCode.IsBad(results[ii].StatusCode)) + { + continue; + } + + // check for continuation point. + if (results[ii].ContinuationPoint != null && results[ii].ContinuationPoint.Length > 0) + { + continuationPoints.Add(results[ii].ContinuationPoint); + } + + // get the node id. + if (results[ii].References.Count > 0) + { + if (NodeId.IsNull(results[ii].References[0].NodeId) || results[ii].References[0].NodeId.IsAbsolute) + { + continue; + } + + targetIds[ii] = (NodeId)results[ii].References[0].NodeId; + } + } + + // release continuation points. + if (continuationPoints.Count > 0) + { + session.BrowseNext( + null, + true, + continuationPoints, + out results, + out diagnosticInfos); + + ClientBase.ValidateResponse(results, nodesToBrowse); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse); + } + + //return complete list. + return targetIds; + } + catch (Exception exception) + { + if (throwOnError) + { + throw new ServiceResultException(exception, StatusCodes.BadUnexpectedError); + } + + return null; + } + } + + /// + /// Finds the targets for the specified reference. + /// + private static void UpdateInstanceDescriptions(Session session, List instances, bool throwOnError) + { + try + { + ReadValueIdCollection nodesToRead = new ReadValueIdCollection(); + + for (int ii = 0; ii < instances.Count; ii++) + { + ReadValueId nodeToRead = new ReadValueId(); + nodeToRead.NodeId = instances[ii].NodeId; + nodeToRead.AttributeId = Attributes.Description; + nodesToRead.Add(nodeToRead); + + nodeToRead = new ReadValueId(); + nodeToRead.NodeId = instances[ii].NodeId; + nodeToRead.AttributeId = Attributes.DataType; + nodesToRead.Add(nodeToRead); + + nodeToRead = new ReadValueId(); + nodeToRead.NodeId = instances[ii].NodeId; + nodeToRead.AttributeId = Attributes.ValueRank; + nodesToRead.Add(nodeToRead); + } + + // start the browse operation. + DataValueCollection results = null; + DiagnosticInfoCollection diagnosticInfos = null; + + session.Read( + null, + 0, + TimestampsToReturn.Neither, + nodesToRead, + out results, + out diagnosticInfos); + + ClientBase.ValidateResponse(results, nodesToRead); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead); + + // update the instances. + for (int ii = 0; ii < nodesToRead.Count; ii += 3) + { + InstanceDeclaration instance = instances[ii / 3]; + + instance.Description = results[ii].GetValue(LocalizedText.Null).Text; + instance.DataType = results[ii + 1].GetValue(NodeId.Null); + instance.ValueRank = results[ii + 2].GetValue(ValueRanks.Any); + + if (!NodeId.IsNull(instance.DataType)) + { + instance.BuiltInType = DataTypes.GetBuiltInType(instance.DataType, session.TypeTree); + instance.DataTypeDisplayText = session.NodeCache.GetDisplayText(instance.DataType); + + if (instance.ValueRank >= 0) + { + instance.DataTypeDisplayText += "[]"; + } + } + } + } + catch (Exception exception) + { + if (throwOnError) + { + throw new ServiceResultException(exception, StatusCodes.BadUnexpectedError); + } + } + } + #endregion + + #region Private Methods + /// + /// Collects the fields for the type. + /// + /// The session. + /// The type id. + /// The fields. + /// The node id for the declaration of the field. + private static void CollectFieldsForType(Session session, NodeId typeId, SimpleAttributeOperandCollection fields, List fieldNodeIds) + { + // get the supertypes. + ReferenceDescriptionCollection supertypes = ClientUtils.BrowseSuperTypes(session, typeId, false); + + if (supertypes == null) + { + return; + } + + // process the types starting from the top of the tree. + Dictionary foundNodes = new Dictionary(); + QualifiedNameCollection parentPath = new QualifiedNameCollection(); + + for (int ii = supertypes.Count - 1; ii >= 0; ii--) + { + CollectFields(session, (NodeId)supertypes[ii].NodeId, parentPath, fields, fieldNodeIds, foundNodes); + } + + // collect the fields for the selected type. + CollectFields(session, typeId, parentPath, fields, fieldNodeIds, foundNodes); + } + + /// + /// Collects the fields for the instance. + /// + /// The session. + /// The instance id. + /// The fields. + /// The node id for the declaration of the field. + private static void CollectFieldsForInstance(Session session, NodeId instanceId, SimpleAttributeOperandCollection fields, List fieldNodeIds) + { + Dictionary foundNodes = new Dictionary(); + QualifiedNameCollection parentPath = new QualifiedNameCollection(); + CollectFields(session, instanceId, parentPath, fields, fieldNodeIds, foundNodes); + } + + /// + /// Collects the fields for the instance node. + /// + /// The session. + /// The node id. + /// The parent path. + /// The event fields. + /// The node id for the declaration of the field. + /// The table of found nodes. + private static void CollectFields( + Session session, + NodeId nodeId, + QualifiedNameCollection parentPath, + SimpleAttributeOperandCollection fields, + List fieldNodeIds, + Dictionary foundNodes) + { + // find all of the children of the field. + BrowseDescription nodeToBrowse = new BrowseDescription(); + + nodeToBrowse.NodeId = nodeId; + nodeToBrowse.BrowseDirection = BrowseDirection.Forward; + nodeToBrowse.ReferenceTypeId = ReferenceTypeIds.Aggregates; + nodeToBrowse.IncludeSubtypes = true; + nodeToBrowse.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable); + nodeToBrowse.ResultMask = (uint)BrowseResultMask.All; + + ReferenceDescriptionCollection children = ClientUtils.Browse(session, nodeToBrowse, false); + + if (children == null) + { + return; + } + + // process the children. + for (int ii = 0; ii < children.Count; ii++) + { + ReferenceDescription child = children[ii]; + + if (child.NodeId.IsAbsolute) + { + continue; + } + + // construct browse path. + QualifiedNameCollection browsePath = new QualifiedNameCollection(parentPath); + browsePath.Add(child.BrowseName); + + // check if the browse path is already in the list. + int index = ContainsPath(fields, browsePath); + + if (index < 0) + { + SimpleAttributeOperand field = new SimpleAttributeOperand(); + + field.TypeDefinitionId = ObjectTypeIds.BaseEventType; + field.BrowsePath = browsePath; + field.AttributeId = (child.NodeClass == NodeClass.Variable) ? Attributes.Value : Attributes.NodeId; + + fields.Add(field); + fieldNodeIds.Add((NodeId)child.NodeId); + } + + // recusively find all of the children. + NodeId targetId = (NodeId)child.NodeId; + + // need to guard against loops. + if (!foundNodes.ContainsKey(targetId)) + { + foundNodes.Add(targetId, browsePath); + CollectFields(session, (NodeId)child.NodeId, browsePath, fields, fieldNodeIds, foundNodes); + } + } + } + + /// + /// Determines whether the specified select clause contains the browse path. + /// + /// The select clause. + /// The browse path. + /// + /// true if the specified select clause contains path; otherwise, false. + /// + private static int ContainsPath(SimpleAttributeOperandCollection selectClause, QualifiedNameCollection browsePath) + { + for (int ii = 0; ii < selectClause.Count; ii++) + { + SimpleAttributeOperand field = selectClause[ii]; + + if (field.BrowsePath.Count != browsePath.Count) + { + continue; + } + + bool match = true; + + for (int jj = 0; jj < field.BrowsePath.Count; jj++) + { + if (field.BrowsePath[jj] != browsePath[jj]) + { + match = false; + break; + } + } + + if (match) + { + return ii; + } + } + + return -1; + } + #endregion + + } +} diff --git a/常用工具集/Utility/Network/OPCUA/FilterDeclaration.cs b/常用工具集/Utility/Network/OPCUA/FilterDeclaration.cs new file mode 100644 index 0000000..27abd5f --- /dev/null +++ b/常用工具集/Utility/Network/OPCUA/FilterDeclaration.cs @@ -0,0 +1,476 @@ +/* ======================================================================== + * Copyright (c) 2005-2019 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; +using System.Text; +using System.Collections.Generic; +using Opc.Ua; +using Opc.Ua.Client; + +namespace OpcUaHelper +{ + /// + /// Stores a type declaration retrieved from a server. + /// + public class TypeDeclaration + { + /// + /// The node if for the type. + /// + public NodeId NodeId; + + /// + /// The fully inhierited list of instance declarations for the type. + /// + public List Declarations; + } + + /// + /// Stores an instance declaration fetched from the server. + /// + public class InstanceDeclaration + { + /// + /// The type that the declaration belongs to. + /// + public NodeId RootTypeId; + + /// + /// The browse path to the instance declaration. + /// + public QualifiedNameCollection BrowsePath; + + /// + /// The browse path to the instance declaration. + /// + public string BrowsePathDisplayText; + + /// + /// A localized path to the instance declaration. + /// + public string DisplayPath; + + /// + /// The node id for the instance declaration. + /// + public NodeId NodeId; + + /// + /// The node class of the instance declaration. + /// + public NodeClass NodeClass; + + /// + /// The browse name for the instance declaration. + /// + public QualifiedName BrowseName; + + /// + /// The display name for the instance declaration. + /// + public string DisplayName; + + /// + /// The description for the instance declaration. + /// + public string Description; + + /// + /// The modelling rule for the instance declaration (i.e. Mandatory or Optional). + /// + public NodeId ModellingRule; + + /// + /// The data type for the instance declaration. + /// + public NodeId DataType; + + /// + /// The value rank for the instance declaration. + /// + public int ValueRank; + + /// + /// The built-in type parent for the data type. + /// + public BuiltInType BuiltInType; + + /// + /// A localized name for the data type. + /// + public string DataTypeDisplayText; + + /// + /// An instance declaration that has been overridden by the current instance. + /// + public InstanceDeclaration OverriddenDeclaration; + } + + /// + /// A field in a filter declaration. + /// + public class FilterDeclarationField + { + /// + /// Creates a new instance of a FilterDeclarationField. + /// + public FilterDeclarationField() + { + Selected = true; + DisplayInList = false; + FilterEnabled = false; + FilterOperator = FilterOperator.Equals; + FilterValue = Variant.Null; + InstanceDeclaration = null; + } + + /// + /// Creates a new instance of a FilterDeclarationField. + /// + public FilterDeclarationField(InstanceDeclaration instanceDeclaration) + { + Selected = true; + DisplayInList = false; + FilterEnabled = false; + FilterOperator = FilterOperator.Equals; + FilterValue = Variant.Null; + InstanceDeclaration = instanceDeclaration; + } + + /// + /// Creates a new instance of a FilterDeclarationField. + /// + public FilterDeclarationField(FilterDeclarationField field) + { + Selected = field.Selected; + DisplayInList = field.DisplayInList; + FilterEnabled = field.FilterEnabled; + FilterOperator = field.FilterOperator; + FilterValue = field.FilterValue; + InstanceDeclaration = field.InstanceDeclaration; + } + + /// + /// Whether the field is returned as part of the event notification. + /// + public bool Selected; + + /// + /// Whether the field is displayed in the list view. + /// + public bool DisplayInList; + + /// + /// Whether the filter is enabled. + /// + public bool FilterEnabled; + + /// + /// The filter operator to use in the where clause. + /// + public FilterOperator FilterOperator; + + /// + /// The filter value to use in the where clause. + /// + public Variant FilterValue; + + /// + /// The instance declaration associated with the field. + /// + public InstanceDeclaration InstanceDeclaration; + } + + /// + /// A declararion of an event filter. + /// + public class FilterDeclaration + { + /// + /// Creates a new instance of a FilterDeclaration. + /// + public FilterDeclaration() + { + EventTypeId = Opc.Ua.ObjectTypeIds.BaseEventType; + Fields = new List(); + } + + /// + /// Creates a new instance of a FilterDeclaration. + /// + public FilterDeclaration(TypeDeclaration eventType, FilterDeclaration template) + { + EventTypeId = eventType.NodeId; + Fields = new List(); + + foreach (InstanceDeclaration instanceDeclaration in eventType.Declarations) + { + if (instanceDeclaration.NodeClass == NodeClass.Method) + { + continue; + } + + if (NodeId.IsNull(instanceDeclaration.ModellingRule)) + { + continue; + } + + FilterDeclarationField element = new FilterDeclarationField(instanceDeclaration); + Fields.Add(element); + + // set reasonable defaults. + if (template == null) + { + if (instanceDeclaration.RootTypeId == Opc.Ua.ObjectTypeIds.BaseEventType && instanceDeclaration.BrowseName != Opc.Ua.BrowseNames.EventId) + { + element.DisplayInList = true; + } + } + + // preserve filter settings. + else + { + foreach (FilterDeclarationField field in template.Fields) + { + if (field.InstanceDeclaration.BrowsePathDisplayText == element.InstanceDeclaration.BrowsePathDisplayText) + { + element.DisplayInList = field.DisplayInList; + element.FilterEnabled = field.FilterEnabled; + element.FilterOperator = field.FilterOperator; + element.FilterValue = field.FilterValue; + break; + } + } + } + } + } + + /// + /// Creates a new instance of a FilterDeclaration. + /// + public FilterDeclaration(FilterDeclaration declaration) + { + EventTypeId = declaration.EventTypeId; + Fields = new List(declaration.Fields.Count); + + for (int ii = 0; ii < declaration.Fields.Count; ii++) + { + Fields.Add(new FilterDeclarationField(declaration.Fields[ii])); + } + } + + /// + /// Returns the event filter defined by the filter declaration. + /// + public EventFilter GetFilter() + { + EventFilter filter = new EventFilter(); + filter.SelectClauses = GetSelectClause(); + filter.WhereClause = GetWhereClause(); + return filter; + } + + /// + /// Adds a simple field to the declaration. + /// + public void AddSimpleField(QualifiedName browseName, BuiltInType dataType, bool displayInList) + { + AddSimpleField(new QualifiedName[] { browseName }, NodeClass.Variable, dataType, ValueRanks.Scalar, displayInList); + } + + /// + /// Adds a simple field to the declaration. + /// + public void AddSimpleField(QualifiedName browseName, BuiltInType dataType, int valueRank, bool displayInList) + { + AddSimpleField(new QualifiedName[] { browseName }, NodeClass.Variable, dataType, valueRank, displayInList); + } + + /// + /// Adds a simple field to the declaration. + /// + public void AddSimpleField(QualifiedName[] browseNames, BuiltInType dataType, int valueRank, bool displayInList) + { + AddSimpleField(browseNames, NodeClass.Variable, dataType, valueRank, displayInList); + } + + /// + /// Adds a simple field to the declaration. + /// + public void AddSimpleField(QualifiedName[] browseNames, NodeClass nodeClass, BuiltInType dataType, int valueRank, bool displayInList) + { + FilterDeclarationField field = new FilterDeclarationField(); + + field.DisplayInList = displayInList; + field.InstanceDeclaration = new InstanceDeclaration(); + field.InstanceDeclaration.NodeClass = nodeClass; + + if (browseNames != null) + { + field.InstanceDeclaration.BrowseName = browseNames[browseNames.Length - 1]; + field.InstanceDeclaration.BrowsePath = new QualifiedNameCollection(); + + StringBuilder path = new StringBuilder(); + + for (int ii = 0; ii < browseNames.Length; ii++) + { + if (path.Length > 0) + { + path.Append('/'); + } + + path.Append(browseNames[ii]); + field.InstanceDeclaration.BrowsePath.Add(browseNames[ii]); + } + + field.InstanceDeclaration.BrowsePathDisplayText = path.ToString(); + } + + field.InstanceDeclaration.BuiltInType = dataType; + field.InstanceDeclaration.DataType = (uint)dataType; + field.InstanceDeclaration.ValueRank = valueRank; + field.InstanceDeclaration.DataTypeDisplayText = dataType.ToString(); + + if (valueRank >= 0) + { + field.InstanceDeclaration.DataTypeDisplayText += "[]"; + } + + field.InstanceDeclaration.DisplayName = field.InstanceDeclaration.BrowseName.Name; + field.InstanceDeclaration.DisplayPath = field.InstanceDeclaration.BrowsePathDisplayText; + field.InstanceDeclaration.RootTypeId = ObjectTypeIds.BaseEventType; + Fields.Add(field); + } + + /// + /// Returns the select clause defined by the filter declaration. + /// + public SimpleAttributeOperandCollection GetSelectClause() + { + SimpleAttributeOperandCollection selectClause = new SimpleAttributeOperandCollection(); + + SimpleAttributeOperand operand = new SimpleAttributeOperand(); + operand.TypeDefinitionId = Opc.Ua.ObjectTypeIds.BaseEventType; + operand.AttributeId = Attributes.NodeId; + selectClause.Add(operand); + + foreach (FilterDeclarationField field in Fields) + { + if (field.Selected) + { + operand = new SimpleAttributeOperand(); + operand.TypeDefinitionId = field.InstanceDeclaration.RootTypeId; + operand.AttributeId = (field.InstanceDeclaration.NodeClass == NodeClass.Object) ? Attributes.NodeId : Attributes.Value; + operand.BrowsePath = field.InstanceDeclaration.BrowsePath; + selectClause.Add(operand); + } + } + + return selectClause; + } + + /// + /// Returns the where clause defined by the filter declaration. + /// + public ContentFilter GetWhereClause() + { + ContentFilter whereClause = new ContentFilter(); + ContentFilterElement element1 = whereClause.Push(FilterOperator.OfType, EventTypeId); + + EventFilter filter = new EventFilter(); + + foreach (FilterDeclarationField field in Fields) + { + if (field.FilterEnabled) + { + SimpleAttributeOperand operand1 = new SimpleAttributeOperand(); + operand1.TypeDefinitionId = field.InstanceDeclaration.RootTypeId; + operand1.AttributeId = (field.InstanceDeclaration.NodeClass == NodeClass.Object) ? Attributes.NodeId : Attributes.Value; + operand1.BrowsePath = field.InstanceDeclaration.BrowsePath; + + LiteralOperand operand2 = new LiteralOperand(); + operand2.Value = field.FilterValue; + + ContentFilterElement element2 = whereClause.Push(field.FilterOperator, operand1, operand2); + element1 = whereClause.Push(FilterOperator.And, element1, element2); + } + } + + return whereClause; + } + + /// + /// Returns the value for the specified browse name. + /// + public T GetValue(QualifiedName browseName, VariantCollection fields, T defaultValue) + { + if (fields == null || fields.Count == 0) + { + return defaultValue; + } + + if (browseName == null) + { + browseName = QualifiedName.Null; + } + + for (int ii = 0; ii < this.Fields.Count; ii++) + { + if (this.Fields[ii].InstanceDeclaration.BrowseName == browseName) + { + if (ii >= fields.Count + 1) + { + return defaultValue; + } + + object value = fields[ii + 1].Value; + + if (typeof(T).IsInstanceOfType(value)) + { + return (T)value; + } + + break; + } + } + + return defaultValue; + } + + /// + /// The type of event. + /// + public NodeId EventTypeId; + + /// + /// The list of declarations for the fields. + /// + public List Fields; + } +} diff --git a/常用工具集/Utility/Network/OPCUA/FormUtils.cs b/常用工具集/Utility/Network/OPCUA/FormUtils.cs new file mode 100644 index 0000000..a070d24 --- /dev/null +++ b/常用工具集/Utility/Network/OPCUA/FormUtils.cs @@ -0,0 +1,1021 @@ +using Opc.Ua; +using Opc.Ua.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpcUaHelper +{ + /// + /// 辅助类 + /// + public class FormUtils + { + /// + /// Gets the display text for the access level attribute. + /// + /// The access level. + /// The access level formatted as a string. + private static string GetAccessLevelDisplayText(byte accessLevel) + { + StringBuilder buffer = new StringBuilder(); + + if (accessLevel == AccessLevels.None) + { + buffer.Append("None"); + } + + if ((accessLevel & AccessLevels.CurrentRead) == AccessLevels.CurrentRead) + { + buffer.Append("Read"); + } + + if ((accessLevel & AccessLevels.CurrentWrite) == AccessLevels.CurrentWrite) + { + if (buffer.Length > 0) + { + buffer.Append(" | "); + } + + buffer.Append("Write"); + } + + if ((accessLevel & AccessLevels.HistoryRead) == AccessLevels.HistoryRead) + { + if (buffer.Length > 0) + { + buffer.Append(" | "); + } + + buffer.Append("HistoryRead"); + } + + if ((accessLevel & AccessLevels.HistoryWrite) == AccessLevels.HistoryWrite) + { + if (buffer.Length > 0) + { + buffer.Append(" | "); + } + + buffer.Append("HistoryWrite"); + } + + if ((accessLevel & AccessLevels.SemanticChange) == AccessLevels.SemanticChange) + { + if (buffer.Length > 0) + { + buffer.Append(" | "); + } + + buffer.Append("SemanticChange"); + } + + return buffer.ToString(); + } + + /// + /// Gets the display text for the event notifier attribute. + /// + /// The event notifier. + /// The event notifier formatted as a string. + private static string GetEventNotifierDisplayText(byte eventNotifier) + { + StringBuilder buffer = new StringBuilder(); + + if (eventNotifier == EventNotifiers.None) + { + buffer.Append("None"); + } + + if ((eventNotifier & EventNotifiers.SubscribeToEvents) == EventNotifiers.SubscribeToEvents) + { + buffer.Append("Subscribe"); + } + + if ((eventNotifier & EventNotifiers.HistoryRead) == EventNotifiers.HistoryRead) + { + if (buffer.Length > 0) + { + buffer.Append(" | "); + } + + buffer.Append("HistoryRead"); + } + + if ((eventNotifier & EventNotifiers.HistoryWrite) == EventNotifiers.HistoryWrite) + { + if (buffer.Length > 0) + { + buffer.Append(" | "); + } + + buffer.Append("HistoryWrite"); + } + + return buffer.ToString(); + } + + /// + /// Gets the display text for the value rank attribute. + /// + /// The value rank. + /// The value rank formatted as a string. + private static string GetValueRankDisplayText(int valueRank) + { + switch (valueRank) + { + case ValueRanks.Any: return "Any"; + case ValueRanks.Scalar: return "Scalar"; + case ValueRanks.ScalarOrOneDimension: return "ScalarOrOneDimension"; + case ValueRanks.OneOrMoreDimensions: return "OneOrMoreDimensions"; + case ValueRanks.OneDimension: return "OneDimension"; + case ValueRanks.TwoDimensions: return "TwoDimensions"; + } + + return valueRank.ToString(); + } + + /// + /// Gets the display text for the specified attribute. + /// + /// The currently active session. + /// The id of the attribute. + /// The value of the attribute. + /// The attribute formatted as a string. + public static string GetAttributeDisplayText(Session session, uint attributeId, Variant value) + { + if (value == Variant.Null) + { + return String.Empty; + } + + switch (attributeId) + { + case Attributes.AccessLevel: + case Attributes.UserAccessLevel: + { + byte? field = value.Value as byte?; + + if (field != null) + { + return GetAccessLevelDisplayText(field.Value); + } + + break; + } + + case Attributes.EventNotifier: + { + byte? field = value.Value as byte?; + + if (field != null) + { + return GetEventNotifierDisplayText(field.Value); + } + + break; + } + + case Attributes.DataType: + { + return session.NodeCache.GetDisplayText(value.Value as NodeId); + } + + case Attributes.ValueRank: + { + int? field = value.Value as int?; + + if (field != null) + { + return GetValueRankDisplayText(field.Value); + } + + break; + } + + case Attributes.NodeClass: + { + int? field = value.Value as int?; + + if (field != null) + { + return ((NodeClass)field.Value).ToString(); + } + + break; + } + + case Attributes.NodeId: + { + NodeId field = value.Value as NodeId; + + if (!NodeId.IsNull(field)) + { + return field.ToString(); + } + + return "Null"; + } + } + + // check for byte strings. + if (value.Value is byte[]) + { + return Utils.ToHexString(value.Value as byte[]); + } + + // use default format. + return value.ToString(); + } + + /// + /// Discovers the servers on the local machine. + /// + /// The configuration. + /// A list of server urls. + public static IList DiscoverServers(ApplicationConfiguration configuration) + { + List serverUrls = new List(); + + // set a short timeout because this is happening in the drop down event. + EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(configuration); + endpointConfiguration.OperationTimeout = 5000; + + // Connect to the local discovery server and find the available servers. + using (DiscoveryClient client = DiscoveryClient.Create(new Uri("opc.tcp://localhost:4840"), endpointConfiguration)) + { + ApplicationDescriptionCollection servers = client.FindServers(null); + + // populate the drop down list with the discovery URLs for the available servers. + for (int ii = 0; ii < servers.Count; ii++) + { + if (servers[ii].ApplicationType == ApplicationType.DiscoveryServer) + { + continue; + } + + for (int jj = 0; jj < servers[ii].DiscoveryUrls.Count; jj++) + { + string discoveryUrl = servers[ii].DiscoveryUrls[jj]; + + // Many servers will use the '/discovery' suffix for the discovery endpoint. + // The URL without this prefix should be the base URL for the server. + if (discoveryUrl.EndsWith("/discovery")) + { + discoveryUrl = discoveryUrl.Substring(0, discoveryUrl.Length - "/discovery".Length); + } + + // ensure duplicates do not get added. + if (!serverUrls.Contains(discoveryUrl)) + { + serverUrls.Add(discoveryUrl); + } + } + } + } + + return serverUrls; + } + + /// + /// Finds the endpoint that best matches the current settings. + /// + /// The discovery URL. + /// if set to true select an endpoint that uses security. + /// The best available endpoint. + public static EndpointDescription SelectEndpoint(string discoveryUrl, bool useSecurity) + { + // needs to add the '/discovery' back onto non-UA TCP URLs. + if (!discoveryUrl.StartsWith(Utils.UriSchemeOpcTcp)) + { + if (!discoveryUrl.EndsWith("/discovery")) + { + discoveryUrl += "/discovery"; + } + } + + // parse the selected URL. + Uri uri = new Uri(discoveryUrl); + + // set a short timeout because this is happening in the drop down event. + EndpointConfiguration configuration = EndpointConfiguration.Create(); + configuration.OperationTimeout = 5000; + + EndpointDescription selectedEndpoint = null; + + // Connect to the server's discovery endpoint and find the available configuration. + using (DiscoveryClient client = DiscoveryClient.Create(uri, configuration)) + { + EndpointDescriptionCollection endpoints = client.GetEndpoints(null); + + // select the best endpoint to use based on the selected URL and the UseSecurity checkbox. + for (int ii = 0; ii < endpoints.Count; ii++) + { + EndpointDescription endpoint = endpoints[ii]; + + // check for a match on the URL scheme. + if (endpoint.EndpointUrl.StartsWith(uri.Scheme)) + { + // check if security was requested. + if (useSecurity) + { + if (endpoint.SecurityMode == MessageSecurityMode.None) + { + continue; + } + } + else + { + if (endpoint.SecurityMode != MessageSecurityMode.None) + { + continue; + } + } + + // pick the first available endpoint by default. + if (selectedEndpoint == null) + { + selectedEndpoint = endpoint; + } + + // The security level is a relative measure assigned by the server to the + // endpoints that it returns. Clients should always pick the highest level + // unless they have a reason not too. + if (endpoint.SecurityLevel > selectedEndpoint.SecurityLevel) + { + selectedEndpoint = endpoint; + } + } + } + + // pick the first available endpoint by default. + if (selectedEndpoint == null && endpoints.Count > 0) + { + selectedEndpoint = endpoints[0]; + } + } + + // if a server is behind a firewall it may return URLs that are not accessible to the client. + // This problem can be avoided by assuming that the domain in the URL used to call + // GetEndpoints can be used to access any of the endpoints. This code makes that conversion. + // Note that the conversion only makes sense if discovery uses the same protocol as the endpoint. + + Uri endpointUrl = Utils.ParseUri(selectedEndpoint.EndpointUrl); + + if (endpointUrl != null && endpointUrl.Scheme == uri.Scheme) + { + UriBuilder builder = new UriBuilder(endpointUrl); + builder.Host = uri.DnsSafeHost; + builder.Port = uri.Port; + selectedEndpoint.EndpointUrl = builder.ToString(); + } + + // return the selected endpoint. + return selectedEndpoint; + } + + /// + /// Browses the address space and returns the references found. + /// + /// The session. + /// The set of browse operations to perform. + /// if set to true a exception will be thrown on an error. + /// + /// The references found. Null if an error occurred. + /// + public static ReferenceDescriptionCollection Browse(Session session, BrowseDescriptionCollection nodesToBrowse, bool throwOnError) + { + try + { + ReferenceDescriptionCollection references = new ReferenceDescriptionCollection(); + BrowseDescriptionCollection unprocessedOperations = new BrowseDescriptionCollection(); + + while (nodesToBrowse.Count > 0) + { + // start the browse operation. + BrowseResultCollection results = null; + DiagnosticInfoCollection diagnosticInfos = null; + + session.Browse( + null, + null, + 0, + nodesToBrowse, + out results, + out diagnosticInfos); + + ClientBase.ValidateResponse(results, nodesToBrowse); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse); + + ByteStringCollection continuationPoints = new ByteStringCollection(); + + for (int ii = 0; ii < nodesToBrowse.Count; ii++) + { + // check for error. + if (StatusCode.IsBad(results[ii].StatusCode)) + { + // this error indicates that the server does not have enough simultaneously active + // continuation points. This request will need to be resent after the other operations + // have been completed and their continuation points released. + if (results[ii].StatusCode == StatusCodes.BadNoContinuationPoints) + { + unprocessedOperations.Add(nodesToBrowse[ii]); + } + + continue; + } + + // check if all references have been fetched. + if (results[ii].References.Count == 0) + { + continue; + } + + // save results. + references.AddRange(results[ii].References); + + // check for continuation point. + if (results[ii].ContinuationPoint != null) + { + continuationPoints.Add(results[ii].ContinuationPoint); + } + } + + // process continuation points. + ByteStringCollection revisedContiuationPoints = new ByteStringCollection(); + + while (continuationPoints.Count > 0) + { + // continue browse operation. + session.BrowseNext( + null, + true, + continuationPoints, + out results, + out diagnosticInfos); + + ClientBase.ValidateResponse(results, continuationPoints); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, continuationPoints); + + for (int ii = 0; ii < continuationPoints.Count; ii++) + { + // check for error. + if (StatusCode.IsBad(results[ii].StatusCode)) + { + continue; + } + + // check if all references have been fetched. + if (results[ii].References.Count == 0) + { + continue; + } + + // save results. + references.AddRange(results[ii].References); + + // check for continuation point. + if (results[ii].ContinuationPoint != null) + { + revisedContiuationPoints.Add(results[ii].ContinuationPoint); + } + } + + // check if browsing must continue; + revisedContiuationPoints = continuationPoints; + } + + // check if unprocessed results exist. + nodesToBrowse = unprocessedOperations; + } + + // return complete list. + return references; + } + catch (Exception exception) + { + if (throwOnError) + { + throw new ServiceResultException(exception, StatusCodes.BadUnexpectedError); + } + + return null; + } + } + + /// + /// Finds the type of the event for the notification. + /// + /// The monitored item. + /// The notification. + /// The NodeId of the EventType. + public static NodeId FindEventType(MonitoredItem monitoredItem, EventFieldList notification) + { + EventFilter filter = monitoredItem.Status.Filter as EventFilter; + + if (filter != null) + { + for (int ii = 0; ii < filter.SelectClauses.Count; ii++) + { + SimpleAttributeOperand clause = filter.SelectClauses[ii]; + + if (clause.BrowsePath.Count == 1 && clause.BrowsePath[0] == BrowseNames.EventType) + { + return notification.EventFields[ii].Value as NodeId; + } + } + } + + return null; + } + + /// + /// Browses the address space and returns the references found. + /// + /// The session. + /// The NodeId for the starting node. + /// if set to true a exception will be thrown on an error. + /// + /// The references found. Null if an error occurred. + /// + public static ReferenceDescriptionCollection Browse(Session session, BrowseDescription nodeToBrowse, bool throwOnError) + { + try + { + ReferenceDescriptionCollection references = new ReferenceDescriptionCollection(); + + // construct browse request. + BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection(); + nodesToBrowse.Add(nodeToBrowse); + + // start the browse operation. + BrowseResultCollection results = null; + DiagnosticInfoCollection diagnosticInfos = null; + + session.Browse( + null, + null, + 0, + nodesToBrowse, + out results, + out diagnosticInfos); + + ClientBase.ValidateResponse(results, nodesToBrowse); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse); + + do + { + // check for error. + if (StatusCode.IsBad(results[0].StatusCode)) + { + throw new ServiceResultException(results[0].StatusCode); + } + + // process results. + for (int ii = 0; ii < results[0].References.Count; ii++) + { + references.Add(results[0].References[ii]); + } + + // check if all references have been fetched. + if (results[0].References.Count == 0 || results[0].ContinuationPoint == null) + { + break; + } + + // continue browse operation. + ByteStringCollection continuationPoints = new ByteStringCollection(); + continuationPoints.Add(results[0].ContinuationPoint); + + session.BrowseNext( + null, + false, + continuationPoints, + out results, + out diagnosticInfos); + + ClientBase.ValidateResponse(results, continuationPoints); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, continuationPoints); + } + while (true); + + //return complete list. + return references; + } + catch (Exception exception) + { + if (throwOnError) + { + throw new ServiceResultException(exception, StatusCodes.BadUnexpectedError); + } + + return null; + } + } + + /// + /// Browses the address space and returns all of the supertypes of the specified type node. + /// + /// The session. + /// The NodeId for a type node in the address space. + /// if set to true a exception will be thrown on an error. + /// + /// The references found. Null if an error occurred. + /// + public static ReferenceDescriptionCollection BrowseSuperTypes(Session session, NodeId typeId, bool throwOnError) + { + ReferenceDescriptionCollection supertypes = new ReferenceDescriptionCollection(); + + try + { + // find all of the children of the field. + BrowseDescription nodeToBrowse = new BrowseDescription(); + + nodeToBrowse.NodeId = typeId; + nodeToBrowse.BrowseDirection = BrowseDirection.Inverse; + nodeToBrowse.ReferenceTypeId = ReferenceTypeIds.HasSubtype; + nodeToBrowse.IncludeSubtypes = false; // more efficient to use IncludeSubtypes=False when possible. + nodeToBrowse.NodeClassMask = 0; // the HasSubtype reference already restricts the targets to Types. + nodeToBrowse.ResultMask = (uint)BrowseResultMask.All; + + ReferenceDescriptionCollection references = Browse(session, nodeToBrowse, throwOnError); + + while (references != null && references.Count > 0) + { + // should never be more than one supertype. + supertypes.Add(references[0]); + + // only follow references within this server. + if (references[0].NodeId.IsAbsolute) + { + break; + } + + // get the references for the next level up. + nodeToBrowse.NodeId = (NodeId)references[0].NodeId; + references = Browse(session, nodeToBrowse, throwOnError); + } + + // return complete list. + return supertypes; + } + catch (Exception exception) + { + if (throwOnError) + { + throw new ServiceResultException(exception, StatusCodes.BadUnexpectedError); + } + + return null; + } + } + + /// + /// Constructs an event object from a notification. + /// + /// The session. + /// The monitored item that produced the notification. + /// The notification. + /// The known event types. + /// Mapping between event types and known event types. + /// + /// The event object. Null if the notification is not a valid event type. + /// + public static BaseEventState ConstructEvent( + Session session, + MonitoredItem monitoredItem, + EventFieldList notification, + Dictionary knownEventTypes, + Dictionary eventTypeMappings) + { + // find the event type. + NodeId eventTypeId = FindEventType(monitoredItem, notification); + + if (eventTypeId == null) + { + return null; + } + + // look up the known event type. + Type knownType = null; + NodeId knownTypeId = null; + + if (eventTypeMappings.TryGetValue(eventTypeId, out knownTypeId)) + { + knownType = knownEventTypes[knownTypeId]; + } + + // try again. + if (knownType == null) + { + if (knownEventTypes.TryGetValue(eventTypeId, out knownType)) + { + knownTypeId = eventTypeId; + eventTypeMappings.Add(eventTypeId, eventTypeId); + } + } + + // try mapping it to a known type. + if (knownType == null) + { + // browse for the supertypes of the event type. + ReferenceDescriptionCollection supertypes = FormUtils.BrowseSuperTypes(session, eventTypeId, false); + + // can't do anything with unknown types. + if (supertypes == null) + { + return null; + } + + // find the first supertype that matches a known event type. + for (int ii = 0; ii < supertypes.Count; ii++) + { + NodeId superTypeId = (NodeId)supertypes[ii].NodeId; + + if (knownEventTypes.TryGetValue(superTypeId, out knownType)) + { + knownTypeId = superTypeId; + eventTypeMappings.Add(eventTypeId, superTypeId); + } + + if (knownTypeId != null) + { + break; + } + } + + // can't do anything with unknown types. + if (knownTypeId == null) + { + return null; + } + } + + // construct the event based on the known event type. + BaseEventState e = (BaseEventState)Activator.CreateInstance(knownType, new object[] { (NodeState)null }); + + // get the filter which defines the contents of the notification. + EventFilter filter = monitoredItem.Status.Filter as EventFilter; + + // initialize the event with the values in the notification. + e.Update(session.SystemContext, filter.SelectClauses, notification); + + // save the orginal notification. + e.Handle = notification; + + return e; + } + + /// + /// Returns the node ids for a set of relative paths. + /// + /// An open session with the server to use. + /// The starting node for the relative paths. + /// The namespace URIs referenced by the relative paths. + /// The relative paths. + /// A collection of local nodes. + public static List TranslateBrowsePaths( + Session session, + NodeId startNodeId, + NamespaceTable namespacesUris, + params string[] relativePaths) + { + // build the list of browse paths to follow by parsing the relative paths. + BrowsePathCollection browsePaths = new BrowsePathCollection(); + + if (relativePaths != null) + { + for (int ii = 0; ii < relativePaths.Length; ii++) + { + BrowsePath browsePath = new BrowsePath(); + + // The relative paths used indexes in the namespacesUris table. These must be + // converted to indexes used by the server. An error occurs if the relative path + // refers to a namespaceUri that the server does not recognize. + + // The relative paths may refer to ReferenceType by their BrowseName. The TypeTree object + // allows the parser to look up the server's NodeId for the ReferenceType. + + browsePath.RelativePath = RelativePath.Parse( + relativePaths[ii], + session.TypeTree, + namespacesUris, + session.NamespaceUris); + + browsePath.StartingNode = startNodeId; + + browsePaths.Add(browsePath); + } + } + + // make the call to the server. + BrowsePathResultCollection results; + DiagnosticInfoCollection diagnosticInfos; + + ResponseHeader responseHeader = session.TranslateBrowsePathsToNodeIds( + null, + browsePaths, + out results, + out diagnosticInfos); + + // ensure that the server returned valid results. + Session.ValidateResponse(results, browsePaths); + Session.ValidateDiagnosticInfos(diagnosticInfos, browsePaths); + + // collect the list of node ids found. + List nodes = new List(); + + for (int ii = 0; ii < results.Count; ii++) + { + // check if the start node actually exists. + if (StatusCode.IsBad(results[ii].StatusCode)) + { + nodes.Add(null); + continue; + } + + // an empty list is returned if no node was found. + if (results[ii].Targets.Count == 0) + { + nodes.Add(null); + continue; + } + + // Multiple matches are possible, however, the node that matches the type model is the + // one we are interested in here. The rest can be ignored. + BrowsePathTarget target = results[ii].Targets[0]; + + if (target.RemainingPathIndex != UInt32.MaxValue) + { + nodes.Add(null); + continue; + } + + // The targetId is an ExpandedNodeId because it could be node in another server. + // The ToNodeId function is used to convert a local NodeId stored in a ExpandedNodeId to a NodeId. + nodes.Add(ExpandedNodeId.ToNodeId(target.TargetId, session.NamespaceUris)); + } + + // return whatever was found. + return nodes; + } + + /// + /// Collects the fields for the type. + /// + /// The session. + /// The fields. + /// The node id for the declaration of the field. + public static void CollectFieldsForType(Session session, NodeId typeId, SimpleAttributeOperandCollection fields, List fieldNodeIds) + { + // get the supertypes. + ReferenceDescriptionCollection supertypes = FormUtils.BrowseSuperTypes(session, typeId, false); + + if (supertypes == null) + { + return; + } + + // process the types starting from the top of the tree. + Dictionary foundNodes = new Dictionary(); + QualifiedNameCollection parentPath = new QualifiedNameCollection(); + + for (int ii = supertypes.Count - 1; ii >= 0; ii--) + { + CollectFields(session, (NodeId)supertypes[ii].NodeId, parentPath, fields, fieldNodeIds, foundNodes); + } + + // collect the fields for the selected type. + CollectFields(session, typeId, parentPath, fields, fieldNodeIds, foundNodes); + } + + /// + /// Collects the fields for the instance. + /// + /// The session. + /// The fields. + /// The node id for the declaration of the field. + public static void CollectFieldsForInstance(Session session, NodeId instanceId, SimpleAttributeOperandCollection fields, List fieldNodeIds) + { + Dictionary foundNodes = new Dictionary(); + QualifiedNameCollection parentPath = new QualifiedNameCollection(); + CollectFields(session, instanceId, parentPath, fields, fieldNodeIds, foundNodes); + } + + /// + /// Collects the fields for the instance node. + /// + /// The session. + /// The node id. + /// The parent path. + /// The event fields. + /// The node id for the declaration of the field. + /// The table of found nodes. + private static void CollectFields( + Session session, + NodeId nodeId, + QualifiedNameCollection parentPath, + SimpleAttributeOperandCollection fields, + List fieldNodeIds, + Dictionary foundNodes) + { + // find all of the children of the field. + BrowseDescription nodeToBrowse = new BrowseDescription(); + + nodeToBrowse.NodeId = nodeId; + nodeToBrowse.BrowseDirection = BrowseDirection.Forward; + nodeToBrowse.ReferenceTypeId = ReferenceTypeIds.Aggregates; + nodeToBrowse.IncludeSubtypes = true; + nodeToBrowse.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable); + nodeToBrowse.ResultMask = (uint)BrowseResultMask.All; + + ReferenceDescriptionCollection children = FormUtils.Browse(session, nodeToBrowse, false); + + if (children == null) + { + return; + } + + // process the children. + for (int ii = 0; ii < children.Count; ii++) + { + ReferenceDescription child = children[ii]; + + if (child.NodeId.IsAbsolute) + { + continue; + } + + // construct browse path. + QualifiedNameCollection browsePath = new QualifiedNameCollection(parentPath); + browsePath.Add(child.BrowseName); + + // check if the browse path is already in the list. + int index = ContainsPath(fields, browsePath); + + if (index < 0) + { + SimpleAttributeOperand field = new SimpleAttributeOperand(); + + field.TypeDefinitionId = ObjectTypeIds.BaseEventType; + field.BrowsePath = browsePath; + field.AttributeId = (child.NodeClass == NodeClass.Variable) ? Attributes.Value : Attributes.NodeId; + + fields.Add(field); + fieldNodeIds.Add((NodeId)child.NodeId); + } + + // recusively find all of the children. + NodeId targetId = (NodeId)child.NodeId; + + // need to guard against loops. + if (!foundNodes.ContainsKey(targetId)) + { + foundNodes.Add(targetId, browsePath); + CollectFields(session, (NodeId)child.NodeId, browsePath, fields, fieldNodeIds, foundNodes); + } + } + } + + /// + /// Determines whether the specified select clause contains the browse path. + /// + /// The select clause. + /// The browse path. + /// + /// true if the specified select clause contains path; otherwise, false. + /// + private static int ContainsPath(SimpleAttributeOperandCollection selectClause, QualifiedNameCollection browsePath) + { + for (int ii = 0; ii < selectClause.Count; ii++) + { + SimpleAttributeOperand field = selectClause[ii]; + + if (field.BrowsePath.Count != browsePath.Count) + { + continue; + } + + bool match = true; + + for (int jj = 0; jj < field.BrowsePath.Count; jj++) + { + if (field.BrowsePath[jj] != browsePath[jj]) + { + match = false; + break; + } + } + + if (match) + { + return ii; + } + } + + return -1; + } + } +} diff --git a/常用工具集/Utility/Network/OPCUA/OpcUaClient.cs b/常用工具集/Utility/Network/OPCUA/OpcUaClient.cs new file mode 100644 index 0000000..9eac026 --- /dev/null +++ b/常用工具集/Utility/Network/OPCUA/OpcUaClient.cs @@ -0,0 +1,1450 @@ +using Opc.Ua; +using Opc.Ua.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace OpcUaHelper +{ + /// + /// 一个二次封装了的OPC UA库,支持从opc ua服务器读写节点数据,批量读写,订阅,批量订阅,历史数据读取,方法调用操作。 + /// + public class OpcUaClient + { + #region Constructors + + /// + /// 默认的构造函数,实例化一个新的OPC UA类 + /// + public OpcUaClient() + { + dic_subscriptions = new Dictionary(); + + var certificateValidator = new CertificateValidator(); + certificateValidator.CertificateValidation += (sender, eventArgs) => + { + if (ServiceResult.IsGood(eventArgs.Error)) + eventArgs.Accept = true; + else if (eventArgs.Error.StatusCode.Code == StatusCodes.BadCertificateUntrusted) + eventArgs.Accept = true; + else + throw new Exception(string.Format("Failed to validate certificate with error code {0}: {1}", eventArgs.Error.Code, eventArgs.Error.AdditionalInfo)); + }; + + SecurityConfiguration securityConfigurationcv = new SecurityConfiguration + { + AutoAcceptUntrustedCertificates = true, + RejectSHA1SignedCertificates = false, + MinimumCertificateKeySize = 1024, + }; + certificateValidator.Update(securityConfigurationcv); + + // Build the application configuration + var configuration = new ApplicationConfiguration + { + ApplicationName = OpcUaName, + ApplicationType = ApplicationType.Client, + CertificateValidator = certificateValidator, + ApplicationUri = "urn:MyClient", //Kepp this syntax + ProductUri = "OpcUaClient", + + ServerConfiguration = new ServerConfiguration + { + MaxSubscriptionCount = 100000, + MaxMessageQueueSize = 1000000, + MaxNotificationQueueSize = 1000000, + MaxPublishRequestCount = 10000000, + }, + + SecurityConfiguration = new SecurityConfiguration + { + AutoAcceptUntrustedCertificates = true, + RejectSHA1SignedCertificates = false, + MinimumCertificateKeySize = 1024, + SuppressNonceValidationErrors = true, + + ApplicationCertificate = new CertificateIdentifier + { + StoreType = CertificateStoreType.X509Store, + StorePath = "CurrentUser\\My", + SubjectName = OpcUaName, + }, + TrustedIssuerCertificates = new CertificateTrustList + { + StoreType = CertificateStoreType.X509Store, + StorePath = "CurrentUser\\Root", + }, + TrustedPeerCertificates = new CertificateTrustList + { + StoreType = CertificateStoreType.X509Store, + StorePath = "CurrentUser\\Root", + } + }, + + TransportQuotas = new TransportQuotas + { + OperationTimeout = 6000000, + MaxStringLength = int.MaxValue, + MaxByteStringLength = int.MaxValue, + MaxArrayLength = 65535, + MaxMessageSize = 419430400, + MaxBufferSize = 65535, + ChannelLifetime = -1, + SecurityTokenLifetime = -1 + }, + ClientConfiguration = new ClientConfiguration + { + DefaultSessionTimeout = -1, + MinSubscriptionLifetime = -1, + }, + DisableHiResClock = true + }; + + configuration.Validate(ApplicationType.Client); + m_configuration = configuration; + } + + #endregion Constructors + + #region Connect And Disconnect + + /// + /// connect to server + /// + /// remote url + public async Task ConnectServer(string serverUrl) + { + m_session = await Connect(serverUrl); + } + + /// + /// Creates a new session. + /// + /// The new session object. + private async Task Connect(string serverUrl) + { + // disconnect from existing session. + Disconnect(); + + if (m_configuration == null) + { + throw new ArgumentNullException("_configuration"); + } + + // select the best endpoint. + EndpointDescription endpointDescription = CoreClientUtils.SelectEndpoint(serverUrl, UseSecurity); + EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(m_configuration); + + ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration); + + m_session = await Session.Create( + m_configuration, + endpoint, + false, + false, + (string.IsNullOrEmpty(OpcUaName)) ? m_configuration.ApplicationName : OpcUaName, + 60000, + UserIdentity, + new string[] { }); + + // set up keep alive callback. + m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive); + + // update the client status + m_IsConnected = true; + + // raise an event. + DoConnectComplete(null); + + // return the new session. + return m_session; + } + + /// + /// Disconnects from the server. + /// + public void Disconnect() + { + UpdateStatus(false, DateTime.UtcNow, "Disconnected"); + + // stop any reconnect operation. + if (m_reConnectHandler != null) + { + m_reConnectHandler.Dispose(); + m_reConnectHandler = null; + } + + // disconnect any existing session. + if (m_session != null) + { + m_session.Close(10000); + m_session = null; + } + + // update the client status + m_IsConnected = false; + + // raise an event. + DoConnectComplete(null); + } + + #endregion Connect And Disconnect + + #region Event Handlers + + /// + /// Report the client status + /// + /// Whether the status represents an error. + /// The time associated with the status. + /// The status message. + /// Arguments used to format the status message. + private void UpdateStatus(bool error, DateTime time, string status, params object[] args) + { + m_OpcStatusChange?.Invoke(this, new OpcUaStatusEventArgs() + { + Error = error, + Time = time.ToLocalTime(), + Text = String.Format(status, args), + }); + } + + /// + /// Handles a keep alive event from a session. + /// + private void Session_KeepAlive(Session session, KeepAliveEventArgs e) + { + try + { + // check for events from discarded sessions. + if (!Object.ReferenceEquals(session, m_session)) + { + return; + } + + // start reconnect sequence on communication error. + if (ServiceResult.IsBad(e.Status)) + { + if (m_reconnectPeriod <= 0) + { + UpdateStatus(true, e.CurrentTime, "Communication Error ({0})", e.Status); + return; + } + + UpdateStatus(true, e.CurrentTime, "Reconnecting in {0}s", m_reconnectPeriod); + + if (m_reConnectHandler == null) + { + m_ReconnectStarting?.Invoke(this, e); + + m_reConnectHandler = new SessionReconnectHandler(); + m_reConnectHandler.BeginReconnect(m_session, m_reconnectPeriod * 1000, Server_ReconnectComplete); + } + + return; + } + + // update status. + UpdateStatus(false, e.CurrentTime, "Connected [{0}]", session.Endpoint.EndpointUrl); + + // raise any additional notifications. + m_KeepAliveComplete?.Invoke(this, e); + } + catch (Exception exception) + { + ClientUtils.HandleException(OpcUaName, exception); + } + } + + /// + /// Handles a reconnect event complete from the reconnect handler. + /// + private void Server_ReconnectComplete(object sender, EventArgs e) + { + try + { + // ignore callbacks from discarded objects. + if (!Object.ReferenceEquals(sender, m_reConnectHandler)) + { + return; + } + + m_session = m_reConnectHandler.Session; + m_reConnectHandler.Dispose(); + m_reConnectHandler = null; + + // raise any additional notifications. + m_ReconnectComplete?.Invoke(this, e); + } + catch (Exception exception) + { +#if !NETSTANDARD2_0 && !NETSTANDARD2_1 + ClientUtils.HandleException(OpcUaName, exception); +#else + throw; +#endif + } + } + + #endregion Event Handlers + + #region LogOut Setting + + /// + /// 设置OPC客户端的日志输出 + /// + /// 完整的文件路径 + /// 是否删除原文件 + public void SetLogPathName(string filePath, bool deleteExisting) + { + Utils.SetTraceLog(filePath, deleteExisting); + Utils.SetTraceMask(515); + } + + #endregion LogOut Setting + + #region Public Members + + /// + /// a name of application name show on server + /// + public string OpcUaName { get; set; } = "Opc Ua Helper"; + + /// + /// Whether to use security when connecting. + /// + public bool UseSecurity + { + get { return m_useSecurity; } + set { m_useSecurity = value; } + } + + /// + /// The user identity to use when creating the session. + /// + public IUserIdentity UserIdentity { get; set; } + + /// + /// The currently active session. + /// + public Session Session + { + get { return m_session; } + } + + /// + /// Indicate the connect status + /// + public bool Connected + { + get { return m_IsConnected; } + } + + /// + /// The number of seconds between reconnect attempts (0 means reconnect is disabled). + /// + public int ReconnectPeriod + { + get { return m_reconnectPeriod; } + set { m_reconnectPeriod = value; } + } + + /// + /// Raised when a good keep alive from the server arrives. + /// + public event EventHandler KeepAliveComplete + { + add { m_KeepAliveComplete += value; } + remove { m_KeepAliveComplete -= value; } + } + + /// + /// Raised when a reconnect operation starts. + /// + public event EventHandler ReconnectStarting + { + add { m_ReconnectStarting += value; } + remove { m_ReconnectStarting -= value; } + } + + /// + /// Raised when a reconnect operation completes. + /// + public event EventHandler ReconnectComplete + { + add { m_ReconnectComplete += value; } + remove { m_ReconnectComplete -= value; } + } + + /// + /// Raised after successfully connecting to or disconnecing from a server. + /// + public event EventHandler ConnectComplete + { + add { m_ConnectComplete += value; } + remove { m_ConnectComplete -= value; } + } + + /// + /// Raised after the client status change + /// + public event EventHandler OpcStatusChange + { + add { m_OpcStatusChange += value; } + remove { m_OpcStatusChange -= value; } + } + + /// + /// 配置信息 + /// + public ApplicationConfiguration AppConfig => m_configuration; + + #endregion Public Members + + #region Node Write/Read Support + + /// + /// Read a value node from server + /// + /// node id + /// DataValue + public DataValue ReadNode(NodeId nodeId) + { + ReadValueIdCollection nodesToRead = new ReadValueIdCollection + { + new ReadValueId( ) + { + NodeId = nodeId, + AttributeId = Attributes.Value + } + }; + + // read the current value + m_session.Read( + null, + 0, + TimestampsToReturn.Neither, + nodesToRead, + out DataValueCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + ClientBase.ValidateResponse(results, nodesToRead); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead); + + return results[0]; + } + + /// + /// Read a value node from server + /// + /// type of value + /// node id + /// 实际值 + public T ReadNode(string tag) + { + DataValue dataValue = ReadNode(new NodeId(tag)); + return (T)dataValue.Value; + } + + /// + /// Read a tag asynchronously + /// + /// The type of tag to read + /// tag值 + /// The value retrieved from the OPC + public Task ReadNodeAsync(string tag) + { + ReadValueIdCollection nodesToRead = new ReadValueIdCollection + { + new ReadValueId() + { + NodeId = new NodeId(tag), + AttributeId = Attributes.Value + } + }; + + // Wrap the ReadAsync logic in a TaskCompletionSource, so we can use C# async/await syntax to call it: + var taskCompletionSource = new TaskCompletionSource(); + m_session.BeginRead( + requestHeader: null, + maxAge: 0, + timestampsToReturn: TimestampsToReturn.Neither, + nodesToRead: nodesToRead, + callback: ar => + { + DataValueCollection results; + DiagnosticInfoCollection diag; + var response = m_session.EndRead( + result: ar, + results: out results, + diagnosticInfos: out diag); + + try + { + CheckReturnValue(response.ServiceResult); + CheckReturnValue(results[0].StatusCode); + var val = results[0]; + taskCompletionSource.TrySetResult((T)val.Value); + } + catch (Exception ex) + { + taskCompletionSource.TrySetException(ex); + } + }, + asyncState: null); + + return taskCompletionSource.Task; + } + + /// + /// read several value nodes from server + /// + /// all NodeIds + /// all values + public List ReadNodes(NodeId[] nodeIds) + { + ReadValueIdCollection nodesToRead = new ReadValueIdCollection(); + for (int i = 0; i < nodeIds.Length; i++) + { + nodesToRead.Add(new ReadValueId() + { + NodeId = nodeIds[i], + AttributeId = Attributes.Value + }); + } + + // 读取当前的值 + m_session.Read( + null, + 0, + TimestampsToReturn.Neither, + nodesToRead, + out DataValueCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + ClientBase.ValidateResponse(results, nodesToRead); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead); + + return results.ToList(); + } + + /// + /// read several value nodes from server + /// + /// all NodeIds + /// all values + public Task> ReadNodesAsync(NodeId[] nodeIds) + { + ReadValueIdCollection nodesToRead = new ReadValueIdCollection(); + for (int i = 0; i < nodeIds.Length; i++) + { + nodesToRead.Add(new ReadValueId() + { + NodeId = nodeIds[i], + AttributeId = Attributes.Value + }); + } + + var taskCompletionSource = new TaskCompletionSource>(); + // 读取当前的值 + m_session.BeginRead( + null, + 0, + TimestampsToReturn.Neither, + nodesToRead, + callback: ar => + { + DataValueCollection results; + DiagnosticInfoCollection diag; + var response = m_session.EndRead( + result: ar, + results: out results, + diagnosticInfos: out diag); + + try + { + CheckReturnValue(response.ServiceResult); + taskCompletionSource.TrySetResult(results.ToList()); + } + catch (Exception ex) + { + taskCompletionSource.TrySetException(ex); + } + }, + asyncState: null); + + return taskCompletionSource.Task; + } + + /// + /// read several value nodes from server + /// + /// 所以的节点数组信息 + /// all values + public List ReadNodes(string[] tags) + { + List result = new List(); + ReadValueIdCollection nodesToRead = new ReadValueIdCollection(); + for (int i = 0; i < tags.Length; i++) + { + nodesToRead.Add(new ReadValueId() + { + NodeId = new NodeId(tags[i]), + AttributeId = Attributes.Value + }); + } + + // 读取当前的值 + m_session.Read( + null, + 0, + TimestampsToReturn.Neither, + nodesToRead, + out DataValueCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + ClientBase.ValidateResponse(results, nodesToRead); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead); + + foreach (var item in results) + { + result.Add((T)item.Value); + } + return result; + } + + /// + /// read several value nodes from server + /// + /// all NodeIds + /// all values + public Task> ReadNodesAsync(string[] tags) + { + ReadValueIdCollection nodesToRead = new ReadValueIdCollection(); + for (int i = 0; i < tags.Length; i++) + { + nodesToRead.Add(new ReadValueId() + { + NodeId = new NodeId(tags[i]), + AttributeId = Attributes.Value + }); + } + + var taskCompletionSource = new TaskCompletionSource>(); + // 读取当前的值 + m_session.BeginRead( + null, + 0, + TimestampsToReturn.Neither, + nodesToRead, + callback: ar => + { + DataValueCollection results; + DiagnosticInfoCollection diag; + var response = m_session.EndRead( + result: ar, + results: out results, + diagnosticInfos: out diag); + + try + { + CheckReturnValue(response.ServiceResult); + List result = new List(); + foreach (var item in results) + { + result.Add((T)item.Value); + } + taskCompletionSource.TrySetResult(result); + } + catch (Exception ex) + { + taskCompletionSource.TrySetException(ex); + } + }, + asyncState: null); + + return taskCompletionSource.Task; + } + + /// + /// write a note to server(you should use try catch) + /// + /// The type of tag to write on + /// 节点名称 + /// 值 + /// if success True,otherwise False + public bool WriteNode(string tag, T value) + { + WriteValue valueToWrite = new WriteValue() + { + NodeId = new NodeId(tag), + AttributeId = Attributes.Value + }; + valueToWrite.Value.Value = value; + valueToWrite.Value.StatusCode = StatusCodes.Good; + valueToWrite.Value.ServerTimestamp = DateTime.MinValue; + valueToWrite.Value.SourceTimestamp = DateTime.MinValue; + + WriteValueCollection valuesToWrite = new WriteValueCollection + { + valueToWrite + }; + + // 写入当前的值 + + m_session.Write( + null, + valuesToWrite, + out StatusCodeCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + ClientBase.ValidateResponse(results, valuesToWrite); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, valuesToWrite); + + if (StatusCode.IsBad(results[0])) + { + throw new ServiceResultException(results[0]); + } + + return !StatusCode.IsBad(results[0]); + } + + /// + /// Write a value on the specified opc tag asynchronously + /// + /// The type of tag to write on + /// The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name. E.g: the tag `foo.bar` writes on the tag `bar` on the folder `foo` + /// The value for the item to write + public Task WriteNodeAsync(string tag, T value) + { + WriteValue valueToWrite = new WriteValue() + { + NodeId = new NodeId(tag), + AttributeId = Attributes.Value, + }; + valueToWrite.Value.Value = value; + valueToWrite.Value.StatusCode = StatusCodes.Good; + valueToWrite.Value.ServerTimestamp = DateTime.MinValue; + valueToWrite.Value.SourceTimestamp = DateTime.MinValue; + WriteValueCollection valuesToWrite = new WriteValueCollection + { + valueToWrite + }; + + // Wrap the WriteAsync logic in a TaskCompletionSource, so we can use C# async/await syntax to call it: + var taskCompletionSource = new TaskCompletionSource(); + m_session.BeginWrite( + requestHeader: null, + nodesToWrite: valuesToWrite, + callback: ar => + { + var response = m_session.EndWrite( + result: ar, + results: out StatusCodeCollection results, + diagnosticInfos: out DiagnosticInfoCollection diag); + + try + { + ClientBase.ValidateResponse(results, valuesToWrite); + ClientBase.ValidateDiagnosticInfos(diag, valuesToWrite); + taskCompletionSource.SetResult(StatusCode.IsGood(results[0])); + } + catch (Exception ex) + { + taskCompletionSource.TrySetException(ex); + } + }, + asyncState: null); + return taskCompletionSource.Task; + } + + /// + /// 所有的节点都写入成功,返回True,否则返回False + /// + /// 节点名称数组 + /// 节点的值数据 + /// 所有的是否都写入成功 + public bool WriteNodes(string[] tags, object[] values) + { + WriteValueCollection valuesToWrite = new WriteValueCollection(); + + for (int i = 0; i < tags.Length; i++) + { + if (i < values.Length) + { + WriteValue valueToWrite = new WriteValue() + { + NodeId = new NodeId(tags[i]), + AttributeId = Attributes.Value + }; + valueToWrite.Value.Value = values[i]; + valueToWrite.Value.StatusCode = StatusCodes.Good; + valueToWrite.Value.ServerTimestamp = DateTime.MinValue; + valueToWrite.Value.SourceTimestamp = DateTime.MinValue; + valuesToWrite.Add(valueToWrite); + } + } + + // 写入当前的值 + + m_session.Write( + null, + valuesToWrite, + out StatusCodeCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + ClientBase.ValidateResponse(results, valuesToWrite); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, valuesToWrite); + + bool result = true; + foreach (var r in results) + { + if (StatusCode.IsBad(r)) + { + result = false; + break; + } + } + + return result; + } + + #endregion Node Write/Read Support + + #region DeleteNode Support + + /// + /// 删除一个节点的操作,除非服务器配置允许,否则引发异常,成功返回True,否则返回False + /// + /// 节点文本描述 + /// 是否删除成功 + public bool DeleteExsistNode(string tag) + { + DeleteNodesItemCollection waitDelete = new DeleteNodesItemCollection(); + + DeleteNodesItem nodesItem = new DeleteNodesItem() + { + NodeId = new NodeId(tag), + }; + + m_session.DeleteNodes( + null, + waitDelete, + out StatusCodeCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + ClientBase.ValidateResponse(results, waitDelete); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, waitDelete); + + return !StatusCode.IsBad(results[0]); + } + + #endregion DeleteNode Support + + #region Test Function + + /// + /// 新增一个节点数据 + /// + /// 父节点tag名称 + [Obsolete("还未经过测试,无法使用")] + public void AddNewNode(NodeId parent) + { + // Create a Variable node. + AddNodesItem node2 = new AddNodesItem(); + node2.ParentNodeId = new NodeId(parent); + node2.ReferenceTypeId = ReferenceTypes.HasComponent; + node2.RequestedNewNodeId = null; + node2.BrowseName = new QualifiedName("DataVariable1"); + node2.NodeClass = NodeClass.Variable; + node2.NodeAttributes = null; + node2.TypeDefinition = VariableTypeIds.BaseDataVariableType; + + //specify node attributes. + VariableAttributes node2Attribtues = new VariableAttributes(); + node2Attribtues.DisplayName = "DataVariable1"; + node2Attribtues.Description = "DataVariable1 Description"; + node2Attribtues.Value = new Variant(123); + node2Attribtues.DataType = (uint)BuiltInType.Int32; + node2Attribtues.ValueRank = ValueRanks.Scalar; + node2Attribtues.ArrayDimensions = new UInt32Collection(); + node2Attribtues.AccessLevel = AccessLevels.CurrentReadOrWrite; + node2Attribtues.UserAccessLevel = AccessLevels.CurrentReadOrWrite; + node2Attribtues.MinimumSamplingInterval = 0; + node2Attribtues.Historizing = false; + node2Attribtues.WriteMask = (uint)AttributeWriteMask.None; + node2Attribtues.UserWriteMask = (uint)AttributeWriteMask.None; + node2Attribtues.SpecifiedAttributes = (uint)NodeAttributesMask.All; + + node2.NodeAttributes = new ExtensionObject(node2Attribtues); + + AddNodesItemCollection nodesToAdd = new AddNodesItemCollection { node2 }; + + m_session.AddNodes( + null, + nodesToAdd, + out AddNodesResultCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + ClientBase.ValidateResponse(results, nodesToAdd); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToAdd); + } + + #endregion Test Function + + #region Monitor Support + + /// + /// 新增一个订阅,需要指定订阅的关键字,订阅的tag名,以及回调方法 + /// + /// 关键字 + /// tag + /// 回调方法 + public void AddSubscription(string key, string tag, Action callback) + { + AddSubscription(key, new string[] { tag }, callback); + } + + /// + /// 新增一批订阅,需要指定订阅的关键字,订阅的tag名数组,以及回调方法 + /// + /// 关键字 + /// 节点名称数组 + /// 回调方法 + public void AddSubscription(string key, string[] tags, Action callback) + { + Subscription m_subscription = new Subscription(m_session.DefaultSubscription); + + m_subscription.PublishingEnabled = true; + m_subscription.PublishingInterval = 0; + m_subscription.KeepAliveCount = uint.MaxValue; + m_subscription.LifetimeCount = uint.MaxValue; + m_subscription.MaxNotificationsPerPublish = uint.MaxValue; + m_subscription.Priority = 100; + m_subscription.DisplayName = key; + + for (int i = 0; i < tags.Length; i++) + { + var item = new MonitoredItem + { + StartNodeId = new NodeId(tags[i]), + AttributeId = Attributes.Value, + DisplayName = tags[i], + SamplingInterval = 100, + }; + item.Notification += (MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs args) => + { + callback?.Invoke(key, monitoredItem, args); + }; + m_subscription.AddItem(item); + } + + m_session.AddSubscription(m_subscription); + m_subscription.Create(); + + lock (dic_subscriptions) + { + if (dic_subscriptions.ContainsKey(key)) + { + // remove + dic_subscriptions[key].Delete(true); + m_session.RemoveSubscription(dic_subscriptions[key]); + dic_subscriptions[key].Dispose(); + dic_subscriptions[key] = m_subscription; + } + else + { + dic_subscriptions.Add(key, m_subscription); + } + } + } + + /// + /// 移除订阅消息,如果该订阅消息是批量的,也直接移除 + /// + /// 订阅关键值 + public void RemoveSubscription(string key) + { + lock (dic_subscriptions) + { + if (dic_subscriptions.ContainsKey(key)) + { + // remove + dic_subscriptions[key].Delete(true); + m_session.RemoveSubscription(dic_subscriptions[key]); + dic_subscriptions[key].Dispose(); + dic_subscriptions.Remove(key); + } + } + } + + /// + /// 移除所有的订阅消息 + /// + public void RemoveAllSubscription() + { + lock (dic_subscriptions) + { + foreach (var item in dic_subscriptions) + { + item.Value.Delete(true); + m_session.RemoveSubscription(item.Value); + item.Value.Dispose(); + } + dic_subscriptions.Clear(); + } + } + + #endregion Monitor Support + + #region ReadHistory Support + + /// + /// read History data + /// + /// 节点的索引 + /// 开始时间 + /// 结束时间 + /// 读取的个数 + /// 是否包含边界 + /// 读取的数据列表 + public IEnumerable ReadHistoryRawDataValues(string tag, DateTime start, DateTime end, uint count = 1, bool containBound = false) + { + HistoryReadValueId m_nodeToContinue = new HistoryReadValueId() + { + NodeId = new NodeId(tag), + }; + + ReadRawModifiedDetails m_details = new ReadRawModifiedDetails + { + StartTime = start, + EndTime = end, + NumValuesPerNode = count, + IsReadModified = false, + ReturnBounds = containBound + }; + + HistoryReadValueIdCollection nodesToRead = new HistoryReadValueIdCollection(); + nodesToRead.Add(m_nodeToContinue); + + m_session.HistoryRead( + null, + new ExtensionObject(m_details), + TimestampsToReturn.Both, + false, + nodesToRead, + out HistoryReadResultCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + ClientBase.ValidateResponse(results, nodesToRead); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead); + + if (StatusCode.IsBad(results[0].StatusCode)) + { + throw new ServiceResultException(results[0].StatusCode); + } + + HistoryData values = ExtensionObject.ToEncodeable(results[0].HistoryData) as HistoryData; + foreach (var value in values.DataValues) + { + yield return value; + } + } + + /// + /// 读取一连串的历史数据,并将其转化成指定的类型 + /// + /// 节点的索引 + /// 开始时间 + /// 结束时间 + /// 读取的个数 + /// 是否包含边界 + /// 读取的数据列表 + public IEnumerable ReadHistoryRawDataValues(string tag, DateTime start, DateTime end, uint count = 1, bool containBound = false) + { + HistoryReadValueId m_nodeToContinue = new HistoryReadValueId() + { + NodeId = new NodeId(tag), + }; + + ReadRawModifiedDetails m_details = new ReadRawModifiedDetails + { + StartTime = start.ToUniversalTime(), + EndTime = end.ToUniversalTime(), + NumValuesPerNode = count, + IsReadModified = false, + ReturnBounds = containBound + }; + + HistoryReadValueIdCollection nodesToRead = new HistoryReadValueIdCollection(); + nodesToRead.Add(m_nodeToContinue); + + m_session.HistoryRead( + null, + new ExtensionObject(m_details), + TimestampsToReturn.Both, + false, + nodesToRead, + out HistoryReadResultCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + ClientBase.ValidateResponse(results, nodesToRead); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead); + + if (StatusCode.IsBad(results[0].StatusCode)) + { + throw new ServiceResultException(results[0].StatusCode); + } + + HistoryData values = ExtensionObject.ToEncodeable(results[0].HistoryData) as HistoryData; + foreach (var value in values.DataValues) + { + yield return (T)value.Value; + } + } + + #endregion ReadHistory Support + + #region BrowseNode Support + + /// + /// 浏览一个节点的引用 + /// + /// 节点值 + /// 引用节点描述 + public ReferenceDescription[] BrowseNodeReference(string tag) + { + NodeId sourceId = new NodeId(tag); + + // 该节点可以读取到方法 + BrowseDescription nodeToBrowse1 = new BrowseDescription(); + + nodeToBrowse1.NodeId = sourceId; + nodeToBrowse1.BrowseDirection = BrowseDirection.Forward; + nodeToBrowse1.ReferenceTypeId = ReferenceTypeIds.Aggregates; + nodeToBrowse1.IncludeSubtypes = true; + nodeToBrowse1.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable | NodeClass.Method); + nodeToBrowse1.ResultMask = (uint)BrowseResultMask.All; + + // 该节点无论怎么样都读取不到方法 + // find all nodes organized by the node. + BrowseDescription nodeToBrowse2 = new BrowseDescription(); + + nodeToBrowse2.NodeId = sourceId; + nodeToBrowse2.BrowseDirection = BrowseDirection.Forward; + nodeToBrowse2.ReferenceTypeId = ReferenceTypeIds.Organizes; + nodeToBrowse2.IncludeSubtypes = true; + nodeToBrowse2.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable); + nodeToBrowse2.ResultMask = (uint)BrowseResultMask.All; + + BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection(); + nodesToBrowse.Add(nodeToBrowse1); + nodesToBrowse.Add(nodeToBrowse2); + + // fetch references from the server. + ReferenceDescriptionCollection references = FormUtils.Browse(m_session, nodesToBrowse, false); + + return references.ToArray(); + } + + #endregion BrowseNode Support + + #region Read Attributes Support + + /// + /// 读取一个节点的所有属性 + /// + /// 节点信息 + /// 节点的特性值 + public OpcNodeAttribute[] ReadNoteAttributes(string tag) + { + NodeId sourceId = new NodeId(tag); + ReadValueIdCollection nodesToRead = new ReadValueIdCollection(); + + // attempt to read all possible attributes. + // 尝试着去读取所有可能的特性 + for (uint ii = Attributes.NodeClass; ii <= Attributes.UserExecutable; ii++) + { + ReadValueId nodeToRead = new ReadValueId(); + nodeToRead.NodeId = sourceId; + nodeToRead.AttributeId = ii; + nodesToRead.Add(nodeToRead); + } + + int startOfProperties = nodesToRead.Count; + + // find all of the pror of the node. + BrowseDescription nodeToBrowse1 = new BrowseDescription(); + + nodeToBrowse1.NodeId = sourceId; + nodeToBrowse1.BrowseDirection = BrowseDirection.Forward; + nodeToBrowse1.ReferenceTypeId = ReferenceTypeIds.HasProperty; + nodeToBrowse1.IncludeSubtypes = true; + nodeToBrowse1.NodeClassMask = 0; + nodeToBrowse1.ResultMask = (uint)BrowseResultMask.All; + + BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection(); + nodesToBrowse.Add(nodeToBrowse1); + + // fetch property references from the server. + ReferenceDescriptionCollection references = FormUtils.Browse(m_session, nodesToBrowse, false); + + if (references == null) + { + return new OpcNodeAttribute[0]; + } + + for (int ii = 0; ii < references.Count; ii++) + { + // ignore external references. + if (references[ii].NodeId.IsAbsolute) + { + continue; + } + + ReadValueId nodeToRead = new ReadValueId(); + nodeToRead.NodeId = (NodeId)references[ii].NodeId; + nodeToRead.AttributeId = Attributes.Value; + nodesToRead.Add(nodeToRead); + } + + // read all values. + DataValueCollection results = null; + DiagnosticInfoCollection diagnosticInfos = null; + + m_session.Read( + null, + 0, + TimestampsToReturn.Neither, + nodesToRead, + out results, + out diagnosticInfos); + + ClientBase.ValidateResponse(results, nodesToRead); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead); + + // process results. + + List nodeAttribute = new List(); + for (int ii = 0; ii < results.Count; ii++) + { + OpcNodeAttribute item = new OpcNodeAttribute(); + + // process attribute value. + if (ii < startOfProperties) + { + // ignore attributes which are invalid for the node. + if (results[ii].StatusCode == StatusCodes.BadAttributeIdInvalid) + { + continue; + } + + // get the name of the attribute. + item.Name = Attributes.GetBrowseName(nodesToRead[ii].AttributeId); + + // display any unexpected error. + if (StatusCode.IsBad(results[ii].StatusCode)) + { + item.Type = Utils.Format("{0}", Attributes.GetDataTypeId(nodesToRead[ii].AttributeId)); + item.Value = Utils.Format("{0}", results[ii].StatusCode); + } + + // display the value. + else + { + TypeInfo typeInfo = TypeInfo.Construct(results[ii].Value); + + item.Type = typeInfo.BuiltInType.ToString(); + + if (typeInfo.ValueRank >= ValueRanks.OneOrMoreDimensions) + { + item.Type += "[]"; + } + + item.Value = results[ii].Value;//Utils.Format("{0}", results[ii].Value); + } + } + + // process property value. + else + { + // ignore properties which are invalid for the node. + if (results[ii].StatusCode == StatusCodes.BadNodeIdUnknown) + { + continue; + } + + // get the name of the property. + item.Name = Utils.Format("{0}", references[ii - startOfProperties]); + + // display any unexpected error. + if (StatusCode.IsBad(results[ii].StatusCode)) + { + item.Type = String.Empty; + item.Value = Utils.Format("{0}", results[ii].StatusCode); + } + + // display the value. + else + { + TypeInfo typeInfo = TypeInfo.Construct(results[ii].Value); + + item.Type = typeInfo.BuiltInType.ToString(); + + if (typeInfo.ValueRank >= ValueRanks.OneOrMoreDimensions) + { + item.Type += "[]"; + } + + item.Value = results[ii].Value; //Utils.Format("{0}", results[ii].Value); + } + } + + nodeAttribute.Add(item); + } + + return nodeAttribute.ToArray(); + } + + /// + /// 读取一个节点的所有属性 + /// + /// 节点值 + /// 所有的数据 + public DataValue[] ReadNoteDataValueAttributes(string tag) + { + NodeId sourceId = new NodeId(tag); + ReadValueIdCollection nodesToRead = new ReadValueIdCollection(); + + // attempt to read all possible attributes. + // 尝试着去读取所有可能的特性 + for (uint ii = Attributes.NodeId; ii <= Attributes.UserExecutable; ii++) + { + ReadValueId nodeToRead = new ReadValueId(); + nodeToRead.NodeId = sourceId; + nodeToRead.AttributeId = ii; + nodesToRead.Add(nodeToRead); + } + + int startOfProperties = nodesToRead.Count; + + // find all of the pror of the node. + BrowseDescription nodeToBrowse1 = new BrowseDescription(); + + nodeToBrowse1.NodeId = sourceId; + nodeToBrowse1.BrowseDirection = BrowseDirection.Forward; + nodeToBrowse1.ReferenceTypeId = ReferenceTypeIds.HasProperty; + nodeToBrowse1.IncludeSubtypes = true; + nodeToBrowse1.NodeClassMask = 0; + nodeToBrowse1.ResultMask = (uint)BrowseResultMask.All; + + BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection(); + nodesToBrowse.Add(nodeToBrowse1); + + // fetch property references from the server. + ReferenceDescriptionCollection references = FormUtils.Browse(m_session, nodesToBrowse, false); + + if (references == null) + { + return new DataValue[0]; + } + + for (int ii = 0; ii < references.Count; ii++) + { + // ignore external references. + if (references[ii].NodeId.IsAbsolute) + { + continue; + } + + ReadValueId nodeToRead = new ReadValueId(); + nodeToRead.NodeId = (NodeId)references[ii].NodeId; + nodeToRead.AttributeId = Attributes.Value; + nodesToRead.Add(nodeToRead); + } + + // read all values. + DataValueCollection results = null; + DiagnosticInfoCollection diagnosticInfos = null; + + m_session.Read( + null, + 0, + TimestampsToReturn.Neither, + nodesToRead, + out results, + out diagnosticInfos); + + ClientBase.ValidateResponse(results, nodesToRead); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead); + + return results.ToArray(); + } + + #endregion Read Attributes Support + + #region Method Call Support + + /// + /// call a server method + /// + /// 方法的父节点tag + /// 方法的节点tag + /// 传递的参数 + /// 输出的结果值 + public object[] CallMethodByNodeId(string tagParent, string tag, params object[] args) + { + if (m_session == null) + { + return null; + } + + IList outputArguments = m_session.Call( + new NodeId(tagParent), + new NodeId(tag), + args); + + return outputArguments.ToArray(); + } + + #endregion Method Call Support + + #region Private Methods + + /// + /// Raises the connect complete event on the main GUI thread. + /// + private void DoConnectComplete(object state) + { + m_ConnectComplete?.Invoke(this, null); + } + + private void CheckReturnValue(StatusCode status) + { + if (!StatusCode.IsGood(status)) + throw new Exception(string.Format("Invalid response from the server. (Response Status: {0})", status)); + } + + #endregion Private Methods + + #region Private Fields + + private ApplicationConfiguration m_configuration; + private Session m_session; + private bool m_IsConnected; //是否已经连接过 + private int m_reconnectPeriod = 10; // 重连状态 + private bool m_useSecurity; + + private SessionReconnectHandler m_reConnectHandler; + private EventHandler m_ReconnectComplete; + private EventHandler m_ReconnectStarting; + private EventHandler m_KeepAliveComplete; + private EventHandler m_ConnectComplete; + private EventHandler m_OpcStatusChange; + + private Dictionary dic_subscriptions; // 系统所有的节点信息 + + #endregion Private Fields + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/OPCUA/OpcUaStatusEventArgs.cs b/常用工具集/Utility/Network/OPCUA/OpcUaStatusEventArgs.cs new file mode 100644 index 0000000..944aa38 --- /dev/null +++ b/常用工具集/Utility/Network/OPCUA/OpcUaStatusEventArgs.cs @@ -0,0 +1,65 @@ +using Opc.Ua; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpcUaHelper +{ + /// + /// OPC UA的状态更新消息 + /// + public class OpcUaStatusEventArgs + { + + + /// + /// 是否异常 + /// + public bool Error { get; set; } + /// + /// 时间 + /// + public DateTime Time { get; set; } + /// + /// 文本 + /// + public string Text { get; set; } + + /// + /// 转化为字符串 + /// + /// + public override string ToString() + { + return Error ? "[异常]" : "[正常]" + Time.ToString(" yyyy-MM-dd HH:mm:ss ") + Text; + } + + + } + + /// + /// 读取属性过程中用于描述的 + /// + public class OpcNodeAttribute + { + /// + /// 属性的名称 + /// + public string Name { get; set; } + /// + /// 属性的类型描述 + /// + public string Type { get; set; } + /// + /// 操作结果状态描述 + /// + public StatusCode StatusCode { get; set; } + /// + /// 属性的值,如果读取错误,返回文本描述 + /// + public object Value { get; set; } + + } +} diff --git a/常用工具集/Utility/Network/Omrons/DataTools.cs b/常用工具集/Utility/Network/Omrons/DataTools.cs new file mode 100644 index 0000000..664f001 --- /dev/null +++ b/常用工具集/Utility/Network/Omrons/DataTools.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OmronLib +{ + public static class DataTools + { + /// + /// 将byte类型数据数组转换为16进制字符串形式 + /// + /// + /// + /// + public static string ByteToHexString(byte[] hex, int len) + { + string returnstr = ""; + for (int i = 0; i < len; i++) + { + returnstr += hex[i].ToString("X2"); + } + return returnstr; + } + + /// + /// 将一个长字符串转化为每个元素包含两个字符的字符数组 + /// + /// + /// + /// + public static string[] StrToStrArray(string src) + { + try + { + string[] res = new string[src.Length / 2]; + + for (int i = 0; i < src.Length / 2; i++) + { + res[i] = src.Substring(i * 2, 2); + } + + return res; + } + catch + { + return null; + } + } + } +} diff --git a/常用工具集/Utility/Network/Omrons/Omron.cs b/常用工具集/Utility/Network/Omrons/Omron.cs new file mode 100644 index 0000000..ed7718c --- /dev/null +++ b/常用工具集/Utility/Network/Omrons/Omron.cs @@ -0,0 +1,960 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net; +using System.Net.Sockets; +using System.Security.Cryptography; + +namespace OmronLib +{ + /* + *Copyright: Copyright (c) 2019 + *Created on 2019-11-7 + *Author: coder_li@outlook.com + */ + + /// + /// 欧姆龙PLC使用FINS通讯,在与PLC建立tcp连接后,必须先进行读写准备,获取到DA1和SA1 + /// 这里默认读取200个D区地址,从D5000开始到D5199,读取的返回值都是高位在前低位在后 + /// 写入时都是单个寄存器写入,写入的内容也是高位在前低位在后 + /// + public class Omron + { + + /// + /// PLCIP + /// + public IPAddress IPAddr { get; set; } + + /// + /// PLC端口号 + /// + public int Port { get; set; } + + public int Timeout = 1000; + /// + /// 获取是否已经连接到PLC + /// + /// + public bool IsConnected + { + get + { + if (PlcClient == null) + return false; + else + return PlcClient.Connected; + } + } + + /// + /// PLC内存区域类型 + /// + public enum AreaType + { + CIO_Bit = 0x30, + WR_Bit = 0x31, + HR_Bit = 0x32, + AR_Bit = 0x33, + DM_Bit = 0x02, + CIO_Word = 0xB0, + WR_Word = 0xB1, + HR_Word = 0xB2, + AR_Word = 0xB3, + DM_Word = 0x82 + } + + + private readonly object locker = new object(); + private TcpClient PlcClient = null; + private NetworkStream Stream = null; + private IPEndPoint PlcIP = null; + + private byte DA1 = 0x00, SA1 = 0x00; + + public Omron() + { } + + #region 命令模板 + /// + /// 写命令 + /// + private readonly byte[] WriteCommand = new byte[] { 0x46, 0x49, 0x4e, 0x53, // F I N S + 0x00, 0x00, 0x00, 0x1c, // 命令长度 + 0x00, 0x00, 0x00, 0x02, // 命令码 + 0x00, 0x00, 0x00, 0x00, // 错误码 + 0x80, 0x00, 0x02, 0x00, // ICF RSV GCT DNA + 0x00, 0x00, 0x00, 0x00, // DA1 DA2 SNA SA1 + 0x00, 0xDA, // SA2 SID + 0x01, 0x02, // FINS主命令 FINS从命令 (01 02 写) + 0x82, 0x00, 0x00, 0x00, // 寄存器控制位 开始字地址(从高到低) 开始位地址 + 0x00, 0x01 }; // 写入数量(从高到低) 后面都是写入的内容(可以加长) + /// + /// 读命令 + /// + private readonly byte[] ReadCommand = new byte[] { 0x46, 0x49, 0x4e, 0x53, // F I N S + 0x00, 0x00, 0x00, 0x1a, // 命令长度 + 0x00, 0x00, 0x00, 0x02, // 命令码 + 0x00, 0x00, 0x00, 0x00, // 错误码 + 0x80, 0x00, 0x02, 0x00, // ICF RSV GCT DNA *************** + 0x00, 0x00, 0x00, 0x00, // DA1 DA2 SNA SA1 FINS Header + 0x00, 0xDA, // SA2 SID *************** + 0x01, 0x01, // FINS主命令 FINS从命令 (01 01 读) *************** + 0x82, 0x00, 0x00, 0x00, // 寄存器控制位 开始字地址(高,低) 开始位地址 FINS Command + 0x00, 0x01 }; // 读取数量(高,低) *************** + /// + /// 获取节点地址的命令 + /// + private readonly byte[] PrepareCmd = new byte[] { 0x46, 0x49, 0x4e, 0x53, + 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 }; + + #endregion + + /// + /// 向PLC发送指令 + /// + /// 指令 + /// 报错信息 + /// 是否发送成功 + private bool DataSend(byte[] commands) + { + string info; + try + { + if (this.PlcClient != null && PlcClient.Connected) + { + //lock (locker) + { + if (Stream == null) Stream = PlcClient.GetStream(); + Stream.Write(commands, 0, commands.Length); + Stream.Flush(); + } + return true; + } + else + { + info = "连接已断开"; + return false; + } + } + catch (Exception exp) + { + info = "发送指令到PLC失败 " + exp.Message; + return false; + } + + } + + /// + /// 从PLC读取数据 + /// + /// + /// 报错信息 + /// + private string DataRead() + { + + byte[] buffer = new byte[1024]; + int bytestoread = 0; + string info; + + try + { + //if (Stream.DataAvailable) + { + int start = Environment.TickCount; + do + { + bytestoread = Stream.Read(buffer, 0, buffer.Length); + if (Math.Abs(Environment.TickCount - start) >= 20000) + { + info = "PLC应答超时,请确认PLC连接线路及cpu是否有报错!"; + break; + } + } while (bytestoread == 0); + + if (buffer[11] == 3) + { + if (buffer[15] == 1) + { + info = "发送的命令不是有效的FINS命令!"; + return ""; + } + else if (buffer[15] == 2) + { + info = "发送的命令长度超出范围!"; + return ""; + } + else if (buffer[15] == 3) + { + info = "PLC不支持该‘FINS’命令!"; + return ""; + } + } + if (buffer[25] == WriteCommand[25]) + return DataTools.ByteToHexString(buffer, bytestoread); + else + { + info = "PLC返回错误,返回值 " + buffer[0].ToString(); + return ""; + } + } + } + catch + { + return ""; + } + + } + + /// + /// 判断是否成功写入命令到PLC + /// + /// + private bool Write2PlcSuccess() + { + byte[] buffer = new byte[1024]; + int bytestoread = 0; + string info; + int start = Environment.TickCount; + + try + { + //lock (locker) + { + do + { + bytestoread = Stream.Read(buffer, 0, buffer.Length); + if (Math.Abs(Environment.TickCount - start) >= 1000) + { + info = "PLC应答超时,请确认PLC连接线路及cpu是否有报错!"; + break; + } + } while (bytestoread == 0); + + } + if (buffer[25] == WriteCommand[25]) return true; + else + { + info = "PLC返回错误,返回值 " + buffer[1].ToString(); + return false; + } + } + catch + { + return false; + } + + } + + /// + /// 发送连接字符串,获取PLC返回的DA1和SA1值 + /// + /// + private bool ConnectPrepare() + { + try + { + Stream.Write(PrepareCmd, 0, PrepareCmd.Length); + byte[] res = new byte[24]; + Stream.Read(res, 0, res.Length); + DA1 = res[23]; + SA1 = res[19]; + } + catch { return false; } + return true; + } + + /// + /// 检查命令帧中的EndCode + /// + /// 主码 + /// 副码 + /// 错误信息 + /// 指示程序是否可以继续进行 + private bool CheckEndCode(byte Main, byte Sub, out string info) + { + info = ""; + switch (Main) + { + case 0x00: + switch (Sub) + { + case 0x00: return true;//the only situation of success + case 0x01: info = "service canceled"; return false; + } + break; + case 0x01: + switch (Sub) + { + case 0x01: info = "local node not in network"; return false; + case 0x02: info = "token timeout"; return false; + case 0x03: info = "retries failed"; return false; + case 0x04: info = "too many send frames"; return false; + case 0x05: info = "node address range error"; return false; + case 0x06: info = "node address duplication"; return false; + } + break; + case 0x02: + switch (Sub) + { + case 0x01: info = "destination node not in network"; return false; + case 0x02: info = "unit missing"; return false; + case 0x03: info = "third node missing"; return false; + case 0x04: info = "destination node busy"; return false; + case 0x05: info = "response timeout"; return false; + } + break; + case 0x03: + switch (Sub) + { + case 0x01: info = "communications controller error"; return false; + case 0x02: info = "CPU unit error"; return false; + case 0x03: info = "controller error"; return false; + case 0x04: info = "unit number error"; return false; + } + break; + case 0x04: + switch (Sub) + { + case 0x01: info = "undefined command"; return false; + case 0x02: info = "not supported by model/version"; return false; + } + break; + case 0x05: + switch (Sub) + { + case 0x01: info = "destination address setting error"; return false; + case 0x02: info = "no routing tables"; return false; + case 0x03: info = "routing table error"; return false; + case 0x04: info = "too many relays"; return false; + } + break; + case 0x10: + switch (Sub) + { + case 0x01: info = "command too long"; return false; + case 0x02: info = "command too short"; return false; + case 0x03: info = "elements/data don't match"; return false; + case 0x04: info = "command format error"; return false; + case 0x05: info = "header error"; return false; + } + break; + case 0x11: + switch (Sub) + { + case 0x01: info = "area classification missing"; return false; + case 0x02: info = "access size error"; return false; + case 0x03: info = "address range error"; return false; + case 0x04: info = "address range exceeded"; return false; + case 0x06: info = "program missing"; return false; + case 0x09: info = "relational error"; return false; + case 0x0a: info = "duplicate data access"; return false; + case 0x0b: info = "response too long"; return false; + case 0x0c: info = "parameter error"; return false; + } + break; + case 0x20: + switch (Sub) + { + case 0x02: info = "protected"; return false; + case 0x03: info = "table missing"; return false; + case 0x04: info = "data missing"; return false; + case 0x05: info = "program missing"; return false; + case 0x06: info = "file missing"; return false; + case 0x07: info = "data mismatch"; return false; + } + break; + case 0x21: + switch (Sub) + { + case 0x01: info = "read-only"; return false; + case 0x02: info = "protected , cannot write data link table"; return false; + case 0x03: info = "cannot register"; return false; + case 0x05: info = "program missing"; return false; + case 0x06: info = "file missing"; return false; + case 0x07: info = "file name already exists"; return false; + case 0x08: info = "cannot change"; return false; + } + break; + case 0x22: + switch (Sub) + { + case 0x01: info = "not possible during execution"; return false; + case 0x02: info = "not possible while running"; return false; + case 0x03: info = "wrong PLC mode"; return false; + case 0x04: info = "wrong PLC mode"; return false; + case 0x05: info = "wrong PLC mode"; return false; + case 0x06: info = "wrong PLC mode"; return false; + case 0x07: info = "specified node not polling node"; return false; + case 0x08: info = "step cannot be executed"; return false; + } + break; + case 0x23: + switch (Sub) + { + case 0x01: info = "file device missing"; return false; + case 0x02: info = "memory missing"; return false; + case 0x03: info = "clock missing"; return false; + } + break; + case 0x24: + switch (Sub) + { case 0x01: info = "table missing"; return false; } + break; + case 0x25: + switch (Sub) + { + case 0x02: info = "memory error"; return false; + case 0x03: info = "I/O setting error"; return false; + case 0x04: info = "too many I/O points"; return false; + case 0x05: info = "CPU bus error"; return false; + case 0x06: info = "I/O duplication"; return false; + case 0x07: info = "CPU bus error"; return false; + case 0x09: info = "SYSMAC BUS/2 error"; return false; + case 0x0a: info = "CPU bus unit error"; return false; + case 0x0d: info = "SYSMAC BUS No. duplication"; return false; + case 0x0f: info = "memory error"; return false; + case 0x10: info = "SYSMAC BUS terminator missing"; return false; + } + break; + case 0x26: + switch (Sub) + { + case 0x01: info = "no protection"; return false; + case 0x02: info = "incorrect password"; return false; + case 0x04: info = "protected"; return false; + case 0x05: info = "service already executing"; return false; + case 0x06: info = "service stopped"; return false; + case 0x07: info = "no execution right"; return false; + case 0x08: info = "settings required before execution"; return false; + case 0x09: info = "necessary items not set"; return false; + case 0x0a: info = "number already defined"; return false; + case 0x0b: info = "error will not clear"; return false; + } + break; + case 0x30: + switch (Sub) + { case 0x01: info = "no access right"; return false; } + break; + case 0x40: + switch (Sub) + { case 0x01: info = "service aborted"; return false; } + break; + } + info = "unknown exception"; + return false; + } + + /// + /// PLC初始化 + /// + /// + /// + private bool PlcInit() + { + bool flag = false; + PlcClient = new TcpClient(); + IAsyncResult asyncResult = PlcClient.BeginConnect(IPAddr, Port, null, null); + if (!asyncResult.AsyncWaitHandle.WaitOne(Timeout)) + { + return flag; + } + PlcClient.EndConnect(asyncResult); + + if (PlcClient != null) + { + Stream = PlcClient.GetStream(); + Stream.ReadTimeout = Timeout; + Stream.WriteTimeout = Timeout; + if (ConnectPrepare()) + { + flag = true; + } + } + return flag; + } + + /// + /// 连接PLC + /// + /// + /// + public bool PlcConnect(out string info) + { + info = ""; + try + { + if (PlcClient == null) + { + PlcInit(); + } + if (!PlcClient.Connected) // 没连上的话重试一遍 + { + PlcClient.Close(); + PlcClient = null; + PlcInit(); + } + return PlcClient.Connected; + } + catch (Exception exp) + { + info = "连接PLC失败\n" + exp.Message; + return false; + } + } + + /// + /// 断开PLC连接 + /// + /// + public bool DisConnect() + { + if (this.PlcClient == null) + return true; + if (!this.PlcClient.Connected) + return true; + + this.PlcClient.Close(); + return true; + } + + + + #region 读 + + /// + /// 读PLC字或位 + /// 读位时从左到右是低位到高位 + /// + /// PLC内存区类型 + /// 开始地址(字) + /// 开始地址(位) + /// 长度 + /// 返回的字符数组,每个元素表示一个字 + /// + public bool Read(AreaType type, int startword, int startbit, int count, out string[] result) + { + result = new string[0]; + byte[] cmd = (byte[])ReadCommand.Clone(); + cmd[20] = DA1; //连接时获取到的DA1 + cmd[23] = SA1; //连接时获取到的SA1 + + // 内存类型 + cmd[28] = (byte)type; + + // 读取起始地址 + byte[] bytesAddr = BitConverter.GetBytes((short)startword); //BitConverter转出来的Byte按从低位到高位排列 + cmd[29] = bytesAddr[1]; + cmd[30] = bytesAddr[0]; + cmd[31] = (byte)startbit; + + // 读取长度 + byte[] bytesLength = BitConverter.GetBytes((short)count); + cmd[32] = bytesLength[1]; + cmd[33] = bytesLength[0]; + + //开始读取 + lock (locker) + { + if (DataSend(cmd)) + { + string res = DataRead(); + if (res.Length == 0) + return false; + + result = DataTools.StrToStrArray(res.Substring(60)); + return true; + } + } + return false; + } + + /// + /// 读取DM区的字 + /// + /// + /// + /// + /// + public bool ReadDWords(int startword, int count, out string[] result) + { + string[] res; + bool isSucess = Read(AreaType.DM_Word, startword, 0, count, out res); + result = res; + return isSucess; + } + + /// + /// 读取DM区的位 + /// + /// + /// + /// + /// + /// + public bool ReadDBits(int startword, int startbit, int count, out string[] result) + { + string[] res; + bool isSucess = Read(AreaType.DM_Bit, startword, startbit, count, out res); + result = res; + return isSucess; + } + + #endregion + + #region 写 + + /// + /// 写PLC字 + /// + /// 地址类型 + /// 开始字地址 + /// 字数 + /// 值 + /// 写入成功与否 + public bool WriteWords(AreaType type, int startword, int count, int[] paras) + { + byte[] hexadd = BitConverter.GetBytes(startword); + byte[] sendCmd = WriteCommand.Concat(new byte[count * 2]).ToArray(); // 这里扩容sendCmd数组,不然会溢出 + + sendCmd[7] = (byte)(26 + count * 2); + sendCmd[20] = DA1; + sendCmd[23] = SA1; + + // 内存类型 + sendCmd[28] = (byte)type; + + // 写入起始地址 + byte[] bytesAddr = BitConverter.GetBytes((short)startword); //BitConverter转出来的Byte按从低位到高位排列 + sendCmd[29] = bytesAddr[1]; + sendCmd[30] = bytesAddr[0]; + sendCmd[31] = 0; + + // 写入长度 + byte[] bytesLength = BitConverter.GetBytes((short)count); + sendCmd[32] = bytesLength[1]; + sendCmd[33] = bytesLength[0]; + + byte[] hexPara; + for (int i = 0; i < count; i++) + { + hexPara = BitConverter.GetBytes(paras[i]); + sendCmd[34 + i * 2] = hexPara[1]; + sendCmd[34 + i * 2 + 1] = hexPara[0]; + } + + lock (locker) + { + if (DataSend(sendCmd)) + { + if (Write2PlcSuccess()) + return true; + } + } + return false; + } + + /// + /// 写PLC位 + /// + /// + /// 地址类型 + /// 开始字地址 + /// 开始位地址 + /// 写入位数 + /// 值 + /// 写入成功与否 + public bool WriteBits(AreaType type, int startword, int startbit, int count, bool[] paras) + { + byte[] hexadd = BitConverter.GetBytes(startword); + byte[] sendCmd = WriteCommand.Concat(new byte[count]).ToArray(); // 这里扩容sendCmd数组,不然会溢出 + + // 命令长度 + sendCmd[7] = (byte)(26 + count); + + sendCmd[20] = DA1; + sendCmd[23] = SA1; + + // 内存类型 + sendCmd[28] = (byte)type; + + // 写入起始地址 + byte[] bytesAddr = BitConverter.GetBytes((short)startword); //BitConverter转出来的Byte按从低位到高位排列 + sendCmd[29] = bytesAddr[1]; + sendCmd[30] = bytesAddr[0]; + sendCmd[31] = (byte)startbit; + + // 写入长度 + byte[] bytesLength = BitConverter.GetBytes((short)count); + sendCmd[32] = bytesLength[1]; + sendCmd[33] = bytesLength[0]; + + for (int i = 0; i < count; i++) + { + sendCmd[34 + i] = Convert.ToByte(paras[i]); + } + + lock (locker) + { + if (DataSend(sendCmd)) + { + if (Write2PlcSuccess()) + return true; + } + } + return false; + } + + /// + /// 写D字 + /// + /// + /// + /// + /// + public bool WriteDWords(int startword, int count, int[] paras) + { + return WriteWords(AreaType.DM_Word, startword, count, paras); + } + + /// + /// 写D位 + /// + /// + /// + /// + /// + /// + public bool WriteDBits(int startword, int startbit, int count, bool[] paras) + { + return WriteBits(AreaType.DM_Bit, startword, startbit, count, paras); + } + + public bool ReadString(int address, int count, out string str) + { + try + { + string[] res; + bool isSucess = ReadDWords(address, count, out res); + if (!isSucess) + { + str = ""; + return false; + } + byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray(); + List list = new List(); + for (int i = 0; i < bRes.Length; i += 2) + { + list.Add((int)(bRes[i] << 8 | bRes[i + 1])); + } + str = GetQRCodeByData(list.ToArray()); + return true; + } + catch (Exception ex) + { + str = ex.Message; + return false; + } + } + + public bool ReadFloat(int address, out float value) + { + try + { + string[] res; + bool isSucess = ReadDWords(address, 2, out res); + if (!isSucess) + { + value = 0; + return false; + } + byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray(); + if (bRes.Length != 4) + { + value = 0; + return false; + } + int i = 0; + value = BitConverter.ToSingle(new byte[] { bRes[i + 1], bRes[i], bRes[i + 3], bRes[i + 2] }, 0); + return true; + } + catch (Exception ex) + { + value = 0; + return false; + } + } + + public bool ReadFloats(int address, int count, out float[] values) + { + try + { + string[] res; + bool isSucess = ReadDWords(address, count * 2, out res); + if (!isSucess) + { + values = null; + return false; + } + byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray(); + if (bRes.Length != (count * 4)) + { + values = null; + return false; + } + List list = new List(); + for (int i = 0; i < bRes.Length; i += 4) + { + list.Add(BitConverter.ToSingle(new byte[] { bRes[i + 1], bRes[i], bRes[i + 3], bRes[i + 2] }, 0)); + } + values = list.ToArray(); + return true; + } + catch (Exception ex) + { + values = null; + return false; + } + } + public bool WriteFloat(int address, float[] values) + { + List writeValues = new List(); + foreach (float f in values) + { + byte[] bytes = BitConverter.GetBytes(f); + writeValues.Add(BitConverter.ToInt32(new byte[] { bytes[0], bytes[1], 0, 0 }, 0)); + writeValues.Add(BitConverter.ToInt32(new byte[] { bytes[2], bytes[3], 0, 0 }, 0)); + } + return WriteWords(AreaType.DM_Word, address, values.Length * 2, writeValues.ToArray()); + } + + + public bool ReadShort(int address, out short value) + { + try + { + string[] res; + bool isSucess = ReadDWords(address, 1, out res); + if (!isSucess) + { + value = 0; + return false; + } + byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray(); + if (bRes.Length != 2) + { + value = 0; + return false; + } + value = BitConverter.ToInt16(new byte[] { bRes[1], bRes[0] }, 0); + return true; + } + catch (Exception ex) + { + value = 0; + return false; + } + } + public bool ReadShorts(int address, int count, out short[] values) + { + try + { + string[] res; + bool isSucess = ReadDWords(address, count, out res); + if (!isSucess) + { + values = null; + return false; + } + byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray(); + if (bRes.Length != (count * 2)) + { + values = null; + return false; + } + List list = new List(); + for (int i = 0; i < bRes.Length; i += 2) + { + list.Add(BitConverter.ToInt16(new byte[] { bRes[i + 1], bRes[i] }, 0)); + } + values = list.ToArray(); + return true; + } + catch (Exception ex) + { + values = null; + return false; + } + } + + public bool WriteShort(int address, short[] values) + { + List writeValues = new List(); + foreach (short f in values) + { + + writeValues.Add(f); + } + return WriteWords(AreaType.DM_Word, address, values.Length, writeValues.ToArray()); + } + public bool WriteString(int address, string str, int count) + { + try + { + int[] aaa = GetDataByQRCode(str, count); + return WriteDWords(address, count, aaa); + } + catch (Exception ex) + { + return false; + } + } + private int[] GetDataByQRCode(string qrcode, int totalCount) + { + byte[] byteList = new byte[2 * totalCount]; + + byte[] bytes = Encoding.ASCII.GetBytes(qrcode); + for (int i = 0; i < byteList.Length; i++) + { + if (i >= bytes.Length) + { + break; + } + byteList[i] = bytes[i]; + } + + List shortList = new List(); + for (int i = 0; i < (2 * totalCount); i += 2) + { + //将字节数组转为int存入list + shortList.Add(BitConverter.ToInt32(new byte[] { byteList[i], byteList[i + 1], 0x00, 0x00 }, 0)); + } + return shortList.ToArray(); + } + + /// + /// 将PLC中获取到的数据转成string + /// + /// + /// + private string GetQRCodeByData(int[] qrcode) + { + List list = new List(); + for (int i = 0; i < qrcode.Length; i++) + { + //int中取两个字节存入list + list.AddRange(BitConverter.GetBytes(qrcode[i]).Take(2)); + } + //去掉多余的/0和空格 转为string + string qrcodes = Encoding.ASCII.GetString(list.ToArray()).Trim('\0').Trim(); + return qrcodes; + } + #endregion + + } +} diff --git a/常用工具集/Utility/Network/S7netplus/COTP.cs b/常用工具集/Utility/Network/S7netplus/COTP.cs new file mode 100644 index 0000000..3e5abbb --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/COTP.cs @@ -0,0 +1,118 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace S7.Net +{ + + /// + /// COTP Protocol functions and types + /// + internal class COTP + { + public enum PduType : byte + { + Data = 0xf0, + ConnectionConfirmed = 0xd0 + } + /// + /// Describes a COTP TPDU (Transport protocol data unit) + /// + public class TPDU + { + public TPKT TPkt { get; } + public byte HeaderLength; + public PduType PDUType; + public int TPDUNumber; + public byte[] Data; + public bool LastDataUnit; + + public TPDU(TPKT tPKT) + { + TPkt = tPKT; + + HeaderLength = tPKT.Data[0]; // Header length excluding this length byte + if (HeaderLength >= 2) + { + PDUType = (PduType)tPKT.Data[1]; + if (PDUType == PduType.Data) //DT Data + { + var flags = tPKT.Data[2]; + TPDUNumber = flags & 0x7F; + LastDataUnit = (flags & 0x80) > 0; + Data = new byte[tPKT.Data.Length - HeaderLength - 1]; // substract header length byte + header length. + Array.Copy(tPKT.Data, HeaderLength + 1, Data, 0, Data.Length); + return; + } + //TODO: Handle other PDUTypes + } + Data = new byte[0]; + } + + /// + /// Reads COTP TPDU (Transport protocol data unit) from the network stream + /// See: https://tools.ietf.org/html/rfc905 + /// + /// The socket to read from + /// COTP DPDU instance + public static async Task ReadAsync(Stream stream, CancellationToken cancellationToken) + { + var tpkt = await TPKT.ReadAsync(stream, cancellationToken).ConfigureAwait(false); + if (tpkt.Length == 0) + { + throw new TPDUInvalidException("No protocol data received"); + } + return new TPDU(tpkt); + } + + public override string ToString() + { + return string.Format("Length: {0} PDUType: {1} TPDUNumber: {2} Last: {3} Segment Data: {4}", + HeaderLength, + PDUType, + TPDUNumber, + LastDataUnit, + BitConverter.ToString(Data) + ); + } + + } + + /// + /// Describes a COTP TSDU (Transport service data unit). One TSDU consist of 1 ore more TPDUs + /// + public class TSDU + { + /// + /// Reads the full COTP TSDU (Transport service data unit) + /// See: https://tools.ietf.org/html/rfc905 + /// + /// The stream to read from + /// Data in TSDU + public static async Task ReadAsync(Stream stream, CancellationToken cancellationToken) + { + var segment = await TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); + + if (segment.LastDataUnit) + { + return segment.Data; + } + + // More segments are expected, prepare a buffer to store all data + var buffer = new byte[segment.Data.Length]; + Array.Copy(segment.Data, buffer, segment.Data.Length); + + while (!segment.LastDataUnit) + { + segment = await TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); + var previousLength = buffer.Length; + Array.Resize(ref buffer, buffer.Length + segment.Data.Length); + Array.Copy(segment.Data, 0, buffer, previousLength, segment.Data.Length); + } + + return buffer; + } + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Compat/TcpClientMixins.cs b/常用工具集/Utility/Network/S7netplus/Compat/TcpClientMixins.cs new file mode 100644 index 0000000..101a5d5 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Compat/TcpClientMixins.cs @@ -0,0 +1,9 @@ +using System.Net.Sockets; + +namespace S7.Net +{ + public static class TcpClientMixins + { + + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Conversion.cs b/常用工具集/Utility/Network/S7netplus/Conversion.cs new file mode 100644 index 0000000..44f9e25 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Conversion.cs @@ -0,0 +1,226 @@ +using System; +using System.Globalization; + +namespace S7.Net +{ + /// + /// Conversion methods to convert from Siemens numeric format to C# and back + /// + public static class Conversion + { + /// + /// Converts a binary string to Int32 value + /// + /// + /// + public static int BinStringToInt32(this string txt) + { + int ret = 0; + + for (int i = 0; i < txt.Length; i++) + { + ret = (ret << 1) | ((txt[i] == '1') ? 1 : 0); + } + return ret; + } + + /// + /// Converts a binary string to a byte. Can return null. + /// + /// + /// + public static byte? BinStringToByte(this string txt) + { + if (txt.Length == 8) return (byte)BinStringToInt32(txt); + return null; + } + + /// + /// Converts the value to a binary string + /// + /// + /// + public static string ValToBinString(this object value) + { + int cnt = 0; + int cnt2 = 0; + int x = 0; + string txt = ""; + long longValue = 0; + + try + { + if (value.GetType().Name.IndexOf("[]") < 0) + { + // ist nur ein Wert + switch (value.GetType().Name) + { + case "Byte": + x = 7; + longValue = (long)((byte)value); + break; + case "Int16": + x = 15; + longValue = (long)((Int16)value); + break; + case "Int32": + x = 31; + longValue = (long)((Int32)value); + break; + case "Int64": + x = 63; + longValue = (long)((Int64)value); + break; + default: + throw new Exception(); + } + + for (cnt = x; cnt >= 0; cnt += -1) + { + if (((Int64)longValue & (Int64)Math.Pow(2, cnt)) > 0) + txt += "1"; + else + txt += "0"; + } + } + else + { + // ist ein Array + switch (value.GetType().Name) + { + case "Byte[]": + x = 7; + byte[] ByteArr = (byte[])value; + for (cnt2 = 0; cnt2 <= ByteArr.Length - 1; cnt2++) + { + for (cnt = x; cnt >= 0; cnt += -1) + if ((ByteArr[cnt2] & (byte)Math.Pow(2, cnt)) > 0) txt += "1"; else txt += "0"; + } + break; + case "Int16[]": + x = 15; + Int16[] Int16Arr = (Int16[])value; + for (cnt2 = 0; cnt2 <= Int16Arr.Length - 1; cnt2++) + { + for (cnt = x; cnt >= 0; cnt += -1) + if ((Int16Arr[cnt2] & (byte)Math.Pow(2, cnt)) > 0) txt += "1"; else txt += "0"; + } + break; + case "Int32[]": + x = 31; + Int32[] Int32Arr = (Int32[])value; + for (cnt2 = 0; cnt2 <= Int32Arr.Length - 1; cnt2++) + { + for (cnt = x; cnt >= 0; cnt += -1) + if ((Int32Arr[cnt2] & (byte)Math.Pow(2, cnt)) > 0) txt += "1"; else txt += "0"; + } + break; + case "Int64[]": + x = 63; + byte[] Int64Arr = (byte[])value; + for (cnt2 = 0; cnt2 <= Int64Arr.Length - 1; cnt2++) + { + for (cnt = x; cnt >= 0; cnt += -1) + if ((Int64Arr[cnt2] & (byte)Math.Pow(2, cnt)) > 0) txt += "1"; else txt += "0"; + } + break; + default: + throw new Exception(); + } + } + return txt; + } + catch + { + return ""; + } + } + + /// + /// Helper to get a bit value given a byte and the bit index. + /// Example: DB1.DBX0.5 -> var bytes = ReadBytes(DB1.DBW0); bool bit = bytes[0].SelectBit(5); + /// + /// + /// + /// + public static bool SelectBit(this byte data, int bitPosition) + { + int mask = 1 << bitPosition; + int result = data & mask; + + return (result != 0); + } + + /// + /// Converts from ushort value to short value; it's used to retrieve negative values from words + /// + /// + /// + public static short ConvertToShort(this ushort input) + { + short output; + output = short.Parse(input.ToString("X"), NumberStyles.HexNumber); + return output; + } + + /// + /// Converts from short value to ushort value; it's used to pass negative values to DWs + /// + /// + /// + public static ushort ConvertToUshort(this short input) + { + ushort output; + output = ushort.Parse(input.ToString("X"), NumberStyles.HexNumber); + return output; + } + + /// + /// Converts from UInt32 value to Int32 value; it's used to retrieve negative values from DBDs + /// + /// + /// + public static Int32 ConvertToInt(this uint input) + { + int output; + output = int.Parse(input.ToString("X"), NumberStyles.HexNumber); + return output; + } + + /// + /// Converts from Int32 value to UInt32 value; it's used to pass negative values to DBDs + /// + /// + /// + public static UInt32 ConvertToUInt(this int input) + { + uint output; + output = uint.Parse(input.ToString("X"), NumberStyles.HexNumber); + return output; + } + + /// + /// Converts from float to DWord (DBD) + /// + /// + /// + public static UInt32 ConvertToUInt(this float input) + { + uint output; + output = S7.Net.Types.DWord.FromByteArray(S7.Net.Types.Real.ToByteArray(input)); + return output; + } + + /// + /// Converts from DWord (DBD) to float + /// + /// + /// + public static float ConvertToFloat(this uint input) + { + float output; + output = S7.Net.Types.Real.FromByteArray(S7.Net.Types.DWord.ToByteArray(input)); + return output; + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Enums.cs b/常用工具集/Utility/Network/S7netplus/Enums.cs new file mode 100644 index 0000000..fc412d3 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Enums.cs @@ -0,0 +1,211 @@ +namespace S7.Net +{ + /// + /// Types of S7 cpu supported by the library + /// + public enum CpuType + { + /// + /// S7 200 cpu type + /// + S7200 = 0, + + /// + /// Siemens Logo 0BA8 + /// + Logo0BA8 = 1, + + /// + /// S7 200 Smart + /// + S7200Smart = 2, + + /// + /// S7 300 cpu type + /// + S7300 = 10, + + /// + /// S7 400 cpu type + /// + S7400 = 20, + + /// + /// S7 1200 cpu type + /// + S71200 = 30, + + /// + /// S7 1500 cpu type + /// + S71500 = 40, + } + + /// + /// Types of error code that can be set after a function is called + /// + public enum ErrorCode + { + /// + /// The function has been executed correctly + /// + NoError = 0, + + /// + /// Wrong type of CPU error + /// + WrongCPU_Type = 1, + + /// + /// Connection error + /// + ConnectionError = 2, + + /// + /// Ip address not available + /// + IPAddressNotAvailable, + + /// + /// Wrong format of the variable + /// + WrongVarFormat = 10, + + /// + /// Wrong number of received bytes + /// + WrongNumberReceivedBytes = 11, + + /// + /// Error on send data + /// + SendData = 20, + + /// + /// Error on read data + /// + ReadData = 30, + + /// + /// Error on write data + /// + WriteData = 50 + } + + /// + /// Types of memory area that can be read + /// + public enum DataType + { + /// + /// Input area memory + /// + Input = 129, + + /// + /// Output area memory + /// + Output = 130, + + /// + /// Merkers area memory (M0, M0.0, ...) + /// + Memory = 131, + + /// + /// DB area memory (DB1, DB2, ...) + /// + DataBlock = 132, + + /// + /// Timer area memory(T1, T2, ...) + /// + Timer = 29, + + /// + /// Counter area memory (C1, C2, ...) + /// + Counter = 28 + } + + /// + /// Types + /// + public enum VarType + { + /// + /// S7 Bit variable type (bool) + /// + Bit, + + /// + /// S7 Byte variable type (8 bits) + /// + Byte, + + /// + /// S7 Word variable type (16 bits, 2 bytes) + /// + Word, + + /// + /// S7 DWord variable type (32 bits, 4 bytes) + /// + DWord, + + /// + /// S7 Int variable type (16 bits, 2 bytes) + /// + Int, + + /// + /// DInt variable type (32 bits, 4 bytes) + /// + DInt, + + /// + /// Real variable type (32 bits, 4 bytes) + /// + Real, + + /// + /// LReal variable type (64 bits, 8 bytes) + /// + LReal, + + /// + /// Char Array / C-String variable type (variable) + /// + String, + + /// + /// S7 String variable type (variable) + /// + S7String, + + /// + /// S7 WString variable type (variable) + /// + S7WString, + + /// + /// Timer variable type + /// + Timer, + + /// + /// Counter variable type + /// + Counter, + + /// + /// DateTIme variable type + /// + DateTime, + + /// + /// DateTimeLong variable type + /// + DateTimeLong + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Helper/MemoryStreamExtension.cs b/常用工具集/Utility/Network/S7netplus/Helper/MemoryStreamExtension.cs new file mode 100644 index 0000000..5758f0b --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Helper/MemoryStreamExtension.cs @@ -0,0 +1,35 @@ +using System.IO; + +namespace S7.Net.Helper +{ + internal static class MemoryStreamExtension + { + /// + /// Helper function to write to whole content of the given byte array to a memory stream. + /// + /// Writes all bytes in value from 0 to value.Length to the memory stream. + /// + /// + /// + public static void Write(this MemoryStream stream, byte[] value) + { + stream.Write(value, 0, value.Length); + } + + /// + /// Helper function to write the whole content of the given byte span to a memory stream. + /// + /// + /// + //public static void Write(this MemoryStream stream, ReadOnlySpan value) + //{ + // byte[] buffer = ArrayPool.Shared.Rent(value.Length); + + // value.CopyTo(buffer); + // stream.Write(buffer, 0, value.Length); + + // ArrayPool.Shared.Return(buffer); + //} + } + +} diff --git a/常用工具集/Utility/Network/S7netplus/Internal/TaskQueue.cs b/常用工具集/Utility/Network/S7netplus/Internal/TaskQueue.cs new file mode 100644 index 0000000..bd0cd6b --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Internal/TaskQueue.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace S7.Net.Internal +{ + internal class TaskQueue + { + private static readonly object Sentinel = new object(); + + private Task prev = Task.FromResult(Sentinel); + + public async Task Enqueue(Func> action) + { + var tcs = new TaskCompletionSource(); + await Interlocked.Exchange(ref prev, tcs.Task).ConfigureAwait(false); + + try + { + return await action.Invoke().ConfigureAwait(false); + } + finally + { + tcs.SetResult(Sentinel); + } + } + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/S7netplus/InvalidDataException.cs b/常用工具集/Utility/Network/S7netplus/InvalidDataException.cs new file mode 100644 index 0000000..44a4ff7 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/InvalidDataException.cs @@ -0,0 +1,39 @@ +using System; + +namespace S7.Net +{ + [Serializable] + public class InvalidDataException : Exception + { + public byte[] ReceivedData { get; } + public int ErrorIndex { get; } + public byte ExpectedValue { get; } + + public InvalidDataException(string message, byte[] receivedData, int errorIndex, byte expectedValue) + : base(FormatMessage(message, receivedData, errorIndex, expectedValue)) + { + ReceivedData = receivedData; + ErrorIndex = errorIndex; + ExpectedValue = expectedValue; + } + + protected InvalidDataException(System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) + { + ReceivedData = (byte[])info.GetValue(nameof(ReceivedData), typeof(byte[])); + ErrorIndex = info.GetInt32(nameof(ErrorIndex)); + ExpectedValue = info.GetByte(nameof(ExpectedValue)); + } + + private static string FormatMessage(string message, byte[] receivedData, int errorIndex, byte expectedValue) + { + if (errorIndex >= receivedData.Length) + throw new ArgumentOutOfRangeException(nameof(errorIndex), + $"{nameof(errorIndex)} {errorIndex} is outside the bounds of {nameof(receivedData)} with length {receivedData.Length}."); + + return $"{message} Invalid data received. Expected '{expectedValue}' at index {errorIndex}, " + + $"but received {receivedData[errorIndex]}. See the {nameof(ReceivedData)} property " + + "for the full message received."; + } + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/S7netplus/ORM/S7Client.cs b/常用工具集/Utility/Network/S7netplus/ORM/S7Client.cs new file mode 100644 index 0000000..6e456bb --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/ORM/S7Client.cs @@ -0,0 +1,47 @@ +using System; +using S7.Net; + +namespace MES.Utility.Network.S7netplus.ORM +{ + public class S7Client : IDisposable + { + public S7Helper Plc; + public bool IsConnected; + public S7Client(CpuType cpu, string ip) + { + try + { + Plc = new S7Helper(cpu, ip); + IsConnected = true; + } + catch (Exception ex) + { + IsConnected = false; + } + } + + public S7Read Readable() where T : class, new() + { + return new S7Read(this); + } + + public S7Write Writeable(T entity) where T : class, new() + { + return new S7Write(this, entity); + } + + public void Dispose() + { + try + { + if (Plc != null) + { + Plc.Dispose(); + Plc = null; + } + + } + catch { } + } + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/S7netplus/ORM/S7NodeType.cs b/常用工具集/Utility/Network/S7netplus/ORM/S7NodeType.cs new file mode 100644 index 0000000..b3e7dde --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/ORM/S7NodeType.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MES.Utility.Network.S7netplus.ORM +{ + public enum S7NodeType + { + [Description("可读可写")] + ReadAndWrite, + [Description("只读")] + ReadOnly, + [Description("只写")] + WriteOnly + } +} diff --git a/常用工具集/Utility/Network/S7netplus/ORM/S7PLCAttribute.cs b/常用工具集/Utility/Network/S7netplus/ORM/S7PLCAttribute.cs new file mode 100644 index 0000000..f6c4b65 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/ORM/S7PLCAttribute.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MES.Utility.Network.S7netplus.ORM; + +namespace MES.Utility.Network.S7netplus.ORM +{ + public class S7PLCAttribute : Attribute + { + public string Address { get; set; } + public int StrLength { get; set; } + public S7NodeType Type { get; set; } + public S7PLCAttribute() { } + + public S7PLCAttribute(string address, S7NodeType type) + { + this.Address = address; + this.Type = type; + } + public S7PLCAttribute(string address, int strLength, S7NodeType type) + { + this.Address = address; + this.StrLength = strLength; + this.Type = type; + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/ORM/S7Read.cs b/常用工具集/Utility/Network/S7netplus/ORM/S7Read.cs new file mode 100644 index 0000000..1fde417 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/ORM/S7Read.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace MES.Utility.Network.S7netplus.ORM +{ + public class S7Read where T : class, new() + { + private S7Client opcClient; + private Dictionary fieldDict; + private Dictionary stringLengthDict; + /// + /// 构造方法,传入参数 + /// + /// + public S7Read(S7Client opcClient) + { + this.opcClient = opcClient; + //默认取出所有的只读或者可读可写的属性 + GetNodeType(out fieldDict, out stringLengthDict); + } + + /// + /// 获得所有具有可读属性的数据 + /// + /// + private void GetNodeType(out Dictionary fieldDict, out Dictionary stringLengthDict) + { + fieldDict = new Dictionary(); + stringLengthDict = new Dictionary(); + + //获得对象T的属性 + PropertyInfo[] properties = typeof(T).GetProperties(); + //得到所有添加注解的属性 + foreach (PropertyInfo info in properties) + { + //判断是否具有HttpController属性 + Object[] attr = info.GetCustomAttributes(false); + if (attr.Length == 0) + { + continue; + } + //从注解数组中取第一个注解(一个属性可以包含多个注解) + S7PLCAttribute myattr = attr[0] as S7PLCAttribute; + if (myattr == null) + continue; + if (myattr.Type == S7NodeType.ReadAndWrite || myattr.Type == S7NodeType.ReadOnly) + { + fieldDict.Add(myattr.Address, info); + stringLengthDict.Add(myattr.Address, myattr.StrLength); + } + } + } + + /// + /// 执行获得数据,映射成对象 + /// + /// 返回错误信息 + /// + public bool Execute(out T t) + { + + if (fieldDict.Count == 0) + { + t = default(T); + return false; + } + try + { + if (!opcClient.IsConnected) + { + t = default(T); + return false; + } + bool flag = false; + T entity = (T)Activator.CreateInstance(typeof(T)); + //循环读取每一个 + foreach (KeyValuePair keyValue in fieldDict) + { + //判断当前keyValue是什么类型 + if (keyValue.Value.PropertyType == typeof(ushort)) + { + ushort value=0; + flag = opcClient.Plc.ReadInt16(keyValue.Key, out value); + if (!flag) + { + t = default(T); + return false; + } + keyValue.Value.SetValue(entity, value); + } + else if (keyValue.Value.PropertyType == typeof(int)) + { + ushort value =0; + flag = opcClient.Plc.ReadInt16(keyValue.Key, out value); + if (!flag) + { + t = default(T); + return false; + } + keyValue.Value.SetValue(entity, value); + } + //else if (keyValue.Value.PropertyType == typeof(float)) + //{ + // float value; + // flag = opcClient.Plc.ReadReal(keyValue.Key, out value); + // if (!flag) + // { + // t = default(T); + // return false; + // } + // keyValue.Value.SetValue(entity, value); + //} + else if (keyValue.Value.PropertyType == typeof(string)) + { + string value; + flag = opcClient.Plc.ReadString(keyValue.Key, stringLengthDict[keyValue.Key], out value); + if (!flag) + { + t = default(T); + return false; + } + keyValue.Value.SetValue(entity, value); + } + } + t = entity; + return true; + } + catch + { + t = default(T); + return false; + } + } + + + /// + /// 传入Lambda表达式,指定读取的数据 + /// + /// + /// + public S7Read ReadField(Expression> columns) + { + Dictionary fieldDict = new Dictionary(); + Dictionary stringLengthDict = new Dictionary(); + + //取出要读的属性 + string lambda = columns.Body.ToString(); + int index1 = lambda.IndexOf('('); + int index2 = lambda.IndexOf(')'); + string str = lambda.Substring(index1 + 1, index2 - index1 - 1); + List keyList = str.Split(',').Select(it => it.Split('=')[0].Trim()).ToList(); + fieldDict.Clear(); + //获得对象T的属性 + PropertyInfo[] properties = typeof(T).GetProperties(); + //得到所有添加注解的属性 + foreach (PropertyInfo info in properties) + { + //判断是否具有HttpController属性 + Object[] attr = info.GetCustomAttributes(false); + if (attr.Length == 0) + { + continue; + } + //从注解数组中取第一个注解(一个属性可以包含多个注解) + S7PLCAttribute myattr = attr[0] as S7PLCAttribute; + if (myattr == null) + continue; + if (myattr.Type == S7NodeType.ReadAndWrite || myattr.Type == S7NodeType.ReadOnly) + { + if (keyList.Contains(info.Name)) + { + fieldDict.Add(myattr.Address, info); + stringLengthDict.Add(myattr.Address, myattr.StrLength); + } + } + } + this.fieldDict = fieldDict; + this.stringLengthDict = stringLengthDict; + return this; + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/ORM/S7Write.cs b/常用工具集/Utility/Network/S7netplus/ORM/S7Write.cs new file mode 100644 index 0000000..c52a972 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/ORM/S7Write.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace MES.Utility.Network.S7netplus.ORM +{ + public class S7Write where T : class, new() + { + private S7Client opcClient; + private T entity; + private Dictionary dict; + private Dictionary strLengthDict; + + /// + /// 构造方法,传入参数 + /// + /// + /// + public S7Write(S7Client opcClient, T entity) + { + this.opcClient = opcClient; + this.entity = entity; + //默认取出所有的只写或者可读可写的属性 + GetAllKeyList(out dict, out strLengthDict); + } + + + /// + /// 获得所有具有可写属性的数据 + /// + /// + private void GetAllKeyList(out Dictionary dict, out Dictionary strLengthDict) + { + dict = new Dictionary(); + strLengthDict = new Dictionary(); + + + //获取entity对象的所有属性 + PropertyInfo[] properties = typeof(T).GetProperties(); + //得到所有添加注解的属性 + foreach (PropertyInfo info in properties) + { + //判断是否具有HttpController属性 + object[] attr = info.GetCustomAttributes(false); + if (attr.Length == 0) + { + continue; + } + //从注解数组中取第一个注解(一个属性可以包含多个注解) + S7PLCAttribute myattr = attr[0] as S7PLCAttribute; + if (myattr == null) + continue; + if (myattr.Type == S7NodeType.ReadAndWrite || myattr.Type == S7NodeType.WriteOnly) + { + dict.Add(myattr.Address, info); + strLengthDict.Add(myattr.Address, myattr.StrLength); + } + } + } + + /// + /// 执行写入数据,将对象中的数据写入到OPC服务中 + /// + /// 返回写入过程中出现的错误信息 + /// + public bool Execute() + { + if (dict.Count == 0) + { + return false; + } + if (!opcClient.IsConnected) + return false; + //循环写入 + try + { + bool flag; + foreach (KeyValuePair keyValue in dict) + { + //判断当前keyValue是什么类型 + if (keyValue.Value.PropertyType == typeof(ushort)) + { + flag = opcClient.Plc.WriteInt16(keyValue.Key, (ushort)keyValue.Value.GetValue(entity)); + if (!flag) + { + return false; + } + } + else if (keyValue.Value.PropertyType == typeof(int)) + { + flag = opcClient.Plc.WriteInt32(keyValue.Key, (int)keyValue.Value.GetValue(entity)); + if (!flag) + { + return false; + } + } + else if (keyValue.Value.PropertyType == typeof(float)) + { + flag = opcClient.Plc.WriteSingle(keyValue.Key, (float)keyValue.Value.GetValue(entity)); + if (!flag) + { + return false; + } + } + else if (keyValue.Value.PropertyType == typeof(string)) + { + flag = opcClient.Plc.WriteString(keyValue.Key, (string)keyValue.Value.GetValue(entity), strLengthDict[keyValue.Key]); + if (!flag) + { + return false; + } + } + + } + return true; + } + catch + { + return false; + } + + } + + + /// + /// 传入Lambda表达式,指定写入的数据 + /// + /// + /// + public S7Write WriteField(Expression> columns) + { + Dictionary dict = new Dictionary(); + Dictionary strLengthDict = new Dictionary(); + + + string lambda = columns.Body.ToString(); + int index1 = lambda.IndexOf('('); + int index2 = lambda.IndexOf(')'); + string str = lambda.Substring(index1 + 1, index2 - index1 - 1); + List keyList = str.Split(',').Select(it => it.Split('=')[0].Trim()).ToList(); + + //获取entity对象的所有属性 + PropertyInfo[] properties = typeof(T).GetProperties(); + //得到所有添加注解的属性 + foreach (PropertyInfo info in properties) + { + //判断是否具有HttpController属性 + Object[] attr = info.GetCustomAttributes(false); + if (attr.Length == 0) + { + continue; + } + //从注解数组中取第一个注解(一个属性可以包含多个注解) + S7PLCAttribute myattr = attr[0] as S7PLCAttribute; + if (myattr == null) + continue; + if (myattr.Type == S7NodeType.ReadAndWrite || myattr.Type == S7NodeType.WriteOnly) + { + string name = info.Name; + if (keyList.Contains(name)) + { + dict.Add(myattr.Address, info); + strLengthDict.Add(myattr.Address, myattr.StrLength); + } + } + } + this.dict = dict; + this.strLengthDict = strLengthDict; + return this; + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/PLC.cs b/常用工具集/Utility/Network/S7netplus/PLC.cs new file mode 100644 index 0000000..57caf92 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/PLC.cs @@ -0,0 +1,329 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Sockets; +using S7.Net.Internal; +using S7.Net.Protocol; +using S7.Net.Types; + + +namespace S7.Net +{ + /// + /// Creates an instance of S7.Net driver + /// + public partial class Plc : IDisposable + { + /// + /// The default port for the S7 protocol. + /// + public const int DefaultPort = 102; + + /// + /// The default timeout (in milliseconds) used for and . + /// + public const int DefaultTimeout = 10_000; + + private readonly TaskQueue queue = new TaskQueue(); + + //TCP connection to device + private TcpClient tcpClient; + private NetworkStream _stream; + + private int readTimeout = DefaultTimeout; // default no timeout + private int writeTimeout = DefaultTimeout; // default no timeout + + /// + /// IP address of the PLC + /// + public string IP { get; } + + /// + /// PORT Number of the PLC, default is 102 + /// + public int Port { get; } + + /// + /// The TSAP addresses used during the connection request. + /// + public TsapPair TsapPair { get; set; } + + /// + /// CPU type of the PLC + /// + public CpuType CPU { get; } + + /// + /// Rack of the PLC + /// + public Int16 Rack { get; } + + /// + /// Slot of the CPU of the PLC + /// + public Int16 Slot { get; } + + /// + /// Max PDU size this cpu supports + /// + public int MaxPDUSize { get; private set; } + + /// Gets or sets the amount of time that a read operation blocks waiting for data from PLC. + /// A that specifies the amount of time, in milliseconds, that will elapse before a read operation fails. The default value, , specifies that the read operation does not time out. + public int ReadTimeout + { + get => readTimeout; + set + { + readTimeout = value; + if (tcpClient != null) tcpClient.ReceiveTimeout = readTimeout; + } + } + + /// Gets or sets the amount of time that a write operation blocks waiting for data to PLC. + /// A that specifies the amount of time, in milliseconds, that will elapse before a write operation fails. The default value, , specifies that the write operation does not time out. + public int WriteTimeout + { + get => writeTimeout; + set + { + writeTimeout = value; + if (tcpClient != null) tcpClient.SendTimeout = writeTimeout; + } + } + + /// + /// Gets a value indicating whether a connection to the PLC has been established. + /// + /// + /// The property gets the connection state of the Client socket as + /// of the last I/O operation. When it returns false, the Client socket was either + /// never connected, or is no longer connected. + /// + /// + /// Because the property only reflects the state of the connection + /// as of the most recent operation, you should attempt to send or receive a message to + /// determine the current state. After the message send fails, this property no longer + /// returns true. Note that this behavior is by design. You cannot reliably test the + /// state of the connection because, in the time between the test and a send/receive, the + /// connection could have been lost. Your code should assume the socket is connected, and + /// gracefully handle failed transmissions. + /// + /// + public bool IsConnected => tcpClient?.Connected ?? false; + + /// + /// Creates a PLC object with all the parameters needed for connections. + /// For S7-1200 and S7-1500, the default is rack = 0 and slot = 0. + /// You need slot > 0 if you are connecting to external ethernet card (CP). + /// For S7-300 and S7-400 the default is rack = 0 and slot = 2. + /// + /// CpuType of the PLC (select from the enum) + /// Ip address of the PLC + /// rack of the PLC, usually it's 0, but check in the hardware configuration of Step7 or TIA portal + /// slot of the CPU of the PLC, usually it's 2 for S7300-S7400, 0 for S7-1200 and S7-1500. + /// If you use an external ethernet card, this must be set accordingly. + public Plc(CpuType cpu, string ip, Int16 rack, Int16 slot) + : this(cpu, ip, DefaultPort, rack, slot) + { + } + + /// + /// Creates a PLC object with all the parameters needed for connections. + /// For S7-1200 and S7-1500, the default is rack = 0 and slot = 0. + /// You need slot > 0 if you are connecting to external ethernet card (CP). + /// For S7-300 and S7-400 the default is rack = 0 and slot = 2. + /// + /// CpuType of the PLC (select from the enum) + /// Ip address of the PLC + /// Port number used for the connection, default 102. + /// rack of the PLC, usually it's 0, but check in the hardware configuration of Step7 or TIA portal + /// slot of the CPU of the PLC, usually it's 2 for S7300-S7400, 0 for S7-1200 and S7-1500. + /// If you use an external ethernet card, this must be set accordingly. + public Plc(CpuType cpu, string ip, int port, Int16 rack, Int16 slot) + : this(ip, port, TsapPair.GetDefaultTsapPair(cpu, rack, slot)) + { + if (!Enum.IsDefined(typeof(CpuType), cpu)) + throw new ArgumentException( + $"The value of argument '{nameof(cpu)}' ({cpu}) is invalid for Enum type '{typeof(CpuType).Name}'.", + nameof(cpu)); + + CPU = cpu; + Rack = rack; + Slot = slot; + } + + /// + /// Creates a PLC object with all the parameters needed for connections. + /// For S7-1200 and S7-1500, the default is rack = 0 and slot = 0. + /// You need slot > 0 if you are connecting to external ethernet card (CP). + /// For S7-300 and S7-400 the default is rack = 0 and slot = 2. + /// + /// Ip address of the PLC + /// The TSAP addresses used for the connection request. + public Plc(string ip, TsapPair tsapPair) : this(ip, DefaultPort, tsapPair) + { + } + + /// + /// Creates a PLC object with all the parameters needed for connections. Use this constructor + /// if you want to manually override the TSAP addresses used during the connection request. + /// + /// Ip address of the PLC + /// Port number used for the connection, default 102. + /// The TSAP addresses used for the connection request. + public Plc(string ip, int port, TsapPair tsapPair) + { + if (string.IsNullOrEmpty(ip)) + throw new ArgumentException("IP address must valid.", nameof(ip)); + + IP = ip; + Port = port; + MaxPDUSize = 240; + TsapPair = tsapPair; + } + + /// + /// Close connection to PLC + /// + public void Close() + { + if (tcpClient != null) + { + if (tcpClient.Connected) tcpClient.Close(); + tcpClient = null; // Can not reuse TcpClient once connection gets closed. + } + } + + private void AssertPduSizeForRead(ICollection dataItems) + { + // send request limit: 19 bytes of header data, 12 bytes of parameter data for each dataItem + var requiredRequestSize = 19 + dataItems.Count * 12; + if (requiredRequestSize > MaxPDUSize) throw new Exception($"Too many vars requested for read. Request size ({requiredRequestSize}) is larger than protocol limit ({MaxPDUSize})."); + + // response limit: 14 bytes of header data, 4 bytes of result data for each dataItem and the actual data + var requiredResponseSize = GetDataLength(dataItems) + dataItems.Count * 4 + 14; + if (requiredResponseSize > MaxPDUSize) throw new Exception($"Too much data requested for read. Response size ({requiredResponseSize}) is larger than protocol limit ({MaxPDUSize})."); + } + + private void AssertPduSizeForWrite(ICollection dataItems) + { + // 12 bytes of header data, 18 bytes of parameter data for each dataItem + if (dataItems.Count * 18 + 12 > MaxPDUSize) throw new Exception("Too many vars supplied for write"); + + // 12 bytes of header data, 16 bytes of data for each dataItem and the actual data + if (GetDataLength(dataItems) + dataItems.Count * 16 + 12 > MaxPDUSize) + throw new Exception("Too much data supplied for write"); + } + + private void ConfigureConnection() + { + if (tcpClient == null) + { + return; + } + tcpClient.ReceiveTimeout = ReadTimeout; + tcpClient.SendTimeout = WriteTimeout; + } + + private int GetDataLength(IEnumerable dataItems) + { + // Odd length variables are 0-padded + return dataItems.Select(di => VarTypeToByteLength(di.VarType, di.Count)) + .Sum(len => (len & 1) == 1 ? len + 1 : len); + } + + private static void AssertReadResponse(byte[] s7Data, int dataLength) + { + var expectedLength = dataLength + 18; + + PlcException NotEnoughBytes() => + new PlcException(ErrorCode.WrongNumberReceivedBytes, + $"Received {s7Data.Length} bytes: '{BitConverter.ToString(s7Data)}', expected {expectedLength} bytes.") + ; + + if (s7Data == null) + throw new PlcException(ErrorCode.WrongNumberReceivedBytes, "No s7Data received."); + + if (s7Data.Length < 15) throw NotEnoughBytes(); + + ValidateResponseCode((ReadWriteErrorCode)s7Data[14]); + + if (s7Data.Length < expectedLength) throw NotEnoughBytes(); + } + + internal static void ValidateResponseCode(ReadWriteErrorCode statusCode) + { + switch (statusCode) + { + case ReadWriteErrorCode.ObjectDoesNotExist: + throw new Exception("Received error from PLC: Object does not exist."); + case ReadWriteErrorCode.DataTypeInconsistent: + throw new Exception("Received error from PLC: Data type inconsistent."); + case ReadWriteErrorCode.DataTypeNotSupported: + throw new Exception("Received error from PLC: Data type not supported."); + case ReadWriteErrorCode.AccessingObjectNotAllowed: + throw new Exception("Received error from PLC: Accessing object not allowed."); + case ReadWriteErrorCode.AddressOutOfRange: + throw new Exception("Received error from PLC: Address out of range."); + case ReadWriteErrorCode.HardwareFault: + throw new Exception("Received error from PLC: Hardware fault."); + case ReadWriteErrorCode.Success: + break; + default: + throw new Exception($"Invalid response from PLC: statusCode={(byte)statusCode}."); + } + } + + private Stream GetStreamIfAvailable() + { + if (_stream == null) + { + throw new PlcException(ErrorCode.ConnectionError, "Plc is not connected"); + } + + return _stream; + } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + /// + /// Dispose Plc Object + /// + /// + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + Close(); + } + + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + + disposedValue = true; + } + } + + // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. + // ~Plc() { + // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + // Dispose(false); + // } + + // This code added to correctly implement the disposable pattern. + void IDisposable.Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // TODO: uncomment the following line if the finalizer is overridden above. + // GC.SuppressFinalize(this); + } + #endregion + + } +} diff --git a/常用工具集/Utility/Network/S7netplus/PLCAddress.cs b/常用工具集/Utility/Network/S7netplus/PLCAddress.cs new file mode 100644 index 0000000..9aea1b0 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/PLCAddress.cs @@ -0,0 +1,207 @@ +namespace S7.Net +{ + internal class PLCAddress + { + private DataType dataType; + private int dbNumber; + private int startByte; + private int bitNumber; + private VarType varType; + + public DataType DataType + { + get => dataType; + set => dataType = value; + } + + public int DbNumber + { + get => dbNumber; + set => dbNumber = value; + } + + public int StartByte + { + get => startByte; + set => startByte = value; + } + + public int BitNumber + { + get => bitNumber; + set => bitNumber = value; + } + + public VarType VarType + { + get => varType; + set => varType = value; + } + + public PLCAddress(string address) + { + Parse(address, out dataType, out dbNumber, out varType, out startByte, out bitNumber); + } + + public static void Parse(string input, out DataType dataType, out int dbNumber, out VarType varType, out int address, out int bitNumber) + { + bitNumber = -1; + dbNumber = 0; + + switch (input.Substring(0, 2)) + { + case "DB": + string[] strings = input.Split(new char[] { '.' }); + if (strings.Length < 2) + throw new InvalidAddressException("To few periods for DB address"); + + dataType = DataType.DataBlock; + dbNumber = int.Parse(strings[0].Substring(2)); + address = int.Parse(strings[1].Substring(3)); + + string dbType = strings[1].Substring(0, 3); + switch (dbType) + { + case "DBB": + varType = VarType.Byte; + return; + case "DBW": + varType = VarType.Word; + return; + case "DBD": + varType = VarType.DWord; + return; + case "DBX": + bitNumber = int.Parse(strings[2]); + if (bitNumber > 7) + throw new InvalidAddressException("Bit can only be 0-7"); + varType = VarType.Bit; + return; + default: + throw new InvalidAddressException(); + } + case "IB": + case "EB": + // Input byte + dataType = DataType.Input; + dbNumber = 0; + address = int.Parse(input.Substring(2)); + varType = VarType.Byte; + return; + case "IW": + case "EW": + // Input word + dataType = DataType.Input; + dbNumber = 0; + address = int.Parse(input.Substring(2)); + varType = VarType.Word; + return; + case "ID": + case "ED": + // Input double-word + dataType = DataType.Input; + dbNumber = 0; + address = int.Parse(input.Substring(2)); + varType = VarType.DWord; + return; + case "QB": + case "AB": + case "OB": + // Output byte + dataType = DataType.Output; + dbNumber = 0; + address = int.Parse(input.Substring(2)); + varType = VarType.Byte; + return; + case "QW": + case "AW": + case "OW": + // Output word + dataType = DataType.Output; + dbNumber = 0; + address = int.Parse(input.Substring(2)); + varType = VarType.Word; + return; + case "QD": + case "AD": + case "OD": + // Output double-word + dataType = DataType.Output; + dbNumber = 0; + address = int.Parse(input.Substring(2)); + varType = VarType.DWord; + return; + case "MB": + // Memory byte + dataType = DataType.Memory; + dbNumber = 0; + address = int.Parse(input.Substring(2)); + varType = VarType.Byte; + return; + case "MW": + // Memory word + dataType = DataType.Memory; + dbNumber = 0; + address = int.Parse(input.Substring(2)); + varType = VarType.Word; + return; + case "MD": + // Memory double-word + dataType = DataType.Memory; + dbNumber = 0; + address = int.Parse(input.Substring(2)); + varType = VarType.DWord; + return; + default: + switch (input.Substring(0, 1)) + { + case "E": + case "I": + // Input + dataType = DataType.Input; + varType = VarType.Bit; + break; + case "Q": + case "A": + case "O": + // Output + dataType = DataType.Output; + varType = VarType.Bit; + break; + case "M": + // Memory + dataType = DataType.Memory; + varType = VarType.Bit; + break; + case "T": + // Timer + dataType = DataType.Timer; + dbNumber = 0; + address = int.Parse(input.Substring(1)); + varType = VarType.Timer; + return; + case "Z": + case "C": + // Counter + dataType = DataType.Counter; + dbNumber = 0; + address = int.Parse(input.Substring(1)); + varType = VarType.Counter; + return; + default: + throw new InvalidAddressException(string.Format("{0} is not a valid address", input.Substring(0, 1))); + } + + string txt2 = input.Substring(1); + if (txt2.IndexOf(".") == -1) + throw new InvalidAddressException("To few periods for DB address"); + + address = int.Parse(txt2.Substring(0, txt2.IndexOf("."))); + bitNumber = int.Parse(txt2.Substring(txt2.IndexOf(".") + 1)); + if (bitNumber > 7) + throw new InvalidAddressException("Bit can only be 0-7"); + return; + } + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/PLCExceptions.cs b/常用工具集/Utility/Network/S7netplus/PLCExceptions.cs new file mode 100644 index 0000000..58efc0c --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/PLCExceptions.cs @@ -0,0 +1,101 @@ +using System; +using System.Runtime.Serialization; + + +namespace S7.Net +{ + public class WrongNumberOfBytesException : Exception + { + public WrongNumberOfBytesException() : base() + { + } + + public WrongNumberOfBytesException(string message) : base(message) + { + } + + public WrongNumberOfBytesException(string message, Exception innerException) : base(message, innerException) + { + } + + protected WrongNumberOfBytesException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } + + public class InvalidAddressException : Exception + { + public InvalidAddressException() : base () + { + } + + public InvalidAddressException(string message) : base(message) + { + } + + public InvalidAddressException(string message, Exception innerException) : base(message, innerException) + { + } + + protected InvalidAddressException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } + + public class InvalidVariableTypeException : Exception + { + public InvalidVariableTypeException() : base() + { + } + + public InvalidVariableTypeException(string message) : base(message) + { + } + + public InvalidVariableTypeException(string message, Exception innerException) : base(message, innerException) + { + } + + protected InvalidVariableTypeException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } + + public class TPKTInvalidException : Exception + { + public TPKTInvalidException() : base() + { + } + + public TPKTInvalidException(string message) : base(message) + { + } + + public TPKTInvalidException(string message, Exception innerException) : base(message, innerException) + { + } + + protected TPKTInvalidException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } + + public class TPDUInvalidException : Exception + { + public TPDUInvalidException() : base() + { + } + + public TPDUInvalidException(string message) : base(message) + { + } + + public TPDUInvalidException(string message, Exception innerException) : base(message, innerException) + { + } + + protected TPDUInvalidException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/PLCHelpers.cs b/常用工具集/Utility/Network/S7netplus/PLCHelpers.cs new file mode 100644 index 0000000..87eac90 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/PLCHelpers.cs @@ -0,0 +1,265 @@ +using S7.Net.Helper; +using S7.Net.Protocol.S7; +using S7.Net.Types; +using System.Collections.Generic; +using System.Linq; +using DateTime = S7.Net.Types.DateTime; + +namespace S7.Net +{ + public partial class Plc + { + /// + /// Creates the header to read bytes from the PLC + /// + /// + /// + private static void BuildHeaderPackage(System.IO.MemoryStream stream, int amount = 1) + { + //header size = 19 bytes + stream.Write(new byte[] { 0x03, 0x00 }); + //complete package size + stream.Write(Int.ToByteArray((short)(19 + (12 * amount)))); + stream.Write(new byte[] { 0x02, 0xf0, 0x80, 0x32, 0x01, 0x00, 0x00, 0x00, 0x00 }); + //data part size + stream.Write(Word.ToByteArray((ushort)(2 + (amount * 12)))); + stream.Write(new byte[] { 0x00, 0x00, 0x04 }); + //amount of requests + stream.WriteByte((byte)amount); + } + + /// + /// Create the bytes-package to request data from the PLC. You have to specify the memory type (dataType), + /// the address of the memory, the address of the byte and the bytes count. + /// + /// MemoryType (DB, Timer, Counter, etc.) + /// Address of the memory to be read + /// Start address of the byte + /// Number of bytes to be read + /// + private static void BuildReadDataRequestPackage(System.IO.MemoryStream stream, DataType dataType, int db, int startByteAdr, int count = 1) + { + //single data req = 12 + stream.Write(new byte[] { 0x12, 0x0a, 0x10 }); + switch (dataType) + { + case DataType.Timer: + case DataType.Counter: + stream.WriteByte((byte)dataType); + break; + default: + stream.WriteByte(0x02); + break; + } + + stream.Write(Word.ToByteArray((ushort)(count))); + stream.Write(Word.ToByteArray((ushort)(db))); + stream.WriteByte((byte)dataType); + var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191 + stream.WriteByte((byte)overflow); + switch (dataType) + { + case DataType.Timer: + case DataType.Counter: + stream.Write(Word.ToByteArray((ushort)(startByteAdr))); + break; + default: + stream.Write(Word.ToByteArray((ushort)((startByteAdr) * 8))); + break; + } + } + + /// + /// Given a S7 variable type (Bool, Word, DWord, etc.), it converts the bytes in the appropriate C# format. + /// + /// + /// + /// + /// + /// + private object ParseBytes(VarType varType, byte[] bytes, int varCount, byte bitAdr = 0) + { + if (bytes == null || bytes.Length == 0) + return null; + + switch (varType) + { + case VarType.Byte: + if (varCount == 1) + return bytes[0]; + else + return bytes; + case VarType.Word: + if (varCount == 1) + return Word.FromByteArray(bytes); + else + return Word.ToArray(bytes); + case VarType.Int: + if (varCount == 1) + return Int.FromByteArray(bytes); + else + return Int.ToArray(bytes); + case VarType.DWord: + if (varCount == 1) + return DWord.FromByteArray(bytes); + else + return DWord.ToArray(bytes); + case VarType.DInt: + if (varCount == 1) + return DInt.FromByteArray(bytes); + else + return DInt.ToArray(bytes); + case VarType.Real: + if (varCount == 1) + return Types.Real.FromByteArray(bytes); + else + return Types.Real.ToArray(bytes); + case VarType.LReal: + if (varCount == 1) + return Types.LReal.FromByteArray(bytes); + else + return Types.LReal.ToArray(bytes); + + case VarType.String: + return Types.String.FromByteArray(bytes); + case VarType.S7String: + return S7String.FromByteArray(bytes); + case VarType.S7WString: + return S7WString.FromByteArray(bytes); + + case VarType.Timer: + if (varCount == 1) + return Timer.FromByteArray(bytes); + else + return Timer.ToArray(bytes); + case VarType.Counter: + if (varCount == 1) + return Counter.FromByteArray(bytes); + else + return Counter.ToArray(bytes); + case VarType.Bit: + if (varCount == 1) + { + if (bitAdr > 7) + return null; + else + return Bit.FromByte(bytes[0], bitAdr); + } + else + { + return Bit.ToBitArray(bytes, varCount); + } + case VarType.DateTime: + if (varCount == 1) + { + return DateTime.FromByteArray(bytes); + } + else + { + return DateTime.ToArray(bytes); + } + case VarType.DateTimeLong: + if (varCount == 1) + { + return DateTimeLong.FromByteArray(bytes); + } + else + { + return DateTimeLong.ToArray(bytes); + } + default: + return null; + } + } + + /// + /// Given a S7 (Bool, Word, DWord, etc.), it returns how many bytes to read. + /// + /// + /// + /// Byte lenght of variable + internal static int VarTypeToByteLength(VarType varType, int varCount = 1) + { + switch (varType) + { + case VarType.Bit: + return (varCount + 7) / 8; + case VarType.Byte: + return (varCount < 1) ? 1 : varCount; + case VarType.String: + return varCount; + case VarType.S7String: + return ((varCount + 2) & 1) == 1 ? (varCount + 3) : (varCount + 2); + case VarType.S7WString: + return (varCount * 2) + 4; + case VarType.Word: + case VarType.Timer: + case VarType.Int: + case VarType.Counter: + return varCount * 2; + case VarType.DWord: + case VarType.DInt: + case VarType.Real: + return varCount * 4; + case VarType.LReal: + case VarType.DateTime: + return varCount * 8; + case VarType.DateTimeLong: + return varCount * 12; + default: + return 0; + } + } + + private byte[] GetS7ConnectionSetup() + { + return new byte[] { 3, 0, 0, 25, 2, 240, 128, 50, 1, 0, 0, 255, 255, 0, 8, 0, 0, 240, 0, 0, 3, 0, 3, + 3, 192 // Use 960 PDU size + }; + } + + private void ParseDataIntoDataItems(byte[] s7data, List dataItems) + { + int offset = 14; + foreach (var dataItem in dataItems) + { + // check for Return Code = Success + if (s7data[offset] != 0xff) + throw new PlcException(ErrorCode.WrongNumberReceivedBytes); + + // to Data bytes + offset += 4; + + int byteCnt = VarTypeToByteLength(dataItem.VarType, dataItem.Count); + dataItem.Value = ParseBytes( + dataItem.VarType, + s7data.Skip(offset).Take(byteCnt).ToArray(), + dataItem.Count, + dataItem.BitAdr + ); + + // next Item + offset += byteCnt; + + // Always align to even offset + if (offset % 2 != 0) + offset++; + } + } + + private static byte[] BuildReadRequestPackage(IList dataItems) + { + int packageSize = 19 + (dataItems.Count * 12); + var package = new System.IO.MemoryStream(packageSize); + + BuildHeaderPackage(package, dataItems.Count); + + foreach (var dataItem in dataItems) + { + BuildReadDataRequestPackage(package, dataItem.DataType, dataItem.DB, dataItem.StartByteAddress, dataItem.ByteLength); + } + + return package.ToArray(); + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/PlcAsynchronous.cs b/常用工具集/Utility/Network/S7netplus/PlcAsynchronous.cs new file mode 100644 index 0000000..d518b6b --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/PlcAsynchronous.cs @@ -0,0 +1,582 @@ +using S7.Net.Types; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Sockets; +using System.Threading.Tasks; +using S7.Net.Protocol; +using System.Threading; +using S7.Net.Protocol.S7; + +namespace S7.Net +{ + /// + /// Creates an instance of S7.Net driver + /// + public partial class Plc + { + /// + /// Connects to the PLC and performs a COTP ConnectionRequest and S7 CommunicationSetup. + /// + /// The token to monitor for cancellation requests. The default value is None. + /// Please note that the cancellation will not affect opening the socket in any way and only affects data transfers for configuring the connection after the socket connection is successfully established. + /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases. + /// A task that represents the asynchronous open operation. + public async Task OpenAsync(CancellationToken cancellationToken = default) + { + var stream = await ConnectAsync(cancellationToken).ConfigureAwait(false); + try + { + await queue.Enqueue(async () => + { + cancellationToken.ThrowIfCancellationRequested(); + await EstablishConnection(stream, cancellationToken).ConfigureAwait(false); + _stream = stream; + + return default(object); + }).ConfigureAwait(false); + } + catch (Exception) + { + stream.Dispose(); + throw; + } + } + + private async Task ConnectAsync(CancellationToken cancellationToken) + { + tcpClient = new TcpClient(); + ConfigureConnection(); + +#if NETFRAMEWORK + IAsyncResult asyncResult = tcpClient.BeginConnect(IP, Port, null, null); + if (!asyncResult.AsyncWaitHandle.WaitOne(1000)) + { + throw new Exception("ӳʱ"); + } + tcpClient.EndConnect(asyncResult); +#else + await tcpClient.ConnectAsync(IP, Port, cancellationToken).ConfigureAwait(false); + +#endif + return tcpClient.GetStream(); + } + + private async Task EstablishConnection(Stream stream, CancellationToken cancellationToken) + { + await RequestConnection(stream, cancellationToken).ConfigureAwait(false); + await SetupConnection(stream, cancellationToken).ConfigureAwait(false); + } + + private async Task RequestConnection(Stream stream, CancellationToken cancellationToken) + { + var requestData = ConnectionRequest.GetCOTPConnectionRequest(TsapPair); + var response = await NoLockRequestTpduAsync(stream, requestData, cancellationToken).ConfigureAwait(false); + + if (response.PDUType != COTP.PduType.ConnectionConfirmed) + { + throw new InvalidDataException("Connection request was denied", response.TPkt.Data, 1, 0x0d); + } + } + + private async Task SetupConnection(Stream stream, CancellationToken cancellationToken) + { + var setupData = GetS7ConnectionSetup(); + + var s7data = await NoLockRequestTsduAsync(stream, setupData, 0, setupData.Length, cancellationToken) + .ConfigureAwait(false); + + if (s7data.Length < 2) + throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup"); + + //Check for S7 Ack Data + if (s7data[1] != 0x03) + throw new InvalidDataException("Error reading Communication Setup response", s7data, 1, 0x03); + + if (s7data.Length < 20) + throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup"); + + // TODO: check if this should not rather be UInt16. + MaxPDUSize = s7data[18] * 256 + s7data[19]; + } + + + /// + /// Reads a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests. + /// If the read was not successful, check LastErrorCode or LastErrorString. + /// + /// Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output. + /// Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc. + /// Start byte address. If you want to read DB1.DBW200, this is 200. + /// Byte count, if you want to read 120 bytes, set this to 120. + /// The token to monitor for cancellation requests. The default value is None. + /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases. + /// Returns the bytes in an array + public async Task ReadBytesAsync(DataType dataType, int db, int startByteAdr, int count, CancellationToken cancellationToken = default) + { + var resultBytes = new byte[count]; + int index = 0; + while (count > 0) + { + //This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0. + var maxToRead = Math.Min(count, MaxPDUSize - 18); + await ReadBytesWithSingleRequestAsync(dataType, db, startByteAdr + index, resultBytes, index, maxToRead, cancellationToken).ConfigureAwait(false); + count -= maxToRead; + index += maxToRead; + } + return resultBytes; + } + + + /// + /// Read and decode a certain number of bytes of the "VarType" provided. + /// This can be used to read multiple consecutive variables of the same type (Word, DWord, Int, etc). + /// If the read was not successful, check LastErrorCode or LastErrorString. + /// + /// Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output. + /// Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc. + /// Start byte address. If you want to read DB1.DBW200, this is 200. + /// Type of the variable/s that you are reading + /// Address of bit. If you want to read DB1.DBX200.6, set 6 to this parameter. + /// + /// The token to monitor for cancellation requests. The default value is None. + /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases. + public async Task ReadAsync(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0, CancellationToken cancellationToken = default) + { + int cntBytes = VarTypeToByteLength(varType, varCount); + byte[] bytes = await ReadBytesAsync(dataType, db, startByteAdr, cntBytes, cancellationToken).ConfigureAwait(false); + return ParseBytes(varType, bytes, varCount, bitAdr); + } + + /// + /// Reads a single variable from the PLC, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc. + /// If the read was not successful, check LastErrorCode or LastErrorString. + /// + /// Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc. + /// The token to monitor for cancellation requests. The default value is None. + /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases. + /// Returns an object that contains the value. This object must be cast accordingly. + public async Task ReadAsync(string variable, CancellationToken cancellationToken = default) + { + var adr = new PLCAddress(variable); + return await ReadAsync(adr.DataType, adr.DbNumber, adr.StartByte, adr.VarType, 1, (byte)adr.BitNumber, cancellationToken).ConfigureAwait(false); + } + + /// + /// Reads all the bytes needed to fill a struct in C#, starting from a certain address, and return an object that can be casted to the struct. + /// + /// Type of the struct to be readed (es.: TypeOf(MyStruct)). + /// Address of the DB. + /// Start byte address. If you want to read DB1.DBW200, this is 200. + /// The token to monitor for cancellation requests. The default value is None. + /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases. + /// Returns a struct that must be cast. + public async Task ReadStructAsync(Type structType, int db, int startByteAdr = 0, CancellationToken cancellationToken = default) + { + int numBytes = Types.Struct.GetStructSize(structType); + // now read the package + var resultBytes = await ReadBytesAsync(DataType.DataBlock, db, startByteAdr, numBytes, cancellationToken).ConfigureAwait(false); + + // and decode it + return Types.Struct.FromBytes(structType, resultBytes); + } + + /// + /// Reads all the bytes needed to fill a struct in C#, starting from a certain address, and returns the struct or null if nothing was read. + /// + /// The struct type + /// Address of the DB. + /// Start byte address. If you want to read DB1.DBW200, this is 200. + /// The token to monitor for cancellation requests. The default value is None. + /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases. + /// Returns a nulable struct. If nothing was read null will be returned. + public async Task ReadStructAsync(int db, int startByteAdr = 0, CancellationToken cancellationToken = default) where T : struct + { + return await ReadStructAsync(typeof(T), db, startByteAdr, cancellationToken).ConfigureAwait(false) as T?; + } + + /// + /// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC. + /// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified. + /// + /// Instance of the class that will store the values + /// Index of the DB; es.: 1 is for DB1 + /// Start byte address. If you want to read DB1.DBW200, this is 200. + /// The token to monitor for cancellation requests. The default value is None. + /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases. + /// The number of read bytes + public async Task> ReadClassAsync(object sourceClass, int db, int startByteAdr = 0, CancellationToken cancellationToken = default) + { + int numBytes = (int)Class.GetClassSize(sourceClass); + if (numBytes <= 0) + { + throw new Exception("The size of the class is less than 1 byte and therefore cannot be read"); + } + + // now read the package + var resultBytes = await ReadBytesAsync(DataType.DataBlock, db, startByteAdr, numBytes, cancellationToken).ConfigureAwait(false); + // and decode it + Class.FromBytes(sourceClass, resultBytes); + + return new Tuple(resultBytes.Length, sourceClass); + } + + /// + /// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC. + /// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified. To instantiate the class defined by the generic + /// type, the class needs a default constructor. + /// + /// The class that will be instantiated. Requires a default constructor + /// Index of the DB; es.: 1 is for DB1 + /// Start byte address. If you want to read DB1.DBW200, this is 200. + /// The token to monitor for cancellation requests. The default value is None. + /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases. + /// An instance of the class with the values read from the PLC. If no data has been read, null will be returned + public async Task ReadClassAsync(int db, int startByteAdr = 0, CancellationToken cancellationToken = default) where T : class + { + return await ReadClassAsync(() => Activator.CreateInstance(), db, startByteAdr, cancellationToken).ConfigureAwait(false); + } + + /// + /// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC. + /// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified. + /// + /// The class that will be instantiated + /// Function to instantiate the class + /// Index of the DB; es.: 1 is for DB1 + /// Start byte address. If you want to read DB1.DBW200, this is 200. + /// The token to monitor for cancellation requests. The default value is None. + /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases. + /// An instance of the class with the values read from the PLC. If no data has been read, null will be returned + public async Task ReadClassAsync(Func classFactory, int db, int startByteAdr = 0, CancellationToken cancellationToken = default) where T : class + { + var instance = classFactory(); + var res = await ReadClassAsync(instance, db, startByteAdr, cancellationToken).ConfigureAwait(false); + int readBytes = res.Item1; + if (readBytes <= 0) + { + return null; + } + + return (T)res.Item2; + } + + /// + /// Reads multiple vars in a single request. + /// You have to create and pass a list of DataItems and you obtain in response the same list with the values. + /// Values are stored in the property "Value" of the dataItem and are already converted. + /// If you don't want the conversion, just create a dataItem of bytes. + /// The number of DataItems as well as the total size of the requested data can not exceed a certain limit (protocol restriction). + /// + /// List of dataitems that contains the list of variables that must be read. + /// The token to monitor for cancellation requests. The default value is None. + /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases. + public async Task> ReadMultipleVarsAsync(List dataItems, CancellationToken cancellationToken = default) + { + //Snap7 seems to choke on PDU sizes above 256 even if snap7 + //replies with bigger PDU size in connection setup. + AssertPduSizeForRead(dataItems); + + try + { + var dataToSend = BuildReadRequestPackage(dataItems.Select(d => DataItem.GetDataItemAddress(d)).ToList()); + var s7data = await RequestTsduAsync(dataToSend, cancellationToken); + + ValidateResponseCode((ReadWriteErrorCode)s7data[14]); + + ParseDataIntoDataItems(s7data, dataItems); + } + catch (SocketException socketException) + { + throw new PlcException(ErrorCode.ReadData, socketException); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception exc) + { + throw new PlcException(ErrorCode.ReadData, exc); + } + return dataItems; + } + + + /// + /// Write a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests. + /// If the write was not successful, check LastErrorCode or LastErrorString. + /// + /// Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output. + /// Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc. + /// Start byte address. If you want to write DB1.DBW200, this is 200. + /// Bytes to write. If more than 200, multiple requests will be made. + /// The token to monitor for cancellation requests. The default value is None. + /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases. + /// A task that represents the asynchronous write operation. + public async Task WriteBytesAsync(DataType dataType, int db, int startByteAdr, byte[] value, CancellationToken cancellationToken = default) + { + int localIndex = 0; + int count = value.Length; + while (count > 0) + { + var maxToWrite = (int)Math.Min(count, MaxPDUSize - 35); + await WriteBytesWithASingleRequestAsync(dataType, db, startByteAdr + localIndex, value, localIndex, maxToWrite, cancellationToken).ConfigureAwait(false); + count -= maxToWrite; + localIndex += maxToWrite; + } + } + + /// + /// Write a single bit from a DB with the specified index. + /// + /// Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output. + /// Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc. + /// Start byte address. If you want to write DB1.DBW200, this is 200. + /// The address of the bit. (0-7) + /// Bytes to write. If more than 200, multiple requests will be made. + /// The token to monitor for cancellation requests. The default value is None. + /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases. + /// A task that represents the asynchronous write operation. + public async Task WriteBitAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool value, CancellationToken cancellationToken = default) + { + if (bitAdr < 0 || bitAdr > 7) + throw new InvalidAddressException(string.Format("Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", bitAdr)); + + await WriteBitWithASingleRequestAsync(dataType, db, startByteAdr, bitAdr, value, cancellationToken).ConfigureAwait(false); + } + + /// + /// Write a single bit from a DB with the specified index. + /// + /// Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output. + /// Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc. + /// Start byte address. If you want to write DB1.DBW200, this is 200. + /// The address of the bit. (0-7) + /// Bytes to write. If more than 200, multiple requests will be made. + /// The token to monitor for cancellation requests. The default value is None. + /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases. + /// A task that represents the asynchronous write operation. + public async Task WriteBitAsync(DataType dataType, int db, int startByteAdr, int bitAdr, int value, CancellationToken cancellationToken = default) + { + if (value < 0 || value > 1) + throw new ArgumentException("Value must be 0 or 1", nameof(value)); + + await WriteBitAsync(dataType, db, startByteAdr, bitAdr, value == 1, cancellationToken).ConfigureAwait(false); + } + + /// + /// Takes in input an object and tries to parse it to an array of values. This can be used to write many data, all of the same type. + /// You must specify the memory area type, memory are address, byte start address and bytes count. + /// If the read was not successful, check LastErrorCode or LastErrorString. + /// + /// Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output. + /// Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc. + /// Start byte address. If you want to read DB1.DBW200, this is 200. + /// Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion. + /// The address of the bit. (0-7) + /// The token to monitor for cancellation requests. The default value is None. + /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases. + /// A task that represents the asynchronous write operation. + public async Task WriteAsync(DataType dataType, int db, int startByteAdr, object value, int bitAdr = -1, CancellationToken cancellationToken = default) + { + if (bitAdr != -1) + { + //Must be writing a bit value as bitAdr is specified + if (value is bool boolean) + { + await WriteBitAsync(dataType, db, startByteAdr, bitAdr, boolean, cancellationToken).ConfigureAwait(false); + } + else if (value is int intValue) + { + if (intValue < 0 || intValue > 7) + throw new ArgumentOutOfRangeException( + string.Format( + "Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", + bitAdr), nameof(bitAdr)); + + await WriteBitAsync(dataType, db, startByteAdr, bitAdr, intValue == 1, cancellationToken).ConfigureAwait(false); + } + else throw new ArgumentException("Value must be a bool or an int to write a bit", nameof(value)); + } + else await WriteBytesAsync(dataType, db, startByteAdr, Serialization.SerializeValue(value), cancellationToken).ConfigureAwait(false); + } + + /// + /// Writes a single variable from the PLC, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc. + /// If the write was not successful, check or . + /// + /// Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc. + /// Value to be written to the PLC + /// The token to monitor for cancellation requests. The default value is None. + /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases. + /// A task that represents the asynchronous write operation. + public async Task WriteAsync(string variable, object value, CancellationToken cancellationToken = default) + { + var adr = new PLCAddress(variable); + await WriteAsync(adr.DataType, adr.DbNumber, adr.StartByte, value, adr.BitNumber, cancellationToken).ConfigureAwait(false); + } + + /// + /// Writes a C# struct to a DB in the PLC + /// + /// The struct to be written + /// Db address + /// Start bytes on the PLC + /// The token to monitor for cancellation requests. The default value is None. + /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases. + /// A task that represents the asynchronous write operation. + public async Task WriteStructAsync(object structValue, int db, int startByteAdr = 0, CancellationToken cancellationToken = default) + { + var bytes = Struct.ToBytes(structValue).ToList(); + await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes.ToArray(), cancellationToken).ConfigureAwait(false); + } + + /// + /// Writes a C# class to a DB in the PLC + /// + /// The class to be written + /// Db address + /// Start bytes on the PLC + /// The token to monitor for cancellation requests. The default value is None. + /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases. + /// A task that represents the asynchronous write operation. + public async Task WriteClassAsync(object classValue, int db, int startByteAdr = 0, CancellationToken cancellationToken = default) + { + byte[] bytes = new byte[(int)Class.GetClassSize(classValue)]; + Types.Class.ToBytes(classValue, bytes); + await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes, cancellationToken).ConfigureAwait(false); + } + + private async Task ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + var dataToSend = BuildReadRequestPackage(new[] { new DataItemAddress(dataType, db, startByteAdr, count) }); + + var s7data = await RequestTsduAsync(dataToSend, cancellationToken); + AssertReadResponse(s7data, count); + + Array.Copy(s7data, 18, buffer, offset, count); + } + + /// + /// Write DataItem(s) to the PLC. Throws an exception if the response is invalid + /// or when the PLC reports errors for item(s) written. + /// + /// The DataItem(s) to write to the PLC. + /// Task that completes when response from PLC is parsed. + public async Task WriteAsync(params DataItem[] dataItems) + { + AssertPduSizeForWrite(dataItems); + + var message = new ByteArray(); + var length = S7WriteMultiple.CreateRequest(message, dataItems); + + var response = await RequestTsduAsync(message.Array, 0, length).ConfigureAwait(false); + + S7WriteMultiple.ParseResponse(response, response.Length, dataItems); + } + + /// + /// Writes up to 200 bytes to the PLC. You must specify the memory area type, memory are address, byte start address and bytes count. + /// + /// Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output. + /// Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc. + /// Start byte address. If you want to read DB1.DBW200, this is 200. + /// Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion. + /// A task that represents the asynchronous write operation. + private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count, CancellationToken cancellationToken) + { + try + { + var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value, dataOffset, count); + var s7data = await RequestTsduAsync(dataToSend, cancellationToken).ConfigureAwait(false); + + ValidateResponseCode((ReadWriteErrorCode)s7data[14]); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception exc) + { + throw new PlcException(ErrorCode.WriteData, exc); + } + } + + private async Task WriteBitWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue, CancellationToken cancellationToken) + { + try + { + var dataToSend = BuildWriteBitPackage(dataType, db, startByteAdr, bitValue, bitAdr); + var s7data = await RequestTsduAsync(dataToSend, cancellationToken).ConfigureAwait(false); + + ValidateResponseCode((ReadWriteErrorCode)s7data[14]); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception exc) + { + throw new PlcException(ErrorCode.WriteData, exc); + } + } + + private Task RequestTsduAsync(byte[] requestData, CancellationToken cancellationToken = default) => + RequestTsduAsync(requestData, 0, requestData.Length, cancellationToken); + + private Task RequestTsduAsync(byte[] requestData, int offset, int length, CancellationToken cancellationToken = default) + { + var stream = GetStreamIfAvailable(); + + return queue.Enqueue(() => + NoLockRequestTsduAsync(stream, requestData, offset, length, cancellationToken)); + } + + private async Task NoLockRequestTpduAsync(Stream stream, byte[] requestData, + CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + using (var closeOnCancellation = cancellationToken.Register(Close)) + { + await stream.WriteAsync(requestData, 0, requestData.Length, cancellationToken).ConfigureAwait(false); + return await COTP.TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); + } + } + catch (Exception exc) + { + if (exc is TPDUInvalidException || exc is TPKTInvalidException) + { + Close(); + } + + throw; + } + } + + private async Task NoLockRequestTsduAsync(Stream stream, byte[] requestData, int offset, int length, + CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + using (var closeOnCancellation = cancellationToken.Register(Close)) + { + await stream.WriteAsync(requestData, offset, length, cancellationToken).ConfigureAwait(false); + return await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); + } + } + catch (Exception exc) + { + if (exc is TPDUInvalidException || exc is TPKTInvalidException) + { + Close(); + } + + throw; + } + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/PlcException.cs b/常用工具集/Utility/Network/S7netplus/PlcException.cs new file mode 100644 index 0000000..e2673e4 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/PlcException.cs @@ -0,0 +1,34 @@ +using System; + +namespace S7.Net +{ + [Serializable] + public class PlcException : Exception + { + public ErrorCode ErrorCode { get; } + + public PlcException(ErrorCode errorCode) : this(errorCode, $"PLC communication failed with error '{errorCode}'.") + { + } + + public PlcException(ErrorCode errorCode, Exception innerException) : this(errorCode, innerException.Message, + innerException) + { + } + + public PlcException(ErrorCode errorCode, string message) : base(message) + { + ErrorCode = errorCode; + } + + public PlcException(ErrorCode errorCode, string message, Exception inner) : base(message, inner) + { + ErrorCode = errorCode; + } + + protected PlcException(System.Runtime.Serialization.SerializationInfo info,System.Runtime.Serialization.StreamingContext context) : base(info, context) + { + ErrorCode = (ErrorCode) info.GetInt32(nameof(ErrorCode)); + } + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/S7netplus/PlcSynchronous.cs b/常用工具集/Utility/Network/S7netplus/PlcSynchronous.cs new file mode 100644 index 0000000..80636de --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/PlcSynchronous.cs @@ -0,0 +1,585 @@ +using S7.Net.Types; +using System; +using System.IO; +using System.Collections.Generic; +using S7.Net.Protocol; +using S7.Net.Helper; +using System.Net.Sockets; +using System.Threading; + +//Implement synchronous methods here +namespace S7.Net +{ + public partial class Plc + { + /// + /// Connects to the PLC and performs a COTP ConnectionRequest and S7 CommunicationSetup. + /// + public void Open(int timeout = 0) + { + try + { + if (timeout == 0) + { + OpenAsync().GetAwaiter().GetResult(); + } + else + { + CancellationTokenSource cts = new CancellationTokenSource(timeout); + OpenAsync(cts.Token).GetAwaiter().GetResult(); + } + } + catch (Exception exc) + { + throw new PlcException(ErrorCode.ConnectionError, + $"Couldn't establish the connection to {IP}.\nMessage: {exc.Message}", exc); + } + } + + + + /// + /// Reads a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests. + /// If the read was not successful, check LastErrorCode or LastErrorString. + /// + /// Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output. + /// Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc. + /// Start byte address. If you want to read DB1.DBW200, this is 200. + /// Byte count, if you want to read 120 bytes, set this to 120. + /// Returns the bytes in an array + public byte[] ReadBytes(DataType dataType, int db, int startByteAdr, int count) + { + //var result = new byte[count]; + + //ReadBytes(result, dataType, db, startByteAdr); + + //return result; + + + var result = new byte[count]; + int index = 0; + while (count > 0) + { + //This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0. + var maxToRead = Math.Min(count, MaxPDUSize - 18); + ReadBytesWithSingleRequest(dataType, db, startByteAdr + index, result, index, maxToRead); + count -= maxToRead; + index += maxToRead; + } + return result; + + } + + private void ReadBytesWithSingleRequest(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count) + { + try + { + // first create the header + int packageSize = 19 + 12; // 19 header + 12 for 1 request + var package = new System.IO.MemoryStream(packageSize); + BuildHeaderPackage(package); + // package.Add(0x02); // datenart + BuildReadDataRequestPackage(package, dataType, db, startByteAdr, count); + + var dataToSend = package.ToArray(); + var s7data = RequestTsdu(dataToSend); + AssertReadResponse(s7data, count); + + Array.Copy(s7data, 18, buffer, offset, count); + } + catch (Exception exc) + { + throw new PlcException(ErrorCode.ReadData, exc); + } + } + + /// + /// Read and decode a certain number of bytes of the "VarType" provided. + /// This can be used to read multiple consecutive variables of the same type (Word, DWord, Int, etc). + /// If the read was not successful, check LastErrorCode or LastErrorString. + /// + /// Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output. + /// Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc. + /// Start byte address. If you want to read DB1.DBW200, this is 200. + /// Type of the variable/s that you are reading + /// Address of bit. If you want to read DB1.DBX200.6, set 6 to this parameter. + /// + public object Read(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0) + { + int cntBytes = VarTypeToByteLength(varType, varCount); + byte[] bytes = ReadBytes(dataType, db, startByteAdr, cntBytes); + + return ParseBytes(varType, bytes, varCount, bitAdr); + } + + /// + /// Reads a single variable from the PLC, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc. + /// If the read was not successful, check LastErrorCode or LastErrorString. + /// + /// Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc. + /// Returns an object that contains the value. This object must be cast accordingly. If no data has been read, null will be returned + public object Read(string variable) + { + var adr = new PLCAddress(variable); + return Read(adr.DataType, adr.DbNumber, adr.StartByte, adr.VarType, 1, (byte)adr.BitNumber); + } + + /// + /// Reads all the bytes needed to fill a struct in C#, starting from a certain address, and return an object that can be casted to the struct. + /// + /// Type of the struct to be readed (es.: TypeOf(MyStruct)). + /// Address of the DB. + /// Start byte address. If you want to read DB1.DBW200, this is 200. + /// Returns a struct that must be cast. If no data has been read, null will be returned + public object ReadStruct(Type structType, int db, int startByteAdr = 0) + { + int numBytes = Struct.GetStructSize(structType); + // now read the package + var resultBytes = ReadBytes(DataType.DataBlock, db, startByteAdr, numBytes); + + // and decode it + return Struct.FromBytes(structType, resultBytes); + } + + /// + /// Reads all the bytes needed to fill a struct in C#, starting from a certain address, and returns the struct or null if nothing was read. + /// + /// The struct type + /// Address of the DB. + /// Start byte address. If you want to read DB1.DBW200, this is 200. + /// Returns a nullable struct. If nothing was read null will be returned. + public T? ReadStruct(int db, int startByteAdr = 0) where T : struct + { + return ReadStruct(typeof(T), db, startByteAdr) as T?; + } + + /// + /// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC. + /// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified. + /// + /// Instance of the class that will store the values + /// Index of the DB; es.: 1 is for DB1 + /// Start byte address. If you want to read DB1.DBW200, this is 200. + /// The number of read bytes + public int ReadClass(object sourceClass, int db, int startByteAdr = 0) + { + int numBytes = (int)Class.GetClassSize(sourceClass); + if (numBytes <= 0) + { + throw new Exception("The size of the class is less than 1 byte and therefore cannot be read"); + } + + // now read the package + var resultBytes = ReadBytes(DataType.DataBlock, db, startByteAdr, numBytes); + // and decode it + Class.FromBytes(sourceClass, resultBytes); + return resultBytes.Length; + } + + /// + /// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC. + /// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified. To instantiate the class defined by the generic + /// type, the class needs a default constructor. + /// + /// The class that will be instantiated. Requires a default constructor + /// Index of the DB; es.: 1 is for DB1 + /// Start byte address. If you want to read DB1.DBW200, this is 200. + /// An instance of the class with the values read from the PLC. If no data has been read, null will be returned + public T ReadClass(int db, int startByteAdr = 0) where T : class + { + return ReadClass(() => Activator.CreateInstance(), db, startByteAdr); + } + + /// + /// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC. + /// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified. + /// + /// The class that will be instantiated + /// Function to instantiate the class + /// Index of the DB; es.: 1 is for DB1 + /// Start byte address. If you want to read DB1.DBW200, this is 200. + /// An instance of the class with the values read from the PLC. If no data has been read, null will be returned + public T ReadClass(Func classFactory, int db, int startByteAdr = 0) where T : class + { + var instance = classFactory(); + int readBytes = ReadClass(instance, db, startByteAdr); + if (readBytes <= 0) + { + return null; + } + return instance; + } + + /// + /// Write a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests. + /// If the write was not successful, check LastErrorCode or LastErrorString. + /// + /// Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output. + /// Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc. + /// Start byte address. If you want to write DB1.DBW200, this is 200. + /// Bytes to write. If more than 200, multiple requests will be made. + public void WriteBytes(DataType dataType, int db, int startByteAdr, byte[] value) + { + //WriteBytes(dataType, db, startByteAdr, value.AsSpan()); + int localIndex = 0; + int count = value.Length; + while (count > 0) + { + //TODO: Figure out how to use MaxPDUSize here + //Snap7 seems to choke on PDU sizes above 256 even if snap7 + //replies with bigger PDU size in connection setup. + var maxToWrite = Math.Min(count, MaxPDUSize - 28);//TODO tested only when the MaxPDUSize is 480 + WriteBytesWithASingleRequest(dataType, db, startByteAdr + localIndex, value, localIndex, maxToWrite); + count -= maxToWrite; + localIndex += maxToWrite; + } + } + private void WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count) + { + try + { + var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value, dataOffset, count); + var s7data = RequestTsdu(dataToSend); + + ValidateResponseCode((ReadWriteErrorCode)s7data[14]); + } + catch (Exception exc) + { + throw new PlcException(ErrorCode.WriteData, exc); + } + } + /// + /// Write a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests. + /// If the write was not successful, check LastErrorCode or LastErrorString. + /// + /// Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output. + /// Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc. + /// Start byte address. If you want to write DB1.DBW200, this is 200. + /// Bytes to write. If more than 200, multiple requests will be made. + //public void WriteBytes(DataType dataType, int db, int startByteAdr, ReadOnlySpan value) + //{ + // int localIndex = 0; + // while (value.Length > 0) + // { + // //TODO: Figure out how to use MaxPDUSize here + // //Snap7 seems to choke on PDU sizes above 256 even if snap7 + // //replies with bigger PDU size in connection setup. + // var maxToWrite = Math.Min(value.Length, MaxPDUSize - 28);//TODO tested only when the MaxPDUSize is 480 + // WriteBytesWithASingleRequest(dataType, db, startByteAdr + localIndex, value.Slice(0, maxToWrite)); + // value = value.Slice(maxToWrite); + // localIndex += maxToWrite; + // } + //} + + /// + /// Write a single bit from a DB with the specified index. + /// + /// Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output. + /// Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc. + /// Start byte address. If you want to write DB1.DBW200, this is 200. + /// The address of the bit. (0-7) + /// Bytes to write. If more than 200, multiple requests will be made. + public void WriteBit(DataType dataType, int db, int startByteAdr, int bitAdr, bool value) + { + if (bitAdr < 0 || bitAdr > 7) + throw new InvalidAddressException(string.Format("Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", bitAdr)); + + WriteBitWithASingleRequest(dataType, db, startByteAdr, bitAdr, value); + } + + /// + /// Write a single bit to a DB with the specified index. + /// + /// Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output. + /// Address of the memory area (if you want to write DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc. + /// Start byte address. If you want to write DB1.DBW200, this is 200. + /// The address of the bit. (0-7) + /// Value to write (0 or 1). + public void WriteBit(DataType dataType, int db, int startByteAdr, int bitAdr, int value) + { + if (value < 0 || value > 1) + throw new ArgumentException("Value must be 0 or 1", nameof(value)); + + WriteBit(dataType, db, startByteAdr, bitAdr, value == 1); + } + + /// + /// Takes in input an object and tries to parse it to an array of values. This can be used to write many data, all of the same type. + /// You must specify the memory area type, memory are address, byte start address and bytes count. + /// If the read was not successful, check LastErrorCode or LastErrorString. + /// + /// Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output. + /// Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc. + /// Start byte address. If you want to read DB1.DBW200, this is 200. + /// Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion. + /// The address of the bit. (0-7) + public void Write(DataType dataType, int db, int startByteAdr, object value, int bitAdr = -1) + { + if (bitAdr != -1) + { + //Must be writing a bit value as bitAdr is specified + if (value is bool boolean) + { + WriteBit(dataType, db, startByteAdr, bitAdr, boolean); + } + else if (value is int intValue) + { + if (intValue < 0 || intValue > 7) + throw new ArgumentOutOfRangeException( + string.Format( + "Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", + bitAdr), nameof(bitAdr)); + + WriteBit(dataType, db, startByteAdr, bitAdr, intValue == 1); + } + else + throw new ArgumentException("Value must be a bool or an int to write a bit", nameof(value)); + } + else WriteBytes(dataType, db, startByteAdr, Serialization.SerializeValue(value)); + } + + /// + /// Writes a single variable from the PLC, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc. + /// If the write was not successful, check or . + /// + /// Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc. + /// Value to be written to the PLC + public void Write(string variable, object value) + { + var adr = new PLCAddress(variable); + Write(adr.DataType, adr.DbNumber, adr.StartByte, value, adr.BitNumber); + } + + /// + /// Writes a C# struct to a DB in the PLC + /// + /// The struct to be written + /// Db address + /// Start bytes on the PLC + public void WriteStruct(object structValue, int db, int startByteAdr = 0) + { + WriteStructAsync(structValue, db, startByteAdr).GetAwaiter().GetResult(); + } + + /// + /// Writes a C# class to a DB in the PLC + /// + /// The class to be written + /// Db address + /// Start bytes on the PLC + public void WriteClass(object classValue, int db, int startByteAdr = 0) + { + WriteClassAsync(classValue, db, startByteAdr).GetAwaiter().GetResult(); + } + + //private void ReadBytesWithSingleRequest(DataType dataType, int db, int startByteAdr, Span buffer) + //{ + // try + // { + // // first create the header + // const int packageSize = 19 + 12; // 19 header + 12 for 1 request + // var dataToSend = new byte[packageSize]; + // var package = new MemoryStream(dataToSend); + // BuildHeaderPackage(package); + // // package.Add(0x02); // datenart + // BuildReadDataRequestPackage(package, dataType, db, startByteAdr, buffer.Length); + + // var s7data = RequestTsdu(dataToSend); + // AssertReadResponse(s7data, buffer.Length); + + // s7data.AsSpan(18, buffer.Length).CopyTo(buffer); + // } + // catch (Exception exc) + // { + // throw new PlcException(ErrorCode.ReadData, exc); + // } + //} + + /// + /// Write DataItem(s) to the PLC. Throws an exception if the response is invalid + /// or when the PLC reports errors for item(s) written. + /// + /// The DataItem(s) to write to the PLC. + public void Write(params DataItem[] dataItems) + { + AssertPduSizeForWrite(dataItems); + + var message = new ByteArray(); + var length = S7WriteMultiple.CreateRequest(message, dataItems); + var response = RequestTsdu(message.Array, 0, length); + + S7WriteMultiple.ParseResponse(response, response.Length, dataItems); + } + + //private void WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, ReadOnlySpan value) + //{ + // try + // { + // var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value); + // var s7data = RequestTsdu(dataToSend); + + // ValidateResponseCode((ReadWriteErrorCode)s7data[14]); + // } + // catch (Exception exc) + // { + // throw new PlcException(ErrorCode.WriteData, exc); + // } + //} + + private byte[] BuildWriteBytesPackage(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count) + { + int varCount = count; + // first create the header + int packageSize = 35 + varCount; + var package = new MemoryStream(new byte[packageSize]); + + package.WriteByte(3); + package.WriteByte(0); + //complete package size + package.Write(Int.ToByteArray((short)packageSize)); + package.Write(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 }); + package.Write(Word.ToByteArray((ushort)(varCount - 1))); + package.Write(new byte[] { 0, 0x0e }); + package.Write(Word.ToByteArray((ushort)(varCount + 4))); + package.Write(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x02 }); + package.Write(Word.ToByteArray((ushort)varCount)); + package.Write(Word.ToByteArray((ushort)(db))); + package.WriteByte((byte)dataType); + var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191 + package.WriteByte((byte)overflow); + package.Write(Word.ToByteArray((ushort)(startByteAdr * 8))); + package.Write(new byte[] { 0, 4 }); + package.Write(Word.ToByteArray((ushort)(varCount * 8))); + + // now join the header and the data + package.Write(value, dataOffset, count); + + return package.ToArray(); + } + + //private byte[] BuildWriteBytesPackage(DataType dataType, int db, int startByteAdr, ReadOnlySpan value) + //{ + // int varCount = value.Length; + // // first create the header + // int packageSize = 35 + varCount; + // var packageData = new byte[packageSize]; + // var package = new MemoryStream(packageData); + + // package.WriteByte(3); + // package.WriteByte(0); + // //complete package size + // package.Write(Int.ToByteArray((short)packageSize)); + // // This overload doesn't allocate the byte array, it refers to assembly's static data segment + // package.Write(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 }); + // package.Write(Word.ToByteArray((ushort)(varCount - 1))); + // package.Write(new byte[] { 0, 0x0e }); + // package.Write(Word.ToByteArray((ushort)(varCount + 4))); + // package.Write(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x02 }); + // package.Write(Word.ToByteArray((ushort)varCount)); + // package.Write(Word.ToByteArray((ushort)(db))); + // package.WriteByte((byte)dataType); + // var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191 + // package.WriteByte((byte)overflow); + // package.Write(Word.ToByteArray((ushort)(startByteAdr * 8))); + // package.Write(new byte[] { 0, 4 }); + // package.Write(Word.ToByteArray((ushort)(varCount * 8))); + + // // now join the header and the data + // package.Write(value); + + // return packageData; + //} + + private byte[] BuildWriteBitPackage(DataType dataType, int db, int startByteAdr, bool bitValue, int bitAdr) + { + var value = new[] { bitValue ? (byte)1 : (byte)0 }; + int varCount = 1; + // first create the header + int packageSize = 35 + varCount; + var packageData = new byte[packageSize]; + var package = new MemoryStream(packageData); + + package.WriteByte(3); + package.WriteByte(0); + //complete package size + package.Write(Int.ToByteArray((short)packageSize)); + package.Write(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 }); + package.Write(Word.ToByteArray((ushort)(varCount - 1))); + package.Write(new byte[] { 0, 0x0e }); + package.Write(Word.ToByteArray((ushort)(varCount + 4))); + package.Write(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x01 }); //ending 0x01 is used for writing a sinlge bit + package.Write(Word.ToByteArray((ushort)varCount)); + package.Write(Word.ToByteArray((ushort)(db))); + package.WriteByte((byte)dataType); + var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191 + package.WriteByte((byte)overflow); + package.Write(Word.ToByteArray((ushort)(startByteAdr * 8 + bitAdr))); + package.Write(new byte[] { 0, 0x03 }); //ending 0x03 is used for writing a sinlge bit + package.Write(Word.ToByteArray((ushort)(varCount))); + + // now join the header and the data + package.Write(value); + + return packageData; + } + + private void WriteBitWithASingleRequest(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue) + { + try + { + var dataToSend = BuildWriteBitPackage(dataType, db, startByteAdr, bitValue, bitAdr); + var s7data = RequestTsdu(dataToSend); + + ValidateResponseCode((ReadWriteErrorCode)s7data[14]); + } + catch (Exception exc) + { + throw new PlcException(ErrorCode.WriteData, exc); + } + } + + /// + /// Reads multiple vars in a single request. + /// You have to create and pass a list of DataItems and you obtain in response the same list with the values. + /// Values are stored in the property "Value" of the dataItem and are already converted. + /// If you don't want the conversion, just create a dataItem of bytes. + /// The number of DataItems as well as the total size of the requested data can not exceed a certain limit (protocol restriction). + /// + /// List of dataitems that contains the list of variables that must be read. + public void ReadMultipleVars(List dataItems) + { + AssertPduSizeForRead(dataItems); + + try + { + // first create the header + int packageSize = 19 + (dataItems.Count * 12); + var dataToSend = new byte[packageSize]; + var package = new MemoryStream(dataToSend); + BuildHeaderPackage(package, dataItems.Count); + // package.Add(0x02); // datenart + foreach (var dataItem in dataItems) + { + BuildReadDataRequestPackage(package, dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarTypeToByteLength(dataItem.VarType, dataItem.Count)); + } + + byte[] s7data = RequestTsdu(dataToSend); + + ValidateResponseCode((ReadWriteErrorCode)s7data[14]); + + ParseDataIntoDataItems(s7data, dataItems); + } + catch (Exception exc) + { + throw new PlcException(ErrorCode.ReadData, exc); + } + } + + private byte[] RequestTsdu(byte[] requestData) => RequestTsdu(requestData, 0, requestData.Length); + + private byte[] RequestTsdu(byte[] requestData, int offset, int length) + { + return RequestTsduAsync(requestData, offset, length).GetAwaiter().GetResult(); + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Protocol/ConnectionRequest.cs b/常用工具集/Utility/Network/S7netplus/Protocol/ConnectionRequest.cs new file mode 100644 index 0000000..9dbd396 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Protocol/ConnectionRequest.cs @@ -0,0 +1,28 @@ +namespace S7.Net.Protocol +{ + internal static class ConnectionRequest + { + public static byte[] GetCOTPConnectionRequest(TsapPair tsapPair) + { + byte[] bSend1 = { + 3, 0, 0, 22, //TPKT + 17, //COTP Header Length + 224, //Connect Request + 0, 0, //Destination Reference + 0, 46, //Source Reference + 0, //Flags + 193, //Parameter Code (src-tasp) + 2, //Parameter Length + tsapPair.Local.FirstByte, tsapPair.Local.SecondByte, //Source TASP + 194, //Parameter Code (dst-tasp) + 2, //Parameter Length + tsapPair.Remote.FirstByte, tsapPair.Remote.SecondByte, //Destination TASP + 192, //Parameter Code (tpdu-size) + 1, //Parameter Length + 10 //TPDU Size (2^10 = 1024) + }; + + return bSend1; + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Protocol/ReadWriteErrorCode.cs b/常用工具集/Utility/Network/S7netplus/Protocol/ReadWriteErrorCode.cs new file mode 100644 index 0000000..9d6b943 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Protocol/ReadWriteErrorCode.cs @@ -0,0 +1,15 @@ + +namespace S7.Net.Protocol +{ + internal enum ReadWriteErrorCode : byte + { + Reserved = 0x00, + HardwareFault = 0x01, + AccessingObjectNotAllowed = 0x03, + AddressOutOfRange = 0x05, + DataTypeNotSupported = 0x06, + DataTypeInconsistent = 0x07, + ObjectDoesNotExist = 0x0a, + Success = 0xff + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Protocol/S7/DataItemAddress.cs b/常用工具集/Utility/Network/S7netplus/Protocol/S7/DataItemAddress.cs new file mode 100644 index 0000000..cf37382 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Protocol/S7/DataItemAddress.cs @@ -0,0 +1,37 @@ +namespace S7.Net.Protocol.S7 +{ + /// + /// Represents an area of memory in the PLC + /// + internal class DataItemAddress + { + public DataItemAddress(DataType dataType, int db, int startByteAddress, int byteLength) + { + DataType = dataType; + DB = db; + StartByteAddress = startByteAddress; + ByteLength = byteLength; + } + + + /// + /// Memory area to read + /// + public DataType DataType { get; } + + /// + /// Address of memory area to read (example: for DB1 this value is 1, for T45 this value is 45) + /// + public int DB { get; } + + /// + /// Address of the first byte to read + /// + public int StartByteAddress { get; } + + /// + /// Length of data to read + /// + public int ByteLength { get; } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Protocol/S7WriteMultiple.cs b/常用工具集/Utility/Network/S7netplus/Protocol/S7WriteMultiple.cs new file mode 100644 index 0000000..8845ad1 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Protocol/S7WriteMultiple.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using S7.Net.Types; + +namespace S7.Net.Protocol +{ + internal static class S7WriteMultiple + { + public static int CreateRequest(ByteArray message, DataItem[] dataItems) + { + message.Add(Header.Template); + + message[Header.Offsets.ParameterCount] = (byte) dataItems.Length; + var paramSize = dataItems.Length * Parameter.Template.Length; + + Serialization.SetWordAt(message, Header.Offsets.ParameterSize, + (ushort) (2 + paramSize)); + + var paramOffset = Header.Template.Length; + var data = new ByteArray(); + + var itemCount = 0; + + foreach (var item in dataItems) + { + itemCount++; + message.Add(Parameter.Template); + var value = Serialization.SerializeDataItem(item); + var wordLen = item.Value is bool ? 1 : 2; + + message[paramOffset + Parameter.Offsets.WordLength] = (byte) wordLen; + Serialization.SetWordAt(message, paramOffset + Parameter.Offsets.Amount, (ushort) value.Length); + Serialization.SetWordAt(message, paramOffset + Parameter.Offsets.DbNumber, (ushort) item.DB); + message[paramOffset + Parameter.Offsets.Area] = (byte) item.DataType; + + data.Add(0x00); + if (item.Value is bool b) + { + if (item.BitAdr > 7) + throw new ArgumentException( + $"Cannot read bit with invalid {nameof(item.BitAdr)} '{item.BitAdr}'.", nameof(dataItems)); + + Serialization.SetAddressAt(message, paramOffset + Parameter.Offsets.Address, item.StartByteAdr, + item.BitAdr); + + data.Add(0x03); + data.AddWord(1); + + data.Add(b ? (byte)1 : (byte)0); + if (itemCount != dataItems.Length) { + data.Add(0); + } + } + else + { + Serialization.SetAddressAt(message, paramOffset + Parameter.Offsets.Address, item.StartByteAdr, 0); + + var len = value.Length; + data.Add(0x04); + data.AddWord((ushort) (len << 3)); + data.Add(value); + + if ((len & 0b1) == 1 && itemCount != dataItems.Length) + { + data.Add(0); + } + } + + paramOffset += Parameter.Template.Length; + } + + message.Add(data.Array); + + Serialization.SetWordAt(message, Header.Offsets.MessageLength, (ushort) message.Length); + Serialization.SetWordAt(message, Header.Offsets.DataLength, (ushort) (message.Length - paramOffset)); + + return message.Length; + } + + public static void ParseResponse(byte[] message, int length, DataItem[] dataItems) + { + if (length < 12) throw new Exception("Not enough data received to parse write response."); + + var messageError = Serialization.GetWordAt(message, 10); + if (messageError != 0) + throw new Exception($"Write failed with error {messageError}."); + + if (length < 14 + dataItems.Length) + throw new Exception("Not enough data received to parse individual item responses."); + + IList itemResults = new ArraySegment(message, 14, dataItems.Length); + + List errors = null; + + for (int i = 0; i < dataItems.Length; i++) + { + try + { + Plc.ValidateResponseCode((ReadWriteErrorCode)itemResults[i]); + } + catch(Exception e) + { + if (errors == null) errors = new List(); + errors.Add(new Exception($"Write of dataItem {dataItems[i]} failed: {e.Message}.")); + } + + } + + if (errors != null) + throw new AggregateException( + $"Write failed for {errors.Count} items. See the innerExceptions for details.", errors); + } + + private static class Header + { + public static byte[] Template { get; } = + { + 0x03, 0x00, 0x00, 0x00, // TPKT + 0x02, 0xf0, 0x80, // ISO DT + 0x32, // S7 protocol ID + 0x01, // JobRequest + 0x00, 0x00, // Reserved + 0x05, 0x00, // PDU reference + 0x00, 0x0e, // Parameters length + 0x00, 0x00, // Data length + 0x05, // Function: Write var + 0x00, // Number of items to write + }; + + public static class Offsets + { + public const int MessageLength = 2; + public const int ParameterSize = 13; + public const int DataLength = 15; + public const int ParameterCount = 18; + } + } + + private static class Parameter + { + public static byte[] Template { get; } = + { + 0x12, // Spec + 0x0a, // Length of remaining bytes + 0x10, // Addressing mode + 0x02, // Transport size + 0x00, 0x00, // Number of elements + 0x00, 0x00, // DB number + 0x84, // Area type + 0x00, 0x00, 0x00 // Area offset + }; + + public static class Offsets + { + public const int WordLength = 3; + public const int Amount = 4; + public const int DbNumber = 6; + public const int Area = 8; + public const int Address = 9; + } + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Protocol/Serialization.cs b/常用工具集/Utility/Network/S7netplus/Protocol/Serialization.cs new file mode 100644 index 0000000..b661f6a --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Protocol/Serialization.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using S7.Net.Types; + +namespace S7.Net.Protocol +{ + internal static class Serialization + { + public static ushort GetWordAt(IList buf, int index) + { + return (ushort)((buf[index] << 8) + buf[index]); + } + + public static byte[] SerializeDataItem(DataItem dataItem) + { + if (dataItem.Value == null) + { + throw new Exception($"DataItem.Value is null, cannot serialize. StartAddr={dataItem.StartByteAdr} VarType={dataItem.VarType}"); + } + + if (dataItem.Value is string s) + { + switch (dataItem.VarType) + { + case VarType.S7String: + return S7String.ToByteArray(s, dataItem.Count); + case VarType.S7WString: + return S7WString.ToByteArray(s, dataItem.Count); + default: + return Types.String.ToByteArray(s, dataItem.Count); + } + } + return SerializeValue(dataItem.Value); + } + + public static byte[] SerializeValue(object value) + { + switch (value.GetType().Name) + { + case "Boolean": + return new[] { (byte)((bool)value ? 1 : 0) }; + case "Byte": + return Types.Byte.ToByteArray((byte)value); + case "Int16": + return Types.Int.ToByteArray((Int16)value); + case "UInt16": + return Types.Word.ToByteArray((UInt16)value); + case "Int32": + return Types.DInt.ToByteArray((Int32)value); + case "UInt32": + return Types.DWord.ToByteArray((UInt32)value); + case "Single": + return Types.Real.ToByteArray((float)value); + case "Double": + return Types.LReal.ToByteArray((double)value); + case "DateTime": + return Types.DateTime.ToByteArray((System.DateTime)value); + case "Byte[]": + return (byte[])value; + case "Int16[]": + return Types.Int.ToByteArray((Int16[])value); + case "UInt16[]": + return Types.Word.ToByteArray((UInt16[])value); + case "Int32[]": + return Types.DInt.ToByteArray((Int32[])value); + case "UInt32[]": + return Types.DWord.ToByteArray((UInt32[])value); + case "Single[]": + return Types.Real.ToByteArray((float[])value); + case "Double[]": + return Types.LReal.ToByteArray((double[])value); + case "String": + // Hack: This is backwards compatible with the old code, but functionally it's broken + // if the consumer does not pay attention to string length. + var stringVal = (string)value; + return Types.String.ToByteArray(stringVal, stringVal.Length); + case "DateTime[]": + return Types.DateTime.ToByteArray((System.DateTime[])value); + case "DateTimeLong[]": + return Types.DateTimeLong.ToByteArray((System.DateTime[])value); + default: + throw new InvalidVariableTypeException(); + } + } + + public static void SetAddressAt(ByteArray buffer, int index, int startByte, byte bitNumber) + { + var start = startByte * 8 + bitNumber; + buffer[index + 2] = (byte)start; + start >>= 8; + buffer[index + 1] = (byte)start; + start >>= 8; + buffer[index] = (byte)start; + } + + public static void SetWordAt(ByteArray buffer, int index, ushort value) + { + buffer[index] = (byte)(value >> 8); + buffer[index + 1] = (byte)value; + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Protocol/Tsap.cs b/常用工具集/Utility/Network/S7netplus/Protocol/Tsap.cs new file mode 100644 index 0000000..dc9d46c --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Protocol/Tsap.cs @@ -0,0 +1,31 @@ +namespace S7.Net.Protocol +{ + /// + /// Provides a representation of the Transport Service Access Point, or TSAP in short. TSAP's are used + /// to specify a client and server address. For most PLC types a default TSAP is available that allows + /// connection from any IP and can be calculated using the rack and slot numbers. + /// + public struct Tsap + { + /// + /// First byte of the TSAP. + /// + public byte FirstByte { get; set; } + + /// + /// Second byte of the TSAP. + /// + public byte SecondByte { get; set; } + + /// + /// Initializes a new instance of the class using the specified values. + /// + /// The first byte of the TSAP. + /// The second byte of the TSAP. + public Tsap(byte firstByte, byte secondByte) + { + FirstByte = firstByte; + SecondByte = secondByte; + } + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/S7netplus/Protocol/TsapPair.cs b/常用工具集/Utility/Network/S7netplus/Protocol/TsapPair.cs new file mode 100644 index 0000000..e54fc32 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Protocol/TsapPair.cs @@ -0,0 +1,96 @@ +using System; + +namespace S7.Net.Protocol +{ + /// + /// Implements a pair of TSAP addresses used to connect to a PLC. + /// + public class TsapPair + { + /// + /// The local . + /// + public Tsap Local { get; set; } + + /// + /// The remote + /// + public Tsap Remote { get; set; } + + /// + /// Initializes a new instance of the class using the specified local and + /// remote TSAP. + /// + /// The local TSAP. + /// The remote TSAP. + public TsapPair(Tsap local, Tsap remote) + { + Local = local; + Remote = remote; + } + + /// + /// Builds a that can be used to connect to a PLC using the default connection + /// addresses. + /// + /// + /// The remote TSAP is constructed using new Tsap(0x03, (byte) ((rack << 5) | slot)). + /// + /// The CPU type of the PLC. + /// The rack of the PLC's network card. + /// The slot of the PLC's network card. + /// A TSAP pair that matches the given parameters. + /// The is invalid. + /// + /// -or- + /// + /// The parameter is less than 0. + /// + /// -or- + /// + /// The parameter is greater than 15. + /// + /// -or- + /// + /// The parameter is less than 0. + /// + /// -or- + /// + /// The parameter is greater than 15. + public static TsapPair GetDefaultTsapPair(CpuType cpuType, int rack, int slot) + { + if (rack < 0) throw InvalidRackOrSlot(rack, nameof(rack), "minimum", 0); + if (rack > 0x0F) throw InvalidRackOrSlot(rack, nameof(rack), "maximum", 0x0F); + + if (slot < 0) throw InvalidRackOrSlot(slot, nameof(slot), "minimum", 0); + if (slot > 0x0F) throw InvalidRackOrSlot(slot, nameof(slot), "maximum", 0x0F); + + switch (cpuType) + { + case CpuType.S7200: + return new TsapPair(new Tsap(0x10, 0x00), new Tsap(0x10, 0x01)); + case CpuType.Logo0BA8: + // The actual values are probably on a per-project basis + return new TsapPair(new Tsap(0x01, 0x00), new Tsap(0x01, 0x02)); + case CpuType.S7200Smart: + case CpuType.S71200: + case CpuType.S71500: + case CpuType.S7300: + case CpuType.S7400: + // Testing with S7 1500 shows only the remote TSAP needs to match. This might differ for other + // PLC types. + return new TsapPair(new Tsap(0x01, 0x00), new Tsap(0x03, (byte) ((rack << 5) | slot))); + default: + throw new ArgumentOutOfRangeException(nameof(cpuType), "Invalid CPU Type specified"); + } + } + + private static ArgumentOutOfRangeException InvalidRackOrSlot(int value, string name, string extrema, + int extremaValue) + { + return new ArgumentOutOfRangeException(name, + $"Invalid {name} value specified (decimal: {value}, hexadecimal: {value:X}), {extrema} value " + + $"is {extremaValue} (decimal) or {extremaValue:X} (hexadecimal)."); + } + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/S7netplus/S7Helper.cs b/常用工具集/Utility/Network/S7netplus/S7Helper.cs new file mode 100644 index 0000000..4a3081d --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/S7Helper.cs @@ -0,0 +1,301 @@ +using System; +using System.Text; +using S7.Net; +using S7.Net.Types; + +namespace MES.Utility.Network.S7netplus +{ + public class S7Helper : IDisposable + { + private object _lock = new object(); + private Plc plc; + public S7Helper(CpuType cpuType, string ipAddress) : this(cpuType, ipAddress, 0, 1) + { + } + + public S7Helper(CpuType cpuType, string ipAddress, short rack, short slot) + { + plc = new Plc(cpuType, ipAddress, 102, rack, slot); + plc.Open(1000); + } + + #region BOOL类型读写 + /// + /// 写入BOOL + /// + /// 例如:DB45.DBX52.0 + /// true/false + /// + public bool WriteBool(string address, bool value) + { + lock (_lock) + try + { + + plc.Write(address, value); + return true; + } + catch + { + return false; + } + } + + /// + /// 读取bool类型 + /// + /// 例如:DB45.DBX52.0 + /// 输出bool值 + /// 返回false 读取失败,PLC通信异常 + public bool ReadBool(string address, out bool value) + { + lock (_lock) + try + { + value = (bool)plc.Read(address); + return true; + } + catch + { + value = false; + return false; + } + } + #endregion + + #region INT16读写 + public bool ReadInt16(string address, out ushort value) + { + lock (_lock) + try + { + value = (ushort)plc.Read(address); + return true; + } + catch + { + value = 0; + return false; + } + } + public bool WriteInt16(string address, ushort value) + { + lock (_lock) + try + { + plc.Write(address, value); + return true; + } + catch + { + return false; + } + } + #endregion + + #region REAL读写 + /// + /// + /// + /// 例如 DB1.DBD4.0 + /// + /// + public bool ReadSingle(string address, out float value) + { + lock (_lock) + try + { + value = ((uint)plc.Read(address)).ConvertToFloat(); + return true; + } + catch + { + value = 0; + return false; + } + } + + public bool ReadBytes(int db, int address, int count, out byte[] value) + { + try + { + value = plc.ReadBytes(DataType.DataBlock, db, address, count); + return true; + } + catch + { + value = null; + return false; + } + } + + /// + /// + /// + /// 例如 DB1.DBD4.0 + /// + /// + public bool WriteSingle(string address, float value) + { + lock (_lock) + try + { + plc.Write(address, value); + return true; + } + catch + { + return false; + } + } + #endregion + + #region String读写 + /// + /// + /// + /// 例如DB45.DBX52.0 + /// 字符串长度 + /// 返回字符串数据 + /// + public bool ReadString(string address, int strLength, out string value) + { + lock (_lock) + try + { + string[] array = address.Split('.'); + int db = Convert.ToInt32(array[0].Replace("DB", "")); + int startAddress = Convert.ToInt32(array[1].Replace("DBX", "")); + value = (string)plc.Read(DataType.DataBlock, db, startAddress, VarType.S7String, strLength);//获取对应长度的字符串 + return true; + } + catch + { + value = ""; + return false; + } + } + + /// + /// 写入字符串 + /// + /// 例如DB45.DBX52.0 + /// 写入数据 + /// 写入长度 + /// + public bool WriteString(string address, string value, int strLength) + { + lock (_lock) + try + { + //转换address,取到DB 和 开始地址 + string[] array = address.Split('.'); + int db = Convert.ToInt32(array[0].Replace("DB", "")); + int startAddress = Convert.ToInt32(array[1].Replace("DBX", "")); + var bytes = S7.Net.Types.S7String.ToByteArray(value, strLength); + plc.WriteBytes(DataType.DataBlock, db, startAddress, bytes); + return true; + } + catch + { + return false; + } + + } + #endregion + + + public bool ReadBytesString(string address, int strLength, out string value) + { + lock (_lock) + try + { + string[] array = address.Split('.'); + int db = Convert.ToInt32(array[0].Replace("DB", "")); + int startAddress = Convert.ToInt32(array[1].Replace("DBX", "")); + byte[] values = plc.ReadBytes(DataType.DataBlock, db, startAddress, strLength); + value = Encoding.ASCII.GetString(values); + return true; + } + catch + { + value = ""; + return false; + } + } + + /// + /// 写入字符串 + /// + /// 例如DB45.DBX52.0 + /// 写入数据 + /// 写入长度 + /// + public bool WriteBytesString(string address, string value, int strLength) + { + lock (_lock) + try + { + //转换address,取到DB 和 开始地址 + string[] array = address.Split('.'); + int db = Convert.ToInt32(array[0].Replace("DB", "")); + int startAddress = Convert.ToInt32(array[1].Replace("DBX", "")); + var bytes = Encoding.ASCII.GetBytes(value); + if (bytes.Length > strLength) throw new ArgumentException($"The provided string length ({bytes.Length} is larger than the specified reserved length ({strLength})."); + var buffer = new byte[strLength]; + Array.Copy(bytes, 0, buffer, 0, bytes.Length); + plc.WriteBytes(DataType.DataBlock, db, startAddress, buffer); + return true; + } + catch + { + return false; + } + + } + + #region INT32读写 + /// + /// + /// + /// 例如:DB1.DBD264.0 + /// + /// + public bool ReadInt32(string address, out int value) + { + lock (_lock) + try + { + value = (int)plc.Read(address); + return true; + } + catch + { + value = 0; + return false; + } + } + public bool WriteInt32(string address, int value) + { + lock (_lock) + try + { + plc.Write(address, value); + return true; + } + catch + { + return false; + } + } + + #endregion + + public void Dispose() + { + plc?.Close(); + plc = null; + } + } + +} diff --git a/常用工具集/Utility/Network/S7netplus/StreamExtensions.cs b/常用工具集/Utility/Network/S7netplus/StreamExtensions.cs new file mode 100644 index 0000000..749b915 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/StreamExtensions.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace S7.Net +{ + /// + /// Extensions for Streams + /// + public static class StreamExtensions + { + /// + /// Reads bytes from the stream into the buffer until exactly the requested number of bytes (or EOF) have been read + /// + /// the Stream to read from + /// the buffer to read into + /// the offset in the buffer to read into + /// the amount of bytes to read into the buffer + /// returns the amount of read bytes + public static int ReadExact(this Stream stream, byte[] buffer, int offset, int count) + { + int read = 0; + int received; + do + { + received = stream.Read(buffer, offset + read, count - read); + read += received; + } + while (read < count && received > 0); + + return read; + } + + /// + /// Reads bytes from the stream into the buffer until exactly the requested number of bytes (or EOF) have been read + /// + /// the Stream to read from + /// the buffer to read into + /// the offset in the buffer to read into + /// the amount of bytes to read into the buffer + /// returns the amount of read bytes + public static async Task ReadExactAsync(this Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + int read = 0; + int received; + do + { + received = await stream.ReadAsync(buffer, offset + read, count - read, cancellationToken).ConfigureAwait(false); + read += received; + } + while (read < count && received > 0); + + return read; + } + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/S7netplus/TPKT.cs b/常用工具集/Utility/Network/S7netplus/TPKT.cs new file mode 100644 index 0000000..a311dce --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/TPKT.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace S7.Net +{ + + /// + /// Describes a TPKT Packet + /// + internal class TPKT + { + + + public byte Version; + public byte Reserved1; + public int Length; + public byte[] Data; + private TPKT(byte version, byte reserved1, int length, byte[] data) + { + Version = version; + Reserved1 = reserved1; + Length = length; + Data = data; + } + + /// + /// Reads a TPKT from the socket Async + /// + /// The stream to read from + /// Task TPKT Instace + public static async Task ReadAsync(Stream stream, CancellationToken cancellationToken) + { + var buf = new byte[4]; + int len = await stream.ReadExactAsync(buf, 0, 4, cancellationToken).ConfigureAwait(false); + if (len < 4) throw new TPKTInvalidException("TPKT is incomplete / invalid"); + + var version = buf[0]; + var reserved1 = buf[1]; + var length = buf[2] * 256 + buf[3]; //BigEndian + + var data = new byte[length - 4]; + len = await stream.ReadExactAsync(data, 0, data.Length, cancellationToken).ConfigureAwait(false); + if (len < data.Length) + throw new TPKTInvalidException("TPKT payload incomplete / invalid"); + + return new TPKT + ( + version: version, + reserved1: reserved1, + length: length, + data: data + ); + } + + public override string ToString() + { + return string.Format("Version: {0} Length: {1} Data: {2}", + Version, + Length, + BitConverter.ToString(Data) + ); + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/Bit.cs b/常用工具集/Utility/Network/S7netplus/Types/Bit.cs new file mode 100644 index 0000000..5214fd9 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/Bit.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections; + +namespace S7.Net.Types +{ + /// + /// Contains the conversion methods to convert Bit from S7 plc to C#. + /// + public static class Bit + { + /// + /// Converts a Bit to bool + /// + public static bool FromByte(byte v, byte bitAdr) + { + return (((int)v & (1 << bitAdr)) != 0); + } + + /// + /// Converts an array of bytes to a BitArray. + /// + /// The bytes to convert. + /// A BitArray with the same number of bits and equal values as . + public static BitArray ToBitArray(byte[] bytes) => ToBitArray(bytes, bytes.Length * 8); + + /// + /// Converts an array of bytes to a BitArray. + /// + /// The bytes to convert. + /// The number of bits to return. + /// A BitArray with bits. + public static BitArray ToBitArray(byte[] bytes, int length) + { + if (length > bytes.Length * 8) throw new ArgumentException($"Not enough data in bytes to return {length} bits.", nameof(bytes)); + + var bitArr = new BitArray(bytes); + var bools = new bool[length]; + for (var i = 0; i < length; i++) bools[i] = bitArr[i]; + + return new BitArray(bools); + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/Boolean.cs b/常用工具集/Utility/Network/S7netplus/Types/Boolean.cs new file mode 100644 index 0000000..f7bc83e --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/Boolean.cs @@ -0,0 +1,64 @@ +namespace S7.Net.Types +{ + /// + /// Contains the methods to read, set and reset bits inside bytes + /// + public static class Boolean + { + /// + /// Returns the value of a bit in a bit, given the address of the bit + /// + public static bool GetValue(byte value, int bit) + { + return (((int)value & (1 << bit)) != 0); + } + + /// + /// Sets the value of a bit to 1 (true), given the address of the bit. Returns + /// a copy of the value with the bit set. + /// + /// The input value to modify. + /// The index (zero based) of the bit to set. + /// The modified value with the bit at index set. + public static byte SetBit(byte value, int bit) + { + SetBit(ref value, bit); + + return value; + } + + /// + /// Sets the value of a bit to 1 (true), given the address of the bit. + /// + /// The value to modify. + /// The index (zero based) of the bit to set. + public static void SetBit(ref byte value, int bit) + { + value = (byte) ((value | (1 << bit)) & 0xFF); + } + + /// + /// Resets the value of a bit to 0 (false), given the address of the bit. Returns + /// a copy of the value with the bit cleared. + /// + /// The input value to modify. + /// The index (zero based) of the bit to clear. + /// The modified value with the bit at index cleared. + public static byte ClearBit(byte value, int bit) + { + ClearBit(ref value, bit); + + return value; + } + + /// + /// Resets the value of a bit to 0 (false), given the address of the bit + /// + /// The input value to modify. + /// The index (zero based) of the bit to clear. + public static void ClearBit(ref byte value, int bit) + { + value = (byte) (value & ~(1 << bit) & 0xFF); + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/Byte.cs b/常用工具集/Utility/Network/S7netplus/Types/Byte.cs new file mode 100644 index 0000000..d52bb45 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/Byte.cs @@ -0,0 +1,33 @@ +using System; + +namespace S7.Net.Types +{ + /// + /// Contains the methods to convert from bytes to byte arrays + /// + public static class Byte + { + /// + /// Converts a byte to byte array + /// + public static byte[] ToByteArray(byte value) + { + return new byte[] { value }; ; + } + + /// + /// Converts a byte array to byte + /// + /// + /// + public static byte FromByteArray(byte[] bytes) + { + if (bytes.Length != 1) + { + throw new ArgumentException("Wrong number of bytes. Bytes array must contain 1 bytes."); + } + return bytes[0]; + } + + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/ByteArray.cs b/常用工具集/Utility/Network/S7netplus/Types/ByteArray.cs new file mode 100644 index 0000000..e80e4b5 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/ByteArray.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; + +namespace S7.Net.Types +{ + class ByteArray + { + List list = new List(); + + public byte this[int index] + { + get => list[index]; + set => list[index] = value; + } + + public byte[] Array + { + get { return list.ToArray(); } + } + + public int Length => list.Count; + + public ByteArray() + { + list = new List(); + } + + public ByteArray(int size) + { + list = new List(size); + } + + public void Clear() + { + list = new List(); + } + + public void Add(byte item) + { + list.Add(item); + } + + public void AddWord(ushort value) + { + list.Add((byte) (value >> 8)); + list.Add((byte) value); + } + + public void Add(byte[] items) + { + list.AddRange(items); + } + + public void Add(IEnumerable items) + { + list.AddRange(items); + } + + public void Add(ByteArray byteArray) + { + list.AddRange(byteArray.Array); + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/Class.cs b/常用工具集/Utility/Network/S7netplus/Types/Class.cs new file mode 100644 index 0000000..b40863c --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/Class.cs @@ -0,0 +1,354 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace S7.Net.Types +{ + /// + /// Contains the methods to convert a C# class to S7 data types + /// + public static class Class + { + private static IEnumerable GetAccessableProperties(Type classType) + { + return classType + .GetProperties( + BindingFlags.SetProperty | + BindingFlags.Public | + BindingFlags.Instance) + .Where(p => p.GetSetMethod() != null); + } + + private static double GetIncreasedNumberOfBytes(double numBytes, Type type, PropertyInfo propertyInfo) + { + switch (type.Name) + { + case "Boolean": + numBytes += 0.125; + break; + case "Byte": + numBytes = Math.Ceiling(numBytes); + numBytes++; + break; + case "Int16": + case "UInt16": + IncrementToEven(ref numBytes); + numBytes += 2; + break; + case "Int32": + case "UInt32": + IncrementToEven(ref numBytes); + numBytes += 4; + break; + case "Single": + IncrementToEven(ref numBytes); + numBytes += 4; + break; + case "Double": + IncrementToEven(ref numBytes); + numBytes += 8; + break; + case "String": + S7StringAttribute attribute = propertyInfo?.GetCustomAttributes().SingleOrDefault(); + if (attribute == default(S7StringAttribute)) + throw new ArgumentException("Please add S7StringAttribute to the string field"); + + IncrementToEven(ref numBytes); + numBytes += attribute.ReservedLengthInBytes; + break; + default: + var propertyClass = Activator.CreateInstance(type); + numBytes = GetClassSize(propertyClass, numBytes, true); + break; + } + + return numBytes; + } + + /// + /// Gets the size of the class in bytes. + /// + /// An instance of the class + /// the number of bytes + public static double GetClassSize(object instance, double numBytes = 0.0, bool isInnerProperty = false) + { + var properties = GetAccessableProperties(instance.GetType()); + foreach (var property in properties) + { + if (property.PropertyType.IsArray) + { + Type elementType = property.PropertyType.GetElementType(); + Array array = (Array)property.GetValue(instance, null); + if (array.Length <= 0) + { + throw new Exception("Cannot determine size of class, because an array is defined which has no fixed size greater than zero."); + } + + IncrementToEven(ref numBytes); + for (int i = 0; i < array.Length; i++) + { + numBytes = GetIncreasedNumberOfBytes(numBytes, elementType, property); + } + } + else + { + numBytes = GetIncreasedNumberOfBytes(numBytes, property.PropertyType, property); + } + } + if (false == isInnerProperty) + { + // enlarge numBytes to next even number because S7-Structs in a DB always will be resized to an even byte count + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + } + return numBytes; + } + + private static object GetPropertyValue(Type propertyType, PropertyInfo propertyInfo, byte[] bytes, ref double numBytes) + { + object value = null; + + switch (propertyType.Name) + { + case "Boolean": + // get the value + int bytePos = (int)Math.Floor(numBytes); + int bitPos = (int)((numBytes - (double)bytePos) / 0.125); + if ((bytes[bytePos] & (int)Math.Pow(2, bitPos)) != 0) + value = true; + else + value = false; + numBytes += 0.125; + break; + case "Byte": + numBytes = Math.Ceiling(numBytes); + value = (byte)(bytes[(int)numBytes]); + numBytes++; + break; + case "Int16": + IncrementToEven(ref numBytes); + // hier auswerten + ushort source = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]); + value = source.ConvertToShort(); + numBytes += 2; + break; + case "UInt16": + IncrementToEven(ref numBytes); + // hier auswerten + value = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]); + numBytes += 2; + break; + case "Int32": + IncrementToEven(ref numBytes); + var wordBuffer = new byte[4]; + Array.Copy(bytes, (int)numBytes, wordBuffer, 0, wordBuffer.Length); + uint sourceUInt = DWord.FromByteArray(wordBuffer); + value = sourceUInt.ConvertToInt(); + numBytes += 4; + break; + case "UInt32": + IncrementToEven(ref numBytes); + var wordBuffer2 = new byte[4]; + Array.Copy(bytes, (int)numBytes, wordBuffer2, 0, wordBuffer2.Length); + value = DWord.FromByteArray(wordBuffer2); + numBytes += 4; + break; + case "Single": + IncrementToEven(ref numBytes); + // hier auswerten + value = Real.FromByteArray( + new byte[] { + bytes[(int)numBytes], + bytes[(int)numBytes + 1], + bytes[(int)numBytes + 2], + bytes[(int)numBytes + 3] }); + numBytes += 4; + break; + case "Double": + IncrementToEven(ref numBytes); + var buffer = new byte[8]; + Array.Copy(bytes, (int)numBytes, buffer, 0, 8); + // hier auswerten + value = LReal.FromByteArray(buffer); + numBytes += 8; + break; + case "String": + S7StringAttribute attribute = propertyInfo?.GetCustomAttributes().SingleOrDefault(); + if (attribute == default(S7StringAttribute)) + throw new ArgumentException("Please add S7StringAttribute to the string field"); + + IncrementToEven(ref numBytes); + + // get the value + var sData = new byte[attribute.ReservedLengthInBytes]; + Array.Copy(bytes, (int)numBytes, sData, 0, sData.Length); + switch (attribute.Type) + { + case S7StringType.S7String: + value = S7String.FromByteArray(sData); break; + case S7StringType.S7WString: + value = S7WString.FromByteArray(sData); break; + default: + throw new ArgumentException("Please use a valid string type for the S7StringAttribute"); + } + numBytes += sData.Length; + break; + default: + var propClass = Activator.CreateInstance(propertyType); + numBytes = FromBytes(propClass, bytes, numBytes); + value = propClass; + break; + } + + return value; + } + + /// + /// Sets the object's values with the given array of bytes + /// + /// The object to fill in the given array of bytes + /// The array of bytes + public static double FromBytes(object sourceClass, byte[] bytes, double numBytes = 0, bool isInnerClass = false) + { + if (bytes == null) + return numBytes; + + var properties = GetAccessableProperties(sourceClass.GetType()); + foreach (var property in properties) + { + if (property.PropertyType.IsArray) + { + Array array = (Array)property.GetValue(sourceClass, null); + IncrementToEven(ref numBytes); + Type elementType = property.PropertyType.GetElementType(); + for (int i = 0; i < array.Length && numBytes < bytes.Length; i++) + { + array.SetValue( + GetPropertyValue(elementType, property, bytes, ref numBytes), + i); + } + } + else + { + property.SetValue( + sourceClass, + GetPropertyValue(property.PropertyType, property, bytes, ref numBytes), + null); + } + } + + return numBytes; + } + + private static double SetBytesFromProperty(object propertyValue, PropertyInfo propertyInfo, byte[] bytes, double numBytes) + { + int bytePos = 0; + int bitPos = 0; + byte[] bytes2 = null; + + switch (propertyValue.GetType().Name) + { + case "Boolean": + // get the value + bytePos = (int)Math.Floor(numBytes); + bitPos = (int)((numBytes - (double)bytePos) / 0.125); + if ((bool)propertyValue) + bytes[bytePos] |= (byte)Math.Pow(2, bitPos); // is true + else + bytes[bytePos] &= (byte)(~(byte)Math.Pow(2, bitPos)); // is false + numBytes += 0.125; + break; + case "Byte": + numBytes = (int)Math.Ceiling(numBytes); + bytePos = (int)numBytes; + bytes[bytePos] = (byte)propertyValue; + numBytes++; + break; + case "Int16": + bytes2 = Int.ToByteArray((Int16)propertyValue); + break; + case "UInt16": + bytes2 = Word.ToByteArray((UInt16)propertyValue); + break; + case "Int32": + bytes2 = DInt.ToByteArray((Int32)propertyValue); + break; + case "UInt32": + bytes2 = DWord.ToByteArray((UInt32)propertyValue); + break; + case "Single": + bytes2 = Real.ToByteArray((float)propertyValue); + break; + case "Double": + bytes2 = LReal.ToByteArray((double)propertyValue); + break; + case "String": + S7StringAttribute attribute = propertyInfo?.GetCustomAttributes().SingleOrDefault(); + if (attribute == default(S7StringAttribute)) + throw new ArgumentException("Please add S7StringAttribute to the string field"); + switch (attribute.Type) + { + case S7StringType.S7String: + bytes2 = S7String.ToByteArray((string)propertyValue, attribute.ReservedLength); break; + case S7StringType.S7WString: + bytes2 = S7WString.ToByteArray((string)propertyValue, attribute.ReservedLength); break; + default: + throw new ArgumentException("Please use a valid string type for the S7StringAttribute"); + } + break; + default: + numBytes = ToBytes(propertyValue, bytes, numBytes); + break; + } + + if (bytes2 != null) + { + IncrementToEven(ref numBytes); + + bytePos = (int)numBytes; + for (int bCnt = 0; bCnt < bytes2.Length; bCnt++) + bytes[bytePos + bCnt] = bytes2[bCnt]; + numBytes += bytes2.Length; + } + + return numBytes; + } + + /// + /// Creates a byte array depending on the struct type. + /// + /// The struct object + /// A byte array or null if fails. + public static double ToBytes(object sourceClass, byte[] bytes, double numBytes = 0.0) + { + var properties = GetAccessableProperties(sourceClass.GetType()); + foreach (var property in properties) + { + if (property.PropertyType.IsArray) + { + Array array = (Array)property.GetValue(sourceClass, null); + IncrementToEven(ref numBytes); + Type elementType = property.PropertyType.GetElementType(); + for (int i = 0; i < array.Length && numBytes < bytes.Length; i++) + { + numBytes = SetBytesFromProperty(array.GetValue(i), property, bytes, numBytes); + } + } + else + { + numBytes = SetBytesFromProperty(property.GetValue(sourceClass, null), property, bytes, numBytes); + } + } + return numBytes; + } + + private static void IncrementToEven(ref double numBytes) + { + numBytes = Math.Ceiling(numBytes); + if (numBytes % 2 > 0) numBytes++; + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/Counter.cs b/常用工具集/Utility/Network/S7netplus/Types/Counter.cs new file mode 100644 index 0000000..f52f727 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/Counter.cs @@ -0,0 +1,63 @@ +using System; + +namespace S7.Net.Types +{ + /// + /// Contains the conversion methods to convert Counter from S7 plc to C# ushort (UInt16). + /// + public static class Counter + { + /// + /// Converts a Counter (2 bytes) to ushort (UInt16) + /// + public static UInt16 FromByteArray(byte[] bytes) + { + if (bytes.Length != 2) + { + throw new ArgumentException("Wrong number of bytes. Bytes array must contain 2 bytes."); + } + // bytes[0] -> HighByte + // bytes[1] -> LowByte + return (UInt16)((bytes[0] << 8) | bytes[1]); + } + + + /// + /// Converts a ushort (UInt16) to word (2 bytes) + /// + public static byte[] ToByteArray(UInt16 value) + { + byte[] bytes = new byte[2]; + + bytes[0] = (byte)((value << 8) & 0xFF); + bytes[1] = (byte)((value) & 0xFF); + + return bytes; + } + + /// + /// Converts an array of ushort (UInt16) to an array of bytes + /// + public static byte[] ToByteArray(UInt16[] value) + { + ByteArray arr = new ByteArray(); + foreach (UInt16 val in value) + arr.Add(ToByteArray(val)); + return arr.Array; + } + + /// + /// Converts an array of bytes to an array of ushort + /// + public static UInt16[] ToArray(byte[] bytes) + { + UInt16[] values = new UInt16[bytes.Length / 2]; + + int counter = 0; + for (int cnt = 0; cnt < bytes.Length / 2; cnt++) + values[cnt] = FromByteArray(new byte[] { bytes[counter++], bytes[counter++] }); + + return values; + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/DInt.cs b/常用工具集/Utility/Network/S7netplus/Types/DInt.cs new file mode 100644 index 0000000..4d42bde --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/DInt.cs @@ -0,0 +1,65 @@ +using System; + +namespace S7.Net.Types +{ + /// + /// Contains the conversion methods to convert DInt from S7 plc to C# int (Int32). + /// + public static class DInt + { + /// + /// Converts a S7 DInt (4 bytes) to int (Int32) + /// + public static Int32 FromByteArray(byte[] bytes) + { + if (bytes.Length != 4) + { + throw new ArgumentException("Wrong number of bytes. Bytes array must contain 4 bytes."); + } + return bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3]; + } + + + /// + /// Converts a int (Int32) to S7 DInt (4 bytes) + /// + public static byte[] ToByteArray(Int32 value) + { + byte[] bytes = new byte[4]; + + bytes[0] = (byte)((value >> 24) & 0xFF); + bytes[1] = (byte)((value >> 16) & 0xFF); + bytes[2] = (byte)((value >> 8) & 0xFF); + bytes[3] = (byte)((value) & 0xFF); + + return bytes; + } + + /// + /// Converts an array of int (Int32) to an array of bytes + /// + public static byte[] ToByteArray(Int32[] value) + { + ByteArray arr = new ByteArray(); + foreach (Int32 val in value) + arr.Add(ToByteArray(val)); + return arr.Array; + } + + /// + /// Converts an array of S7 DInt to an array of int (Int32) + /// + public static Int32[] ToArray(byte[] bytes) + { + Int32[] values = new Int32[bytes.Length / 4]; + + int counter = 0; + for (int cnt = 0; cnt < bytes.Length / 4; cnt++) + values[cnt] = FromByteArray(new byte[] { bytes[counter++], bytes[counter++], bytes[counter++], bytes[counter++] }); + + return values; + } + + + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/DWord.cs b/常用工具集/Utility/Network/S7netplus/Types/DWord.cs new file mode 100644 index 0000000..87c254f --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/DWord.cs @@ -0,0 +1,73 @@ +using System; + +namespace S7.Net.Types +{ + /// + /// Contains the conversion methods to convert DWord from S7 plc to C#. + /// + public static class DWord + { + /// + /// Converts a S7 DWord (4 bytes) to uint (UInt32) + /// + public static UInt32 FromByteArray(byte[] bytes) + { + return (UInt32)(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3]); + } + + + /// + /// Converts 4 bytes to DWord (UInt32) + /// + public static UInt32 FromBytes(byte b1, byte b2, byte b3, byte b4) + { + return (UInt32)((b4 << 24) | (b3 << 16) | (b2 << 8) | b1); + } + + + /// + /// Converts a uint (UInt32) to S7 DWord (4 bytes) + /// + public static byte[] ToByteArray(UInt32 value) + { + byte[] bytes = new byte[4]; + + bytes[0] = (byte)((value >> 24) & 0xFF); + bytes[1] = (byte)((value >> 16) & 0xFF); + bytes[2] = (byte)((value >> 8) & 0xFF); + bytes[3] = (byte)((value) & 0xFF); + + return bytes; + } + + + + + + + /// + /// Converts an array of uint (UInt32) to an array of S7 DWord (4 bytes) + /// + public static byte[] ToByteArray(UInt32[] value) + { + ByteArray arr = new ByteArray(); + foreach (UInt32 val in value) + arr.Add(ToByteArray(val)); + return arr.Array; + } + + /// + /// Converts an array of S7 DWord to an array of uint (UInt32) + /// + public static UInt32[] ToArray(byte[] bytes) + { + UInt32[] values = new UInt32[bytes.Length / 4]; + + int counter = 0; + for (int cnt = 0; cnt < bytes.Length / 4; cnt++) + values[cnt] = FromByteArray(new byte[] { bytes[counter++], bytes[counter++], bytes[counter++], bytes[counter++] }); + + return values; + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/DataItem.cs b/常用工具集/Utility/Network/S7netplus/Types/DataItem.cs new file mode 100644 index 0000000..f5348d2 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/DataItem.cs @@ -0,0 +1,104 @@ +using S7.Net.Protocol.S7; +using System; + +namespace S7.Net.Types +{ + /// + /// Create an instance of a memory block that can be read by using ReadMultipleVars + /// + public class DataItem + { + /// + /// Memory area to read + /// + public DataType DataType { get; set; } + + /// + /// Type of data to be read (default is bytes) + /// + public VarType VarType { get; set; } + + /// + /// Address of memory area to read (example: for DB1 this value is 1, for T45 this value is 45) + /// + public int DB { get; set; } + + /// + /// Address of the first byte to read + /// + public int StartByteAdr { get; set; } + + /// + /// Addess of bit to read from StartByteAdr + /// + public byte BitAdr { get; set; } + + /// + /// Number of variables to read + /// + public int Count { get; set; } + + /// + /// Contains the value of the memory area after the read has been executed + /// + public object Value { get; set; } + + /// + /// Create an instance of DataItem + /// + public DataItem() + { + VarType = VarType.Byte; + Count = 1; + } + + /// + /// Create an instance of from the supplied address. + /// + /// The address to create the DataItem for. + /// A new instance with properties parsed from . + /// The property is not parsed from the address. + public static DataItem FromAddress(string address) + { + PLCAddress.Parse(address, out var dataType, out var dbNumber, out var varType, out var startByte, + out var bitNumber); + + return new DataItem + { + DataType = dataType, + DB = dbNumber, + VarType = varType, + StartByteAdr = startByte, + BitAdr = (byte)(bitNumber == -1 ? 0 : bitNumber) + }; + } + + /// + /// Create an instance of from the supplied address and value. + /// + /// The address to create the DataItem for. + /// The value to be applied to the DataItem. + /// A new instance with properties parsed from and the supplied value set. + public static DataItem FromAddressAndValue(string address, T value) + { + var dataItem = FromAddress(address); + dataItem.Value = value; + + if (typeof(T).IsArray) + { + var array = ((Array)dataItem.Value); + if (array != null) + { + dataItem.Count = array.Length; + } + } + + return dataItem; + } + + internal static DataItemAddress GetDataItemAddress(DataItem dataItem) + { + return new DataItemAddress(dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, Plc.VarTypeToByteLength(dataItem.VarType, dataItem.Count)); + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/DateTime.cs b/常用工具集/Utility/Network/S7netplus/Types/DateTime.cs new file mode 100644 index 0000000..9cafa67 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/DateTime.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; + +namespace S7.Net.Types +{ + /// + /// Contains the methods to convert between and S7 representation of datetime values. + /// + public static class DateTime + { + /// + /// The minimum value supported by the specification. + /// + public static readonly System.DateTime SpecMinimumDateTime = new System.DateTime(1990, 1, 1); + + /// + /// The maximum value supported by the specification. + /// + public static readonly System.DateTime SpecMaximumDateTime = new System.DateTime(2089, 12, 31, 23, 59, 59, 999); + + /// + /// Parses a value from bytes. + /// + /// Input bytes read from PLC. + /// A object representing the value read from PLC. + /// Thrown when the length of + /// is not 8 or any value in + /// is outside the valid range of values. + public static System.DateTime FromByteArray(byte[] bytes) + { + return FromByteArrayImpl(bytes); + } + + /// + /// Parses an array of values from bytes. + /// + /// Input bytes read from PLC. + /// An array of objects representing the values read from PLC. + /// Thrown when the length of + /// is not a multiple of 8 or any value in + /// is outside the valid range of values. + public static System.DateTime[] ToArray(byte[] bytes) + { + if (bytes.Length % 8 != 0) + throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length, + $"Parsing an array of DateTime requires a multiple of 8 bytes of input data, input data is '{bytes.Length}' long."); + + var cnt = bytes.Length / 8; + var result = new System.DateTime[bytes.Length / 8]; + + for (var i = 0; i < cnt; i++) + result[i] = FromByteArrayImpl(new ArraySegment(bytes, i * 8, 8)); + + return result; + } + + private static System.DateTime FromByteArrayImpl(IList bytes) + { + if (bytes.Count != 8) + throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Count, + $"Parsing a DateTime requires exactly 8 bytes of input data, input data is {bytes.Count} bytes long."); + + int DecodeBcd(byte input) => 10 * (input >> 4) + (input & 0b00001111); + + int ByteToYear(byte bcdYear) + { + var input = DecodeBcd(bcdYear); + if (input < 90) return input + 2000; + if (input < 100) return input + 1900; + + throw new ArgumentOutOfRangeException(nameof(bcdYear), bcdYear, + $"Value '{input}' is higher than the maximum '99' of S7 date and time representation."); + } + + int AssertRangeInclusive(int input, byte min, byte max, string field) + { + if (input < min) + throw new ArgumentOutOfRangeException(nameof(input), input, + $"Value '{input}' is lower than the minimum '{min}' allowed for {field}."); + if (input > max) + throw new ArgumentOutOfRangeException(nameof(input), input, + $"Value '{input}' is higher than the maximum '{max}' allowed for {field}."); + + return input; + } + + var year = ByteToYear(bytes[0]); + var month = AssertRangeInclusive(DecodeBcd(bytes[1]), 1, 12, "month"); + var day = AssertRangeInclusive(DecodeBcd(bytes[2]), 1, 31, "day of month"); + var hour = AssertRangeInclusive(DecodeBcd(bytes[3]), 0, 23, "hour"); + var minute = AssertRangeInclusive(DecodeBcd(bytes[4]), 0, 59, "minute"); + var second = AssertRangeInclusive(DecodeBcd(bytes[5]), 0, 59, "second"); + var hsec = AssertRangeInclusive(DecodeBcd(bytes[6]), 0, 99, "first two millisecond digits"); + var msec = AssertRangeInclusive(bytes[7] >> 4, 0, 9, "third millisecond digit"); + var dayOfWeek = AssertRangeInclusive(bytes[7] & 0b00001111, 1, 7, "day of week"); + + return new System.DateTime(year, month, day, hour, minute, second, hsec * 10 + msec); + } + + /// + /// Converts a value to a byte array. + /// + /// The DateTime value to convert. + /// A byte array containing the S7 date time representation of . + /// Thrown when the value of + /// is before + /// or after . + public static byte[] ToByteArray(System.DateTime dateTime) + { + byte EncodeBcd(int value) + { + return (byte) ((value / 10 << 4) | value % 10); + } + + if (dateTime < SpecMinimumDateTime) + throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime, + $"Date time '{dateTime}' is before the minimum '{SpecMinimumDateTime}' supported in S7 date time representation."); + + if (dateTime > SpecMaximumDateTime) + throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime, + $"Date time '{dateTime}' is after the maximum '{SpecMaximumDateTime}' supported in S7 date time representation."); + + byte MapYear(int year) => (byte) (year < 2000 ? year - 1900 : year - 2000); + + int DayOfWeekToInt(DayOfWeek dayOfWeek) => (int) dayOfWeek + 1; + + return new[] + { + EncodeBcd(MapYear(dateTime.Year)), + EncodeBcd(dateTime.Month), + EncodeBcd(dateTime.Day), + EncodeBcd(dateTime.Hour), + EncodeBcd(dateTime.Minute), + EncodeBcd(dateTime.Second), + EncodeBcd(dateTime.Millisecond / 10), + (byte) (dateTime.Millisecond % 10 << 4 | DayOfWeekToInt(dateTime.DayOfWeek)) + }; + } + + /// + /// Converts an array of values to a byte array. + /// + /// The DateTime values to convert. + /// A byte array containing the S7 date time representations of . + /// Thrown when any value of + /// is before + /// or after . + public static byte[] ToByteArray(System.DateTime[] dateTimes) + { + var bytes = new List(dateTimes.Length * 8); + foreach (var dateTime in dateTimes) bytes.AddRange(ToByteArray(dateTime)); + + return bytes.ToArray(); + } + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/S7netplus/Types/DateTimeLong.cs b/常用工具集/Utility/Network/S7netplus/Types/DateTimeLong.cs new file mode 100644 index 0000000..6479f6f --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/DateTimeLong.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace S7.Net.Types +{ + /// + /// Contains the methods to convert between and S7 representation of DateTimeLong (DTL) values. + /// + public static class DateTimeLong + { + public const int TypeLengthInBytes = 12; + /// + /// The minimum value supported by the specification. + /// + public static readonly System.DateTime SpecMinimumDateTime = new System.DateTime(1970, 1, 1); + + /// + /// The maximum value supported by the specification. + /// + public static readonly System.DateTime SpecMaximumDateTime = new System.DateTime(2262, 4, 11, 23, 47, 16, 854); + + /// + /// Parses a value from bytes. + /// + /// Input bytes read from PLC. + /// A object representing the value read from PLC. + /// + /// Thrown when the length of + /// is not 12 or any value in + /// is outside the valid range of values. + /// + public static System.DateTime FromByteArray(byte[] bytes) + { + return FromByteArrayImpl(bytes); + } + + /// + /// Parses an array of values from bytes. + /// + /// Input bytes read from PLC. + /// An array of objects representing the values read from PLC. + /// + /// Thrown when the length of + /// is not a multiple of 12 or any value in + /// is outside the valid range of values. + /// + public static System.DateTime[] ToArray(byte[] bytes) + { + if (bytes.Length % TypeLengthInBytes != 0) + { + throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length, + $"Parsing an array of DateTimeLong requires a multiple of 12 bytes of input data, input data is '{bytes.Length}' long."); + } + + var cnt = bytes.Length / TypeLengthInBytes; + var result = new System.DateTime[cnt]; + + for (var i = 0; i < cnt; i++) + { + var slice = new byte[TypeLengthInBytes]; + Array.Copy(bytes, i * TypeLengthInBytes, slice, 0, TypeLengthInBytes); + result[i] = FromByteArrayImpl(slice); + } + + return result; + } + + private static System.DateTime FromByteArrayImpl(byte[] bytes) + { + if (bytes.Length != TypeLengthInBytes) + { + throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length, + $"Parsing a DateTimeLong requires exactly 12 bytes of input data, input data is {bytes.Length} bytes long."); + } + + + var year = AssertRangeInclusive(Word.FromBytes(bytes[1], bytes[0]), 1970, 2262, "year"); + var month = AssertRangeInclusive(bytes[2], 1, 12, "month"); + var day = AssertRangeInclusive(bytes[3], 1, 31, "day of month"); + var dayOfWeek = AssertRangeInclusive(bytes[4], 1, 7, "day of week"); + var hour = AssertRangeInclusive(bytes[5], 0, 23, "hour"); + var minute = AssertRangeInclusive(bytes[6], 0, 59, "minute"); + var second = AssertRangeInclusive(bytes[7], 0, 59, "second"); + ; + + var nanoseconds = AssertRangeInclusive(DWord.FromBytes(bytes[11], bytes[10], bytes[9], bytes[8]), 0, + 999999999, "nanoseconds"); + + var time = new System.DateTime(year, month, day, hour, minute, second); + return time.AddTicks(nanoseconds / 100); + } + + /// + /// Converts a value to a byte array. + /// + /// The DateTime value to convert. + /// A byte array containing the S7 DateTimeLong representation of . + /// + /// Thrown when the value of + /// is before + /// or after . + /// + public static byte[] ToByteArray(System.DateTime dateTime) + { + if (dateTime < SpecMinimumDateTime) + { + throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime, + $"Date time '{dateTime}' is before the minimum '{SpecMinimumDateTime}' supported in S7 DateTimeLong representation."); + } + + if (dateTime > SpecMaximumDateTime) + { + throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime, + $"Date time '{dateTime}' is after the maximum '{SpecMaximumDateTime}' supported in S7 DateTimeLong representation."); + } + + var stream = new MemoryStream(TypeLengthInBytes); + // Convert Year + stream.Write(Word.ToByteArray(Convert.ToUInt16(dateTime.Year)), 0, 2); + + // Convert Month + stream.WriteByte(Convert.ToByte(dateTime.Month)); + + // Convert Day + stream.WriteByte(Convert.ToByte(dateTime.Day)); + + // Convert WeekDay. NET DateTime starts with Sunday = 0, while S7DT has Sunday = 1. + stream.WriteByte(Convert.ToByte(dateTime.DayOfWeek + 1)); + + // Convert Hour + stream.WriteByte(Convert.ToByte(dateTime.Hour)); + + // Convert Minutes + stream.WriteByte(Convert.ToByte(dateTime.Minute)); + + // Convert Seconds + stream.WriteByte(Convert.ToByte(dateTime.Second)); + + // Convert Nanoseconds. Net DateTime has a representation of 1 Tick = 100ns. + // Thus First take the ticks Mod 1 Second (1s = 10'000'000 ticks), and then Convert to nanoseconds. + stream.Write(DWord.ToByteArray(Convert.ToUInt32(dateTime.Ticks % 10000000 * 100)), 0, 4); + + return stream.ToArray(); + } + + /// + /// Converts an array of values to a byte array. + /// + /// The DateTime values to convert. + /// A byte array containing the S7 DateTimeLong representations of . + /// + /// Thrown when any value of + /// is before + /// or after . + /// + public static byte[] ToByteArray(System.DateTime[] dateTimes) + { + var bytes = new List(dateTimes.Length * TypeLengthInBytes); + foreach (var dateTime in dateTimes) + { + bytes.AddRange(ToByteArray(dateTime)); + } + + return bytes.ToArray(); + } + + private static T AssertRangeInclusive(T input, T min, T max, string field) where T : IComparable + { + if (input.CompareTo(min) < 0) + { + throw new ArgumentOutOfRangeException(nameof(input), input, + $"Value '{input}' is lower than the minimum '{min}' allowed for {field}."); + } + + if (input.CompareTo(max) > 0) + { + throw new ArgumentOutOfRangeException(nameof(input), input, + $"Value '{input}' is higher than the maximum '{max}' allowed for {field}."); + } + + return input; + } + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Network/S7netplus/Types/Double.cs b/常用工具集/Utility/Network/S7netplus/Types/Double.cs new file mode 100644 index 0000000..c9daf24 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/Double.cs @@ -0,0 +1,68 @@ +using System; + +namespace S7.Net.Types +{ + /// + /// Contains the conversion methods to convert Real from S7 plc to C# double. + /// + [Obsolete("Class Double is obsolete. Use Real instead for 32bit floating point, or LReal for 64bit floating point.")] + public static class Double + { + /// + /// Converts a S7 Real (4 bytes) to double + /// + public static double FromByteArray(byte[] bytes) => Real.FromByteArray(bytes); + + /// + /// Converts a S7 DInt to double + /// + public static double FromDWord(Int32 value) + { + byte[] b = DInt.ToByteArray(value); + double d = FromByteArray(b); + return d; + } + + /// + /// Converts a S7 DWord to double + /// + public static double FromDWord(UInt32 value) + { + byte[] b = DWord.ToByteArray(value); + double d = FromByteArray(b); + return d; + } + + + /// + /// Converts a double to S7 Real (4 bytes) + /// + public static byte[] ToByteArray(double value) => Real.ToByteArray((float)value); + + /// + /// Converts an array of double to an array of bytes + /// + public static byte[] ToByteArray(double[] value) + { + ByteArray arr = new ByteArray(); + foreach (double val in value) + arr.Add(ToByteArray(val)); + return arr.Array; + } + + /// + /// Converts an array of S7 Real to an array of double + /// + public static double[] ToArray(byte[] bytes) + { + double[] values = new double[bytes.Length / 4]; + + int counter = 0; + for (int cnt = 0; cnt < bytes.Length / 4; cnt++) + values[cnt] = FromByteArray(new byte[] { bytes[counter++], bytes[counter++], bytes[counter++], bytes[counter++] }); + + return values; + } + + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/Int.cs b/常用工具集/Utility/Network/S7netplus/Types/Int.cs new file mode 100644 index 0000000..5489691 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/Int.cs @@ -0,0 +1,87 @@ +using System; + +namespace S7.Net.Types +{ + /// + /// Contains the conversion methods to convert Int from S7 plc to C#. + /// + public static class Int + { + /// + /// Converts a S7 Int (2 bytes) to short (Int16) + /// + public static short FromByteArray(byte[] bytes) + { + if (bytes.Length != 2) + { + throw new ArgumentException("Wrong number of bytes. Bytes array must contain 2 bytes."); + } + // bytes[0] -> HighByte + // bytes[1] -> LowByte + return (short)((int)(bytes[1]) | ((int)(bytes[0]) << 8)); + } + + + /// + /// Converts a short (Int16) to a S7 Int byte array (2 bytes) + /// + public static byte[] ToByteArray(Int16 value) + { + byte[] bytes = new byte[2]; + + bytes[0] = (byte) (value >> 8 & 0xFF); + bytes[1] = (byte)(value & 0xFF); + + return bytes; + } + + /// + /// Converts an array of short (Int16) to a S7 Int byte array (2 bytes) + /// + public static byte[] ToByteArray(Int16[] value) + { + byte[] bytes = new byte[value.Length * 2]; + int bytesPos = 0; + + for(int i=0; i< value.Length; i++) + { + bytes[bytesPos++] = (byte)((value[i] >> 8) & 0xFF); + bytes[bytesPos++] = (byte) (value[i] & 0xFF); + } + return bytes; + } + + /// + /// Converts an array of S7 Int to an array of short (Int16) + /// + public static Int16[] ToArray(byte[] bytes) + { + int shortsCount = bytes.Length / 2; + + Int16[] values = new Int16[shortsCount]; + + int counter = 0; + for (int cnt = 0; cnt < shortsCount; cnt++) + values[cnt] = FromByteArray(new byte[] { bytes[counter++], bytes[counter++] }); + + return values; + } + + /// + /// Converts a C# int value to a C# short value, to be used as word. + /// + /// + /// + public static Int16 CWord(int value) + { + if (value > 32767) + { + value -= 32768; + value = 32768 - value; + value *= -1; + } + return (short)value; + } + + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/LReal.cs b/常用工具集/Utility/Network/S7netplus/Types/LReal.cs new file mode 100644 index 0000000..c541645 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/LReal.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; + +namespace S7.Net.Types +{ + /// + /// Contains the conversion methods to convert Real from S7 plc to C# double. + /// + public static class LReal + { + /// + /// Converts a S7 LReal (8 bytes) to double + /// + public static double FromByteArray(byte[] bytes) + { + if (bytes.Length != 8) + { + throw new ArgumentException("Wrong number of bytes. Bytes array must contain 8 bytes."); + } + var buffer = bytes; + + // sps uses bigending so we have to reverse if platform needs + if (BitConverter.IsLittleEndian) + { + Array.Reverse(buffer); + } + + return BitConverter.ToDouble(buffer, 0); + } + + /// + /// Converts a double to S7 LReal (8 bytes) + /// + public static byte[] ToByteArray(double value) + { + var bytes = BitConverter.GetBytes(value); + + // sps uses bigending so we have to check if platform is same + if (BitConverter.IsLittleEndian) + { + Array.Reverse(bytes); + } + return bytes; + } + + /// + /// Converts an array of double to an array of bytes + /// + public static byte[] ToByteArray(double[] value) => TypeHelper.ToByteArray(value, ToByteArray); + + /// + /// Converts an array of S7 LReal to an array of double + /// + public static double[] ToArray(byte[] bytes) => TypeHelper.ToArray(bytes, FromByteArray); + + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/Real.cs b/常用工具集/Utility/Network/S7netplus/Types/Real.cs new file mode 100644 index 0000000..1d28202 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/Real.cs @@ -0,0 +1,75 @@ +using System; +using System.IO; + +namespace S7.Net.Types +{ + /// + /// Contains the conversion methods to convert Real from S7 plc to C# double. + /// + public static class Real + { + /// + /// Converts a S7 Real (4 bytes) to float + /// + public static float FromByteArray(byte[] bytes) + { + if (bytes.Length != 4) + { + throw new ArgumentException("Wrong number of bytes. Bytes array must contain 4 bytes."); + } + + // sps uses bigending so we have to reverse if platform needs + if (BitConverter.IsLittleEndian) + { + // create deep copy of the array and reverse + bytes = new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] }; + } + + return BitConverter.ToSingle(bytes, 0); + } + + /// + /// Converts a float to S7 Real (4 bytes) + /// + public static byte[] ToByteArray(float value) + { + byte[] bytes = BitConverter.GetBytes(value); + + // sps uses bigending so we have to check if platform is same + if (!BitConverter.IsLittleEndian) return bytes; + + // create deep copy of the array and reverse + return new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] }; + } + + /// + /// Converts an array of float to an array of bytes + /// + public static byte[] ToByteArray(float[] value) + { + var buffer = new byte[4 * value.Length]; + var stream = new MemoryStream(buffer); + foreach (var val in value) + { + stream.Write(ToByteArray(val), 0, 4); + } + + return buffer; + } + + /// + /// Converts an array of S7 Real to an array of float + /// + public static float[] ToArray(byte[] bytes) + { + var values = new float[bytes.Length / 4]; + + int counter = 0; + for (int cnt = 0; cnt < bytes.Length / 4; cnt++) + values[cnt] = FromByteArray(new byte[] { bytes[counter++], bytes[counter++], bytes[counter++], bytes[counter++] }); + + return values; + } + + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/S7String.cs b/常用工具集/Utility/Network/S7netplus/Types/S7String.cs new file mode 100644 index 0000000..06cef56 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/S7String.cs @@ -0,0 +1,80 @@ +using System; +using System.Text; + +namespace S7.Net.Types +{ + /// + /// Contains the methods to convert from S7 strings to C# strings + /// An S7 String has a preceeding 2 byte header containing its capacity and length + /// + public static class S7String + { + private static Encoding stringEncoding = Encoding.ASCII; + + /// + /// The Encoding used when serializing and deserializing S7String (Encoding.ASCII by default) + /// + /// StringEncoding must not be null + public static Encoding StringEncoding + { + get => stringEncoding; + set => stringEncoding = value ?? throw new ArgumentNullException(nameof(StringEncoding)); + } + + /// + /// Converts S7 bytes to a string + /// + /// + /// + public static string FromByteArray(byte[] bytes) + { + if (bytes.Length < 2) + { + throw new PlcException(ErrorCode.ReadData, "Malformed S7 String / too short"); + } + + int size = bytes[0]; + int length = bytes[1]; + if (length > size) + { + throw new PlcException(ErrorCode.ReadData, "Malformed S7 String / length larger than capacity"); + } + + try + { + return StringEncoding.GetString(bytes, 2, length); + } + catch (Exception e) + { + throw new PlcException(ErrorCode.ReadData, + $"Failed to parse {VarType.S7String} from data. Following fields were read: size: '{size}', actual length: '{length}', total number of bytes (including header): '{bytes.Length}'.", + e); + } + } + + /// + /// Converts a to S7 string with 2-byte header. + /// + /// The string to convert to byte array. + /// The length (in characters) allocated in PLC for the string. + /// A containing the string header and string value with a maximum length of + 2. + public static byte[] ToByteArray(string value, int reservedLength) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (reservedLength > 254) throw new ArgumentException($"The maximum string length supported is 254."); + + var bytes = StringEncoding.GetBytes(value); + if (bytes.Length > reservedLength) throw new ArgumentException($"The provided string length ({bytes.Length} is larger than the specified reserved length ({reservedLength})."); + + var buffer = new byte[2 + reservedLength]; + Array.Copy(bytes, 0, buffer, 2, bytes.Length); + buffer[0] = (byte)reservedLength; + buffer[1] = (byte)bytes.Length; + return buffer; + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/S7StringAttribute.cs b/常用工具集/Utility/Network/S7netplus/Types/S7StringAttribute.cs new file mode 100644 index 0000000..768667d --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/S7StringAttribute.cs @@ -0,0 +1,67 @@ +using System; + +namespace S7.Net.Types +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] + public sealed class S7StringAttribute : Attribute + { + private readonly S7StringType type; + private readonly int reservedLength; + + /// + /// Initializes a new instance of the class. + /// + /// The string type. + /// Reserved length of the string in characters. + /// Please use a valid value for the string type + public S7StringAttribute(S7StringType type, int reservedLength) + { + if (!Enum.IsDefined(typeof(S7StringType), type)) + throw new ArgumentException("Please use a valid value for the string type"); + + this.type = type; + this.reservedLength = reservedLength; + } + + /// + /// Gets the type of the string. + /// + /// + /// The string type. + /// + public S7StringType Type => type; + + /// + /// Gets the reserved length of the string in characters. + /// + /// + /// The reserved length of the string in characters. + /// + public int ReservedLength => reservedLength; + + /// + /// Gets the reserved length in bytes. + /// + /// + /// The reserved length in bytes. + /// + public int ReservedLengthInBytes => type == S7StringType.S7String ? reservedLength + 2 : (reservedLength * 2) + 4; + } + + + /// + /// String type. + /// + public enum S7StringType + { + /// + /// ASCII string. + /// + S7String = VarType.S7String, + + /// + /// Unicode string. + /// + S7WString = VarType.S7WString + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/S7WString.cs b/常用工具集/Utility/Network/S7netplus/Types/S7WString.cs new file mode 100644 index 0000000..8d8aabf --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/S7WString.cs @@ -0,0 +1,72 @@ +using System; +using System.Text; + +namespace S7.Net.Types +{ + /// + /// Contains the methods to convert from S7 wstrings to C# strings + /// An S7 WString has a preceding 4 byte header containing its capacity and length + /// + public static class S7WString + { + /// + /// Converts S7 bytes to a string + /// + /// + /// + public static string FromByteArray(byte[] bytes) + { + if (bytes.Length < 4) + { + throw new PlcException(ErrorCode.ReadData, "Malformed S7 WString / too short"); + } + + int size = (bytes[0] << 8) | bytes[1]; + int length = (bytes[2] << 8) | bytes[3]; + + if (length > size) + { + throw new PlcException(ErrorCode.ReadData, "Malformed S7 WString / length larger than capacity"); + } + + try + { + return Encoding.BigEndianUnicode.GetString(bytes, 4, length * 2); + } + catch (Exception e) + { + throw new PlcException(ErrorCode.ReadData, + $"Failed to parse {VarType.S7WString} from data. Following fields were read: size: '{size}', actual length: '{length}', total number of bytes (including header): '{bytes.Length}'.", + e); + } + + } + + /// + /// Converts a to S7 wstring with 4-byte header. + /// + /// The string to convert to byte array. + /// The length (in characters) allocated in PLC for the string. + /// A containing the string header and string value with a maximum length of + 4. + public static byte[] ToByteArray(string value, int reservedLength) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (reservedLength > 16382) throw new ArgumentException("The maximum string length supported is 16382."); + + var buffer = new byte[4 + reservedLength * 2]; + buffer[0] = (byte)((reservedLength >> 8) & 0xFF); + buffer[1] = (byte)(reservedLength & 0xFF); + buffer[2] = (byte)((value.Length >> 8) & 0xFF); + buffer[3] = (byte)(value.Length & 0xFF); + + var stringLength = Encoding.BigEndianUnicode.GetBytes(value, 0, value.Length, buffer, 4) / 2; + if (stringLength > reservedLength) throw new ArgumentException($"The provided string length ({stringLength} is larger than the specified reserved length ({reservedLength})."); + + return buffer; + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/Single.cs b/常用工具集/Utility/Network/S7netplus/Types/Single.cs new file mode 100644 index 0000000..4f27553 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/Single.cs @@ -0,0 +1,68 @@ +using System; + +namespace S7.Net.Types +{ + /// + /// Contains the conversion methods to convert Real from S7 plc to C# float. + /// + [Obsolete("Class Single is obsolete. Use Real instead.")] + public static class Single + { + /// + /// Converts a S7 Real (4 bytes) to float + /// + public static float FromByteArray(byte[] bytes) => Real.FromByteArray(bytes); + + /// + /// Converts a S7 DInt to float + /// + public static float FromDWord(Int32 value) + { + byte[] b = DInt.ToByteArray(value); + float d = FromByteArray(b); + return d; + } + + /// + /// Converts a S7 DWord to float + /// + public static float FromDWord(UInt32 value) + { + byte[] b = DWord.ToByteArray(value); + float d = FromByteArray(b); + return d; + } + + + /// + /// Converts a double to S7 Real (4 bytes) + /// + public static byte[] ToByteArray(float value) => Real.ToByteArray(value); + + /// + /// Converts an array of float to an array of bytes + /// + public static byte[] ToByteArray(float[] value) + { + ByteArray arr = new ByteArray(); + foreach (float val in value) + arr.Add(ToByteArray(val)); + return arr.Array; + } + + /// + /// Converts an array of S7 Real to an array of float + /// + public static float[] ToArray(byte[] bytes) + { + float[] values = new float[bytes.Length / 4]; + + int counter = 0; + for (int cnt = 0; cnt < bytes.Length / 4; cnt++) + values[cnt] = FromByteArray(new byte[] { bytes[counter++], bytes[counter++], bytes[counter++], bytes[counter++] }); + + return values; + } + + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/String.cs b/常用工具集/Utility/Network/S7netplus/Types/String.cs new file mode 100644 index 0000000..3917635 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/String.cs @@ -0,0 +1,37 @@ +namespace S7.Net.Types +{ + /// + /// Contains the methods to convert from S7 Array of Chars (like a const char[N] C-String) to C# strings + /// + public class String + { + /// + /// Converts a string to of bytes, padded with 0-bytes if required. + /// + /// The string to write to the PLC. + /// The amount of bytes reserved for the in the PLC. + public static byte[] ToByteArray(string value, int reservedLength) + { + var length = value?.Length; + if (length > reservedLength) length = reservedLength; + var bytes = new byte[reservedLength]; + + if (length == null || length == 0) return bytes; + + System.Text.Encoding.ASCII.GetBytes(value, 0, length.Value, bytes, 0); + + return bytes; + } + + /// + /// Converts S7 bytes to a string + /// + /// + /// + public static string FromByteArray(byte[] bytes) + { + return System.Text.Encoding.ASCII.GetString(bytes); + } + + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/StringEx.cs b/常用工具集/Utility/Network/S7netplus/Types/StringEx.cs new file mode 100644 index 0000000..6c0381a --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/StringEx.cs @@ -0,0 +1,15 @@ +using System; + +namespace S7.Net.Types +{ + /// + [Obsolete("Please use S7String class")] + public static class StringEx + { + /// + public static string FromByteArray(byte[] bytes) => S7String.FromByteArray(bytes); + + /// + public static byte[] ToByteArray(string value, int reservedLength) => S7String.ToByteArray(value, reservedLength); + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/Struct.cs b/常用工具集/Utility/Network/S7netplus/Types/Struct.cs new file mode 100644 index 0000000..21d0714 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/Struct.cs @@ -0,0 +1,310 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace S7.Net.Types +{ + /// + /// Contains the method to convert a C# struct to S7 data types + /// + public static class Struct + { + /// + /// Gets the size of the struct in bytes. + /// + /// the type of the struct + /// the number of bytes + public static int GetStructSize(Type structType) + { + double numBytes = 0.0; + + var infos = structType.GetFields(); + + foreach (var info in infos) + { + switch (info.FieldType.Name) + { + case "Boolean": + numBytes += 0.125; + break; + case "Byte": + numBytes = Math.Ceiling(numBytes); + numBytes++; + break; + case "Int16": + case "UInt16": + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + numBytes += 2; + break; + case "Int32": + case "UInt32": + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + numBytes += 4; + break; + case "Single": + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + numBytes += 4; + break; + case "Double": + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + numBytes += 8; + break; + case "String": + S7StringAttribute attribute = info.GetCustomAttributes().SingleOrDefault(); + if (attribute == default(S7StringAttribute)) + throw new ArgumentException("Please add S7StringAttribute to the string field"); + + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + numBytes += attribute.ReservedLengthInBytes; + break; + default: + numBytes += GetStructSize(info.FieldType); + break; + } + } + return (int)numBytes; + } + + /// + /// Creates a struct of a specified type by an array of bytes. + /// + /// The struct type + /// The array of bytes + /// The object depending on the struct type or null if fails(array-length != struct-length + public static object FromBytes(Type structType, byte[] bytes) + { + if (bytes == null) + return null; + + if (bytes.Length != GetStructSize(structType)) + return null; + + // and decode it + int bytePos = 0; + int bitPos = 0; + double numBytes = 0.0; + object structValue = Activator.CreateInstance(structType); + + + var infos = structValue.GetType().GetFields(); + + foreach (var info in infos) + { + switch (info.FieldType.Name) + { + case "Boolean": + // get the value + bytePos = (int)Math.Floor(numBytes); + bitPos = (int)((numBytes - (double)bytePos) / 0.125); + if ((bytes[bytePos] & (int)Math.Pow(2, bitPos)) != 0) + info.SetValue(structValue, true); + else + info.SetValue(structValue, false); + numBytes += 0.125; + break; + case "Byte": + numBytes = Math.Ceiling(numBytes); + info.SetValue(structValue, (byte)(bytes[(int)numBytes])); + numBytes++; + break; + case "Int16": + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + // get the value + ushort source = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]); + info.SetValue(structValue, source.ConvertToShort()); + numBytes += 2; + break; + case "UInt16": + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + // get the value + info.SetValue(structValue, Word.FromBytes(bytes[(int)numBytes + 1], + bytes[(int)numBytes])); + numBytes += 2; + break; + case "Int32": + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + // get the value + uint sourceUInt = DWord.FromBytes(bytes[(int)numBytes + 3], + bytes[(int)numBytes + 2], + bytes[(int)numBytes + 1], + bytes[(int)numBytes + 0]); + info.SetValue(structValue, sourceUInt.ConvertToInt()); + numBytes += 4; + break; + case "UInt32": + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + // get the value + info.SetValue(structValue, DWord.FromBytes(bytes[(int)numBytes], + bytes[(int)numBytes + 1], + bytes[(int)numBytes + 2], + bytes[(int)numBytes + 3])); + numBytes += 4; + break; + case "Single": + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + // get the value + info.SetValue(structValue, Real.FromByteArray(new byte[] { bytes[(int)numBytes], + bytes[(int)numBytes + 1], + bytes[(int)numBytes + 2], + bytes[(int)numBytes + 3] })); + numBytes += 4; + break; + case "Double": + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + // get the value + var data = new byte[8]; + Array.Copy(bytes, (int)numBytes, data, 0, 8); + info.SetValue(structValue, LReal.FromByteArray(data)); + numBytes += 8; + break; + case "String": + S7StringAttribute attribute = info.GetCustomAttributes().SingleOrDefault(); + if (attribute == default(S7StringAttribute)) + throw new ArgumentException("Please add S7StringAttribute to the string field"); + + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + + // get the value + var sData = new byte[attribute.ReservedLengthInBytes]; + Array.Copy(bytes, (int)numBytes, sData, 0, sData.Length); + switch (attribute.Type) + { + case S7StringType.S7String: + info.SetValue(structValue, S7String.FromByteArray(sData)); + break; + case S7StringType.S7WString: + info.SetValue(structValue, S7WString.FromByteArray(sData)); + break; + default: + throw new ArgumentException("Please use a valid string type for the S7StringAttribute"); + } + + numBytes += sData.Length; + break; + default: + var buffer = new byte[GetStructSize(info.FieldType)]; + if (buffer.Length == 0) + continue; + Buffer.BlockCopy(bytes, (int)Math.Ceiling(numBytes), buffer, 0, buffer.Length); + info.SetValue(structValue, FromBytes(info.FieldType, buffer)); + numBytes += buffer.Length; + break; + } + } + return structValue; + } + + /// + /// Creates a byte array depending on the struct type. + /// + /// The struct object + /// A byte array or null if fails. + public static byte[] ToBytes(object structValue) + { + Type type = structValue.GetType(); + + int size = Struct.GetStructSize(type); + byte[] bytes = new byte[size]; + byte[] bytes2 = null; + + int bytePos = 0; + int bitPos = 0; + double numBytes = 0.0; + + var infos = type.GetFields(); + + + foreach (var info in infos) + { + bytes2 = null; + switch (info.FieldType.Name) + { + case "Boolean": + // get the value + bytePos = (int)Math.Floor(numBytes); + bitPos = (int)((numBytes - (double)bytePos) / 0.125); + if ((bool)info.GetValue(structValue)) + bytes[bytePos] |= (byte)Math.Pow(2, bitPos); // is true + else + bytes[bytePos] &= (byte)(~(byte)Math.Pow(2, bitPos)); // is false + numBytes += 0.125; + break; + case "Byte": + numBytes = (int)Math.Ceiling(numBytes); + bytePos = (int)numBytes; + bytes[bytePos] = (byte)info.GetValue(structValue); + numBytes++; + break; + case "Int16": + bytes2 = Int.ToByteArray((Int16)info.GetValue(structValue)); + break; + case "UInt16": + bytes2 = Word.ToByteArray((UInt16)info.GetValue(structValue)); + break; + case "Int32": + bytes2 = DInt.ToByteArray((Int32)info.GetValue(structValue)); + break; + case "UInt32": + bytes2 = DWord.ToByteArray((UInt32)info.GetValue(structValue)); + break; + case "Single": + bytes2 = Real.ToByteArray((float)info.GetValue(structValue)); + break; + case "Double": + bytes2 = LReal.ToByteArray((double)info.GetValue(structValue)); + break; + case "String": + S7StringAttribute attribute = info.GetCustomAttributes().SingleOrDefault(); + if (attribute == default(S7StringAttribute)) + throw new ArgumentException("Please add S7StringAttribute to the string field"); + switch (attribute.Type) + { + case S7StringType.S7String: + bytes2 = S7String.ToByteArray((string)info.GetValue(structValue), attribute.ReservedLength); break; + case S7StringType.S7WString: + bytes2 = S7WString.ToByteArray((string)info.GetValue(structValue), attribute.ReservedLength); break; + default: + throw new ArgumentException("Please use a valid string type for the S7StringAttribute"); + } + break; + } + if (bytes2 != null) + { + // add them + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + bytePos = (int)numBytes; + for (int bCnt = 0; bCnt < bytes2.Length; bCnt++) + bytes[bytePos + bCnt] = bytes2[bCnt]; + numBytes += bytes2.Length; + } + } + return bytes; + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/Timer.cs b/常用工具集/Utility/Network/S7netplus/Types/Timer.cs new file mode 100644 index 0000000..b5ecd45 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/Timer.cs @@ -0,0 +1,82 @@ +using System; + +namespace S7.Net.Types +{ + /// + /// Converts the Timer data type to C# data type + /// + public static class Timer + { + /// + /// Converts the timer bytes to a double + /// + public static double FromByteArray(byte[] bytes) + { + double wert = 0; + + wert = ((bytes[0]) & 0x0F) * 100.0; + wert += ((bytes[1] >> 4) & 0x0F) * 10.0; + wert += ((bytes[1]) & 0x0F) * 1.0; + + // this value is not used... may for a nother exponation + //int unknown = (bytes[0] >> 6) & 0x03; + + switch ((bytes[0] >> 4) & 0x03) + { + case 0: + wert *= 0.01; + break; + case 1: + wert *= 0.1; + break; + case 2: + wert *= 1.0; + break; + case 3: + wert *= 10.0; + break; + } + + return wert; + } + + /// + /// Converts a ushort (UInt16) to an array of bytes formatted as time + /// + public static byte[] ToByteArray(UInt16 value) + { + byte[] bytes = new byte[2]; + bytes[1] = (byte)((int)value & 0xFF); + bytes[0] = (byte)((int)value >> 8 & 0xFF); + + return bytes; + } + + /// + /// Converts an array of ushorts (Uint16) to an array of bytes formatted as time + /// + public static byte[] ToByteArray(UInt16[] value) + { + ByteArray arr = new ByteArray(); + foreach (UInt16 val in value) + arr.Add(ToByteArray(val)); + return arr.Array; + } + + /// + /// Converts an array of bytes formatted as time to an array of doubles + /// + /// + /// + public static double[] ToArray(byte[] bytes) + { + double[] values = new double[bytes.Length / 2]; + + int counter = 0; + for (int cnt = 0; cnt < bytes.Length / 2; cnt++) + values[cnt] = FromByteArray(new byte[] { bytes[counter++], bytes[counter++] }); + + return values; + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/TypeHelper.cs b/常用工具集/Utility/Network/S7netplus/Types/TypeHelper.cs new file mode 100644 index 0000000..af5441b --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/TypeHelper.cs @@ -0,0 +1,43 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace S7.Net.Types +{ + internal static class TypeHelper + { + /// + /// Converts an array of T to an array of bytes + /// + public static byte[] ToByteArray(T[] value, Func converter) where T : struct + { + var buffer = new byte[Marshal.SizeOf(default(T)) * value.Length]; + var stream = new MemoryStream(buffer); + foreach (var val in value) + { + stream.Write(converter(val), 0, 4); + } + + return buffer; + } + + /// + /// Converts an array of T repesented as S7 binary data to an array of T + /// + public static T[] ToArray(byte[] bytes, Func converter) where T : struct + { + var typeSize = Marshal.SizeOf(default(T)); + var entries = bytes.Length / typeSize; + var values = new T[entries]; + + for(int i = 0; i < entries; ++i) + { + var buffer = new byte[typeSize]; + Array.Copy(bytes, i * typeSize, buffer, 0, typeSize); + values[i] = converter(buffer); + } + + return values; + } + } +} diff --git a/常用工具集/Utility/Network/S7netplus/Types/Word.cs b/常用工具集/Utility/Network/S7netplus/Types/Word.cs new file mode 100644 index 0000000..8004992 --- /dev/null +++ b/常用工具集/Utility/Network/S7netplus/Types/Word.cs @@ -0,0 +1,71 @@ +using System; + +namespace S7.Net.Types +{ + /// + /// Contains the conversion methods to convert Words from S7 plc to C#. + /// + public static class Word + { + /// + /// Converts a word (2 bytes) to ushort (UInt16) + /// + public static UInt16 FromByteArray(byte[] bytes) + { + if (bytes.Length != 2) + { + throw new ArgumentException("Wrong number of bytes. Bytes array must contain 2 bytes."); + } + + return (UInt16)((bytes[0] << 8) | bytes[1]); + } + + + /// + /// Converts 2 bytes to ushort (UInt16) + /// + public static UInt16 FromBytes(byte b1, byte b2) + { + return (UInt16)((b2 << 8) | b1); + } + + + /// + /// Converts a ushort (UInt16) to word (2 bytes) + /// + public static byte[] ToByteArray(UInt16 value) + { + byte[] bytes = new byte[2]; + + bytes[1] = (byte)(value & 0xFF); + bytes[0] = (byte)((value>>8) & 0xFF); + + return bytes; + } + + /// + /// Converts an array of ushort (UInt16) to an array of bytes + /// + public static byte[] ToByteArray(UInt16[] value) + { + ByteArray arr = new ByteArray(); + foreach (UInt16 val in value) + arr.Add(ToByteArray(val)); + return arr.Array; + } + + /// + /// Converts an array of bytes to an array of ushort + /// + public static UInt16[] ToArray(byte[] bytes) + { + UInt16[] values = new UInt16[bytes.Length/2]; + + int counter = 0; + for (int cnt = 0; cnt < bytes.Length/2; cnt++) + values[cnt] = FromByteArray(new byte[] {bytes[counter++], bytes[counter++]}); + + return values; + } + } +} diff --git a/常用工具集/Utility/Network/SocketHelper.cs b/常用工具集/Utility/Network/SocketHelper.cs new file mode 100644 index 0000000..94e594b --- /dev/null +++ b/常用工具集/Utility/Network/SocketHelper.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; + +namespace 常用工具集.Utility.Network +{ + public class SocketHelper + { + + //1.声明关于事件的委托; + public delegate void MessageEventHandler(object sender, byte[] bytesList); + //2.声明事件; + public event MessageEventHandler MessageHandler; + + private TimeOutSocket socket; + private string ipAddress; + private int port; + + public SocketHelper(string ipAddress, int port) + { + this.ipAddress = ipAddress; + this.port = port; + socket = new TimeOutSocket(ipAddress, port, 1000); + } + public SocketHelper() + { + } + + public void SendAsyn(string ipAddress, int port, byte[] bytes) + { + new Thread(() => + { + + try + { + byte[] retBytes = new byte[1024]; + TimeOutSocket socket = new TimeOutSocket(ipAddress, port, 1000); + TcpClient client = socket.Connect(); + client.SendTimeout = 1000; + client.ReceiveTimeout = 1000; + NetworkStream ns = client.GetStream(); + ns.Write(bytes, 0, bytes.Length); + Thread.Sleep(250); + int length = ns.Read(retBytes, 0, retBytes.Length); + ns.Close(); + client.Close(); + byte[] newRetBytes = new byte[length]; + for (int i = 0; i < length; i++) + newRetBytes[i] = retBytes[i]; + MessageHandler?.Invoke(this, newRetBytes); + } + catch (Exception ex) + { + byte[] retBytes = new byte[1]; + retBytes[0] = 0x00; + MessageHandler?.Invoke(this, retBytes); + } + }).Start(); + + } + } +} diff --git a/常用工具集/Utility/Network/TCPUtils.cs b/常用工具集/Utility/Network/TCPUtils.cs new file mode 100644 index 0000000..3df4230 --- /dev/null +++ b/常用工具集/Utility/Network/TCPUtils.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace 常用工具集.Utility.Network +{ + public class TCPUtils + { + private TcpClient tcpClient; + private string ipAddress = "127.0.0.1"; + private int port = 502; + private int connectTimeout = 1000; + + private bool connected = false; + + private NetworkStream stream; + + /// + /// 判断是否已经连接 + /// + public bool Connected + { + get + { + if (tcpClient == null) + { + return false; + } + return connected; + } + } + + public string IPAddress + { + get + { + return ipAddress; + } + set + { + ipAddress = value; + } + } + + public int Port + { + get + { + return port; + } + set + { + port = value; + } + } + + public int ConnectionTimeout + { + get + { + return connectTimeout; + } + set + { + connectTimeout = value; + } + } + + public TCPUtils() { } + public TCPUtils(string ipAddress, int port) + { + this.ipAddress = ipAddress; + this.port = port; + } + + public TCPUtils(string ipAddress, int port,int timeout) + { + this.ipAddress = ipAddress; + this.port = port; + this.ConnectionTimeout = timeout; + } + + public void Connect() + { + tcpClient = new TcpClient(); + IAsyncResult asyncResult = tcpClient.BeginConnect(ipAddress, port, null, null); + if (!asyncResult.AsyncWaitHandle.WaitOne(connectTimeout)) + { + throw new Exception("connection timed out"); + } + tcpClient.EndConnect(asyncResult); + stream = tcpClient.GetStream(); + stream.ReadTimeout = connectTimeout; + connected = true; + } + + public void Connect(string ipAddress, int port) + { + tcpClient = new TcpClient(); + IAsyncResult asyncResult = tcpClient.BeginConnect(ipAddress, port, null, null); + if (!asyncResult.AsyncWaitHandle.WaitOne(connectTimeout)) + { + throw new Exception("connection timed out"); + } + tcpClient.EndConnect(asyncResult); + stream = tcpClient.GetStream(); + stream.ReadTimeout = connectTimeout; + connected = true; + } + + public byte[] Send(byte[] sendData) + { + if (tcpClient == null) + { + throw new Exception("connection error"); + } + if (!tcpClient.Client.Connected) + { + throw new Exception("connection error"); + } + //连接成功 + stream.Write(sendData, 0, sendData.Length); + byte[] revData = new byte[512]; + int num = stream.Read(revData, 0, revData.Length); + return revData.Take(num).ToArray(); + } + + public void Disconnect() + { + if (stream != null) + { + stream.Close(); + } + if (tcpClient != null) + { + tcpClient.Close(); + } + connected = false; + } + + ~TCPUtils() + { + if (tcpClient != null) + { + if (stream != null) + { + stream.Close(); + } + tcpClient.Close(); + } + } + + public bool Available(int timeout) + { + Ping ping = new Ping(); + IPAddress address = System.Net.IPAddress.Parse(ipAddress); + string s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + byte[] bytes = Encoding.ASCII.GetBytes(s); + PingReply pingReply = ping.Send(address, timeout, bytes); + if (pingReply.Status == IPStatus.Success) + { + return true; + } + return false; + } + } +} diff --git a/常用工具集/Utility/Network/TimeOutSocket.cs b/常用工具集/Utility/Network/TimeOutSocket.cs new file mode 100644 index 0000000..59605ee --- /dev/null +++ b/常用工具集/Utility/Network/TimeOutSocket.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace 常用工具集.Utility.Network +{ + public class TimeOutSocket + { + private bool IsConnectionSuccessful = false; + private Exception socketexception; + private ManualResetEvent TimeoutObject = new ManualResetEvent(false); + private string ip; + private int port; + private int timeout; + private TcpClient tcpClient; + public TimeOutSocket(string hostname, int port, int timeout_milliseconds) + { + this.ip = hostname; + this.port = port; + this.timeout = timeout_milliseconds; + } + + public TcpClient Connect() + { + TimeoutObject.Reset(); + socketexception = null; + + TcpClient tcpclient = new TcpClient(); + tcpclient.BeginConnect(this.ip, this.port, new AsyncCallback(CallBackMethod), tcpclient); + + if (TimeoutObject.WaitOne(timeout, false)) + { + if (IsConnectionSuccessful) + { + return tcpclient; + } + else + { + throw socketexception; + } + } + else + { + tcpclient.Close(); + throw new TimeoutException("TimeOut Exception"); + } + } + + public void Disconnect() + { + tcpClient.Close(); + } + private void CallBackMethod(IAsyncResult asyncresult) + { + try + { + IsConnectionSuccessful = false; + TcpClient tcpclient = asyncresult.AsyncState as TcpClient; + + if (tcpclient.Client != null) + { + tcpclient.EndConnect(asyncresult); + IsConnectionSuccessful = true; + } + } + catch (Exception ex) + { + IsConnectionSuccessful = false; + socketexception = ex; + } + finally + { + TimeoutObject.Set(); + } + } + } +} diff --git a/常用工具集/Utility/Qrcode/BarCodeUtils.cs b/常用工具集/Utility/Qrcode/BarCodeUtils.cs new file mode 100644 index 0000000..0a06a29 --- /dev/null +++ b/常用工具集/Utility/Qrcode/BarCodeUtils.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SkiaSharp; +using ZXing; +using ZXing.Common; +using ZXing.QrCode.Internal; + +namespace 常用工具集.Utility.Qrcode +{ + public class BarCodeUtils + { + public static SKBitmap EncodeBarCode(BarcodeFormat barcodeFormat, string message, int widthPixels, int hightPixels, out string svg) + { + Dictionary hints = new Dictionary(); + hints.Add(EncodeHintType.CHARACTER_SET, "utf-8"); + hints.Add(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); + hints.Add(EncodeHintType.MARGIN, 1); + BitMatrix matrix = new MultiFormatWriter().encode(message, barcodeFormat, widthPixels, hightPixels, hints); + //matrix = deleteWhite(matrix);//删除白边 + SKBitmap bitmap = new SKBitmap(matrix.Width, matrix.Height); + SKCanvas canvas = new SKCanvas(bitmap); + canvas.Clear(); + SKPaint paint = new SKPaint() + { + Color = SKColors.Black, + IsAntialias = true, + Style = SKPaintStyle.Fill + }; + + + StringBuilder sb = new StringBuilder(); + sb.AppendLine(""); + sb.AppendLine($""); + sb.AppendLine(""); + bool[,] handled = new bool[matrix.Width, matrix.Height];//默认全部false + for (int x = 0; x < matrix.Width; x++) + { + for (int y = 0; y < matrix.Height; y++) + { + if (matrix[x, y] && !handled[x, y]) + { + //从当前位置找矩形区域 + FindRect(x, y, matrix, ref handled, out int width, out int height); + //x从x到x+width + //y从y到y+height + //这些区域是已处理的 + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + handled[x + i, y + j] = true; + } + } + sb.AppendLine($""); + canvas.DrawRect(x, y, width, height, paint); + } + } + } + sb.AppendLine(""); + svg = sb.ToString(); + canvas.Dispose(); + return bitmap; + + + + //BarcodeWriter writer = new BarcodeWriter(); + ////使用ITF 格式,不能被现在常用的支付宝、微信扫出来 + ////如果想生成可识别的可以使用 CODE_128 格式 + ////writer.Format = BarcodeFormat.ITF; + //writer.Format = barcodeFormat; + //EncodingOptions options = new EncodingOptions() + //{ + // Width = widthPixels, + // Height = hightPixels, + // Margin = 2 + //}; + //writer.Options = options; + //Bitmap map = writer.Write(message); + //return map; + } + + + /// + /// 找到未处理的矩形区域 + /// + /// + /// + /// + /// + /// + /// + private static void FindRect(int x, int y, BitMatrix matrix, ref bool[,] handled, out int width, out int height) + { + width = 1; + height = 1; + bool flag = false; + int maxOffset = FindXMax(matrix, x, y, ref handled); + A: + if (maxOffset == 0) + { + return; + } + while (true) + { + height++; + if ((y + height) >= matrix.Height) + { + break; + } + if (!matrix[x, y + height] || handled[x, y + height]) + { + break; + } + bool allTrue = true;//默认最后一行全是true + for (int i = 0; i < maxOffset; i++) + { + if (!matrix[x + i, y + height]) + { + allTrue = false; + break; + } + if (handled[x + i, y + height]) + { + allTrue = false; + break; + } + } + if (allTrue) + { + flag = true; + } + else + { + if (flag || height == 2) + { + height--; + break; + } + maxOffset--; + flag = false; + goto A; + } + } + width = maxOffset + 1; + } + + private static int FindXMax(BitMatrix matrix, int x, int y, ref bool[,] handled) + { + int offset = 0; + while (true) + { + if ((x + offset) > matrix.Width) + { + break; + } + if (!matrix[x + offset, y]) + { + offset--; + break; + } + offset++; + } + return offset; + } + public static SKBitmap EncodeBarCode(string message, int widthPixels, int hightPixels, out string svg) + { + return EncodeBarCode(BarcodeFormat.CODE_128, message, widthPixels, hightPixels, out svg); + } + + + public static SKBitmap EncodeBarCode(string msg, out string svg) + { + return EncodeBarCode(msg, 960, 500, out svg); + } + + } +} \ No newline at end of file diff --git a/常用工具集/Utility/Qrcode/DataMatrixUtils.cs b/常用工具集/Utility/Qrcode/DataMatrixUtils.cs new file mode 100644 index 0000000..c916c76 --- /dev/null +++ b/常用工具集/Utility/Qrcode/DataMatrixUtils.cs @@ -0,0 +1,268 @@ +using System.Text; +using ZXing.Common; +using ZXing; +using ZXing.Datamatrix.Encoder; +using SkiaSharp; +using ZXing.Datamatrix; + +namespace 常用工具集.Utility.Qrcode +{ + public class DataMatrixUtils + { + /// + /// 默认size为12 + /// + /// DataMatrix内容 + /// 图片长宽 + /// + public static SKBitmap EncodeDataMatrix(DmtxSymbolSize size, string msg, int codeSizeInPixels, out string svg) + { + + var writer = new DataMatrixWriter(); + var hints = new DatamatrixEncodingOptions() + { + //CharacterSet = "utf-8", + Width = codeSizeInPixels, + Height = codeSizeInPixels, + SymbolShape = SymbolShapeHint.FORCE_SQUARE, + Margin = 0, + PureBarcode = true, + MinSize = new Dimension(12, 12), + MaxSize = new Dimension(12, 12) + }; + switch (size) + { + case DmtxSymbolSize.DmtxSymbol10x10: hints.MaxSize = new Dimension(10, 10); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol12x12: hints.MaxSize = new Dimension(12, 12); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol14x14: hints.MaxSize = new Dimension(14, 14); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol16x16: hints.MaxSize = new Dimension(16, 16); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol18x18: hints.MaxSize = new Dimension(18, 18); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol20x20: hints.MaxSize = new Dimension(20, 20); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol22x22: hints.MaxSize = new Dimension(22, 22); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol24x24: hints.MaxSize = new Dimension(24, 24); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol26x26: hints.MaxSize = new Dimension(26, 26); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol32x32: hints.MaxSize = new Dimension(32, 32); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol36x36: hints.MaxSize = new Dimension(36, 36); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol40x40: hints.MaxSize = new Dimension(40, 40); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol44x44: hints.MaxSize = new Dimension(44, 44); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol48x48: hints.MaxSize = new Dimension(48, 48); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol52x52: hints.MaxSize = new Dimension(52, 52); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol64x64: hints.MaxSize = new Dimension(64, 64); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol72x72: hints.MaxSize = new Dimension(72, 72); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol80x80: hints.MaxSize = new Dimension(80, 80); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol88x88: hints.MaxSize = new Dimension(88, 88); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol96x96: hints.MaxSize = new Dimension(96, 96); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol104x104: hints.MaxSize = new Dimension(104, 104); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol120x120: hints.MaxSize = new Dimension(120, 120); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol132x132: hints.MaxSize = new Dimension(132, 132); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol144x144: hints.MaxSize = new Dimension(144, 144); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol8x18: hints.SymbolShape = SymbolShapeHint.FORCE_NONE; hints.MaxSize = new Dimension(8, 18); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol8x32: hints.SymbolShape = SymbolShapeHint.FORCE_NONE; hints.MaxSize = new Dimension(8, 32); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol12x26: hints.SymbolShape = SymbolShapeHint.FORCE_NONE; hints.MaxSize = new Dimension(12, 26); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol12x36: hints.SymbolShape = SymbolShapeHint.FORCE_NONE; hints.MaxSize = new Dimension(12, 36); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol16x36: hints.SymbolShape = SymbolShapeHint.FORCE_NONE; hints.MaxSize = new Dimension(16, 36); hints.MinSize = hints.MaxSize; break; + case DmtxSymbolSize.DmtxSymbol16x48: hints.SymbolShape = SymbolShapeHint.FORCE_NONE; hints.MaxSize = new Dimension(16, 48); hints.MinSize = hints.MaxSize; break; + } + BitMatrix matrix = writer.encode(msg, BarcodeFormat.DATA_MATRIX, hints.Width, hints.Height, hints.Hints); + //Dictionary hints = new Dictionary(); + //hints.Add(EncodeHintType.CHARACTER_SET, "utf-8"); + //hints.Add(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); + //hints.Add(EncodeHintType.MARGIN, 1); + //hints.Add(EncodeHintType.DATA_MATRIX_SHAPE, SymbolShapeHint.FORCE_SQUARE); + //hints.Add(EncodeHintType.WIDTH, 16); + //hints.Add(EncodeHintType.HEIGHT, 16); + //hints.Add(EncodeHintType.MIN_SIZE, 16); + //hints.Add(EncodeHintType.MAX_SIZE, 16); + //BitMatrix matrix = new MultiFormatWriter().encode(msg, BarcodeFormat.DATA_MATRIX, codeSizeInPixels, codeSizeInPixels, hints); + //matrix = deleteWhite(matrix);//删除白边 + SKBitmap bitmap = new SKBitmap(matrix.Width, matrix.Height); + SKCanvas canvas = new SKCanvas(bitmap); + canvas.Clear(); + SKPaint paint = new SKPaint() + { + Color = SKColors.Black, + IsAntialias = true, + Style = SKPaintStyle.Fill + }; + StringBuilder sb = new StringBuilder(); + sb.AppendLine(""); + sb.AppendLine($""); + sb.AppendLine(""); + bool[,] handled = new bool[matrix.Width, matrix.Height];//默认全部false + for (int x = 0; x < matrix.Width; x++) + { + for (int y = 0; y < matrix.Height; y++) + { + if (matrix[x, y] && !handled[x, y]) + { + //从当前位置找矩形区域 + FindRect(x, y, matrix, ref handled, out int width, out int height); + //x从x到x+width + //y从y到y+height + //这些区域是已处理的 + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + handled[x + i, y + j] = true; + } + } + sb.AppendLine($""); + canvas.DrawRect(x, y, width, height, paint); + } + } + } + sb.AppendLine(""); + svg = sb.ToString(); + canvas.Dispose(); + return bitmap; + } + + /// + /// 找到未处理的矩形区域 + /// + /// + /// + /// + /// + /// + /// + private static void FindRect(int x, int y, BitMatrix matrix, ref bool[,] handled, out int width, out int height) + { + width = 1; + height = 1; + bool flag = false; + int maxOffset = FindXMax(matrix, x, y, ref handled); + A: + if (maxOffset == 0) + { + return; + } + while (true) + { + height++; + if ((y + height) >= matrix.Height) + { + break; + } + if (!matrix[x, y + height] || handled[x, y + height]) + { + break; + } + bool allTrue = true;//默认最后一行全是true + for (int i = 0; i < maxOffset; i++) + { + if (!matrix[x + i, y + height]) + { + allTrue = false; + break; + } + if (handled[x + i, y + height]) + { + allTrue = false; + break; + } + } + if (allTrue) + { + flag = true; + } + else + { + if (flag || height == 2) + { + height--; + break; + } + maxOffset--; + flag = false; + goto A; + } + } + width = maxOffset + 1; + } + + private static int FindXMax(BitMatrix matrix, int x, int y, ref bool[,] handled) + { + int offset = 0; + while (true) + { + if ((x + offset) > matrix.Width) + { + break; + } + if (!matrix[x + offset, y]) + { + offset--; + break; + } + offset++; + } + return offset; + } + + + ///// + ///// 去除白边 + ///// + ///// + ///// + //private static BitMatrix deleteWhite(BitMatrix matrix) + //{ + // int[] rec = matrix.getEnclosingRectangle(); + // int resWidth = rec[2] + 1; + // int resHeight = rec[3] + 1; + + // BitMatrix resMatrix = new BitMatrix(resWidth, resHeight); + // resMatrix.clear(); + // for (int i = 0; i < resWidth; i++) + // { + // for (int j = 0; j < resHeight; j++) + // { + // if (matrix[i + rec[0], j + rec[1]]) + // resMatrix[i, j] = true; + // } + // } + // return resMatrix; + //} + + //public static SKBitmap EncodeDataMatrix(string msg, out string svg) + //{ + // return EncodeDataMatrix(DmtxSymbolSize.DmtxSymbol14x14, msg, 900, out svg); + //} + } + + public enum DmtxSymbolSize + { + DmtxSymbol10x10, + DmtxSymbol12x12, + DmtxSymbol14x14, + DmtxSymbol16x16, + DmtxSymbol18x18, + DmtxSymbol20x20, + DmtxSymbol22x22, + DmtxSymbol24x24, + DmtxSymbol26x26, + DmtxSymbol32x32, + DmtxSymbol36x36, + DmtxSymbol40x40, + DmtxSymbol44x44, + DmtxSymbol48x48, + DmtxSymbol52x52, + DmtxSymbol64x64, + DmtxSymbol72x72, + DmtxSymbol80x80, + DmtxSymbol88x88, + DmtxSymbol96x96, + DmtxSymbol104x104, + DmtxSymbol120x120, + DmtxSymbol132x132, + DmtxSymbol144x144, + DmtxSymbol8x18, + DmtxSymbol8x32, + DmtxSymbol12x26, + DmtxSymbol12x36, + DmtxSymbol16x36, + DmtxSymbol16x48 + } +} diff --git a/常用工具集/Utility/Qrcode/QRCodeUtils.cs b/常用工具集/Utility/Qrcode/QRCodeUtils.cs new file mode 100644 index 0000000..f15932f --- /dev/null +++ b/常用工具集/Utility/Qrcode/QRCodeUtils.cs @@ -0,0 +1,185 @@ +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Text; +using SkiaSharp; +using ZXing; +using ZXing.Common; +using ZXing.QrCode.Internal; + +namespace 常用工具集.Utility.Qrcode +{ + public class QRCodeUtils + { + /// + /// + /// + /// 二维码内容 + /// 图片长宽 + /// + public static SKBitmap EncodeQrCode(string msg, int codeSizeInPixels, out string svg) + { + Dictionary hints = new Dictionary(); + hints.Add(EncodeHintType.CHARACTER_SET, "utf-8"); + hints.Add(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); + hints.Add(EncodeHintType.MARGIN, 1); + BitMatrix matrix = new MultiFormatWriter().encode(msg, BarcodeFormat.QR_CODE, codeSizeInPixels, codeSizeInPixels, hints); + matrix = deleteWhite(matrix);//删除白边 + SKBitmap bitmap = new SKBitmap(matrix.Width, matrix.Height); + SKCanvas canvas = new SKCanvas(bitmap); + canvas.Clear(); + SKPaint paint = new SKPaint() + { + Color = SKColors.Black, + IsAntialias = true, + Style = SKPaintStyle.Fill + }; + StringBuilder sb = new StringBuilder(); + sb.AppendLine(""); + sb.AppendLine($""); + sb.AppendLine(""); + bool[,] handled = new bool[matrix.Width, matrix.Height];//默认全部false + for (int x = 0; x < matrix.Width; x++) + { + for (int y = 0; y < matrix.Height; y++) + { + if (matrix[x, y] && !handled[x, y]) + { + //从当前位置找矩形区域 + FindRect(x, y, matrix, ref handled, out int width, out int height); + //x从x到x+width + //y从y到y+height + //这些区域是已处理的 + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + handled[x + i, y + j] = true; + } + } + sb.AppendLine($""); + canvas.DrawRect(x, y, width, height, paint); + } + } + } + sb.AppendLine(""); + svg = sb.ToString(); + canvas.Dispose(); + return bitmap; + } + + + /// + /// 找到未处理的矩形区域 + /// + /// + /// + /// + /// + /// + /// + private static void FindRect(int x, int y, BitMatrix matrix, ref bool[,] handled, out int width, out int height) + { + width = 1; + height = 1; + bool flag = false; + int maxOffset = FindXMax(matrix, x, y, ref handled); + A: + if (maxOffset == 0) + { + return; + } + while (true) + { + height++; + if ((y + height) >= matrix.Height) + { + break; + } + if (!matrix[x, y + height] || handled[x, y + height]) + { + break; + } + bool allTrue = true;//默认最后一行全是true + for (int i = 0; i < maxOffset; i++) + { + if (!matrix[x + i, y + height]) + { + allTrue = false; + break; + } + if (handled[x + i, y + height]) + { + allTrue = false; + break; + } + } + if (allTrue) + { + flag = true; + } + else + { + if (flag || height == 2) + { + height--; + break; + } + maxOffset--; + flag = false; + goto A; + } + } + width = maxOffset + 1; + } + + private static int FindXMax(BitMatrix matrix, int x, int y, ref bool[,] handled) + { + int offset = 0; + while (true) + { + if ((x + offset) > matrix.Width) + { + break; + } + if (!matrix[x + offset, y]) + { + offset--; + break; + } + offset++; + } + return offset; + } + + + /// + /// 去除白边 + /// + /// + /// + private static BitMatrix deleteWhite(BitMatrix matrix) + { + int[] rec = matrix.getEnclosingRectangle(); + int resWidth = rec[2] + 1; + int resHeight = rec[3] + 1; + + BitMatrix resMatrix = new BitMatrix(resWidth, resHeight); + resMatrix.clear(); + for (int i = 0; i < resWidth; i++) + { + for (int j = 0; j < resHeight; j++) + { + if (matrix[i + rec[0], j + rec[1]]) + resMatrix[i, j] = true; + } + } + return resMatrix; + } + + public static SKBitmap EncodeQrCode(string msg, out string svg) + { + return EncodeQrCode(msg, 900, out svg); + } + } +} diff --git a/常用工具集/Utility/Security/Base64Helper.cs b/常用工具集/Utility/Security/Base64Helper.cs new file mode 100644 index 0000000..35d8bd7 --- /dev/null +++ b/常用工具集/Utility/Security/Base64Helper.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; + +namespace CCS.Utility.Security +{ + public static class Base64Helper + { + #region Base64加密解密 + /// + /// Base64加密,采用指定字符编码方式加密。 + /// + /// 待加密的明文 + /// 字符编码 + /// + public static string Base64Encrypt(this string input, Encoding encode) + { + return Convert.ToBase64String(encode.GetBytes(input)); + } + + /// + /// Base64加密,采用UTF8编码方式加密。 + /// + /// 待加密的明文 + /// + public static string Base64Encrypt(this string input) + { + return Base64Encrypt(input, new UTF8Encoding()); + } + + /// + /// Base64解密,采用UTF8编码方式解密。 + /// + /// 待解密的秘文 + /// + public static string Base64Decrypt(this string input) + { + return Base64Decrypt(input, new UTF8Encoding()); + } + + /// + /// Base64解密,采用指定字符编码方式解密。 + /// + /// 待解密的秘文 + /// 字符的编码 + /// + public static string Base64Decrypt(this string input, Encoding encode) + { + return encode.GetString(Convert.FromBase64String(input)); + } + #endregion + + /// + /// 文件转base64字符串 + /// + /// + /// + public static string FileToBase64(string filePath) + { + try + { + string base64Str; + using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) + { + byte[] bt = new byte[fileStream.Length]; + // 调用read读取方法  + fileStream.Read(bt, 0, bt.Length); + base64Str = Convert.ToBase64String(bt); + } + return base64Str; + } + catch + { + return ""; + } + } + + /// + /// base64字符串转文件字节数组 + /// + /// + /// + public static byte[] FileFromBase64(string base64) + { + return Convert.FromBase64String(base64); + } + + + + /// + /// 将传进来的文件转换成字符串 + /// + /// 待处理的文件路径(本地或服务器) + /// + public static string FileToBinary(string filePath) + { + //利用新传来的路径实例化一个FileStream对像 + System.IO.FileStream fs = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read); + //得到对象的大小 + int fileLength = Convert.ToInt32(fs.Length); + //声明一个byte数组 + byte[] fileByteArray = new byte[fileLength]; + //声明一个读取二进流的BinaryReader对像 + System.IO.BinaryReader br = new System.IO.BinaryReader(fs); + for (int i = 0; i < fileLength; i++) + { + //将数据读取出来放在数组中 + br.Read(fileByteArray, 0, fileLength); + } + //装数组转换为String字符串 + string strData = Convert.ToBase64String(fileByteArray); + br.Close(); + fs.Close(); + return strData; + } + + /// + /// 将传进来的字符串保存为文件 + /// + /// 需要保存的位置路径 + /// 需要转换的字符串 + public static void BinaryToFile(string path, string binary) + { + System.IO.FileStream fs = new System.IO.FileStream(path, System.IO.FileMode.Create, System.IO.FileAccess.Write); + //利用新传来的路径实例化一个FileStream对像 + System.IO.BinaryWriter bw = new System.IO.BinaryWriter(fs); + //实例化一个用于写的BinaryWriter + byte[] buffer = Convert.FromBase64String(binary); + bw.Write(buffer); + bw.Close(); + fs.Close(); + } + } +} diff --git a/常用工具集/Utility/Security/DESHelper.cs b/常用工具集/Utility/Security/DESHelper.cs new file mode 100644 index 0000000..a8c80da --- /dev/null +++ b/常用工具集/Utility/Security/DESHelper.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; + +namespace CCS.Utility.Security +{ + public static class DESHelper + { + #region DES加密解密 + + /// + /// 默认密钥。 + /// + private const string DESENCRYPT_KEY = "hsdjxlzf"; + + /// + /// DES加密,使用自定义密钥。 + /// + /// 待加密的明文 + /// 8位字符的密钥字符串 + /// + public static string DESEncrypt(this string text, string key) + { + if (key.Length != 8) + { + key = DESENCRYPT_KEY; + } + DESCryptoServiceProvider des = new DESCryptoServiceProvider(); + byte[] inputByteArray = Encoding.GetEncoding("UTF-8").GetBytes(text); + + byte[] a = ASCIIEncoding.ASCII.GetBytes(key); + des.Key = ASCIIEncoding.ASCII.GetBytes(key); + des.IV = ASCIIEncoding.ASCII.GetBytes(key); + MemoryStream ms = new MemoryStream(); + CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write); + + cs.Write(inputByteArray, 0, inputByteArray.Length); + cs.FlushFinalBlock(); + + StringBuilder ret = new StringBuilder(); + foreach (byte b in ms.ToArray()) + { + ret.AppendFormat("{0:X2}", b);//将第一个参数转换为十六进制数,长度为2,不足前面补0 + } + return ret.ToString(); + } + + /// + /// DES解密,使用自定义密钥。 + /// + /// 待解密的秘文 + /// 必须是8位字符的密钥字符串(不能有特殊字符) + /// + public static string DESDecrypt(this string cyphertext, string key) + { + if (key.Length != 8) + { + key = DESENCRYPT_KEY; + } + if (string.IsNullOrEmpty(cyphertext)) + return string.Empty; + DESCryptoServiceProvider des = new DESCryptoServiceProvider(); + + byte[] inputByteArray = new byte[cyphertext.Length / 2]; + for (int x = 0; x < cyphertext.Length / 2; x++) + { + int i = (Convert.ToInt32(cyphertext.Substring(x * 2, 2), 16)); + inputByteArray[x] = (byte)i; + } + + des.Key = ASCIIEncoding.ASCII.GetBytes(key); + des.IV = ASCIIEncoding.ASCII.GetBytes(key); + MemoryStream ms = new MemoryStream(); + CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write); + cs.Write(inputByteArray, 0, inputByteArray.Length); + cs.FlushFinalBlock(); + + StringBuilder ret = new StringBuilder(); + + return System.Text.Encoding.GetEncoding("UTF-8").GetString(ms.ToArray()); + } + + /// + /// DES加密,使用默认密钥。 + /// + /// 待加密的明文 + /// + public static string DESEncrypt(this string text) + { + return DESEncrypt(text, DESENCRYPT_KEY); + } + + /// + /// DES解密,使用默认密钥。 + /// + /// 待解密的秘文 + /// + public static string DESDecrypt(this string cyphertext) + { + return DESDecrypt(cyphertext, DESENCRYPT_KEY); + } + #endregion + } +} diff --git a/常用工具集/Utility/Security/MD5Helper.cs b/常用工具集/Utility/Security/MD5Helper.cs new file mode 100644 index 0000000..d501882 --- /dev/null +++ b/常用工具集/Utility/Security/MD5Helper.cs @@ -0,0 +1,113 @@ +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace CCS.Utility.Security +{ + public static class MD5Helper + { + /// + /// 字符串MD5加密。 + /// + /// 需要加密的字符串 + /// + public static string md5(this string text) + { + return md5(text, Encoding.Default); + } + public static string MD5(this string text) + { + return MD5(text, Encoding.Default); + } + /// + /// 字符串MD5加密。 + /// + /// 需要加密的字符串 + /// + public static string md5(this string text, Encoding encoder) + { + // Create a new instance of the MD5CryptoServiceProvider object. + System.Security.Cryptography.MD5 md5Hasher = System.Security.Cryptography.MD5.Create(); + // Convert the input string to a byte array and compute the hash. + byte[] data = md5Hasher.ComputeHash(encoder.GetBytes(text)); + // Create a new Stringbuilder to collect the bytes + // and create a string. + StringBuilder sBuilder = new StringBuilder(); + // Loop through each byte of the hashed data + // and format each one as a hexadecimal string. + for (int i = 0; i < data.Length; i++) + { + sBuilder.Append(data[i].ToString("x2")); + } + // Return the hexadecimal string. + return sBuilder.ToString().ToLower(); + } + public static string MD5(this string text, Encoding encoder) + { + return md5(text, encoder).ToUpper(); + } + /// + /// 文件流MD5加密。 + /// + /// 需要加密的文件流 + /// + public static string md5(this Stream stream) + { + MD5 md5serv = MD5CryptoServiceProvider.Create(); + byte[] buffer = md5serv.ComputeHash(stream); + StringBuilder sb = new StringBuilder(); + foreach (byte var in buffer) + { + sb.Append(var.ToString("x2")); + } + return sb.ToString().ToLower(); + } + public static string MD5(this Stream stream) + { + return md5(stream).ToUpper(); + } + + #region MD5加密 + /// + /// 字符串MD5加密。 + /// + /// 需要加密的字符串 + /// + public static string MD5Encrypt(this string text) + { + // Create a new instance of the MD5CryptoServiceProvider object. + System.Security.Cryptography.MD5 md5Hasher = System.Security.Cryptography.MD5.Create(); + // Convert the input string to a byte array and compute the hash. + byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(text)); + // Create a new Stringbuilder to collect the bytes + // and create a string. + StringBuilder sBuilder = new StringBuilder(); + // Loop through each byte of the hashed data + // and format each one as a hexadecimal string. + for (int i = 0; i < data.Length; i++) + { + sBuilder.Append(data[i].ToString("x2")); + } + // Return the hexadecimal string. + return sBuilder.ToString(); + } + + /// + /// 文件流MD5加密。 + /// + /// 需要加密的文件流 + /// + public static string MD5Encrypt(this Stream stream) + { + MD5 md5serv = MD5CryptoServiceProvider.Create(); + byte[] buffer = md5serv.ComputeHash(stream); + StringBuilder sb = new StringBuilder(); + foreach (byte var in buffer) + { + sb.Append(var.ToString("x2")); + } + return sb.ToString(); + } + #endregion + } +} diff --git a/常用工具集/Utility/Security/SHA1Helper.cs b/常用工具集/Utility/Security/SHA1Helper.cs new file mode 100644 index 0000000..965a4a0 --- /dev/null +++ b/常用工具集/Utility/Security/SHA1Helper.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; + +namespace CCS.Utility.Security +{ + public static class SHA1Helper + { + /// + /// 获得一个字符串的加密密文 + + /// 此密文为单向加密,即不可逆(解密)密文 + /// + /// 待加密明文 + /// 已加密密文 + public static string SHA1(string plainText) + { + if (string.IsNullOrEmpty(plainText)) return string.Empty; + System.Security.Cryptography.SHA1 sha1 = System.Security.Cryptography.SHA1.Create(); + byte[] buffer = sha1.ComputeHash(Encoding.Default.GetBytes(plainText)); + StringBuilder sb = new StringBuilder(); + foreach (byte var in buffer) + { + sb.Append(var.ToString("x2")); + } + return sb.ToString().ToLower(); + } + + public static string SHA1Lower(string plainText) + { + return SHA1(plainText).ToLower(); + } + + public static string SHA1Upper(string plainText) + { + return SHA1(plainText).ToUpper(); + } + } +} diff --git a/常用工具集/Utility/SerialDebug/CSendParam.cs b/常用工具集/Utility/SerialDebug/CSendParam.cs new file mode 100644 index 0000000..408b7ea --- /dev/null +++ b/常用工具集/Utility/SerialDebug/CSendParam.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace SerialDebug +{ + public class CSendParam + { + private SendParamFormat _Format; + private SendParamMode _Mode; + private int _DelayTime; + private readonly string _Data; + private readonly byte[] _DataBytes = null; + + public CSendParam(SendParamFormat format, SendParamMode mode, int delayTime, byte[] data, int startIndex, int count) + { + _Format = format; + _Mode = mode; + _DelayTime = delayTime; + _Data = string.Empty; + if (data != null) + { + _DataBytes = new byte[count]; + Array.Copy(data, startIndex, _DataBytes, 0, count); + + if (Format == SendParamFormat.Hex) + { + _Data = BitConverter.ToString(_DataBytes).Replace('-', ' ').TrimEnd(new char[] { ' ' }); + } + else + { + _Data = System.Text.ASCIIEncoding.Default.GetString(_DataBytes); + } + } + } + + public CSendParam(SendParamFormat format, SendParamMode mode, int delayTime, string data, bool trans = true) + { + _Format = format; + _Mode = mode; + _DelayTime = delayTime; + _Data = data; + + switch (_Format) + { + case SendParamFormat.ASCII: + { + byte[] bytes = System.Text.ASCIIEncoding.Default.GetBytes(_Data); + if (trans) + { + + List list = new List(); + for (int i = 0; i < bytes.Length; i++) + { + if (bytes[i] == (byte)'\\') + { + if ((i + 1) >= bytes.Length) + { + list.Add(bytes[i]); + continue; + } + //\r + //һr + if (bytes[i + 1] == (byte)'r' || bytes[i + 1] == (byte)'R') + { + list.Add(0x0D); + i++;//һ + } + // \n + else if (bytes[i + 1] == (byte)'n' || bytes[i + 1] == (byte)'N') + { + list.Add(0x0A); + i++;//һ + } + //\t 09 + else if (bytes[i + 1] == (byte)'t' || bytes[i + 1] == (byte)'T') + { + list.Add(0x09); + i++;//һ + } + //\v 0B + else if (bytes[i + 1] == (byte)'v' || bytes[i + 1] == (byte)'V') + { + list.Add(0x0B); + i++;//һ + } + else if (bytes[i + 1] == (byte)'a' || bytes[i + 1] == (byte)'A') + { + list.Add(0x07); + i++;//һ + } + else if (bytes[i + 1] == (byte)'b' || bytes[i + 1] == (byte)'B') + { + list.Add(0x08); + i++;//һ + } + else if (bytes[i + 1] == (byte)'f' || bytes[i + 1] == (byte)'F') + { + list.Add(0x0C); + i++;//һ + } + else + { + list.Add(bytes[i]); + } + } + else + { + list.Add(bytes[i]); + } + } + _DataBytes = list.ToArray(); + } + else + { + _DataBytes = bytes; + } + } + break; + case SendParamFormat.Hex: + + string inputText = Regex.Replace(_Data, @"[0-9A-Fa-f]{2}", "$0 "); + string[] strArray = inputText.Split(new string[] { ",", " ", "0x", ",0X", "", "(", ")" }, StringSplitOptions.RemoveEmptyEntries); + + + StringBuilder sbOut = new StringBuilder(); + foreach (string s in strArray) + { + sbOut.AppendFormat("{0:X2} ", Convert.ToByte(s, 16)); + } + _Data = sbOut.ToString().TrimEnd(' '); + + + _DataBytes = Array.ConvertAll(strArray, new Converter(HexStringToByte)); + + break; + default: + break; + } + } + + /// + /// ʮַתʮơ + /// + /// + /// + byte HexStringToByte(string hexStr) + { + return Convert.ToByte(hexStr, 16); + } + + public SendParamFormat Format + { + get { return _Format; } + set { _Format = value; } + } + + public SendParamMode Mode + { + get { return _Mode; } + set { _Mode = value; } + } + + public int DelayTime + { + get { return _DelayTime; } + set { _DelayTime = value; } + } + + public int DataLen + { + get + { + if (_DataBytes != null) + { + return _DataBytes.Length; + } + else + { + return 0; + } + } + } + + public string Data + { + get { return _Data; } + } + + public string HexString + { + get + { + return string.Format("{0} ", BitConverter.ToString(_DataBytes).Replace('-', ' ')); + } + } + + public string ASCIIString + { + get { return System.Text.ASCIIEncoding.Default.GetString(_DataBytes); } + } + + public string DecString + { + get + { + StringBuilder sb = new StringBuilder(); + + foreach (byte b in _DataBytes) + { + sb.AppendFormat("{0} ", Convert.ToInt32(b)); + } + + return sb.ToString(); + } + + } + + public byte[] DataBytes + { + get + { + return _DataBytes; + } + } + + + public string ParameterString + { + get + { + return string.Format("{0}:{1}:{2}", (int)_Format, (int)_Mode, _DelayTime); + } + } + } + + + public enum SendParamFormat : int + { + ASCII = 0, + Hex = 1 + } + + public enum SendParamMode : int + { + //SendDelayTime = 0,// ʱ + SendAfterLastSend = 0,//֡ɺ + SendAfterReceived = 1//յ֡ + } +} diff --git a/常用工具集/Utility/SerialDebug/CSerialDebug.cs b/常用工具集/Utility/SerialDebug/CSerialDebug.cs new file mode 100644 index 0000000..3cc9e00 --- /dev/null +++ b/常用工具集/Utility/SerialDebug/CSerialDebug.cs @@ -0,0 +1,562 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO.Ports; +using System.Threading; +using System.Diagnostics; +using System.Net.Sockets; +using System.IO; + +namespace SerialDebug +{ + class CSerialDebug + { + private Queue receiveQueue = new Queue(); + private List sendList = new List(); + + private SerialPort _serialPort = new SerialPort(); + private bool IsReceiveStart = false; + private Thread receiveThread; + private Thread parseThread; + private bool IsSendStart = false; + private Thread sendThread; + private TimeSpan delayTime = new TimeSpan(10 * 100); // 100us + private int LoopCount = 0; + private int _ReceiveTimeOut = 3; + private bool _SendThreadBusy = false; + public delegate void ReceivedEventHandler(object sender, SerialDebugReceiveData e); + public event ReceivedEventHandler ReceivedEvent; + + public delegate void SendCompletedEventHandler(object sender, SendCompletedEventArgs e); + public event SendCompletedEventHandler SendCompletedEvent; + public event EventHandler SendOverEvent; + + private AutoResetEvent waitReceiveEvent = new AutoResetEvent(false); + private ManualResetEvent waitParseEvent = new ManualResetEvent(false); + private ManualResetEvent uartReceivedEvent = new ManualResetEvent(false); + + public CSerialDebug(SerialPort sport) + { + _serialPort = sport; + } + + public SerialPort serialPort + { + get { return _serialPort; } + set { _serialPort = value; } + } + + public int ReceiveTimeOut + { + get { return _ReceiveTimeOut; } + set { _ReceiveTimeOut = value; } + } + + public bool SendThreadBusy + { + get { return _SendThreadBusy; } + } + + public void Start() + { + + try + { + IsReceiveStart = true; + lock (receiveQueue) + { + receiveQueue.Clear(); + } + + lock (sendList) + { + sendList.Clear(); + } + + uartReceivedEvent.Reset(); + waitReceiveEvent.Reset(); + waitParseEvent.Reset(); + + if (serialPort.IsOpen) + { + serialPort.Close(); + } + serialPort.Open(); + serialPort.DataReceived += new SerialDataReceivedEventHandler(serialPort_DataReceived); + + releaseThread(receiveThread); + releaseThread(sendThread); + + receiveThread = new Thread(new ThreadStart(ReceiveThreadHandler)); + receiveThread.IsBackground = true; + receiveThread.Name = "receiveThread"; + receiveThread.Start(); + + parseThread = new Thread(new ThreadStart(ParseThreadHandler)); + parseThread.IsBackground = true; + parseThread.Name = "parseThread"; + parseThread.Start(); + + Send(sendList, 1); + } + catch (System.Exception ex) + { + throw ex; + } + + } + + public void Stop() + { + try + { + IsReceiveStart = false; + IsSendStart = false; + if (serialPort != null) + { + + lock (serialPort) + { + serialPort.DataReceived -= new SerialDataReceivedEventHandler(serialPort_DataReceived); + if (serialPort.IsOpen) + { + serialPort.Close(); + } + + } + } + releaseThread(receiveThread); + + } + catch (System.Exception ex) + { + throw ex; + } + + } + + public void StopReceive() + { + IsReceiveStart = false; + } + + public void StopSend() + { + IsSendStart = false; + waitParseEvent.Set(); + waitParseEvent.Reset(); + + while (SendThreadBusy) + { + Thread.Sleep(10); + } + + //releaseThread(sendThread); + } + public void Send(List list) + { + LoopCount = 1; + Send(list, LoopCount); + } + public void Send(List list, int loop) + { + + if (sendThread != null) + { + if (sendThread.IsAlive) + { + releaseThread(sendThread); + } + } + + + LoopCount = loop; + lock (sendList) + { + sendList.Clear(); + } + sendList = list; + IsSendStart = true; + + sendThread = new Thread(new ThreadStart(SendThreadHandler)); + sendThread.IsBackground = true; + sendThread.Name = "sendThread"; + sendThread.Start(); + } + + private bool SerialPortWrite(byte[] bytes, int offset, int count) + { + if (_serialPort != null && _serialPort.IsOpen) + { + lock (_serialPort) + { + _serialPort.Write(bytes, offset, count); + return true; + } + } + return false; + } + + void releaseThread(Thread th) + { + try + { + if (th != null) + { + lock (th) + { + if (th.IsAlive) + { + th.Abort(); + } + } + } + } + catch (System.Exception ex) + { + Debug.WriteLine(ex.ToString()); + } + } + + void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) + { + if (uartReceivedEvent != null) + { + uartReceivedEvent.Set(); + } + + } + + /// + /// ߳ + /// + private void ReceiveThreadHandler() + { + + //byte[] receiveBytes = new byte[4 * 1024]; + while (IsReceiveStart) + { + try + { + if (_serialPort.IsOpen) + { + try + { + bool IsNeedContinueReceive = false; + int dataLen; + if (uartReceivedEvent.WaitOne() || IsNeedContinueReceive) + { + uartReceivedEvent.Reset(); + + //DateTime beginTime = DateTime.Now; + int buffSize = 0; + do + { + buffSize = serialPort.BytesToRead; + if (buffSize >= 4 * 1024) + { + IsNeedContinueReceive = true; + break; + } + Thread.Sleep(ReceiveTimeOut); + + } while (buffSize != serialPort.BytesToRead); + + if (buffSize > 0) + { + byte[] receiveBytes = new byte[buffSize]; + dataLen = serialPort.Read(receiveBytes, 0, receiveBytes.Length); + if (dataLen > 0) + { + byte[] bytes = new byte[dataLen]; + Array.Copy(receiveBytes, bytes, dataLen); + lock (receiveQueue) + { + receiveQueue.Enqueue(new SerialDebugReceiveData(bytes)); + } + waitReceiveEvent.Set(); + } + } + } + + } + catch (System.Exception ex) + { + if (ex is ThreadInterruptedException) + throw; + Debug.WriteLine(ex.ToString()); + } + + } + else + { + Thread.Sleep(10); + } + + } + catch (System.Exception ex) + { + if (ex is ThreadInterruptedException) + break; + Debug.WriteLine(ex.ToString()); + } + + } + } + + /// + /// Ϣ߳ + /// + private void ParseThreadHandler() + { + while (IsReceiveStart) + { + try + { + SerialDebugReceiveData data = null; + lock (receiveQueue) + { + if (receiveQueue.Count > 0) + { + data = receiveQueue.Dequeue(); + } + } + + if (data != null) + { + + if (ReceivedEvent != null) + { + ReceivedEvent(this, data); + } + waitParseEvent.Set(); + } + else + { + waitReceiveEvent.WaitOne(10); + } + + } + catch (Exception ex) + { + Console.WriteLine("ݴ̣߳" + ex.Message); + } + } + } + + /// + /// ߳ + /// + private void SendThreadHandler() + { + bool IsFirstSend = true; + + if (LoopCount == 0) + { + LoopCount = int.MaxValue; + } + try + { + while (LoopCount > 0 && IsSendStart) + { + LoopCount--; + + waitParseEvent.Reset(); + + int index = 0; + while (index < sendList.Count && IsSendStart) + { + _SendThreadBusy = true; + + CSendParam sendParam = null; + lock (sendList) + { + sendParam = sendList[index]; + } + index++; + + if (sendParam != null) + { + bool DelayEnable = true; + if (sendParam.Mode == SendParamMode.SendAfterLastSend) + { + if (IsFirstSend) + { + DelayEnable = false; + } + } + else if (sendParam.Mode == SendParamMode.SendAfterReceived) + { + waitParseEvent.WaitOne(); + } + IsFirstSend = false; + + if (DelayEnable && sendParam.DelayTime > 0) + { + DateTime startTime = DateTime.Now; + TimeSpan ts = DateTime.Now - startTime; + while (ts.TotalMilliseconds < sendParam.DelayTime) + { + Thread.Sleep(delayTime); + ts = DateTime.Now - startTime; + if (!IsSendStart) + { + break; + } + } + ; + } + + if (IsSendStart && SerialPortWrite(sendParam.DataBytes, 0, sendParam.DataBytes.Length)) + { + if (SendCompletedEvent != null) + { + SendCompletedEventHandler handler = SendCompletedEvent; + handler(this, new SendCompletedEventArgs(sendParam)); + } + } + else + { + IsSendStart = false; + } + + waitParseEvent.Reset(); + } + _SendThreadBusy = false; + } + + } + + } + catch (System.Exception ex) + { + Debug.WriteLine(ex.ToString()); + } + finally + { + if (SendOverEvent != null) + { + SendOverEvent(this, null); + } + } + + + } + } + + public class SerialDebugReceiveData : EventArgs + { + private readonly DateTime _ReceiveTime; + private readonly byte[] _ReceiveData; + private readonly int _DataLen; + + public SerialDebugReceiveData(byte[] data) + { + _ReceiveData = data; + _ReceiveTime = DateTime.Now; + if (data != null) + { + _DataLen = data.Length; + } + else + { + _DataLen = 0; + } + } + + public byte[] ReceiveData + { + get { return _ReceiveData; } + } + + public DateTime ReceiveTime + { + get { return _ReceiveTime; } + } + + public int DataLen + { + get { return _DataLen; } + } + + public string TimeString + { + get + { + return string.Format("[{0}.{1:D3}]", _ReceiveTime.ToString("yyyy-MM-dd HH:mm:ss.fff"), _ReceiveTime.Millisecond); + } + } + + public string HexString + { + get + { + return string.Format("{0} ", BitConverter.ToString(_ReceiveData).Replace('-', ' ')); + } + } + + public string ASCIIString + { + get { return System.Text.ASCIIEncoding.Default.GetString(_ReceiveData); } + } + + public string DecString + { + get + { + StringBuilder sb = new StringBuilder(); + + foreach (byte b in _ReceiveData) + { + sb.AppendFormat("{0} ", Convert.ToInt32(b)); + } + + return sb.ToString(); + } + + } + } + + public class SendCompletedEventArgs : EventArgs + { + private readonly DateTime _SendTime; + private CSendParam _SendParam; + + public SendCompletedEventArgs(CSendParam sendParam) + { + _SendTime = DateTime.Now; + _SendParam = sendParam; + + //if (sendParam!=null) + //{ + // _SendParam = new CSendParam(sendParam.Format, + // sendParam.Mode, + // sendParam.DelayTime, + // sendParam.DataBytes, 0, sendParam.DataLen); + //} + //else + //{ + // _SendParam = null; + //} + } + + public DateTime SendTime + { + get { return _SendTime; } + } + + public CSendParam SendParam + { + get { return _SendParam; } + } + + public string TimeString + { + get + { + return string.Format("[{0}.{1:D3}]", _SendTime.ToString("yyyy-MM-dd HH:mm:ss"), _SendTime.Millisecond); + } + } + } + +} diff --git a/常用工具集/Utility/SerialDebug/DataCheck.cs b/常用工具集/Utility/SerialDebug/DataCheck.cs new file mode 100644 index 0000000..06b8742 --- /dev/null +++ b/常用工具集/Utility/SerialDebug/DataCheck.cs @@ -0,0 +1,357 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace XMX +{ + + public enum CRCType : int + { + CRC16_IBM = 0, + CRC16_MAXIM, + CRC16_USB, + CRC16_MODBUS, + CRC16_CCITT, + CRC16_CCITT_FALSE, + CRC16_X25, + CRC16_XMODEM, + CRC16_DNP, + CRC32, + CRC32_MPEG2, + } + + public class DataCheck + { + static public CRCInfo GetCRCInfo(CRCType type) + { + CRCInfo param = null; + switch (type) + { + case CRCType.CRC16_IBM: + param = new CRCInfo(0x8005, 0x0000, true, true, 0x0000); + break; + case CRCType.CRC16_MAXIM: + param = new CRCInfo(0x8005, 0x0000, true, true, 0xFFFF); + break; + + case CRCType.CRC16_USB: + param = new CRCInfo(0x8005, 0xFFFF, true, true, 0xFFFF); + break; + + case CRCType.CRC16_MODBUS: + param = new CRCInfo(0x8005, 0xFFFF, true, true, 0x0000); + break; + + case CRCType.CRC16_CCITT: + param = new CRCInfo(0x1021, 0x0000, true, true, 0x0000); + break; + + case CRCType.CRC16_CCITT_FALSE: + param = new CRCInfo(0x1021, 0xFFFF, false, false, 0x0000); + break; + + case CRCType.CRC16_X25: + param = new CRCInfo(0x1021, 0xFFFF, true, true, 0xFFFF); + break; + + case CRCType.CRC16_XMODEM: + param = new CRCInfo(0x1021, 0x0000, false, false, 0x0000); + break; + + case CRCType.CRC16_DNP: + param = new CRCInfo(0x3D65, 0x0000, true, true, 0xFFFF); + break; + + case CRCType.CRC32: + param = new CRCInfo(0x04C11DB7, 0xFFFFFFFF, true, true, 0xFFFFFFFF); + break; + case CRCType.CRC32_MPEG2: + param = new CRCInfo(0x04C11DB7, 0xFFFFFFFF, false, false, 0x00000000); + break; + } + + return param; + } + + static public UInt32 GetCRC(CRCType type, byte[] data) + { + CRCInfo param; + + param = GetCRCInfo(type); + if (type >= CRCType.CRC16_IBM && type <= CRCType.CRC16_DNP) + { + return GetCRC16(param, data); + } + else if (type>=CRCType.CRC32 && type<=CRCType.CRC32_MPEG2) + { + return GetCRC32(param, data); + } + return 0; + } + + static public UInt16 GetCRC16(CRCInfo param, byte[] data) + { + UInt16 crc = (UInt16)param.Init; + UInt16 Poly = 0; + UInt16 XorOut = (UInt16)param.XorOut; + + if (param.RefIn) + { + for (int i = 0; i < 16; i++) + { + Poly <<= 1; + if ((param.Poly & (1u << i)) != 0) + { + Poly |= 0x01; + } + } + } + else + { + Poly = (UInt16)param.Poly; + } + + foreach (byte b in data) + { + UInt16 bValue; + if (param.RefOut) + { + bValue = Convert.ToUInt16(b); + } + else + { + bValue = Convert.ToUInt16((UInt16)b << 8); + } + + crc = Convert.ToUInt16(crc ^ bValue); + for (int i = 0; i < 8; i++) + { + if (param.RefOut) + { + if ((crc & 0x01) != 0) + { + crc >>= 1; + crc ^= Poly; + } + else + { + crc >>= 1; + } + } + else + { + if ((crc & 0x8000) != 0) + { + crc <<= 1; + crc ^= Poly; + } + else + { + crc <<= 1; + } + } + } + } + + return Convert.ToUInt16(crc ^ XorOut); + } + + static public UInt32 GetCRC32(CRCInfo param, byte[] data) + { + UInt32 crc = (UInt32)param.Init; + UInt32 Poly = 0; + UInt32 XorOut = (UInt32)param.XorOut; + + if (param.RefIn) + { + for (int i = 0; i < 32; i++) + { + Poly <<= 1; + if ((param.Poly & (1u << i)) != 0) + { + Poly |= 0x01; + } + } + } + else + { + Poly = (UInt32)param.Poly; + } + + foreach (byte b in data) + { + UInt32 bValue; + if (param.RefOut) + { + bValue = Convert.ToUInt32(b); + } + else + { + bValue = Convert.ToUInt32((UInt32)b << 24); + } + + crc = Convert.ToUInt32(crc ^ bValue); + for (int i = 0; i < 8; i++) + { + if (param.RefOut) + { + if ((crc & 0x01) != 0) + { + crc >>= 1; + crc ^= Poly; + } + else + { + crc >>= 1; + } + } + else + { + if ((crc & 0x80000000) != 0) + { + crc <<= 1; + crc ^= Poly; + } + else + { + crc <<= 1; + } + } + } + } + + return Convert.ToUInt32(crc ^ XorOut); + } + + + static public UInt32 GetCheckSum(byte[] data) + { + UInt32 sum = 0; + + foreach (byte b in data) + { + sum += b; + } + + return sum; + } + + static public byte GetXor(byte[] data) + { + byte xor = 0; + + foreach (byte b in data) + { + xor ^= b; + } + + return xor; + } + + } + + public class CRCInfo + { + private UInt32 _Poly; + private UInt32 _Init; + private UInt32 _XorOut; + private bool _RefIn; + private bool _RefOut; + + public CRCInfo() + : this(0, 0, false, false, 0) + { + + } + public CRCInfo(UInt32 poly, UInt32 init, bool refIn, bool refOut, UInt32 xorOut) + { + _Poly = poly; + _Init = init; + _RefIn = refIn; + _RefOut = refOut; + _XorOut = xorOut; + } + + /// + /// 多项式 + /// + public UInt32 Poly + { + get + { + return _Poly; + } + set + { + _Poly = value; + } + } + + /// + /// 初始值 + /// + public UInt32 Init + { + get + { + return _Init; + } + set + { + _Init = value; + } + } + + /// + /// 输出CRC异或值 + /// + public UInt32 XorOut + { + get { return _XorOut; } + set { _XorOut = value; } + } + + /// + /// 输入多项式反序 + /// + public bool RefIn + { + get { return _RefIn; } + set { _RefIn = value; } + } + + /// + /// 输出数据反序 + /// + public bool RefOut + { + get { return _RefOut; } + set { _RefOut = value; } + } + + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + if ((Poly & 0xFFFF0000) != 0) + { + sb.AppendFormat("Poly: {0:X8}\r\n", Poly); + sb.AppendFormat("Init: {0:X8}\r\n", Init); + sb.AppendFormat("RefIn: {0}\r\n", RefIn ? "True" : "False"); + sb.AppendFormat("RefOut:{0}\r\n", RefOut ? "True" : "False"); + sb.AppendFormat("XorOut:{0:X8}", XorOut); + } + else + { + sb.AppendFormat("Poly: {0:X4}\r\n", Poly); + sb.AppendFormat("Init: {0:X4}\r\n", Init); + sb.AppendFormat("RefIn: {0}\r\n", RefIn ? "True" : "False"); + sb.AppendFormat("RefOut:{0}\r\n", RefOut ? "True" : "False"); + sb.AppendFormat("XorOut:{0:X4}", XorOut); + } + + return sb.ToString(); + } + + } +} diff --git a/常用工具集/Utility/SerialDebug/FileTransmit/BinarySend.cs b/常用工具集/Utility/SerialDebug/FileTransmit/BinarySend.cs new file mode 100644 index 0000000..ae75574 --- /dev/null +++ b/常用工具集/Utility/SerialDebug/FileTransmit/BinarySend.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Text; +using XMX.FileTransmit; +using System.Threading; + + +namespace XMX.FileTransmit +{ + public class BinarySend : IFileTramsmit, ITransmitUart + { + private bool IsStart = false; + Thread SendThread; + + private int DelayTime = 10; + public BinarySend(int delayTime) + { + DelayTime = delayTime; + } + + + private void SendThreadHandler() + { + while (IsStart) + { + if (SendNextPacket != null) + { + SendNextPacket(this, null); + Thread.Sleep(DelayTime); + } + } + } + + #region IFileTramsmit Ա + + public event EventHandler StartSend = null; + + public event EventHandler StartReceive = null; + + public event EventHandler SendNextPacket; + + public event EventHandler ReSendPacket = null; + + public event EventHandler AbortTransmit = null; + + public event EventHandler TransmitTimeOut = null; + + public event EventHandler EndOfTransmit = null; + + public event PacketEventHandler ReceivedPacket = null; + + public void SendPacket(PacketEventArgs packet) + { + if (SendToUartEvent != null) + { + SendToUartEvent(null, new SendToUartEventArgs(packet.Packet)); + } + } + + public void Start() + { + IsStart = true; + SendThread = new Thread(new ThreadStart(SendThreadHandler)); + SendThread.IsBackground = true; + SendThread.Start(); + } + + public void Stop() + { + IsStart = false; + if (EndOfTransmit!=null) + { + EndOfTransmit(this, null); + } + } + + public void Abort() + { + IsStart = false; + if (AbortTransmit!=null) + { + AbortTransmit(this, null); + } + } + + #endregion + + #region ITransmitUart Ա + + public event SendToUartEventHandler SendToUartEvent; + + public void ReceivedFromUart(byte[] data) + { + Console.WriteLine("Ʒ账"); + } + + #endregion + + + } +} diff --git a/常用工具集/Utility/SerialDebug/FileTransmit/IFileTramsmit.cs b/常用工具集/Utility/SerialDebug/FileTransmit/IFileTramsmit.cs new file mode 100644 index 0000000..ee2a85b --- /dev/null +++ b/常用工具集/Utility/SerialDebug/FileTransmit/IFileTramsmit.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace XMX.FileTransmit +{ + public enum TransmitMode + { + Receive, + Send + } + + public delegate void PacketEventHandler(object sender, PacketEventArgs e); + + interface IFileTramsmit:ITransmitUart + { + + + event EventHandler StartSend; + event EventHandler StartReceive; + event EventHandler SendNextPacket; + event EventHandler ReSendPacket; + event EventHandler AbortTransmit; + event EventHandler TransmitTimeOut; + event EventHandler EndOfTransmit; + + event PacketEventHandler ReceivedPacket; + + + void SendPacket(PacketEventArgs packet); + void Start(); + void Stop(); + void Abort(); + + } + + public class PacketEventArgs : EventArgs + { + private readonly int _PacketNo; + private readonly int _PacketLen; + private readonly byte[] _Packet; + + + public PacketEventArgs(int packetNo, byte[] packet) + : this(packetNo, packet, packet.Length) + { + + } + + public PacketEventArgs(int packetNo, byte[] packet, int packetLen) + { + _PacketNo = packetNo; + + + if (packet != null) + { + if (packet.Length <= packetLen) + { + _PacketLen = packetLen; + } + + _Packet = new byte[_PacketLen]; + Array.Copy(packet, 0, _Packet, 0, _PacketLen); + } + + } + + public int PacketNo + { + get { return _PacketNo; } + } + + public int PacketLen + { + get { return _PacketLen; } + } + + public byte[] Packet + { + get { return _Packet; } + } + } + +} diff --git a/常用工具集/Utility/SerialDebug/FileTransmit/ITransmitUart.cs b/常用工具集/Utility/SerialDebug/FileTransmit/ITransmitUart.cs new file mode 100644 index 0000000..2da2602 --- /dev/null +++ b/常用工具集/Utility/SerialDebug/FileTransmit/ITransmitUart.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace XMX.FileTransmit +{ + public delegate void SendToUartEventHandler(object sender,SendToUartEventArgs e); + + interface ITransmitUart + { + event SendToUartEventHandler SendToUartEvent; + + void ReceivedFromUart(byte[] data); + } + + public class SendToUartEventArgs : EventArgs + { + private readonly byte[] _Data; + + public SendToUartEventArgs(byte[] data) + { + _Data = data; + } + + public byte[] Data + { + get { return _Data; } + } + } +} diff --git a/常用工具集/Utility/SerialDebug/FileTransmit/XModem.cs b/常用工具集/Utility/SerialDebug/FileTransmit/XModem.cs new file mode 100644 index 0000000..286dec4 --- /dev/null +++ b/常用工具集/Utility/SerialDebug/FileTransmit/XModem.cs @@ -0,0 +1,674 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Collections; + +namespace XMX.FileTransmit +{ + + public enum XModemCheckMode + { + CheckSum, + CRC16 + } + + public enum XModemType + { + XModem, + XModem_1K + } + + /// + /// XModemյϢ + /// + enum XmodemMessageType : int + { + KEY_C, + ACK, + NAK, + EOT, + PACKET, + PACKET_ERROR, + CAN + } + + /// + /// XModemյϢ + /// + internal class XmodemMessage + { + private XmodemMessageType _MessageType; + private object _Value; + + + public XmodemMessage(XmodemMessageType type) + : this(type, null) + { + + } + + public XmodemMessage(XmodemMessageType type, object value) + { + _MessageType = type; + _Value = value; + } + + + public XmodemMessageType MessageType + { + get { return _MessageType; } + } + + public object Value + { + get { return _Value; } + } + } + + /// + /// XmodemͲ + /// + internal enum XmodemSendStage : int + { + WaitReceiveRequest, //ȴն + PacketSending, + WaitReceiveAnswerEndTransmit + } + + /// + /// Xmodemղ + /// + internal enum XmodemReceiveStage : int + { + WaitForFirstPacket, + PacketReceiving, + } + + public class XModemInfo + { + private XModemType _XModemType; + private TransmitMode _TransType; + private XModemCheckMode _CheckMode; + + public XModemInfo() + : this(XModemType.XModem, TransmitMode.Send, XModemCheckMode.CheckSum) + { + + } + + public XModemInfo(XModemType type, TransmitMode transType, XModemCheckMode checkType) + { + _XModemType = type; + _TransType = transType; + _CheckMode = checkType; + } + + + public XModemType Type + { + get { return _XModemType; } + set { _XModemType = value; } + } + + public TransmitMode TransMode + { + get { return _TransType; } + set { _TransType = value; } + } + + public XModemCheckMode CheckMode + { + get { return _CheckMode; } + set { _CheckMode = value; } + } + + } + + public class XModem : IFileTramsmit, ITransmitUart + { + private readonly byte SOH = 0x01; + private readonly byte EOT = 0x04; + private readonly byte ACK = 0x06; + private readonly byte NAK = 0x15; + private readonly byte CAN = 0x18; + + private readonly byte STX = 0x02; + private readonly byte KEY_C = 0x43; //'C'; + + private int RetryMax = 6; + XModemInfo xmodemInfo = new XModemInfo(); + + + private Thread TransThread; + private bool IsStart = false; + private int reTryCount; + private ManualResetEvent waitReceiveEvent = new ManualResetEvent(false); + + + private XmodemReceiveStage ReceiveStage; + private XmodemSendStage SendStage; + private Queue msgQueue = new Queue(); + + public XModem(TransmitMode transType, XModemType xmodemType, int reTryCount) + { + RetryMax = reTryCount; + + xmodemInfo.CheckMode = XModemCheckMode.CheckSum; + xmodemInfo.Type = xmodemType; + xmodemInfo.TransMode = transType; + } + + public void Start() + { + IsStart = true; + reTryCount = 0; + + + ReceiveStage = XmodemReceiveStage.WaitForFirstPacket; + SendStage = XmodemSendStage.WaitReceiveRequest; + msgQueue.Clear(); + + TransThread = new Thread(new ThreadStart(TransThreadHandler)); + TransThread.IsBackground = true; + TransThread.Name = "XmodemTransThread"; + TransThread.Start(); + if (xmodemInfo.TransMode == TransmitMode.Receive) + { + if (StartReceive != null) + { + StartReceive(xmodemInfo, null); + } + } + } + + public void Stop() + { + if (xmodemInfo.TransMode == TransmitMode.Receive) + { + Abort(); + } + else + { + SendEOT(); + } + + if (EndOfTransmit != null) + { + EndOfTransmit(xmodemInfo, null); + } + } + + public void Abort() + { + IsStart = false; + SendCAN(); + + if (EndOfTransmit != null) + { + EndOfTransmit(xmodemInfo, null); + } + } + + + /// + /// + /// + /// + private void ParseReceivedMessage(byte[] data) + { + XmodemMessage ReceivedMessage = null; + + if (data == null) + { + ReceivedMessage = null; + } + else + { + if (data[0] == STX || data[0] == SOH) + { + ReceivedMessage = new XmodemMessage(XmodemMessageType.PACKET_ERROR); + int packetLen = 0; + if (data[0] == STX) + { + packetLen = 1024; + } + else if (data[0] == SOH) + { + packetLen = 128; + } + + int checkDataLen = xmodemInfo.CheckMode == XModemCheckMode.CheckSum ? 1 : 2; + if (packetLen + 3 + checkDataLen == data.Length) + { + int packetNo = 0; + if (data[1] == Convert.ToByte((~data[2]) & 0xFF)) + { + packetNo = data[1]; + } + + int frameCheckCode = 0; + int calCheckCode = -1; + byte[] packet = new byte[packetLen]; + + Array.Copy(data, 3, packet, 0, packetLen); + if (xmodemInfo.CheckMode == XModemCheckMode.CheckSum) + { + frameCheckCode = data[3 + packetLen]; + calCheckCode = Convert.ToByte(DataCheck.GetCheckSum(packet) & 0xFF); + } + else + { + frameCheckCode = (data[3 + packetLen] << 8) + data[3 + packetLen + 1]; + calCheckCode = Convert.ToUInt16(DataCheck.GetCRC(CRCType.CRC16_XMODEM, packet) & 0xFFFF); + } + + if (frameCheckCode == calCheckCode) + { + ReceivedMessage = new XmodemMessage(XmodemMessageType.PACKET, new PacketEventArgs(packetNo, packet)); + } + + } + msgQueue.Enqueue(ReceivedMessage); + } + else + { + foreach (byte b in data) + { + ReceivedMessage = null; + if (b == EOT) + { + ReceivedMessage = new XmodemMessage(XmodemMessageType.EOT); + } + else if (b == CAN) + { + ReceivedMessage = new XmodemMessage(XmodemMessageType.CAN); + } + else if (b == NAK) + { + ReceivedMessage = new XmodemMessage(XmodemMessageType.NAK); + } + else if (b == ACK) + { + ReceivedMessage = new XmodemMessage(XmodemMessageType.ACK); + } + else if (b == KEY_C) + { + ReceivedMessage = new XmodemMessage(XmodemMessageType.KEY_C); + } + + if (ReceivedMessage != null) + { + msgQueue.Enqueue(ReceivedMessage); + } + } + } + + } + + waitReceiveEvent.Set(); + + } + + + private void SendFrameToUart(byte data) + { + byte[] bytes = new byte[1]; + bytes[0] = data; + SendFrameToUart(bytes); + } + private void SendFrameToUart(byte[] data) + { + if (SendToUartEvent != null) + { + SendToUartEvent(xmodemInfo, new SendToUartEventArgs(data)); + } + } + + private void SendACK() + { + SendFrameToUart(ACK); + + } + + private void SendNAK() + { + SendFrameToUart(NAK); + } + + private void SendKEYC() + { + SendFrameToUart(KEY_C); + } + + private void SendCAN() + { + byte[] bytes = new byte[5]; + for (int i = 0; i < 5; i++) + { + bytes[i] = CAN; + } + SendFrameToUart(bytes); + } + + private void SendEOT() + { + SendFrameToUart(EOT); + SendStage = XmodemSendStage.WaitReceiveAnswerEndTransmit; + } + + + + + void TransThreadHandler() + { + while (IsStart) + { + if (xmodemInfo.TransMode == TransmitMode.Send) + { + SendHandler(); + } + else if (xmodemInfo.TransMode == TransmitMode.Receive) + { + ReceiveHandler(); + } + } + } + + void SendHandler() + { + XmodemMessage msg = null; + lock (msgQueue) + { + if (msgQueue.Count > 0) + { + msg = msgQueue.Dequeue(); + } + } + + if (msg != null) + { + reTryCount = 0; + + switch (msg.MessageType) + { + case XmodemMessageType.NAK: + if (SendStage == XmodemSendStage.WaitReceiveRequest) + { + SendStage = XmodemSendStage.PacketSending; + + xmodemInfo.CheckMode = XModemCheckMode.CheckSum; + if (StartSend != null) + { + StartSend(xmodemInfo, null); + } + } + else if (SendStage == XmodemSendStage.WaitReceiveAnswerEndTransmit) + { + SendEOT(); + } + else + { + // ֪ͨطͷһ + if (ReSendPacket != null) + { + ReSendPacket(xmodemInfo, null); + } + } + break; + + case XmodemMessageType.KEY_C: + if (SendStage == XmodemSendStage.WaitReceiveRequest) + { + SendStage = XmodemSendStage.PacketSending; + // ֪ͨͷһCRC + xmodemInfo.CheckMode = XModemCheckMode.CRC16; + if (StartSend != null) + { + StartSend(xmodemInfo, null); + } + } + break; + + case XmodemMessageType.ACK: + if (SendStage == XmodemSendStage.PacketSending) + { + // ֪ͨһ + if (SendNextPacket != null) + { + SendNextPacket(xmodemInfo, null); + } + } + else if (SendStage == XmodemSendStage.WaitReceiveAnswerEndTransmit) + { + // ֹ֪ͨ + //if (AbortTransmit != null) + //{ + // AbortTransmit(xmodemInfo, null); + //} + if (EndOfTransmit != null) + { + EndOfTransmit(xmodemInfo, null); + } + IsStart = false; + } + break; + + case XmodemMessageType.CAN: + // ֹ֪ͨ + if (AbortTransmit != null) + { + AbortTransmit(xmodemInfo, null); + } + break; + + default: + break; + } + } + else + { + if (waitReceiveEvent.WaitOne(3000)) + { + waitReceiveEvent.Reset(); + + } + else + { + reTryCount++; + if (reTryCount > RetryMax) + { + IsStart = false; + //֪ͨճʱ + if (TransmitTimeOut != null) + { + TransmitTimeOut(xmodemInfo, null); + } + } + } + } + + } + + void ReceiveHandler() + { + if (ReceiveStage == XmodemReceiveStage.WaitForFirstPacket) + { + if (reTryCount % 2 == 0) + { + xmodemInfo.CheckMode = XModemCheckMode.CheckSum; + SendKEYC(); + } + else + { + xmodemInfo.CheckMode = XModemCheckMode.CRC16; + SendNAK(); + } + } + + + XmodemMessage msg = null; + lock (msgQueue) + { + if (msgQueue.Count > 0) + { + msg = msgQueue.Dequeue(); + } + + } + if (msg != null) + { + reTryCount = 0; + + switch (msg.MessageType) + { + case XmodemMessageType.PACKET: + ReceiveStage = XmodemReceiveStage.PacketReceiving; + SendACK(); + if (ReceivedPacket != null) + { + PacketEventArgs e = msg.Value as PacketEventArgs; + ReceivedPacket(xmodemInfo, new PacketEventArgs(e.PacketNo, e.Packet)); + } + + // ֪ͨһ + if (SendNextPacket != null) + { + SendNextPacket(xmodemInfo, null); + } + break; + case XmodemMessageType.PACKET_ERROR: + SendNAK(); + // ֪ͨط + if (ReSendPacket != null) + { + ReSendPacket(xmodemInfo, null); + } + break; + case XmodemMessageType.EOT: + SendACK(); + // ֪ͨ + if (EndOfTransmit != null) + { + EndOfTransmit(xmodemInfo, null); + } + break; + case XmodemMessageType.CAN: + SendACK(); + // ֹ֪ͨ + if (AbortTransmit != null) + { + AbortTransmit(xmodemInfo, null); + } + break; + default: + break; + } + } + else + { + if (waitReceiveEvent.WaitOne(3000)) + { + waitReceiveEvent.Reset(); + } + else + { + reTryCount++; + if (reTryCount > RetryMax) + { + IsStart = false; + //֪ͨճʱ + if (TransmitTimeOut != null) + { + TransmitTimeOut(xmodemInfo, null); + } + } + + } + } + + } + + + #region IFileTramsmit Ա + + public event EventHandler StartSend; + + public event EventHandler StartReceive; + + public event EventHandler SendNextPacket; + + public event EventHandler ReSendPacket; + + public event EventHandler AbortTransmit; + + public event EventHandler EndOfTransmit; + + public event EventHandler TransmitTimeOut; + + public event PacketEventHandler ReceivedPacket; + + public void SendPacket(PacketEventArgs packet) + { + int packetLen = 0; + int checkLen = 0; + byte[] data; + + if (xmodemInfo.CheckMode == XModemCheckMode.CheckSum) + { + checkLen = 1; + } + else + { + checkLen = 2; + } + + if (xmodemInfo.Type == XModemType.XModem_1K) + { + packetLen = 1024; + } + else + { + packetLen = 128; + } + + data = new byte[3 + packetLen + checkLen]; + + data[0] = SOH; + if (xmodemInfo.Type == XModemType.XModem_1K) + { + data[0] = STX; + } + + data[1] = Convert.ToByte(packet.PacketNo & 0xFF); + data[2] = Convert.ToByte((~data[1]) & 0xFF); + Array.Copy(packet.Packet, 0, data, 3, packetLen); + + if (xmodemInfo.CheckMode == XModemCheckMode.CheckSum) + { + data[3 + packetLen] = Convert.ToByte(DataCheck.GetCheckSum(packet.Packet) & 0xFF); + } + else + { + UInt16 crc = Convert.ToUInt16(DataCheck.GetCRC(CRCType.CRC16_XMODEM, packet.Packet) & 0xFFFF); + data[3 + packetLen] = Convert.ToByte(crc >> 8); + data[3 + packetLen + 1] = Convert.ToByte(crc & 0xFF); + } + + SendFrameToUart(data); + } + + #endregion + + + #region ITransmitUart Ա + + public event SendToUartEventHandler SendToUartEvent; + public void ReceivedFromUart(byte[] data) + { + ParseReceivedMessage(data); + + } + #endregion + } +} diff --git a/常用工具集/Utility/SerialDebug/FileTransmit/YModem.cs b/常用工具集/Utility/SerialDebug/FileTransmit/YModem.cs new file mode 100644 index 0000000..5e92708 --- /dev/null +++ b/常用工具集/Utility/SerialDebug/FileTransmit/YModem.cs @@ -0,0 +1,682 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace XMX.FileTransmit +{ + + public enum YModemCheckMode + { + CheckSum, + CRC16 + } + + public enum YModemType + { + YModem, + YModem_1K + } + + /// + /// YModemյϢ + /// + enum YmodemMessageType : int + { + KEY_C, + ACK, + NAK, + EOT, + PACKET, + PACKET_ERROR, + CAN + } + + /// + /// YModemյϢ + /// + internal class YmodemMessage + { + private YmodemMessageType _MessageType; + private object _Value; + + + public YmodemMessage(YmodemMessageType type) + : this(type, null) + { + + } + + public YmodemMessage(YmodemMessageType type, object value) + { + _MessageType = type; + _Value = value; + } + + + public YmodemMessageType MessageType + { + get { return _MessageType; } + } + + public object Value + { + get { return _Value; } + } + } + + /// + /// XmodemͲ + /// + internal enum YmodemSendStage : int + { + WaitReceiveRequestFileInfo, //ȴնļͷ + WaitReceiveRequestFirstPacket, + PacketSending, + WaitReceiveAnswerEndTransmit, // ȴշӦEOT + WaitReceiveNextFileReq, // ȵȽշһļ + } + + /// + /// Xmodemղ + /// + internal enum YmodemReceiveStage : int + { + WaitForFileInfo, + WaitForFirstPacket, + PacketReceiving, + } + + public class YModemInfo + { + private YModemType _YModemType; + private TransmitMode _TransType; + + public YModemInfo() + : this(YModemType.YModem, TransmitMode.Send) + { + + } + + public YModemInfo(YModemType type, TransmitMode transType) + { + _YModemType = type; + _TransType = transType; + } + + + public YModemType Type + { + get { return _YModemType; } + set { _YModemType = value; } + } + + public TransmitMode TransMode + { + get { return _TransType; } + set { _TransType = value; } + } + + //public YModemCheckMode CheckMode + //{ + // get { return _CheckMode; } + // set { _CheckMode = value; } + //} + + } + + public class YModem : IFileTramsmit, ITransmitUart + { + private readonly byte SOH = 0x01; + private readonly byte STX = 0x02; + private readonly byte EOT = 0x04; + private readonly byte ACK = 0x06; + private readonly byte NAK = 0x15; + private readonly byte CAN = 0x18; + private readonly byte KEY_C = 0x43; //'C'; + + private int RetryMax = 6; + + YModemInfo ymodemInfo = new YModemInfo(); + + + private Thread TransThread; + private bool IsStart = false; + + private int reTryCount; + private ManualResetEvent waitReceiveEvent = new ManualResetEvent(false); + private YmodemReceiveStage ReceiveStage; + private YmodemSendStage SendStage; + private Queue msgQueue = new Queue(); + + public YModem(TransmitMode transType, YModemType ymodemType, int reTryCount) + { + RetryMax = reTryCount; + + ymodemInfo.Type = ymodemType; + ymodemInfo.TransMode = transType; + } + + public void Start() + { + IsStart = true; + reTryCount = 0; + + ReceiveStage = YmodemReceiveStage.WaitForFileInfo; + SendStage = YmodemSendStage.WaitReceiveRequestFileInfo; + + + TransThread = new Thread(new ThreadStart(TransThreadHandler)); + TransThread.IsBackground = true; + TransThread.Name = "YmodemTransThread"; + TransThread.Start(); + if (ymodemInfo.TransMode == TransmitMode.Receive) + { + if (StartReceive != null) + { + StartReceive(ymodemInfo, null); + } + } + } + + public void Stop() + { + if (ymodemInfo.TransMode == TransmitMode.Receive) + { + Abort(); + } + else + { + SendEOT(); + + } + + } + + public void Abort() + { + IsStart = false; + SendCAN(); + + if (EndOfTransmit != null) + { + EndOfTransmit(ymodemInfo, null); + } + } + + + /// + /// + /// + /// + private void ParseReceivedMessage(byte[] data) + { + + YmodemMessage ReceivedMessage = null; + + if (data == null) + { + ReceivedMessage = null; + } + else + { + if (data[0] == STX || data[0] == SOH) + { + ReceivedMessage = new YmodemMessage(YmodemMessageType.PACKET_ERROR); + int packetLen = 0; + if (data[0] == STX) + { + packetLen = 1024; + } + else if (data[0] == SOH) + { + packetLen = 128; + } + + int checkDataLen = 2; + if (packetLen + 3 + checkDataLen == data.Length) + { + int packetNo = 0; + if (data[1] == Convert.ToByte((~data[2]) & 0xFF)) + { + packetNo = data[1]; + } + + int frameCheckCode = 0; + int calCheckCode = -1; + byte[] packet = new byte[packetLen]; + + Array.Copy(data, 3, packet, 0, packetLen); + + frameCheckCode = (data[3 + packetLen] << 8) + data[3 + packetLen + 1]; + calCheckCode = Convert.ToUInt16(DataCheck.GetCRC(CRCType.CRC16_XMODEM, packet) & 0xFFFF); + + + if (frameCheckCode == calCheckCode) + { + ReceivedMessage = new YmodemMessage(YmodemMessageType.PACKET, new PacketEventArgs(packetNo, packet)); + } + + } + } + else + { + foreach (byte b in data) + { + ReceivedMessage = null; + + if (b == EOT) + { + ReceivedMessage = new YmodemMessage(YmodemMessageType.EOT); + } + else if (b == CAN) + { + ReceivedMessage = new YmodemMessage(YmodemMessageType.CAN); + } + else if (b == NAK) + { + ReceivedMessage = new YmodemMessage(YmodemMessageType.NAK); + } + else if (b == ACK) + { + ReceivedMessage = new YmodemMessage(YmodemMessageType.ACK); + } + else if (b == KEY_C) + { + ReceivedMessage = new YmodemMessage(YmodemMessageType.KEY_C); + } + else + { + } + + if (ReceivedMessage!=null) + { + msgQueue.Enqueue(ReceivedMessage); + } + } + } + + } + + waitReceiveEvent.Set(); + + + } + + + private void SendFrameToUart(byte data) + { + byte[] bytes = new byte[1]; + bytes[0] = data; + SendFrameToUart(bytes); + } + private void SendFrameToUart(byte[] data) + { + if (SendToUartEvent != null) + { + SendToUartEvent(ymodemInfo, new SendToUartEventArgs(data)); + } + } + + + + private void SendACK() + { + SendFrameToUart(ACK); + + } + + private void SendNAK() + { + SendFrameToUart(NAK); + } + + private void SendKEYC() + { + SendFrameToUart(KEY_C); + } + + private void SendCAN() + { + byte[] bytes = new byte[5]; + for (int i = 0; i < 5; i++) + { + bytes[i] = CAN; + } + SendFrameToUart(bytes); + + } + + private void SendEOT() + { + SendFrameToUart(EOT); + SendStage = YmodemSendStage.WaitReceiveAnswerEndTransmit; + } + + + private void SendNoFilesToSend() + { + //int packetLen = ymodemInfo.Type == YModemType.YModem ? 128 : 1024; + byte[] endPacket = new byte[3 + 128 + 2]; + endPacket[0] = 0x01; + endPacket[1] = 0x00; + endPacket[2] = 0xFF; + SendFrameToUart(endPacket); + + if (EndOfTransmit != null) + { + EndOfTransmit(ymodemInfo, null); + } + IsStart = false; + } + + void TransThreadHandler() + { + while (IsStart) + { + if (ymodemInfo.TransMode == TransmitMode.Send) + { + SendHandler(); + } + else if (ymodemInfo.TransMode == TransmitMode.Receive) + { + ReceiveHandler(); + } + } + } + + void SendHandler() + { + + YmodemMessage msg; + if (msgQueue.Count > 0) + { + msg = msgQueue.Dequeue(); + if (msg != null) + { + reTryCount = 0; + switch (msg.MessageType) + { + case YmodemMessageType.NAK: + if (SendStage == YmodemSendStage.WaitReceiveAnswerEndTransmit) + { + SendEOT(); + } + else + { + // ֪ͨط + if (ReSendPacket != null) + { + ReSendPacket(ymodemInfo, null); + } + + } + + break; + case YmodemMessageType.KEY_C: + if (SendStage == YmodemSendStage.WaitReceiveRequestFileInfo) + { + // ֪ͨͷһCRC + if (StartSend != null) + { + StartSend(ymodemInfo, null); + } + } + else if (SendStage == YmodemSendStage.WaitReceiveRequestFirstPacket) //ȴһ + { + SendStage = YmodemSendStage.PacketSending; + // ֪ͨһ + if (SendNextPacket != null) + { + SendNextPacket(ymodemInfo, null); + } + } + else if (SendStage == YmodemSendStage.WaitReceiveNextFileReq) //շһļ + { + SendNoFilesToSend(); + } + + + break; + case YmodemMessageType.ACK: + if (SendStage == YmodemSendStage.WaitReceiveRequestFileInfo) + { + SendStage = YmodemSendStage.WaitReceiveRequestFirstPacket; //ȴշһ + } + else if (SendStage == YmodemSendStage.PacketSending) + { + // ֪ͨһ + if (SendNextPacket != null) + { + SendNextPacket(ymodemInfo, null); + } + } + else if (SendStage == YmodemSendStage.WaitReceiveAnswerEndTransmit) + { + SendStage = YmodemSendStage.WaitReceiveNextFileReq; //ȴշһļ + } + + break; + case YmodemMessageType.CAN: + // ֹ֪ͨ + if (AbortTransmit != null) + { + AbortTransmit(ymodemInfo, null); + } + break; + default: + + break; + } + + } + } + else + { + if (waitReceiveEvent.WaitOne(3000)) + { + waitReceiveEvent.Reset(); + } + else + { + reTryCount++; + if (reTryCount > RetryMax) + { + IsStart = false; + //֪ͨճʱ + if (TransmitTimeOut != null) + { + TransmitTimeOut(ymodemInfo, null); + } + } + } + } + + + } + + void ReceiveHandler() + { + if (ReceiveStage == YmodemReceiveStage.WaitForFileInfo || ReceiveStage == YmodemReceiveStage.WaitForFirstPacket) + { + SendKEYC(); + } + + if (msgQueue.Count > 0) + { + YmodemMessage msg = msgQueue.Dequeue(); + if (msg != null) + { + reTryCount = 0; + + switch (msg.MessageType) + { + case YmodemMessageType.PACKET: + + PacketEventArgs e = msg.Value as PacketEventArgs; + if (ReceiveStage == YmodemReceiveStage.WaitForFileInfo) + { + if (e.PacketNo == 0) + { + ReceiveStage = YmodemReceiveStage.WaitForFirstPacket; + SendACK(); + + if (ReceivedPacket != null) + { + ReceivedPacket(ymodemInfo, new PacketEventArgs(e.PacketNo, e.Packet)); + } + } + //else + //{ + // SendNAK(); + //} + } + + else if (ReceiveStage == YmodemReceiveStage.WaitForFirstPacket || + ReceiveStage == YmodemReceiveStage.PacketReceiving) + { + if (ReceiveStage == YmodemReceiveStage.WaitForFirstPacket) + { + ReceiveStage = YmodemReceiveStage.PacketReceiving; + } + + SendACK(); + + if (ReceivedPacket != null) + { + ReceivedPacket(ymodemInfo, new PacketEventArgs(e.PacketNo, e.Packet)); + } + + // ֪ͨһ + if (SendNextPacket != null) + { + SendNextPacket(ymodemInfo, null); + } + } + + + break; + case YmodemMessageType.PACKET_ERROR: + SendNAK(); + // ֪ͨط + if (ReSendPacket != null) + { + ReSendPacket(ymodemInfo, null); + } + break; + case YmodemMessageType.EOT: + SendACK(); + // ֪ͨ + if (EndOfTransmit != null) + { + EndOfTransmit(ymodemInfo, null); + } + break; + case YmodemMessageType.CAN: + SendACK(); + // ֹ֪ͨ + if (AbortTransmit != null) + { + AbortTransmit(ymodemInfo, null); + } + break; + default: + break; + } + } + + } + else + { + if (waitReceiveEvent.WaitOne(3000)) + { + waitReceiveEvent.Reset(); + } + else + { + reTryCount++; + if (reTryCount > RetryMax) + { + IsStart = false; + //֪ͨճʱ + if (TransmitTimeOut != null) + { + TransmitTimeOut(ymodemInfo, null); + } + } + + } + } + + } + + + #region IFileTramsmit Ա + + public event EventHandler StartSend; + + public event EventHandler StartReceive; + + public event EventHandler SendNextPacket; + + public event EventHandler ReSendPacket; + + public event EventHandler AbortTransmit; + + public event EventHandler EndOfTransmit; + + public event EventHandler TransmitTimeOut; + + public event PacketEventHandler ReceivedPacket; + + public void SendPacket(PacketEventArgs packet) + { + int packetLen = 0; + int checkLen = 0; + byte[] data; + + checkLen = 2; + + if (ymodemInfo.Type == YModemType.YModem_1K) + { + packetLen = 1024; + } + else + { + packetLen = 128; + } + + data = new byte[3 + packetLen + checkLen]; + + data[0] = SOH; + if (ymodemInfo.Type == YModemType.YModem_1K) + { + data[0] = STX; + } + + data[1] = Convert.ToByte(packet.PacketNo & 0xFF); + data[2] = Convert.ToByte((~data[1]) & 0xFF); + Array.Copy(packet.Packet, 0, data, 3, packetLen); + + UInt16 crc = Convert.ToUInt16(DataCheck.GetCRC(CRCType.CRC16_XMODEM, packet.Packet) & 0xFFFF); + data[3 + packetLen] = Convert.ToByte(crc >> 8); + data[3 + packetLen + 1] = Convert.ToByte(crc & 0xFF); + + + SendFrameToUart(data); + } + + #endregion + + + #region ITransmitUart Ա + + public event SendToUartEventHandler SendToUartEvent; + public void ReceivedFromUart(byte[] data) + { + ParseReceivedMessage(data); + + } + #endregion + } +} diff --git a/常用工具集/Utility/SerialDebug/ISendForm.cs b/常用工具集/Utility/SerialDebug/ISendForm.cs new file mode 100644 index 0000000..1e5d68e --- /dev/null +++ b/常用工具集/Utility/SerialDebug/ISendForm.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SerialDebug +{ + public interface ISendForm + { + List getSendList(); + + + int LoopCount + { + get; + } + } + + + +} diff --git a/常用工具集/Utility/SqlFormat/BasicFormatter.cs b/常用工具集/Utility/SqlFormat/BasicFormatter.cs new file mode 100644 index 0000000..86fb671 --- /dev/null +++ b/常用工具集/Utility/SqlFormat/BasicFormatter.cs @@ -0,0 +1,430 @@ +namespace NHibernate.AdoNet.Util +{ + using NHibernate.Util; + using System; + using System.Collections.Generic; + using System.Text; + + public class BasicFormatter : IFormatter + { + protected static readonly HashSet beginClauses = new HashSet(); + protected static readonly HashSet dml = new HashSet(); + protected static readonly HashSet endClauses = new HashSet(); + protected const string IndentString = " "; + protected const string Initial = "\n "; + protected static readonly HashSet logical = new HashSet(); + protected static readonly HashSet misc = new HashSet(); + protected static readonly HashSet quantifiers = new HashSet(); + + static BasicFormatter() + { + beginClauses.Add("left"); + beginClauses.Add("right"); + beginClauses.Add("inner"); + beginClauses.Add("outer"); + beginClauses.Add("group"); + beginClauses.Add("order"); + endClauses.Add("where"); + endClauses.Add("set"); + endClauses.Add("having"); + endClauses.Add("join"); + endClauses.Add("from"); + endClauses.Add("by"); + endClauses.Add("join"); + endClauses.Add("into"); + endClauses.Add("union"); + logical.Add("and"); + logical.Add("or"); + logical.Add("when"); + logical.Add("else"); + logical.Add("end"); + quantifiers.Add("in"); + quantifiers.Add("all"); + quantifiers.Add("exists"); + quantifiers.Add("some"); + quantifiers.Add("any"); + dml.Add("insert"); + dml.Add("update"); + dml.Add("delete"); + misc.Add("select"); + misc.Add("on"); + } + + public virtual string Format(string source) + { + return new FormatProcess(source).Perform(); + } + + private class FormatProcess + { + private bool afterBeginBeforeEnd; + private bool afterBetween; + private readonly List afterByOrFromOrSelects = new List(); + private bool afterByOrSetOrFromOrSelect; + private bool afterInsert; + private bool afterOn; + private bool beginLine = true; + private bool endCommandFound; + private int indent = 1; + private int inFunction; + private string lastToken; + private string lcToken; + private readonly List parenCounts = new List(); + private int parensSinceSelect; + private readonly StringBuilder result = new StringBuilder(); + private string token; + private readonly IEnumerator tokens; + + public FormatProcess(string sql) + { + this.tokens = new StringTokenizer(sql, "()+*/-=<>'`\"[],; \n\r\f\t", true).GetEnumerator(); + } + + private void BeginNewClause() + { + if (!this.afterBeginBeforeEnd) + { + if (this.afterOn) + { + this.indent--; + this.afterOn = false; + } + this.indent--; + this.Newline(); + } + this.Out(); + this.beginLine = false; + this.afterBeginBeforeEnd = true; + } + + private void CloseParen() + { + if (this.endCommandFound) + { + this.Out(); + } + else + { + this.parensSinceSelect--; + if (this.parensSinceSelect < 0) + { + this.indent--; + int num = this.parenCounts[this.parenCounts.Count - 1]; + this.parenCounts.RemoveAt(this.parenCounts.Count - 1); + this.parensSinceSelect = num; + bool flag = this.afterByOrFromOrSelects[this.afterByOrFromOrSelects.Count - 1]; + this.afterByOrFromOrSelects.RemoveAt(this.afterByOrFromOrSelects.Count - 1); + this.afterByOrSetOrFromOrSelect = flag; + } + if (this.inFunction > 0) + { + this.inFunction--; + this.Out(); + } + else + { + if (!this.afterByOrSetOrFromOrSelect) + { + this.indent--; + this.Newline(); + } + this.Out(); + } + this.beginLine = false; + } + } + + private void CommaAfterByOrFromOrSelect() + { + this.Out(); + this.Newline(); + } + + private void CommaAfterOn() + { + this.Out(); + this.indent--; + this.Newline(); + this.afterOn = false; + this.afterByOrSetOrFromOrSelect = true; + } + + private void EndNewClause() + { + if (!this.afterBeginBeforeEnd) + { + this.indent--; + if (this.afterOn) + { + this.indent--; + this.afterOn = false; + } + this.Newline(); + } + this.Out(); + if (!"union".Equals(this.lcToken)) + { + this.indent++; + } + this.Newline(); + this.afterBeginBeforeEnd = false; + this.afterByOrSetOrFromOrSelect = ("by".Equals(this.lcToken) || "set".Equals(this.lcToken)) || "from".Equals(this.lcToken); + } + + private void ExtractStringEnclosedBy(string stringDelimiter) + { + while (this.tokens.MoveNext()) + { + string current = this.tokens.Current; + this.token = this.token + current; + if (stringDelimiter.Equals(current)) + { + return; + } + } + } + + private static bool IsFunctionName(string tok) + { + char c = tok[0]; + return (((((((char.IsLetter(c) || (c.CompareTo('$') == 0)) || (c.CompareTo('_') == 0)) || ('"' == c)) && !BasicFormatter.logical.Contains(tok)) && (!BasicFormatter.endClauses.Contains(tok) && !BasicFormatter.quantifiers.Contains(tok))) && !BasicFormatter.dml.Contains(tok)) && !BasicFormatter.misc.Contains(tok)); + } + + private bool IsMultiQueryDelimiter(string delimiter) + { + return ";".Equals(delimiter); + } + + private static bool IsWhitespace(string token) + { + return (" \n\r\f\t".IndexOf(token) >= 0); + } + + private void Logical() + { + if ("end".Equals(this.lcToken)) + { + this.indent--; + } + this.Newline(); + this.Out(); + this.beginLine = false; + } + + private void Misc() + { + this.Out(); + if ("between".Equals(this.lcToken)) + { + this.afterBetween = true; + } + if (this.afterInsert) + { + this.Newline(); + this.afterInsert = false; + } + else + { + this.beginLine = false; + if ("case".Equals(this.lcToken)) + { + this.indent++; + } + } + } + + private void Newline() + { + this.result.Append("\n"); + for (int i = 0; i < this.indent; i++) + { + this.result.Append(" "); + } + this.beginLine = true; + } + + private void On() + { + this.indent++; + this.afterOn = true; + this.Newline(); + this.Out(); + this.beginLine = false; + } + + private void OpenParen() + { + if (this.endCommandFound) + { + this.Out(); + } + else + { + if (IsFunctionName(this.lastToken) || (this.inFunction > 0)) + { + this.inFunction++; + } + this.beginLine = false; + if (this.inFunction > 0) + { + this.Out(); + } + else + { + this.Out(); + if (!this.afterByOrSetOrFromOrSelect) + { + this.indent++; + this.Newline(); + this.beginLine = true; + } + } + this.parensSinceSelect++; + } + } + + private void Out() + { + this.result.Append(this.token); + } + + public string Perform() + { + this.result.Append("\n "); + while (this.tokens.MoveNext()) + { + this.token = this.tokens.Current; + this.lcToken = this.token.ToLowerInvariant(); + if ("'".Equals(this.token)) + { + this.ExtractStringEnclosedBy("'"); + } + else if ("\"".Equals(this.token)) + { + this.ExtractStringEnclosedBy("\""); + } + if (this.IsMultiQueryDelimiter(this.token)) + { + this.StartingNewQuery(); + } + else if (this.afterByOrSetOrFromOrSelect && ",".Equals(this.token)) + { + this.CommaAfterByOrFromOrSelect(); + } + else if (this.afterOn && ",".Equals(this.token)) + { + this.CommaAfterOn(); + } + else if ("(".Equals(this.token)) + { + this.OpenParen(); + } + else if (")".Equals(this.token)) + { + this.CloseParen(); + } + else if (BasicFormatter.beginClauses.Contains(this.lcToken)) + { + this.BeginNewClause(); + } + else if (BasicFormatter.endClauses.Contains(this.lcToken)) + { + this.EndNewClause(); + } + else if ("select".Equals(this.lcToken)) + { + this.Select(); + } + else if (BasicFormatter.dml.Contains(this.lcToken)) + { + this.UpdateOrInsertOrDelete(); + } + else if ("values".Equals(this.lcToken)) + { + this.Values(); + } + else if ("on".Equals(this.lcToken)) + { + this.On(); + } + else if (this.afterBetween && this.lcToken.Equals("and")) + { + this.Misc(); + this.afterBetween = false; + } + else if (BasicFormatter.logical.Contains(this.lcToken)) + { + this.Logical(); + } + else if (IsWhitespace(this.token)) + { + this.White(); + } + else + { + this.Misc(); + } + if (!IsWhitespace(this.token)) + { + this.lastToken = this.lcToken; + } + } + return this.result.ToString(); + } + + private void Select() + { + this.Out(); + this.indent++; + this.Newline(); + this.parenCounts.Insert(this.parenCounts.Count, this.parensSinceSelect); + this.afterByOrFromOrSelects.Insert(this.afterByOrFromOrSelects.Count, this.afterByOrSetOrFromOrSelect); + this.parensSinceSelect = 0; + this.afterByOrSetOrFromOrSelect = true; + this.endCommandFound = false; + } + + private void StartingNewQuery() + { + this.Out(); + this.indent = 1; + this.endCommandFound = true; + this.Newline(); + } + + private void UpdateOrInsertOrDelete() + { + this.Out(); + this.indent++; + this.beginLine = false; + if ("update".Equals(this.lcToken)) + { + this.Newline(); + } + if ("insert".Equals(this.lcToken)) + { + this.afterInsert = true; + } + this.endCommandFound = false; + } + + private void Values() + { + this.indent--; + this.Newline(); + this.Out(); + this.indent++; + this.Newline(); + } + + private void White() + { + if (!this.beginLine) + { + this.result.Append(" "); + } + } + } + } +} + diff --git a/常用工具集/Utility/SqlFormat/DdlFormatter.cs b/常用工具集/Utility/SqlFormat/DdlFormatter.cs new file mode 100644 index 0000000..fb8526a --- /dev/null +++ b/常用工具集/Utility/SqlFormat/DdlFormatter.cs @@ -0,0 +1,139 @@ +namespace NHibernate.AdoNet.Util +{ + using NHibernate.Util; + using System; + using System.Collections.Generic; + using System.Text; + + public class DdlFormatter : IFormatter + { + private const string Indent1 = "\n "; + private const string Indent2 = "\n "; + private const string Indent3 = "\n "; + + public virtual string Format(string sql) + { + if (sql.ToLowerInvariant().StartsWith("create table")) + { + return this.FormatCreateTable(sql); + } + if (sql.ToLowerInvariant().StartsWith("alter table")) + { + return this.FormatAlterTable(sql); + } + if (sql.ToLowerInvariant().StartsWith("comment on")) + { + return this.FormatCommentOn(sql); + } + return ("\n " + sql); + } + + protected virtual string FormatAlterTable(string sql) + { + StringBuilder builder = new StringBuilder(60).Append("\n "); + IEnumerator enumerator = new StringTokenizer(sql, " (,)'[]\"", true).GetEnumerator(); + bool flag = false; + while (enumerator.MoveNext()) + { + string current = enumerator.Current; + if (IsQuote(current)) + { + flag = !flag; + } + else if (!flag && IsBreak(current)) + { + builder.Append("\n "); + } + builder.Append(current); + } + return builder.ToString(); + } + + protected virtual string FormatCommentOn(string sql) + { + StringBuilder builder = new StringBuilder(60).Append("\n "); + IEnumerator enumerator = new StringTokenizer(sql, " '[]\"", true).GetEnumerator(); + bool flag = false; + while (enumerator.MoveNext()) + { + string current = enumerator.Current; + builder.Append(current); + if (IsQuote(current)) + { + flag = !flag; + } + else if (!flag && "is".Equals(current)) + { + builder.Append("\n "); + } + } + return builder.ToString(); + } + + protected virtual string FormatCreateTable(string sql) + { + StringBuilder builder = new StringBuilder(60).Append("\n "); + IEnumerator enumerator = new StringTokenizer(sql, "(,)'[]\"", true).GetEnumerator(); + int num = 0; + bool flag = false; + while (enumerator.MoveNext()) + { + string current = enumerator.Current; + if (IsQuote(current)) + { + flag = !flag; + builder.Append(current); + } + else + { + if (flag) + { + builder.Append(current); + continue; + } + if (")".Equals(current)) + { + num--; + if (num == 0) + { + builder.Append("\n "); + } + } + builder.Append(current); + if (",".Equals(current) && (num == 1)) + { + builder.Append("\n "); + } + if ("(".Equals(current)) + { + num++; + if (num == 1) + { + builder.Append("\n "); + } + } + } + } + return builder.ToString(); + } + + private static bool IsBreak(string token) + { + if ((!"drop".Equals(token) && !"add".Equals(token)) && (!"references".Equals(token) && !"foreign".Equals(token))) + { + return "on".Equals(token); + } + return true; + } + + private static bool IsQuote(string token) + { + if ((!"\"".Equals(token) && !"`".Equals(token)) && (!"]".Equals(token) && !"[".Equals(token))) + { + return "'".Equals(token); + } + return true; + } + } +} + diff --git a/常用工具集/Utility/SqlFormat/FormatStyle.cs b/常用工具集/Utility/SqlFormat/FormatStyle.cs new file mode 100644 index 0000000..fe4e58d --- /dev/null +++ b/常用工具集/Utility/SqlFormat/FormatStyle.cs @@ -0,0 +1,54 @@ +namespace NHibernate.AdoNet.Util +{ + using System; + using System.Runtime.CompilerServices; + + public class FormatStyle + { + public static readonly FormatStyle Basic = new FormatStyle("basic", new BasicFormatter()); + public static readonly FormatStyle Ddl = new FormatStyle("ddl", new DdlFormatter()); + public static readonly FormatStyle None = new FormatStyle("none", new NoFormatImpl()); + + private FormatStyle(string name, IFormatter formatter) + { + this.Name = name; + this.Formatter = formatter; + } + + public bool Equals(FormatStyle other) + { + if (other == null) + { + return false; + } + return (object.ReferenceEquals(this, other) || object.Equals(other.Name, this.Name)); + } + + public override bool Equals(object obj) + { + return this.Equals(obj as FormatStyle); + } + + public override int GetHashCode() + { + if (this.Name == null) + { + return 0; + } + return this.Name.GetHashCode(); + } + + public IFormatter Formatter { get; private set; } + + public string Name { get; private set; } + + private class NoFormatImpl : IFormatter + { + public string Format(string source) + { + return source; + } + } + } +} + diff --git a/常用工具集/Utility/SqlFormat/IFormatter.cs b/常用工具集/Utility/SqlFormat/IFormatter.cs new file mode 100644 index 0000000..b0c7d65 --- /dev/null +++ b/常用工具集/Utility/SqlFormat/IFormatter.cs @@ -0,0 +1,10 @@ +namespace NHibernate.AdoNet.Util +{ + using System; + + public interface IFormatter + { + string Format(string source); + } +} + diff --git a/常用工具集/Utility/SqlFormat/StringTokenizer.cs b/常用工具集/Utility/SqlFormat/StringTokenizer.cs new file mode 100644 index 0000000..fddb222 --- /dev/null +++ b/常用工具集/Utility/SqlFormat/StringTokenizer.cs @@ -0,0 +1,115 @@ +namespace NHibernate.Util +{ + using System; + using System.Collections; + using System.Collections.Generic; + + public class StringTokenizer : IEnumerable, IEnumerable + { + private const string _defaultDelim = " \t\n\r\f"; + private string _delim; + private string _origin; + private bool _returnDelim; + + public StringTokenizer(string str) + { + this._origin = str; + this._delim = " \t\n\r\f"; + this._returnDelim = false; + } + + public StringTokenizer(string str, string delim) + { + this._origin = str; + this._delim = delim; + this._returnDelim = true; + } + + public StringTokenizer(string str, string delim, bool returnDelims) + { + this._origin = str; + this._delim = delim; + this._returnDelim = returnDelims; + } + + public IEnumerator GetEnumerator() + { + return new StringTokenizerEnumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new StringTokenizerEnumerator(this); + } + + private class StringTokenizerEnumerator : IEnumerator, IDisposable, IEnumerator + { + private int _cursor; + private string _next; + private StringTokenizer _stokenizer; + + public StringTokenizerEnumerator(StringTokenizer stok) + { + this._stokenizer = stok; + } + + public void Dispose() + { + } + + private string GetNext() + { + if (this._cursor >= this._stokenizer._origin.Length) + { + return null; + } + char ch = this._stokenizer._origin[this._cursor]; + if (this._stokenizer._delim.IndexOf(ch) != -1) + { + this._cursor++; + if (this._stokenizer._returnDelim) + { + return ch.ToString(); + } + return this.GetNext(); + } + int length = this._stokenizer._origin.IndexOfAny(this._stokenizer._delim.ToCharArray(), this._cursor); + if (length == -1) + { + length = this._stokenizer._origin.Length; + } + string str = this._stokenizer._origin.Substring(this._cursor, length - this._cursor); + this._cursor = length; + return str; + } + + public bool MoveNext() + { + this._next = this.GetNext(); + return (this._next != null); + } + + public void Reset() + { + this._cursor = 0; + } + + public string Current + { + get + { + return this._next; + } + } + + object IEnumerator.Current + { + get + { + return this.Current; + } + } + } + } +} + diff --git a/常用工具集/ViewModels/01PLC通信调试/MC3E服务模拟ViewModel.cs b/常用工具集/ViewModels/01PLC通信调试/MC3E服务模拟ViewModel.cs new file mode 100644 index 0000000..1421791 --- /dev/null +++ b/常用工具集/ViewModels/01PLC通信调试/MC3E服务模拟ViewModel.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using 常用工具集.Base; +using EasyModbus; +using MES.Utility.Core; +using System.Windows; +using 常用工具集.Utility.Network.Mitsubishi; +using System.Linq; +using Avalonia.Threading; + +namespace 常用工具集.ViewModel._01PLC通信调试 +{ + public class MC3E服务模拟ViewModel : ViewModelBase + { + internal static MC3EServer server; + public bool Enabled1 { get; set; } = true; + public bool Enabled2 { get; set; } = false; + public int Port { get; set; } = 5001; + private List logMessageList = new List(); + public string Message + { + get + { + return logMessageList.GetStrArray("\r\n"); + } + set + { + NotifyPropertyChanged(); + } + } + public string ButtonText { get; set; } = "开启服务"; + + private ushort _startAddess = 1; + public ushort StartAddress + { + get { return _startAddess; } + set + { + _startAddess = value; + SetEveryList(); + NotifyPropertyChanged(); + } + } + + public int SelectIndex1 { get; set; } = -1; + public ObservableCollection List1 { get; set; } = new ObservableCollection(); + public DelegateCommand StartCmd { get; set; } + public DelegateCommand CellClickCmd { get; set; } + public MC3E服务模拟ViewModel() + { + StartCmd = new DelegateCommand(StartCmdFunc); + } + + + + private void StartCmdFunc(object obj) + { + if (ButtonText == "开启服务") + { + server = new MC3EServer(Port); + server.Start(); + //监听数据变化 + server.DataChangedEvent += DataChanged; + server.LogChangedEvent += LogDataChanged; + SetEveryList(); + Enabled1 = false; + Enabled2 = true; + ButtonText = "停止服务"; + } + else + { + logMessageList.Clear(); + Message = Guid.NewGuid().ToString(); + server.Stop(); + server = null; + List1.Clear(); + Enabled1 = true; + Enabled2 = false; + ButtonText = "开启服务"; + } + } + + private void LogDataChanged(string message) + { + + try + { + logMessageList.Insert(0, message); + while (logMessageList.Count > 50) + { + logMessageList.RemoveAt(logMessageList.Count - 1); + } + } + catch (Exception) { } + Message = Guid.NewGuid().ToString(); + + + } + + + + private void SetEveryList() + { + Dispatcher.UIThread.Invoke(() => + { + ushort startAddress = StartAddress; + List1.Clear(); + for (int i = startAddress; i < 10 + startAddress; i++) + { + MyDataGrid data = new MyDataGrid { Which = 3, Address = i, Value2 = MC3EServer._dataStorage[i] }; + List1.Add(data); + } + }); + + } + + private void DataChanged(int address) + { + SetEveryList(); + } + } +} \ No newline at end of file diff --git a/常用工具集/ViewModels/01PLC通信调试/ModbusRTUViewModel.cs b/常用工具集/ViewModels/01PLC通信调试/ModbusRTUViewModel.cs new file mode 100644 index 0000000..7cfba51 --- /dev/null +++ b/常用工具集/ViewModels/01PLC通信调试/ModbusRTUViewModel.cs @@ -0,0 +1,293 @@ +using MES.Utility.Network; +using System; +using System.Collections.Generic; +using System.IO.Ports; +using System.Linq; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._01PLC通信调试 +{ + public class ModbusRTUViewModel : ViewModelBase + { + public int SlaveId { get; set; } = 1; + //串口号下拉列表数据 + public List SerialList { get; set; } + public int SerialIndex { get; set; } = 0; + + /// + /// 波特率 + /// + public List BaudRateList { get; set; } + public int BaudRateIndex { get; set; } = 0; + //校验 + public List ParityList { get; set; } + public int ParityIndex { get; set; } = 0; + + //数据为 + public List DataBitList { get; set; } + public int DataBitIndex { get; set; } = 0; + //停止位 + public List StopBitList { get; set; } + public int StopBitIndex { get; set; } = 0; + + public bool Enabled1 { get; set; } = true; + public bool Enabled2 { get; set; } = false; + public int Address { get; set; } = 4001; + public int StringLength { get; set; } = 100; + public string ReadedValue { get; set; } = ""; + public string Message { get; set; } = ""; + + public string SendedBytesValue { get; set; } = ""; + public string RecivedBytesValue { get; set; } = ""; + public string Int16Value { get; set; } = ""; + public string Int32Value { get; set; } = ""; + public string RealValue { get; set; } = ""; + public string StringValue { get; set; } = ""; + public string DoubleValue { get; set; } = ""; + public string LongValue { get; set; } = ""; + + //STRING DOUBLE INT64 INT32 INT16 REAL + private ModbusHelper modbus; + + public DelegateCommand ConnectCmd { get; set; } + public DelegateCommand DisconnectCmd { get; set; } + public DelegateCommand WriteCmd { get; set; } + public DelegateCommand ReadCmd { get; set; } + public ModbusRTUViewModel() + { + //串口号 + string[] portNames = SerialPort.GetPortNames(); + if (portNames != null && portNames.Length > 0) + { + SerialList = portNames.ToList(); + } + //波特率 + BaudRateList = new List() { 110, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 56000, 57600, 115200, 128000 }; + BaudRateIndex = 6; + //校验 + ParityList = new List { "None", "Odd", "Even", "Mark", "Space" }; + ParityIndex = 0; + //数据位 + DataBitList = new List { 5, 6, 7, 8 }; + DataBitIndex = 3; + //停止位 + StopBitList = new List { "None", "One", "Two", "OnePointFive" }; + StopBitIndex = 1; + ConnectCmd = new DelegateCommand(ConnectCmdFunc); + DisconnectCmd = new DelegateCommand(DisconnectCmdFunc); + ReadCmd = new DelegateCommand(ReadCmdFunc); + WriteCmd = new DelegateCommand(WriteCmdFunc); + } + + private void ConnectCmdFunc(object obj) + { + try + { + SharpModbus.SerialSettings setting = new SharpModbus.SerialSettings(); + setting.PortName = SerialList[SerialIndex]; + setting.BaudRate = BaudRateList[BaudRateIndex]; + setting.DataBits = DataBitList[DataBitIndex]; + setting.Parity = (Parity)ParityIndex; + setting.StopBits = (StopBits)StopBitIndex; + + modbus = new ModbusHelper(setting, 500, (byte)SlaveId); + modbus.OnSended += Modbus_OnSended; + modbus.OnRecived += Modbus_OnRecived; + Enabled1 = false; + Enabled2 = true; + } + catch + { + return; + } + } + + + + private void DisconnectCmdFunc(object obj) + { + modbus.OnSended -= Modbus_OnSended; + modbus.OnRecived -= Modbus_OnRecived; + modbus.Dispose(); + modbus = null; + Enabled1 = true; + Enabled2 = false; + } + + private void ReadCmdFunc(object obj) + { + string cmd = obj.ToString(); + + ReadedValue = ""; + Message = ""; + SendedBytesValue = ""; + RecivedBytesValue = ""; + if (cmd == "INT16") + { + ushort value; + bool flag = modbus.ReadHoldRegisterInt16(Address, out value); + if (!flag) + { + Message = "读取失败"; + DisconnectCmdFunc(null); + return; + } + Message = "读取成功"; + ReadedValue = value.ToString(); + } + else if (cmd == "INT32") + { + int value; + bool flag = modbus.ReadHoldRegisterInt32(Address, out value); + if (!flag) + { + Message = "读取失败"; + DisconnectCmdFunc(null); + return; + } + Message = "读取成功"; + ReadedValue = value.ToString(); + } + else if (cmd == "INT64") + { + long value; + bool flag = modbus.ReadHoldRegisterInt64(Address, out value); + if (!flag) + { + Message = "读取失败"; + DisconnectCmdFunc(null); + return; + } + Message = "读取成功"; + ReadedValue = value.ToString(); + } + else if (cmd == "REAL") + { + float value; + bool flag = modbus.ReadHoldRegisterFloat(Address, out value); + if (!flag) + { + Message = "读取失败"; + DisconnectCmdFunc(null); + return; + } + Message = "读取成功"; + ReadedValue = value.ToString(); + } + else if (cmd == "DOUBLE") + { + double value; + bool flag = modbus.ReadHoldRegisterDouble(Address, out value); + if (!flag) + { + Message = "读取失败"; + DisconnectCmdFunc(null); + return; + } + Message = "读取成功"; + ReadedValue = value.ToString(); + } + else if (cmd == "STRING") + { + string value; + bool flag = modbus.ReadHoldRegisterString(Address, StringLength, out value); + if (!flag) + { + Message = "读取失败"; + DisconnectCmdFunc(null); + return; + } + Message = "读取成功"; + ReadedValue = value.ToString(); + } + + // DOUBLE + + } + + private void WriteCmdFunc(object obj) + { + string cmd = obj.ToString(); + ReadedValue = ""; + Message = ""; + SendedBytesValue = ""; + RecivedBytesValue = ""; + if (cmd == "INT16") + { + ushort value; + bool flag = ushort.TryParse(Int16Value, out value); + if (!flag) + { + Message = "请输入正确的数据"; + return; + } + flag = modbus.WriteHoldRegisterInt16(Address, value); + Message = flag ? "写入成功" : "写入失败"; + } + else if (cmd == "INT32") + { + int value; + bool flag = int.TryParse(Int32Value, out value); + if (!flag) + { + Message = "请输入正确的数据"; + return; + } + flag = modbus.WriteHoldRegisterInt32(Address, value); + Message = flag ? "写入成功" : "写入失败"; + } + else if (cmd == "INT64") + { + long value; + bool flag = long.TryParse(LongValue, out value); + if (!flag) + { + Message = "请输入正确的数据"; + return; + } + flag = modbus.WriteHoldRegisterInt64(Address, value); + Message = flag ? "写入成功" : "写入失败"; + } + else if (cmd == "REAL") + { + float value; + bool flag = float.TryParse(RealValue, out value); + if (!flag) + { + Message = "请输入正确的数据"; + return; + } + flag = modbus.WriteHoldRegisterFloat(Address, value); + Message = flag ? "写入成功" : "写入失败"; + } + else if (cmd == "DOUBLE") + { + double value; + bool flag = double.TryParse(DoubleValue, out value); + if (!flag) + { + Message = "请输入正确的数据"; + return; + } + flag = modbus.WriteHoldRegisterDouble(Address, value); + Message = flag ? "写入成功" : "写入失败"; + } + else if (cmd == "STRING") + { + bool flag = modbus.WriteHoldRegisterString(Address, StringLength, StringValue); + Message = flag ? "写入成功" : "写入失败"; + } + } + + + private void Modbus_OnRecived(byte[] bytes, string hexString) + { + RecivedBytesValue = hexString; + } + + private void Modbus_OnSended(byte[] bytes, string hexString) + { + SendedBytesValue = hexString; + } + } +} diff --git a/常用工具集/ViewModels/01PLC通信调试/Modbus服务ViewModel.cs b/常用工具集/ViewModels/01PLC通信调试/Modbus服务ViewModel.cs new file mode 100644 index 0000000..8bdd406 --- /dev/null +++ b/常用工具集/ViewModels/01PLC通信调试/Modbus服务ViewModel.cs @@ -0,0 +1,300 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using 常用工具集.Base; +using EasyModbus; +using MES.Utility.Core; +using System.Windows; +using Avalonia.Threading; + +namespace 常用工具集.ViewModel._01PLC通信调试 +{ + public class Modbus服务ViewModel : ViewModelBase + { + internal static ModbusServer server; + public bool Enabled1 { get; set; } = true; + public bool Enabled2 { get; set; } = false; + public int Port { get; set; } = 502; + private List logMessageList = new List(); + public string Message + { + get + { + return logMessageList.GetStrArray("\r\n"); + } + set + { + NotifyPropertyChanged(); + } + } + public string ButtonText { get; set; } = "开启服务"; + + private ushort _startAddess = 1; + public ushort StartAddress + { + get { return _startAddess; } + set + { + _startAddess = value; + SetEveryList(); + NotifyPropertyChanged(); + } + } + + public int SelectIndex1 { get; set; } = -1; + public int SelectIndex2 { get; set; } = -1; + public int SelectIndex3 { get; set; } = -1; + public int SelectIndex4 { get; set; } = -1; + public ObservableCollection List1 { get; set; } = new ObservableCollection(); + public ObservableCollection List2 { get; set; } = new ObservableCollection(); + public ObservableCollection List3 { get; set; } = new ObservableCollection(); + public ObservableCollection List4 { get; set; } = new ObservableCollection(); + public DelegateCommand StartCmd { get; set; } + public DelegateCommand CellClickCmd { get; set; } + public Modbus服务ViewModel() + { + StartCmd = new DelegateCommand(StartCmdFunc); + } + + + + private void StartCmdFunc(object obj) + { + if (ButtonText == "开启服务") + { + server = new ModbusServer(Port); + server.Start(); + //监听数据变化 + server.CoilsChanged += new ModbusServer.CoilsChangedHandler(CoilsChanged); + server.HoldingRegistersChanged += new ModbusServer.HoldingRegistersChangedHandler(HoldingRegistersChanged); + server.LogDataChanged += new ModbusServer.LogDataChangedHandler(LogDataChanged); + SetEveryList(); + Enabled1 = false; + Enabled2 = true; + ButtonText = "停止服务"; + } + else + { + logMessageList.Clear(); + Message = Guid.NewGuid().ToString(); + server.Dispose(); + server = null; + List1.Clear(); List2.Clear(); List3.Clear(); List4.Clear(); + Enabled1 = true; + Enabled2 = false; + ButtonText = "开启服务"; + } + } + + private void LogDataChanged() + { + Dispatcher.UIThread.Invoke(() => + { + try + { + logMessageList.Clear(); + string listBoxData; + for (int i = 0; i < server.ModbusLogData.Length; i++) + { + if (server.ModbusLogData[i] == null) + break; + if (server.ModbusLogData[i].request) + { + listBoxData = server.ModbusLogData[i].timeStamp.ToString("H:mm:ss.ff") + " Request from Client - Functioncode: " + server.ModbusLogData[i].functionCode.ToString(); + if (server.ModbusLogData[i].functionCode <= 4) + { + listBoxData = listBoxData + " ; Starting Address: " + server.ModbusLogData[i].startingAdress.ToString() + " Quantity: " + server.ModbusLogData[i].quantity.ToString(); + } + if (server.ModbusLogData[i].functionCode == 5) + { + listBoxData = listBoxData + " ; Output Address: " + server.ModbusLogData[i].startingAdress.ToString() + " Output Value: "; + if (server.ModbusLogData[i].receiveCoilValues[0] == 0) + listBoxData = listBoxData + "False"; + if (server.ModbusLogData[i].receiveCoilValues[0] == 0xFF00) + listBoxData = listBoxData + "True"; + } + if (server.ModbusLogData[i].functionCode == 6) + { + listBoxData = listBoxData + " ; Starting Address: " + server.ModbusLogData[i].startingAdress.ToString() + " Register Value: " + server.ModbusLogData[i].receiveRegisterValues[0].ToString(); + } + if (server.ModbusLogData[i].functionCode == 15) + { + listBoxData = listBoxData + " ; Starting Address: " + server.ModbusLogData[i].startingAdress.ToString() + " Quantity: " + server.ModbusLogData[i].quantity.ToString() + " Byte Count: " + server.ModbusLogData[i].byteCount.ToString() + " Values Received: "; + for (int j = 0; j < server.ModbusLogData[i].quantity; j++) + { + int shift = j % 16; + if ((i == server.ModbusLogData[i].quantity - 1) & (server.ModbusLogData[i].quantity % 2 != 0)) + { + if (shift < 8) + shift = shift + 8; + else + shift = shift - 8; + } + int mask = 0x1; + mask = mask << (shift); + if ((server.ModbusLogData[i].receiveCoilValues[j / 16] & (ushort)mask) == 0) + listBoxData = listBoxData + " False"; + else + listBoxData = listBoxData + " True"; + } + } + if (server.ModbusLogData[i].functionCode == 16) + { + listBoxData = listBoxData + " ; Starting Address: " + server.ModbusLogData[i].startingAdress.ToString() + " Quantity: " + server.ModbusLogData[i].quantity.ToString() + " Byte Count: " + server.ModbusLogData[i].byteCount.ToString() + " Values Received: "; + for (int j = 0; j < server.ModbusLogData[i].quantity; j++) + { + listBoxData = listBoxData + " " + server.ModbusLogData[i].receiveRegisterValues[j]; + } + } + if (server.ModbusLogData[i].functionCode == 23) + { + listBoxData = listBoxData + " ; Starting Address Read: " + server.ModbusLogData[i].startingAddressRead.ToString() + " ; Quantity Read: " + server.ModbusLogData[i].quantityRead.ToString() + " ; Starting Address Write: " + server.ModbusLogData[i].startingAddressWrite.ToString() + " ; Quantity Write: " + server.ModbusLogData[i].quantityWrite.ToString() + " ; Byte Count: " + server.ModbusLogData[i].byteCount.ToString() + " ; Values Received: "; + for (int j = 0; j < server.ModbusLogData[i].quantityWrite; j++) + { + listBoxData = listBoxData + " " + server.ModbusLogData[i].receiveRegisterValues[j]; + } + } + + logMessageList.Add(listBoxData); + } + if (server.ModbusLogData[i].response) + { + if (server.ModbusLogData[i].exceptionCode > 0) + { + listBoxData = server.ModbusLogData[i].timeStamp.ToString("H:mm:ss.ff"); + listBoxData = listBoxData + (" Response To Client - Error code: " + Convert.ToString(server.ModbusLogData[i].errorCode, 16)); + listBoxData = listBoxData + " Exception Code: " + server.ModbusLogData[i].exceptionCode.ToString(); + logMessageList.Add(listBoxData); + + + } + else + { + listBoxData = (server.ModbusLogData[i].timeStamp.ToString("H:mm:ss.ff") + " Response To Client - Functioncode: " + server.ModbusLogData[i].functionCode.ToString()); + + if (server.ModbusLogData[i].functionCode <= 4) + { + listBoxData = listBoxData + " ; Bytecount: " + server.ModbusLogData[i].byteCount.ToString() + " ; Send values: "; + } + if (server.ModbusLogData[i].functionCode == 5) + { + listBoxData = listBoxData + " ; Starting Address: " + server.ModbusLogData[i].startingAdress.ToString() + " ; Output Value: "; + if (server.ModbusLogData[i].receiveCoilValues[0] == 0) + listBoxData = listBoxData + "False"; + if (server.ModbusLogData[i].receiveCoilValues[0] == 0xFF00) + listBoxData = listBoxData + "True"; + } + if (server.ModbusLogData[i].functionCode == 6) + { + listBoxData = listBoxData + " ; Starting Address: " + server.ModbusLogData[i].startingAdress.ToString() + " ; Register Value: " + server.ModbusLogData[i].receiveRegisterValues[0].ToString(); + } + if (server.ModbusLogData[i].functionCode == 15) + { + listBoxData = listBoxData + " ; Starting Address: " + server.ModbusLogData[i].startingAdress.ToString() + " ; Quantity: " + server.ModbusLogData[i].quantity.ToString(); + } + if (server.ModbusLogData[i].functionCode == 16) + { + listBoxData = listBoxData + " ; Starting Address: " + server.ModbusLogData[i].startingAdress.ToString() + " ; Quantity: " + server.ModbusLogData[i].quantity.ToString(); + } + if (server.ModbusLogData[i].functionCode == 23) + { + listBoxData = listBoxData + " ; ByteCount: " + server.ModbusLogData[i].byteCount.ToString() + " ; Send Register Values: "; + } + if (server.ModbusLogData[i].sendCoilValues != null) + { + for (int j = 0; j < server.ModbusLogData[i].sendCoilValues.Length; j++) + { + listBoxData = listBoxData + server.ModbusLogData[i].sendCoilValues[j].ToString() + " "; + } + } + if (server.ModbusLogData[i].sendRegisterValues != null) + { + for (int j = 0; j < server.ModbusLogData[i].sendRegisterValues.Length; j++) + { + listBoxData = listBoxData + server.ModbusLogData[i].sendRegisterValues[j].ToString() + " "; + } + } + logMessageList.Add(listBoxData); + } + } + } + } + catch (Exception) { } + Message = Guid.NewGuid().ToString(); + }); + + } + + + + private void SetEveryList() + { + ushort startAddress = StartAddress; + List1.Clear(); + for (int i = startAddress; i < 10 + startAddress; i++) + { + MyDataGrid data = new MyDataGrid { Which = 1, Address = i, Value1 = server.coils[i + 1] }; + + if (server.coils[i + 1]) + { + data.Color = "Green"; + } + else + { + data.Color = "Red"; + } + List1.Add(data); + } + + List2.Clear(); + for (int i = startAddress; i < 10 + startAddress; i++) + { + MyDataGrid data = new MyDataGrid { Which = 2, Address = i, Value1 = server.discreteInputs[i + 1] }; + if (server.discreteInputs[i + 1]) + { + data.Color = "Green"; + } + else + { + data.Color = "Red"; + } + List2.Add(data); + } + + List3.Clear(); + for (int i = startAddress; i < 10 + startAddress; i++) + { + MyDataGrid data = new MyDataGrid { Which = 3, Address = i, Value2 = server.holdingRegisters[i + 1] }; + List3.Add(data); + } + + List4.Clear(); + for (int i = startAddress; i < 10 + startAddress; i++) + { + MyDataGrid data = new MyDataGrid { Which = 4, Address = i, Value2 = server.inputRegisters[i + 1] }; + List4.Add(data); + } + } + + private void HoldingRegistersChanged(int register, int numberOfRegisters) + { + Dispatcher.UIThread.Invoke(() => { SetEveryList(); }); + } + + private void CoilsChanged(int coil, int numberOfCoils) + { + Dispatcher.UIThread.Invoke(() => { SetEveryList(); }); + } + + } + + public class MyDataGrid : ViewModelBase + { + public int Which { get; set; } + public int Address { get; set; } + + public bool Value1 { get; set; } + public ushort Value2 { get; set; } + public string Color { get; set; } + } +} \ No newline at end of file diff --git a/常用工具集/ViewModels/01PLC通信调试/Modbus调试1ViewModel.cs b/常用工具集/ViewModels/01PLC通信调试/Modbus调试1ViewModel.cs new file mode 100644 index 0000000..bed1cbf --- /dev/null +++ b/常用工具集/ViewModels/01PLC通信调试/Modbus调试1ViewModel.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using 常用工具集.Base; +using MES.Utility.Network; +using MES.Utility.Core; +namespace 常用工具集.ViewModel._01PLC通信调试 +{ + public class Modbus调试1ViewModel : ViewModelBase + { + private ModbusHelper modbus; + public bool Enabled1 { get; set; } = true; + public bool Enabled2 { get; set; } = false; + + + public string IpAddress { get; set; } = "192.168.1.100"; + public int Port { get; set; } = 502; + + public ushort ReadAddress { get; set; } = 100; + public ushort WriteAddress { get; set; } = 100; + public ushort ReadCount { get; set; } = 5; + public string ReadResult1 { get; set; } = ""; + public string ReadResult2 { get; set; } = ""; + public string ReadResult3 { get; set; } = ""; + public string ReadResult4 { get; set; } = ""; + public string WriteValue1 { get; set; } = ""; + public string WriteValue2 { get; set; } = ""; + public string WriteValue3 { get; set; } = ""; + public string WriteValue4 { get; set; } = ""; + + + public DelegateCommand ConnectCmd { get; set; } + public DelegateCommand DisconnectCmd { get; set; } + + + public DelegateCommand ReadCmd { get; set; } //1线圈 2状态. 3保持.4输入 + public DelegateCommand WriteCmd { get; set; } //1单个线圈 2单个保持. 3多个线圈 4多个保持 + + + public Modbus调试1ViewModel() + { + ConnectCmd = new DelegateCommand(ConnectCmdFunc); + DisconnectCmd = new DelegateCommand(DisconnectCmdFunc); + ReadCmd = new DelegateCommand(ReadCmdFunc); + WriteCmd = new DelegateCommand(WriteCmdFunc); + } + + private void ConnectCmdFunc(object obj) + { + try + { + modbus = new ModbusHelper(IpAddress, Port); + Enabled1 = false; + Enabled2 = true; + } + catch (Exception ex) + { + modbus = null; + GlobalValues.Error("连接失败"); + } + } + + private void DisconnectCmdFunc(object obj) + { + modbus.Dispose(); + modbus = null; + ReadResult1 = ""; ReadResult2 = ""; ReadResult3 = ""; ReadResult4 = ""; + WriteValue1 = ""; WriteValue2 = ""; WriteValue3 = ""; WriteValue4 = ""; + Enabled1 = true; + Enabled2 = false; + } + + private void ReadCmdFunc(object obj) + { + int cmd = Convert.ToInt32(obj.ToString()); + bool flag = false; + switch (cmd) + { + case 1: + flag = modbus.ReadCoils(ReadAddress, ReadCount, out bool[] values); + if (!flag) + { + GlobalValues.Error("读取失败"); + DisconnectCmdFunc(null); + return; + } + ReadResult1 = values.Select(it => it ? "1" : "0").ToList().GetStrArray(" "); + return; + case 2: + flag = modbus.ReadInputs(ReadAddress, ReadCount, out bool[] values2); + if (!flag) + { + GlobalValues.Error("读取失败"); + DisconnectCmdFunc(null); + return; + } + ReadResult2 = values2.Select(it => it ? "1" : "0").ToList().GetStrArray(" "); + return; + case 3: + flag = modbus.ReadHoldingRegisters(ReadAddress, ReadCount, out ushort[] values3); + if (!flag) + { + GlobalValues.Error("读取失败"); + DisconnectCmdFunc(null); + return; + } + ReadResult3 = values3.Select(it => Convert.ToString(it)).ToList().GetStrArray(" "); + return; + case 4: + flag = modbus.ReadInputRegisters(ReadAddress, ReadCount, out ushort[] values4); + if (!flag) + { + GlobalValues.Error("读取失败"); + DisconnectCmdFunc(null); + return; + } + ReadResult4 = values4.Select(it => Convert.ToString(it)).ToList().GetStrArray(" "); ; + return; + + } + } + + private void WriteCmdFunc(object obj) + { + try + { + int cmd = Convert.ToInt32(obj.ToString()); + bool flag = false; + switch (cmd) + { + case 1: + flag = modbus.WriteCoil(WriteAddress, WriteValue1 == "1" ? true : false); + if (!flag) + { + GlobalValues.Error("写入失败"); + DisconnectCmdFunc(null); + } + else + { + GlobalValues.Success("写入成功"); + } + return; + case 2: + flag = modbus.WriteRegister(WriteAddress, Convert.ToUInt16(WriteValue2)); + if (!flag) + { + GlobalValues.Error("写入失败"); + DisconnectCmdFunc(null); + } + else + { + GlobalValues.Success("写入成功"); + } + return; + case 3: + List data1 = WriteValue3.Split(' ').Where(it => !it.IsNullOrEmpty()).Select(it => it == "1" ? true : false).ToList(); + flag = modbus.WriteCoils(WriteAddress, data1.ToArray()); + if (!flag) + { + GlobalValues.Error("写入失败"); + DisconnectCmdFunc(null); + } + else + { + GlobalValues.Success("写入成功"); + } + return; + case 4: + List data2 = WriteValue4.Split(' ').Where(it => !it.IsNullOrEmpty()).Select(it => Convert.ToUInt16(it)).ToList(); + flag = modbus.WriteRegisters(ReadAddress, data2.ToArray()); + if (!flag) + { + GlobalValues.Error("写入失败"); + DisconnectCmdFunc(null); + } + else + { + GlobalValues.Success("写入成功"); + } + return; + + } + } + catch (Exception ex) + { + GlobalValues.Error($"写入失败:{ex.Message}"); + } + } + } +} diff --git a/常用工具集/ViewModels/01PLC通信调试/Modbus调试2ViewModel.cs b/常用工具集/ViewModels/01PLC通信调试/Modbus调试2ViewModel.cs new file mode 100644 index 0000000..6e8ba7c --- /dev/null +++ b/常用工具集/ViewModels/01PLC通信调试/Modbus调试2ViewModel.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using 常用工具集.Base; +using MES.Utility.Network; +using S7.Net; + +namespace 常用工具集.ViewModel._01PLC通信调试 +{ + public class Modbus调试2ViewModel : ViewModelBase + { + public bool Enabled1 { get; set; } = true; + public bool Enabled2 { get; set; } = false; + public string IpAddress { get; set; } = "192.168.1.10"; + public int Port { get; set; } = 502; + + public int Address { get; set; } = 4001; + public int StringLength { get; set; } = 100; + public string ReadedValue { get; set; } = ""; + public string Message { get; set; } = ""; + + public string SendedBytesValue { get; set; } = ""; + public string RecivedBytesValue { get; set; } = ""; + public string Int16Value { get; set; } = ""; + public string Int32Value { get; set; } = ""; + public string RealValue { get; set; } = ""; + public string StringValue { get; set; } = ""; + public string DoubleValue { get; set; } = ""; + public string LongValue { get; set; } = ""; + + //STRING DOUBLE INT64 INT32 INT16 REAL + private ModbusHelper modbus; + + public DelegateCommand ConnectCmd { get; set; } + public DelegateCommand DisconnectCmd { get; set; } + public DelegateCommand WriteCmd { get; set; } + public DelegateCommand ReadCmd { get; set; } + public Modbus调试2ViewModel() + { + ConnectCmd = new DelegateCommand(ConnectCmdFunc); + DisconnectCmd = new DelegateCommand(DisconnectCmdFunc); + ReadCmd = new DelegateCommand(ReadCmdFunc); + WriteCmd = new DelegateCommand(WriteCmdFunc); + } + + private void ConnectCmdFunc(object obj) + { + try + { + modbus = new ModbusHelper(IpAddress, Port); + modbus.OnSended += Modbus_OnSended; + modbus.OnRecived += Modbus_OnRecived; + Enabled1 = false; + Enabled2 = true; + } + catch + { + GlobalValues.Error("连接失败"); + return; + } + } + + + + private void DisconnectCmdFunc(object obj) + { + modbus.OnSended -= Modbus_OnSended; + modbus.OnRecived -= Modbus_OnRecived; + modbus.Dispose(); + modbus = null; + Enabled1 = true; + Enabled2 = false; + } + + private void ReadCmdFunc(object obj) + { + string cmd = obj.ToString(); + + ReadedValue = ""; + Message = ""; + SendedBytesValue = ""; + RecivedBytesValue = ""; + if (cmd == "INT16") + { + ushort value; + bool flag = modbus.ReadHoldRegisterInt16(Address, out value); + if (!flag) + { + Message = "读取失败"; + DisconnectCmdFunc(null); + return; + } + Message = "读取成功"; + ReadedValue = value.ToString(); + } + else if (cmd == "INT32") + { + int value; + bool flag = modbus.ReadHoldRegisterInt32(Address, out value); + if (!flag) + { + Message = "读取失败"; + DisconnectCmdFunc(null); + return; + } + Message = "读取成功"; + ReadedValue = value.ToString(); + } + else if (cmd == "INT64") + { + long value; + bool flag = modbus.ReadHoldRegisterInt64(Address, out value); + if (!flag) + { + Message = "读取失败"; + DisconnectCmdFunc(null); + return; + } + Message = "读取成功"; + ReadedValue = value.ToString(); + } + else if (cmd == "REAL") + { + float value; + bool flag = modbus.ReadHoldRegisterFloat(Address, out value); + if (!flag) + { + Message = "读取失败"; + DisconnectCmdFunc(null); + return; + } + Message = "读取成功"; + ReadedValue = value.ToString(); + } + else if (cmd == "DOUBLE") + { + double value; + bool flag = modbus.ReadHoldRegisterDouble(Address, out value); + if (!flag) + { + Message = "读取失败"; + DisconnectCmdFunc(null); + return; + } + Message = "读取成功"; + ReadedValue = value.ToString(); + } + else if (cmd == "STRING") + { + string value; + bool flag = modbus.ReadHoldRegisterString(Address, StringLength, out value); + if (!flag) + { + Message = "读取失败"; + DisconnectCmdFunc(null); + return; + } + Message = "读取成功"; + ReadedValue = value.ToString(); + } + + // DOUBLE + + } + + private void WriteCmdFunc(object obj) + { + string cmd = obj.ToString(); + ReadedValue = ""; + Message = ""; + SendedBytesValue = ""; + RecivedBytesValue = ""; + if (cmd == "INT16") + { + ushort value; + bool flag = ushort.TryParse(Int16Value, out value); + if (!flag) + { + Message = "请输入正确的数据"; + return; + } + flag = modbus.WriteHoldRegisterInt16(Address, value); + Message = flag ? "写入成功" : "写入失败"; + } + else if (cmd == "INT32") + { + int value; + bool flag = int.TryParse(Int32Value, out value); + if (!flag) + { + Message = "请输入正确的数据"; + return; + } + flag = modbus.WriteHoldRegisterInt32(Address, value); + Message = flag ? "写入成功" : "写入失败"; + } + else if (cmd == "INT64") + { + long value; + bool flag = long.TryParse(LongValue, out value); + if (!flag) + { + Message = "请输入正确的数据"; + return; + } + flag = modbus.WriteHoldRegisterInt64(Address, value); + Message = flag ? "写入成功" : "写入失败"; + } + else if (cmd == "REAL") + { + float value; + bool flag = float.TryParse(RealValue, out value); + if (!flag) + { + Message = "请输入正确的数据"; + return; + } + flag = modbus.WriteHoldRegisterFloat(Address, value); + Message = flag ? "写入成功" : "写入失败"; + } + else if (cmd == "DOUBLE") + { + double value; + bool flag = double.TryParse(DoubleValue, out value); + if (!flag) + { + Message = "请输入正确的数据"; + return; + } + flag = modbus.WriteHoldRegisterDouble(Address, value); + Message = flag ? "写入成功" : "写入失败"; + } + else if (cmd == "STRING") + { + bool flag = modbus.WriteHoldRegisterString(Address, StringLength, StringValue); + Message = flag ? "写入成功" : "写入失败"; + } + } + + + private void Modbus_OnRecived(byte[] bytes, string hexString) + { + RecivedBytesValue = hexString; + } + + private void Modbus_OnSended(byte[] bytes, string hexString) + { + SendedBytesValue = hexString; + } + } +} diff --git a/常用工具集/ViewModels/01PLC通信调试/OPCUA调试ViewModel.cs b/常用工具集/ViewModels/01PLC通信调试/OPCUA调试ViewModel.cs new file mode 100644 index 0000000..981c797 --- /dev/null +++ b/常用工具集/ViewModels/01PLC通信调试/OPCUA调试ViewModel.cs @@ -0,0 +1,389 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq; +using 常用工具集.Base; +using System.Threading; +using OpcUaHelper; +using Org.BouncyCastle.Crypto.Tls; +using System.Threading.Tasks; +using Opc.Ua.Client; +using Opc.Ua; +using Ursa.Controls; + +namespace 常用工具集.ViewModel._01PLC通信调试 +{ + public class OPCUA调试ViewModel : ViewModelBase + { + private bool stop = false; + private OpcUaClient client; + private object _lock = new object(); + private Thread thread; + public bool Enabled1 { get; set; } = true; + public bool Enabled2 { get; set; } = false; + + public string OpcServer { get; set; } = "opc.tcp://10.10.0.40"; + public string Node { get; set; } = "ns=4;s=|var|Inovance-ARM-Linux.Application.StorageDatas.gStorageDatas[10].MaterialInfo.Width"; + public string Result { get; set; } = ""; + + public string WriteValue { get; set; } = ""; + public string SelecedValue { get; set; } = ""; + public int SelectedIndex { get; set; } = -1; + public ObservableCollection NodeList { get; set; } = new ObservableCollection(); + public DelegateCommand ConnectCmd { get; set; } + public DelegateCommand DisconnectCmd { get; set; } + public DelegateCommand SubscribeCmd { get; set; } + public DelegateCommand ReadValueCmd { get; set; } + public DelegateCommand WriteValueCmd { get; set; } + public DelegateCommand CancelSubscribeCmd { get; set; } + + public DelegateCommand OpenBrowerCmd { get; set; } + public OPCUA调试ViewModel() + { + ConnectCmd = new DelegateCommand(ConnectCmdFunc); + DisconnectCmd = new DelegateCommand(DisconnectCmdFunc); + SubscribeCmd = new DelegateCommand(SubscribeCmdFunc); + ReadValueCmd = new DelegateCommand(ReadValueCmdFunc); + WriteValueCmd = new DelegateCommand(WriteValueCmdFunc); + CancelSubscribeCmd = new DelegateCommand(CancelSubscribeCmdFunc); + OpenBrowerCmd = new DelegateCommand(OpenBrowerCmdFunc); + } + + private void OpenBrowerCmdFunc(object obj) + { + //new OpcUaHelper.Forms.FormBrowseServer().Show(); + } + + + + + + + /// + /// 建立连接 + /// + /// + private void ConnectCmdFunc(object obj) + { + try + { + client = new OpcUaClient(); + DateTime start = DateTime.Now; + bool connected = false; + Task task = client.ConnectServer(OpcServer); + while (true) + { + if (client.Connected) + { + connected = true; + break; + } + if ((DateTime.Now - start).TotalMilliseconds > 1000) + { + break; + } + } + if (!connected) + { + GlobalValues.Error($"连接失败"); + client = null; + return; + } + Enabled1 = false; + Enabled2 = true; + stop = false; + thread = new Thread(() => + { + while (true) + { + try + { + if (stop) + { + break; + } + Loop(); + Thread.Sleep(1000); + } + catch (ThreadInterruptedException ex) + { + break; + } + catch (Exception e) + { + + } + + } + }); + thread.Start(); + } + catch (Exception ex) + { + GlobalValues.Error($"连接失败:{ex.Message}"); + } + } + + + + /// + /// 断开连接 + /// + /// + private void DisconnectCmdFunc(object obj) + { + foreach (MyNode node in NodeList) + { + client.RemoveSubscription(node.NodeName); + } + NodeList.Clear(); + client.Disconnect(); + client = null; + Enabled1 = true; + Enabled2 = false; + stop = true; + thread.Interrupt(); + thread = null; + } + + + private void SubscribeCmdFunc(object obj) + { + string node = Node; + if (NodeList.Any(x => x.NodeName == node)) + return; + lock (_lock) + { + NodeList.Add(new MyNode { NodeName = node, Value = null }); + } + client.AddSubscription(node, node, CallBack); + } + + private void CallBack(string key, MonitoredItem item, MonitoredItemNotificationEventArgs itemNotify) + { + MonitoredItemNotification notification = itemNotify.NotificationValue as MonitoredItemNotification; + DataValue dataValue = notification.Value; + bool isGood = StatusCode.IsGood(dataValue.StatusCode); + if (isGood) + { + lock (_lock) + { + MyNode node = NodeList.Where(it => it.NodeName == key).FirstOrDefault(); + node.Value = dataValue.Value; + } + } + else + { + lock (_lock) + { + MyNode node = NodeList.Where(it => it.NodeName == key).FirstOrDefault(); + node.Value = null; + } + } + } + private void ReadValueCmdFunc(object obj) + { + try + { + DataValue dataValue = client.ReadNode(new NodeId(Node)); + if (!StatusCode.IsGood(dataValue.StatusCode)) + { + Result = ""; + GlobalValues.Error("读取失败"); + return; + } + Result = dataValue.Value.ToString(); + GlobalValues.Success($"读取成功"); + } + catch (Exception ex) + { + Result = ""; + GlobalValues.Error($"读取失败:{ex.Message}"); + } + + } + + private void WriteValueCmdFunc(object obj) + { + try + { + string cmd = obj.ToString(); + if (cmd == "bool") + { + bool value; + bool flag = bool.TryParse(WriteValue, out value); + if (!flag) + { + GlobalValues.Error("数据转换失败,请输入正确的数据"); + return; + } + flag = client.WriteNode(Node, value); + if (!flag) + { + GlobalValues.Error("写入失败"); + return; + } + GlobalValues.Success("写入成功"); + } + else if (cmd == "uint16") + { + ushort value; + bool flag = ushort.TryParse(WriteValue, out value); + if (!flag) + { + GlobalValues.Error("数据转换失败,请输入正确的数据"); + return; + } + flag = client.WriteNode(Node, value); + if (!flag) + { + GlobalValues.Error("写入失败"); + return; + } + GlobalValues.Success("写入成功"); + } + else if (cmd == "int16") + { + short value; + bool flag = short.TryParse(WriteValue, out value); + if (!flag) + { + GlobalValues.Error("数据转换失败,请输入正确的数据"); + return; + } + flag = client.WriteNode(Node, value); + if (!flag) + { + GlobalValues.Error("写入失败"); + return; + } + GlobalValues.Success("写入成功"); + } + else if (cmd == "int32") + { + int value; + bool flag = int.TryParse(WriteValue, out value); + if (!flag) + { + GlobalValues.Error("数据转换失败,请输入正确的数据"); + return; + } + flag = client.WriteNode(Node, value); + if (!flag) + { + GlobalValues.Error("写入失败"); + return; + } + GlobalValues.Success("写入成功"); + } + else if (cmd == "int64") + { + long value; + bool flag = long.TryParse(WriteValue, out value); + if (!flag) + { + GlobalValues.Error("数据转换失败,请输入正确的数据"); + return; + } + flag = client.WriteNode(Node, value); + if (!flag) + { + GlobalValues.Error("写入失败"); + return; + } + GlobalValues.Success("写入成功"); + } + else if (cmd == "float") + { + float value; + bool flag = float.TryParse(WriteValue, out value); + if (!flag) + { + GlobalValues.Error("数据转换失败,请输入正确的数据"); + return; + } + flag = client.WriteNode(Node, value); + if (!flag) + { + GlobalValues.Error("写入失败"); + return; + } + GlobalValues.Success("写入成功"); + } + else if (cmd == "double") + { + double value; + bool flag = double.TryParse(WriteValue, out value); + if (!flag) + { + GlobalValues.Error("数据转换失败,请输入正确的数据"); + return; + } + flag = client.WriteNode(Node, value); + if (!flag) + { + GlobalValues.Error("写入失败"); + return; + } + GlobalValues.Success("写入成功"); + } + } + catch (Exception ex) + { + MessageBox.ShowAsync(ex.Message); + } + } + + /// + /// 取消订阅 + /// + /// + private void CancelSubscribeCmdFunc(object obj) + { + try + { + if (SelectedIndex < 0) + { + return; + } + lock (_lock) + { + MyNode selectKey = NodeList[SelectedIndex]; + client.RemoveSubscription(selectKey.NodeName); + NodeList.RemoveAt(SelectedIndex); + } + } + catch (Exception ex) + { + + } + } + + private void Loop() + { + if (SelectedIndex < 0) + { + return; + } + lock (_lock) + { + MyNode selectKey = NodeList[SelectedIndex]; + object value = selectKey.Value; + if (value == null) + { + SelecedValue = ""; + } + else + { + SelecedValue = value.ToString(); + } + } + } + } + + public class MyNode : ViewModelBase + { + public string NodeName { get; set; } + + public object Value { get; set; } + } +} diff --git a/常用工具集/ViewModels/01PLC通信调试/Socket调试ViewModel.cs b/常用工具集/ViewModels/01PLC通信调试/Socket调试ViewModel.cs new file mode 100644 index 0000000..9d04ef4 --- /dev/null +++ b/常用工具集/ViewModels/01PLC通信调试/Socket调试ViewModel.cs @@ -0,0 +1,363 @@ +using MES.Utility.Core; +using SerialDebug; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Ports; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using 常用工具集.Base; +using 常用工具集.Utility.Network; +using static EasyModbus.TCPHandler; + +namespace 常用工具集.ViewModel._01PLC通信调试 +{ + public class Socket调试ViewModel : ViewModelBase + { + private System.IO.Ports.SerialPort serialPort; + public bool Enabled1 { get; set; } = true; + public bool Enabled2 { get; set; } = false; + public string ButtonText { get; set; } = "打开"; + + public string IpAddress { get; set; } = "127.0.0.1"; + public int Port { get; set; } = 502; + public int Timeout { get; set; } = 1000; + + public bool ReciveASCChecked { get; set; } = true; + public bool ReciveHexChecked { get; set; } = false; + + + public bool SendASCChecked { get; set; } = true; + public bool SendHexChecked { get; set; } = false; + public bool ConvertChecked { get; set; } = true; + public bool CycleChecked { get; set; } = false; + public int CycleTime { get; set; } = 1000; + + + public string SendContent { get; set; } = ""; + + private object _lock = new object(); + private List msgList = new List(); + public string Message + { + get + { + lock (_lock) + { + return msgList.GetStrArray("\r\n"); + } + } + set + { + NotifyPropertyChanged(); + } + } + + public DelegateCommand OpenSerialCmd { get; set; } + public DelegateCommand ClearReciveCmd { get; set; } + public DelegateCommand ClearSendCmd { get; set; } + public DelegateCommand SendCmd { get; set; } + + private TcpClient client; + private Stream stream; + private Thread sendThread; + public Thread readThread; + + public Socket调试ViewModel() + { + serialPort = new SerialPort(); + //打开关闭串口 + OpenSerialCmd = new DelegateCommand(OpenSerialCmdFunc); + ClearReciveCmd = new DelegateCommand(ClearReciveCmdFunc); + ClearSendCmd = new DelegateCommand(ClearSendCmdFunc); + SendCmd = new DelegateCommand(SendCmdFunc); + } + + private void OpenSerialCmdFunc(object obj) + { + try + { + if (ButtonText == "打开") + { + client = new TcpClient(); + IAsyncResult asyncResult = client.BeginConnect(IpAddress, Port, null, null); + if (!asyncResult.AsyncWaitHandle.WaitOne(Timeout)) + { + GlobalValues.Error("连接超时"); + return; + } + client.EndConnect(asyncResult); + stream = client.GetStream(); + ButtonText = "关闭"; + Enabled1 = false; + Enabled2 = true; + readThread = new Thread(ReadThread); + readThread.Start(); + } + else + { + sendThread?.Interrupt(); + sendThread = null; + readThread.Interrupt(); + readThread = null; + stream.Close(); + client.Close(); + client.Dispose(); + client = null; + ButtonText = "打开"; + Enabled1 = true; + Enabled2 = false; + } + } + catch (Exception ex) + { + client.Close(); + client = null; + GlobalValues.Error(ex.Message); + } + } + + + + private void ClearReciveCmdFunc(object obj) + { + lock (_lock) + { + msgList.Clear(); + } + Message = Guid.NewGuid().ToString(); + } + + private void ClearSendCmdFunc(object obj) + { + SendContent = string.Empty; + } + + private void SendCmdFunc(object obj) + { + try + { + + if (sendThread != null) + { + GlobalValues.Error("正在执行循环周期发送,不能发送数据"); + return; + } + if (SendContent.IsNullOrEmpty()) + { + GlobalValues.Error("没有任何可发送的数据"); + return; + } + //要发送的数据 + SendParamFormat format = SendParamFormat.ASCII; + if (SendHexChecked) + { + format = SendParamFormat.Hex; + } + int sendInterval = Convert.ToInt32(CycleTime); + CSendParam param = new CSendParam(format, SendParamMode.SendAfterLastSend, sendInterval, SendContent, ConvertChecked); + + if (CycleChecked) + { + sendThread = new Thread(SendThread); + sendThread.Start(param); + } + else + { + SendData(param); + } + + } + catch (Exception ex) + { + GlobalValues.Error(ex.Message); + } + } + + + + private void sp_SendCompletedEvent(object sender, SendCompletedEventArgs e) + { + if (e.SendParam == null) + { + GlobalValues.Error("发送失败"); + OpenSerialCmdFunc(null); + return; + } + lock (_lock) + { + msgList.Add($"{e.TimeString}[-->]"); + if (SendASCChecked) + { + msgList.Add(e.SendParam.ASCIIString); + } + else + { + msgList.Add(e.SendParam.HexString); + } + if (msgList.Count > 300) + { + int cha = msgList.Count - 300; + //lines.Reverse(); + //差多少,删多少 + for (int i = 0; i < cha; i++) + { + msgList.RemoveAt(0); + } + } + } + Message = Guid.NewGuid().ToString(); + } + + /// + /// 接收事件 + /// + /// + /// + void sp_ReceivedEvent(object sender, SerialDebugReceiveData e) + { + if (e != null) + { + lock (_lock) + { + msgList.Add($"{e.TimeString}[<--]"); + if (ReciveASCChecked) + { + msgList.Add(e.ASCIIString); + } + else + { + msgList.Add(e.HexString); + } + if (msgList.Count > 300) + { + int cha = msgList.Count - 300; + //lines.Reverse(); + //差多少,删多少 + for (int i = 0; i < cha; i++) + { + msgList.RemoveAt(0); + } + } + } + Message = Guid.NewGuid().ToString(); + } + } + + + void sp_SendOverEvent(object sender, EventArgs e) + { + } + + + + + private void ReadThread(object obj) + { + while (true) + { + try + { + var available = client.Available; + if (available > 0) + { + byte[] bytes = new byte[available]; + stream.Read(bytes, 0, bytes.Length); + SerialDebugReceiveData data = new SerialDebugReceiveData(bytes); + lock (_lock) + { + msgList.Add($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}[<--]"); + if (ReciveASCChecked) + { + msgList.Add(data.ASCIIString); + } + else + { + msgList.Add(data.HexString); + } + if (msgList.Count > 300) + { + int cha = msgList.Count - 300; + //lines.Reverse(); + //差多少,删多少 + for (int i = 0; i < cha; i++) + { + msgList.RemoveAt(0); + } + } + } + Message = Guid.NewGuid().ToString(); + } + Thread.Sleep(1); + } + catch (ThreadInterruptedException ex) + { + break; + } + catch (Exception ex) + { + + } + } + } + + private void SendData(CSendParam param) + { + try + { + stream.Write(param.DataBytes, 0, param.DataBytes.Length); + lock (_lock) + { + msgList.Add($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}[<--]"); + if (ReciveASCChecked) + { + msgList.Add(param.ASCIIString); + } + else + { + msgList.Add(param.HexString); + } + if (msgList.Count > 300) + { + int cha = msgList.Count - 300; + //lines.Reverse(); + //差多少,删多少 + for (int i = 0; i < cha; i++) + { + msgList.RemoveAt(0); + } + } + } + Message = Guid.NewGuid().ToString(); + } + catch (Exception ex) + { + } + + } + + private void SendThread(object obj) + { + CSendParam parm = (CSendParam)obj; + while (true) + { + try + { + SendData(parm); + Thread.Sleep(CycleTime); + } + catch (ThreadInterruptedException ex) + { + break; + } + catch (Exception ex) + { + + } + } + } + } +} \ No newline at end of file diff --git a/常用工具集/ViewModels/01PLC通信调试/三菱MC协议ViewModel.cs b/常用工具集/ViewModels/01PLC通信调试/三菱MC协议ViewModel.cs new file mode 100644 index 0000000..d4febb5 --- /dev/null +++ b/常用工具集/ViewModels/01PLC通信调试/三菱MC协议ViewModel.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using 常用工具集.Base; +using McProtocol; +using McProtocol.Mitsubishi; + +namespace 常用工具集.ViewModel._01PLC通信调试 +{ + public class 三菱MC协议ViewModel : ViewModelBase + { + public List MCList { get; set; } = new List() { "MC1E", "MC3E", "MC4E" }; + public int MCIndex { get; set; } = 1; + + public bool Enabled1 { get; set; } = true; + public bool Enabled2 { get; set; } = false; + public string IpAddress { get; set; } = "127.0.0.1"; + public int Port { get; set; } = 5001; + public string Address { get; set; } = "D100"; + public string Address2 { get; set; } = "D10"; + public int StringLength { get; set; } = 50; + public string BoolValue { get; set; } = "true"; + public string Int16Value { get; set; } = ""; + public string Int32Value { get; set; } = ""; + public string RealValue { get; set; } = ""; + public string StringValue { get; set; } = ""; + public int BytesCount { get; set; } = 50; + public string ReadedValue { get; set; } = ""; + public string Message { get; set; } = ""; + + public DelegateCommand ConnectCmd { get; set; } + public DelegateCommand DisconnectCmd { get; set; } + + public DelegateCommand ReadCmd { get; set; } + public DelegateCommand WriteCmd { get; set; } + private McHelper plc; + public 三菱MC协议ViewModel() + { + ConnectCmd = new DelegateCommand(ConnectCmdFunc); + DisconnectCmd = new DelegateCommand(DisconnectCmdFunc); + ReadCmd = new DelegateCommand(ReadCmdFunc); + WriteCmd = new DelegateCommand(WriteCmdFunc); + } + + + /// + /// 连接 + /// + /// + private void ConnectCmdFunc(object obj) + { + // // MC1E = 4, MC3E = 11, MC4E = 15 + if (MCIndex == 0) + { + this.plc = new McHelper(IpAddress, Port, McFrame.MC1E); + } + else if (MCIndex == 1) + { + this.plc = new McHelper(IpAddress, Port, McFrame.MC3E); + } + else if (MCIndex == 2) + { + this.plc = new McHelper(IpAddress, Port, McFrame.MC4E); + } + else + { + this.plc = new McHelper(IpAddress, Port); + } + + + bool flag = plc.Connect(); + if (flag) + { + Enabled2 = true; + Enabled1 = false; + } + else + { + GlobalValues.Error("PLC连接失败"); + } + } + /// + /// 断开连接 + /// + /// + private void DisconnectCmdFunc(object obj) + { + try { plc.Dispose(); } catch { } + plc = null; + Enabled1 = true; + Enabled2 = false; + } + + /// + /// 读取操作 + /// + /// + private void ReadCmdFunc(object obj) + { + ReadedValue = string.Empty; + Message = string.Empty; + string cmd = obj.ToString(); + if (cmd == "BOOL") + { + //bool value; + //bool flag = plc.ReadBool(Address, out value); + //if (!flag) + //{ + // ReadedValue = string.Empty; + // Message = "读取失败"; + // return; + //} + //ReadedValue = value.ToString(); + Message = "该功能暂未实现"; + } + else if (cmd == "INT16") + { + short value; + bool flag = plc.ReadInt16(Address, out value); + if (!flag) + { + ReadedValue = string.Empty; + Message = "读取失败"; + return; + } + ReadedValue = value.ToString(); + Message = "读取成功"; + } + else if (cmd == "INT32") + { + int value; + bool flag = plc.ReadInt32(Address, out value); + if (!flag) + { + ReadedValue = string.Empty; + Message = "读取失败"; + return; + } + ReadedValue = value.ToString(); + Message = "读取成功"; + } + else if (cmd == "REAL") + { + float value; + bool flag = plc.ReadFloat(Address, out value); + if (!flag) + { + ReadedValue = string.Empty; + Message = "读取失败"; + return; + } + ReadedValue = value.ToString(); + Message = "读取成功"; + } + else if (cmd == "STRING") + { + string value; + bool flag = plc.ReadString(Address, StringLength, out value); + if (!flag) + { + ReadedValue = string.Empty; + Message = "读取失败"; + return; + } + ReadedValue = value.ToString(); + Message = "读取成功"; + } + else if (cmd == "Bytes") + { + //ReadedValue = string.Empty; + //Message = string.Empty; + //byte[] value; + //bool flag = plc.ReadBytes(Address2, BytesCount, out value); + //if (!flag) + //{ + // ReadedValue = string.Empty; + // Message = "读取失败"; + // return; + //} + //ReadedValue = ToHexStrFromByte(value); + Message = "该功能暂未实现"; + } + } + + private void WriteCmdFunc(object obj) + { + ReadedValue = string.Empty; + Message = string.Empty; + string cmd = obj.ToString(); + if (cmd == "BOOL") + { + Message = "该功能有问题,已弃用"; + return; + } + else if (cmd == "INT16") + { + short value; + bool flag = short.TryParse(Int16Value, out value); + if (!flag) + { + Message = "请输入正确的数据"; + return; + } + flag = plc.WriteInt16(Address, value); + Message = flag ? "写入成功" : "写入失败"; + } + else if (cmd == "INT32") + { + int value; + bool flag = int.TryParse(Int32Value, out value); + if (!flag) + { + Message = "请输入正确的数据"; + return; + } + flag = plc.WriteInt32(Address, value); + Message = flag ? "写入成功" : "写入失败"; + } + else if (cmd == "REAL") + { + float value; + bool flag = float.TryParse(RealValue, out value); + if (!flag) + { + Message = "请输入正确的数据"; + return; + } + flag = plc.WriteFloat(Address, value); + Message = flag ? "写入成功" : "写入失败"; + } + else if (cmd == "STRING") + { + bool flag = plc.WriteString(Address, StringLength, StringValue); + Message = flag ? "写入成功" : "写入失败"; + } + } + + + } +} diff --git a/常用工具集/ViewModels/01PLC通信调试/串口调试工具ViewModel.cs b/常用工具集/ViewModels/01PLC通信调试/串口调试工具ViewModel.cs new file mode 100644 index 0000000..e324500 --- /dev/null +++ b/常用工具集/ViewModels/01PLC通信调试/串口调试工具ViewModel.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.Generic; +using System.IO.Ports; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using SerialDebug; +using 常用工具集.Base; +using MES.Utility.Core; +using System.IO; +using System.Text.RegularExpressions; +using System.Threading; + +namespace 常用工具集.ViewModel._01PLC通信调试 +{ + public class 串口调试工具ViewModel : ViewModelBase + { + private System.IO.Ports.SerialPort serialPort; + public bool Enabled1 { get; set; } = true; + public bool Enabled2 { get; set; } = false; + public string ButtonText { get; set; } = "打开"; + //串口号下拉列表数据 + public List SerialList { get; set; } + public int SerialIndex { get; set; } = 0; + + /// + /// 波特率 + /// + public List BaudRateList { get; set; } + public int BaudRateIndex { get; set; } = 0; + //校验 + public List ParityList { get; set; } + public int ParityIndex { get; set; } = 0; + + //数据为 + public List DataBitList { get; set; } + public int DataBitIndex { get; set; } = 0; + + //数据为 + public List FlowControlList { get; set; } = new List() { "None", "XOnXOff", "RequestToSend", "RequestToSendXOnXOff" }; + + public int FlowControlIndex { get; set; } = 0; + //停止位 + public List StopBitList { get; set; } + public int StopBitIndex { get; set; } = 0; + + public int Timeout { get; set; } = 100; + + + public bool RTSChecked { get; set; } = true; + + + public bool DTRChecked { get; set; } = false; + + public bool ReciveASCChecked { get; set; } = true; + public bool ReciveHexChecked { get; set; } = false; + + + public bool SendASCChecked { get; set; } = true; + public bool SendHexChecked { get; set; } = false; + public bool ConvertChecked { get; set; } = true; + public bool CycleChecked { get; set; } = false; + public int CycleTime { get; set; } = 1000; + + + public string SendContent { get; set; } = ""; + + private object _lock = new object(); + private List msgList = new List(); + public string Message + { + get + { + lock (_lock) + { + return msgList.GetStrArray("\r\n"); + } + } + set + { + NotifyPropertyChanged(); + } + } + + public DelegateCommand OpenSerialCmd { get; set; } + public DelegateCommand ClearReciveCmd { get; set; } + public DelegateCommand ClearSendCmd { get; set; } + public DelegateCommand SendCmd { get; set; } + + + private CSerialDebug sp; + public 串口调试工具ViewModel() + { + serialPort = new SerialPort(); + sp = new CSerialDebug(serialPort); + //串口号 + string[] portNames = SerialPort.GetPortNames(); + if (portNames != null && portNames.Length > 0) + { + SerialList = portNames.ToList(); + } + //波特率 + BaudRateList = new List() { 110, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 56000, 57600, 115200, 128000 }; + BaudRateIndex = 6; + //校验 + ParityList = new List { "None", "Odd", "Even", "Mark", "Space" }; + ParityIndex = 0; + //数据位 + DataBitList = new List { 5, 6, 7, 8 }; + DataBitIndex = 3; + //停止位 + StopBitList = new List { "None", "One", "Two", "OnePointFive" }; + StopBitIndex = 1; + + //打开关闭串口 + OpenSerialCmd = new DelegateCommand(OpenSerialCmdFunc); + ClearReciveCmd = new DelegateCommand(ClearReciveCmdFunc); + ClearSendCmd = new DelegateCommand(ClearSendCmdFunc); + SendCmd = new DelegateCommand(SendCmdFunc); + } + + private void OpenSerialCmdFunc(object obj) + { + try + { + if (ButtonText == "打开") + { + serialPort.PortName = SerialList[SerialIndex]; + serialPort.BaudRate = BaudRateList[BaudRateIndex]; + serialPort.Parity = (Parity)ParityIndex; + serialPort.DataBits = DataBitList[DataBitIndex]; + serialPort.StopBits = (StopBits)StopBitIndex; + serialPort.Handshake = (Handshake)FlowControlIndex; + serialPort.DtrEnable = DTRChecked; + serialPort.RtsEnable = RTSChecked; + serialPort.ReadBufferSize = 2 * 1024 * 1024;// 2M + + sp.ReceiveTimeOut = Timeout; + sp.Start(); + sp.ReceivedEvent += new CSerialDebug.ReceivedEventHandler(sp_ReceivedEvent); + sp.SendCompletedEvent += new CSerialDebug.SendCompletedEventHandler(sp_SendCompletedEvent); + sp.SendOverEvent += new EventHandler(sp_SendOverEvent); + ButtonText = "关闭"; + Enabled1 = false; + Enabled2 = true; + } + else + { + sp.StopSend(); + sp.ReceivedEvent -= new CSerialDebug.ReceivedEventHandler(sp_ReceivedEvent); + sp.SendCompletedEvent -= new CSerialDebug.SendCompletedEventHandler(sp_SendCompletedEvent); + sp.SendOverEvent -= new EventHandler(sp_SendOverEvent); + sp.Stop(); + ButtonText = "打开"; + Enabled1 = true; + Enabled2 = false; + } + } + catch (Exception ex) + { + GlobalValues.Error(ex.Message); + } + } + + + private void ClearReciveCmdFunc(object obj) + { + lock (_lock) + { + msgList.Clear(); + } + Message = Guid.NewGuid().ToString(); + } + + private void ClearSendCmdFunc(object obj) + { + SendContent = string.Empty; + } + + private void SendCmdFunc(object obj) + { + try + { + + if (SendContent.IsNullOrEmpty()) + { + GlobalValues.Error("没有任何可发送的数据"); + return; + } + //要发送的数据 + List list = new List(); + SendParamFormat format = SendParamFormat.ASCII; + if (SendHexChecked) + { + format = SendParamFormat.Hex; + } + int sendInterval = Convert.ToInt32(CycleTime); + CSendParam param = new CSendParam(format, SendParamMode.SendAfterLastSend, sendInterval, SendContent, ConvertChecked); + list.Add(param); + if (CycleChecked) + { + sp.Send(list, 0); + } + else + { + sp.Send(list, 1); + } + + } + catch (Exception ex) + { + GlobalValues.Error(ex.Message); + } + } + + + + private void sp_SendCompletedEvent(object sender, SendCompletedEventArgs e) + { + if (e.SendParam == null) + { + GlobalValues.Error("发送失败"); + OpenSerialCmdFunc(null); + return; + } + lock (_lock) + { + msgList.Add($"{e.TimeString}[-->]"); + if (SendASCChecked) + { + msgList.Add(e.SendParam.ASCIIString); + } + else + { + msgList.Add(e.SendParam.HexString); + } + if (msgList.Count > 300) + { + int cha = msgList.Count - 300; + //lines.Reverse(); + //差多少,删多少 + for (int i = 0; i < cha; i++) + { + msgList.RemoveAt(0); + } + } + } + Message = Guid.NewGuid().ToString(); + } + + /// + /// 接收事件 + /// + /// + /// + void sp_ReceivedEvent(object sender, SerialDebugReceiveData e) + { + if (e != null) + { + lock (_lock) + { + msgList.Add($"{e.TimeString}[<--]"); + if (ReciveASCChecked) + { + msgList.Add(e.ASCIIString); + } + else + { + msgList.Add(e.HexString); + } + if (msgList.Count > 300) + { + int cha = msgList.Count - 300; + //lines.Reverse(); + //差多少,删多少 + for (int i = 0; i < cha; i++) + { + msgList.RemoveAt(0); + } + } + } + Message = Guid.NewGuid().ToString(); + } + } + + + void sp_SendOverEvent(object sender, EventArgs e) + { + } + } +} diff --git a/常用工具集/ViewModels/01PLC通信调试/倍福ADS调试ViewModel.cs b/常用工具集/ViewModels/01PLC通信调试/倍福ADS调试ViewModel.cs new file mode 100644 index 0000000..f90d243 --- /dev/null +++ b/常用工具集/ViewModels/01PLC通信调试/倍福ADS调试ViewModel.cs @@ -0,0 +1,635 @@ +using System; +using 常用工具集.Base; +using System.Collections.Generic; +using MES.Utility.Core; +using System.Text; +using System.Linq; +using TwinCAT.Ads.TypeSystem; +using TwinCAT.Ads; + +namespace 常用工具集.ViewModel._01PLC通信调试 +{ + public class 倍福ADS调试ViewModel : ViewModelBase + { + public bool Enabled1 { get; set; } = true; + public bool Enabled2 { get; set; } = false; + public string NetId { get; set; } = "192.168.1.10.1.1"; + public int Port { get; set; } = 851; + + public string VarName { get; set; } = "Global_LaserCutting.gHmiPlc.Output.Mes.DeviceStatus.bEMG"; + public int StringLength { get; set; } = 80; + public string DataType { get; set; } = ""; + public string ReadedValue { get; set; } = ""; + public string Message1 { get; set; } = ""; + public string Message2 { get; set; } = ""; + + public string BoolValue { get; set; } = ""; + + private AdsServer ads; + + public DelegateCommand ConnectCmd { get; set; } + public DelegateCommand DisconnectCmd { get; set; } + public DelegateCommand WriteCmd { get; set; } + public DelegateCommand ReadCmd { get; set; } + public 倍福ADS调试ViewModel() + { + ConnectCmd = new DelegateCommand(ConnectCmdFunc); + DisconnectCmd = new DelegateCommand(DisconnectCmdFunc); + ReadCmd = new DelegateCommand(ReadCmdFunc); + WriteCmd = new DelegateCommand(WriteCmdFunc); + } + + private void ConnectCmdFunc(object obj) + { + try + { + ads = new AdsServer(NetId, Port); + ads.Connect(); + Enabled1 = false; + Enabled2 = true; + } + catch + { + GlobalValues.Error("连接失败"); + return; + } + } + + + + private void DisconnectCmdFunc(object obj) + { + ads.Disconnect(); + ads = null; + Enabled1 = true; + Enabled2 = false; + } + + private void ReadCmdFunc(object obj) + { + string cmd = obj.ToString(); + ReadedValue = ""; + Message1 = ""; + DataType = ""; + try + { + object value = ads.Read(VarName, out string type); + Message1 = "读取成功"; + DataType = type; + if (value.GetType().IsArray) + { + Array array = value as Array; + List list = new List(); + for (int i = 0; i < array.Length; i++) + { + list.Add(array.GetValue(i).ToString()); + } + ReadedValue = list.GetStrArray(" "); + } + else + { + ReadedValue = value.ToString(); + } + + } + catch (Exception ex) + { + Message1 = $"读取失败:{ex.Message}"; + return; + } + } + + private void WriteCmdFunc(object obj) + { + string cmd = obj.ToString(); + try + { + + if (string.IsNullOrEmpty(BoolValue)) + { + Message2 = "请输入数据"; + return; + } + ads.Write(VarName, BoolValue); + Message2 = $"写入成功"; + } + catch (Exception ex) + { + Message2 = $"写入失败:{ex.Message}"; + } + } + } + + + public class AdsServer + { + Dictionary symbolDict = new Dictionary(); + + public delegate void TcAdsStateChangedEventHandler(AdsState state); + public delegate void TcAdsRouteChangedEventHandler(AmsRouterState state); + public AmsRouterState AsmRouteState { get; set; } = AmsRouterState.Stop; + public AdsState AdsState { get; set; } = AdsState.Stop; + + public AdsClient ads; + + public bool SystemIsRunning => ads.IsConnected; + + private TcAdsRouteChangedEventHandler tcAdsSystemStateChangedHandler; + public event TcAdsRouteChangedEventHandler TcSystemStateChanged + { + add + { + this.tcAdsSystemStateChangedHandler = (TcAdsRouteChangedEventHandler)Delegate.Combine(this.tcAdsSystemStateChangedHandler, value); + value(AsmRouteState); + } + remove + { + this.tcAdsSystemStateChangedHandler = (TcAdsRouteChangedEventHandler)Delegate.Remove(this.tcAdsSystemStateChangedHandler, value); + } + } + + private TcAdsStateChangedEventHandler tcAdsClientStateChangedHandler; + public event TcAdsStateChangedEventHandler TcPlcStateChanged + { + add + { + this.tcAdsClientStateChangedHandler = (TcAdsStateChangedEventHandler)Delegate.Combine(this.tcAdsClientStateChangedHandler, value); + value(AdsState); + } + remove + { + this.tcAdsClientStateChangedHandler = (TcAdsStateChangedEventHandler)Delegate.Remove(this.tcAdsClientStateChangedHandler, value); + } + } + + private int _clientPortNr = 801; + + private string _netId = ""; + + public AdsServer(string netId, int portNr) + { + this._netId = netId; + this._clientPortNr = portNr; + ads = new AdsClient(); + ads.RouterStateChanged += Client_AmsRouterNotification; + ads.AdsStateChanged += Client_AdsStateChanged; + } + + private void Client_AmsRouterNotification(object sender, AmsRouterNotificationEventArgs e) + { + AsmRouteState = e.State; + tcAdsSystemStateChangedHandler?.Invoke(AsmRouteState); + } + + public void Connect() + { + ads.Connect(this._netId, this._clientPortNr); + + } + + public void Disconnect() + { + ads.Disconnect(); + ads.Dispose(); + } + + private void Client_AdsStateChanged(object sender, AdsStateChangedEventArgs e) + { + AdsState = e.State.AdsState; + tcAdsClientStateChangedHandler?.Invoke(AdsState); + } + + + public object Read(string symbolPath, out string oDataType) + { + if (!SystemIsRunning) + { + throw new Exception("PLC链接失败"); + } + //bool isArray = symbolPath.Contains("[") && symbolPath.Contains("]"); + //int index1 = symbolPath.IndexOf("["); int index2 = symbolPath.IndexOf("]"); + //uint index = uint.Parse(symbolPath.Substring(index1 + 1, index2 - index1 - 1)); + //symbolPath = symbolPath.Substring(0, index1); + + IAdsSymbol symbol = null; + if (symbolDict.ContainsKey(symbolPath)) + { + symbol = symbolDict[symbolPath]; + } + else + { + symbol = ads.ReadSymbol(symbolPath); + if (symbol == null) + throw new Exception("找不到该变量"); + symbolDict.Add(symbolPath, symbol); + } + oDataType = symbol.TypeName; + if (symbol.TypeName.Substring(0, 3) != "ARR") + { + AdsDataTypeId datatype = symbol.DataTypeId; + //BOOL + if (datatype == AdsDataTypeId.ADST_BIT) + return ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, typeof(bool)); + //byte + if (datatype == AdsDataTypeId.ADST_UINT8) + return ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, typeof(byte)); + //sbyte + if (datatype == AdsDataTypeId.ADST_INT8) + return ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, typeof(sbyte)); + //ushort + if (datatype == AdsDataTypeId.ADST_UINT16) + return ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, typeof(ushort)); + //short + if (datatype == AdsDataTypeId.ADST_INT16) + return ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, typeof(short)); + if (datatype == AdsDataTypeId.ADST_UINT32) + return ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, typeof(uint)); + if (datatype == AdsDataTypeId.ADST_INT32) + return ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, typeof(int)); + if (datatype == AdsDataTypeId.ADST_REAL32) + return ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, typeof(float)); + if (datatype == AdsDataTypeId.ADST_REAL64) + return ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, typeof(double)); + if (datatype == AdsDataTypeId.ADST_STRING) + return ads.ReadAnyString((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, symbol.Size, Encoding.Default); + if (datatype == AdsDataTypeId.ADST_WSTRING) + { + Memory stream = new Memory(new byte[symbol.Size]); + int readLength = ads.Read((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, stream); + byte[] bytes = stream.ToArray().ToList().Take(readLength).ToArray(); + int charCount = readLength / 2; + char[] charArray = new char[charCount]; + for (int i = 0; i < charCount; i++) + { + char value = (char)(bytes[i * 2 + 1] << 8 | bytes[i * 2]); + charArray[i] = value; + if (value == 0) + break; + } + return new String(charArray).Trim('\0'); + } + if (datatype == AdsDataTypeId.ADST_BIGTYPE) + { + if (symbol.TypeName.StartsWith("TIME") || symbol.TypeName.StartsWith("DATE") || symbol.TypeName.StartsWith("TOD")) + { + return ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, typeof(DateTime)); + } + } + } + else + { + AdsDataTypeId datatype = symbol.DataTypeId; + if (datatype == AdsDataTypeId.ADST_BIT) + { + //if (isArray) + //{ + // return (bool)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + index, typeof(bool)); + //} + bool[] values = new bool[symbol.Size]; + for (uint i = 0; i < symbol.Size; i++) + { + values[i] = (bool)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + i, typeof(bool)); + } + return values; + } + + //byte + if (datatype == AdsDataTypeId.ADST_UINT8) + { + //if (isArray) + //{ + // return (byte)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + index, typeof(byte)); ; + //} + byte[] values = new byte[symbol.Size]; + for (uint i = 0; i < symbol.Size; i++) + { + values[i] = (byte)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + i, typeof(byte)); + } + return values; + } + //sbyte + if (datatype == AdsDataTypeId.ADST_INT8) + { + //if (isArray) + //{ + // return (sbyte)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + index, typeof(sbyte)); + //} + sbyte[] values = new sbyte[symbol.Size]; + for (uint i = 0; i < symbol.Size; i++) + { + values[i] = (sbyte)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + i, typeof(sbyte)); + } + return values; + } + //ushort + if (datatype == AdsDataTypeId.ADST_UINT16) + { + //if (isArray) + //{ + // return (ushort)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + (index * 2U), typeof(ushort)); + //} + int count = symbol.Size / 2; + ushort[] values = new ushort[count]; + for (uint i = 0; i < count; i++) + { + values[i] = (ushort)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + (i * 2U), typeof(ushort)); + } + return values; + } + //short + if (datatype == AdsDataTypeId.ADST_INT16) + { + //if (isArray) + //{ + // return (short)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + (index * 2U), typeof(short)); + //} + int count = symbol.Size / 2; + short[] values = new short[count]; + for (uint i = 0; i < count; i++) + { + values[i] = (short)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + (i * 2U), typeof(short)); + } + return values; + } + if (datatype == AdsDataTypeId.ADST_UINT32) + { + //if (isArray) + //{ + // return (uint)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + (index * 4U), typeof(uint)); + //} + int count = symbol.Size / 4; + uint[] values = new uint[count]; + for (uint i = 0; i < count; i++) + { + values[i] = (uint)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + (i * 4U), typeof(uint)); + } + return values; + } + if (datatype == AdsDataTypeId.ADST_INT32) + { + //if (isArray) + //{ + // return (int)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + (index * 4U), typeof(int)); + //} + int count = symbol.Size / 4; + int[] values = new int[count]; + for (uint i = 0; i < count; i++) + { + values[i] = (int)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + (i * 4U), typeof(int)); + } + return values; + } + if (datatype == AdsDataTypeId.ADST_REAL32) + { + //if (isArray) + //{ + // return (float)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + (index * 4U), typeof(float)); + //} + int count = symbol.Size / 4; + float[] values = new float[count]; + for (uint i = 0; i < count; i++) + { + values[i] = (float)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + (i * 4U), typeof(float)); + } + return values; + } + if (datatype == AdsDataTypeId.ADST_REAL64) + { + //if (isArray) + //{ + // return (double)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + (index * 8U), typeof(double)); + //} + int count = symbol.Size / 8; + double[] values = new double[count]; + for (uint i = 0; i < count; i++) + { + values[i] = (double)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + (i * 8U), typeof(double)); + } + return values; + } + //TC3 字符串数组为 ADST_STRING + if (datatype == AdsDataTypeId.ADST_STRING) + { + int every = 81; + if (symbol.TypeName.Contains("OF STRING")) + { + int index = symbol.TypeName.IndexOf("OF STRING("); + if (index != 1) + { + int index2 = symbol.TypeName.LastIndexOf(")"); + every = Convert.ToInt32(symbol.TypeName.Substring(index + 10, index2 - index - 10)) + 1; + } + int count = symbol.Size / every; + string[] values = new string[count]; + for (uint i = 0; i < count; i++) + { + values[i] = ads.ReadAnyString((uint)symbol.IndexGroup, (uint)(symbol.IndexOffset + (i * every)), every, Encoding.Default); + } + return values; + } + } + if (datatype == AdsDataTypeId.ADST_WSTRING) + { + int every = 512; + if (symbol.TypeName.Contains("OF WSTRING")) + { + int index = symbol.TypeName.IndexOf("OF WSTRING("); + if (index != 1) + { + int index2 = symbol.TypeName.LastIndexOf(")"); + every = (Convert.ToInt32(symbol.TypeName.Substring(index + 11, index2 - index - 11)) + 1) * 2; + } + int count = symbol.Size / every; + string[] values = new string[count]; + for (uint i = 0; i < count; i++) + { + Memory stream = new Memory(new byte[every]); + int readLength = ads.Read((uint)symbol.IndexGroup, (uint)(symbol.IndexOffset + (i * every)), stream); + byte[] bytes = stream.ToArray().ToList().Take(readLength).ToArray(); + int charCount = readLength / 2; + char[] charArray = new char[charCount]; + for (int j = 0; j < charCount; j++) + { + char value = (char)(bytes[j * 2 + 1] << 8 | bytes[j * 2]); + charArray[j] = value; + if (value == 0) + break; + } + values[i] = new String(charArray).Trim('\0'); + } + return values; + } + } + if (datatype == AdsDataTypeId.ADST_BIGTYPE) + { + //TC2 字符串为ADST_BIGTYPE + if (symbol.TypeName.Contains("OF STRING")) + { + //if (isArray) + //{ + // return ads.ReadAnyString((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + (index * 81U), 81, Encoding.Default); + //} + int count = symbol.Size / 81; + string[] values = new string[count]; + for (uint i = 0; i < count; i++) + { + values[i] = ads.ReadAnyString((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + (i * 81U), 81, Encoding.Default); + } + return values; + } + + if (symbol.TypeName.Contains("OF TIME") || symbol.TypeName.Contains("OF DATE")) + { + //if (isArray) + //{ + // return (DateTime)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + (index * 4), typeof(DateTime)); + //} + int count = symbol.Size / 4; + DateTime[] values = new DateTime[count]; + for (uint i = 0; i < count; i++) + { + values[i] = (DateTime)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + (i * 4), typeof(DateTime)); + } + return values; + } + if (symbol.TypeName.Contains("OF TOD")) + { + //if (isArray) + //{ + // return (DateTime)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + (index * 4), typeof(DateTime)); + //} + int count = symbol.Size / 4; + DateTime[] values = new DateTime[count]; + for (uint i = 0; i < count; i++) + { + values[i] = (DateTime)ads.ReadAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset + (i * 4), typeof(DateTime)); + } + return values; + } + } + + } + throw new Exception("不支持的数据类型"); + } + + public void Write(string symbolPath, string value) + { + if (!SystemIsRunning) + { + throw new Exception("PLC链接失败"); + } + IAdsSymbol symbol = null; + if (symbolDict.ContainsKey(symbolPath)) + { + symbol = symbolDict[symbolPath]; + } + else + { + symbol = ads.ReadSymbol(symbolPath); + if (symbol == null) + throw new Exception("找不到该变量"); + symbolDict.Add(symbolPath, symbol); + } + if (symbol.TypeName.Substring(0, 3) != "ARR") + { + AdsDataTypeId datatype = symbol.DataTypeId; + //BOOL + if (datatype == AdsDataTypeId.ADST_BIT) + { + ads.WriteAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, bool.Parse(value)); + return; + } + //byte + if (datatype == AdsDataTypeId.ADST_UINT8) + { + ads.WriteAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, byte.Parse(value)); + return; + } + //sbyte + if (datatype == AdsDataTypeId.ADST_INT8) + { + ads.WriteAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, sbyte.Parse(value)); + return; + } + //ushort + if (datatype == AdsDataTypeId.ADST_UINT16) + { + ads.WriteAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, ushort.Parse(value)); + return; + } + if (datatype == AdsDataTypeId.ADST_INT16) + { + ads.WriteAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, short.Parse(value)); + return; + } + if (datatype == AdsDataTypeId.ADST_UINT32) + { + ads.WriteAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, uint.Parse(value)); + return; + } + if (datatype == AdsDataTypeId.ADST_INT32) + { + ads.WriteAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, int.Parse(value)); + return; + } + if (datatype == AdsDataTypeId.ADST_REAL32) + { + ads.WriteAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, float.Parse(value)); + return; + } + if (datatype == AdsDataTypeId.ADST_REAL64) + { + ads.WriteAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, double.Parse(value)); + return; + } + if (datatype == AdsDataTypeId.ADST_STRING) + { + ads.WriteAnyString(symbolPath, value, symbol.Size,Encoding.Default); + //ads.WriteAnyString((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, value, symbol.Size, Encoding.Default); + return; + } + if (datatype == AdsDataTypeId.ADST_WSTRING) + { + char[] charArray = value.ToCharArray(); + byte[] bytes = new byte[charArray.Length * 2]; + for (int i = 0; i < charArray.Length; i++) + { + bytes[i * 2] = (byte)(charArray[i] & 0xFF); + bytes[i * 2 + 1] = (byte)(charArray[i] >> 8 & 0xFF); + } + if (bytes.Length > symbol.Size) + { + ads.Write((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, new ReadOnlyMemory(bytes)); + } + else + { + byte[] newBytes = new byte[symbol.Size]; + for (int i = 0; i < bytes.Length; i++) + { + newBytes[i] = bytes[i]; + } + ads.Write((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, new ReadOnlyMemory(newBytes)); + } + return; + } + if (datatype == AdsDataTypeId.ADST_BIGTYPE) + { + if (symbol.TypeName.StartsWith("TIME") || symbol.TypeName.StartsWith("DATE") || symbol.TypeName.StartsWith("TOD")) + { + bool flag = DateTime.TryParse(value, out DateTime result); + if (!flag) + throw new Exception("格式转换失败"); + ads.WriteAny((uint)symbol.IndexGroup, (uint)symbol.IndexOffset, result); + return; + } + } + throw new Exception("不支持的数据类型"); + } + else + { + throw new Exception("不支持数组批量写入"); + } + } + } + +} diff --git a/常用工具集/ViewModels/01PLC通信调试/欧姆龙Fins调试ViewModel.cs b/常用工具集/ViewModels/01PLC通信调试/欧姆龙Fins调试ViewModel.cs new file mode 100644 index 0000000..fbe58d8 --- /dev/null +++ b/常用工具集/ViewModels/01PLC通信调试/欧姆龙Fins调试ViewModel.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using 常用工具集.Base; +using OmronLib; +using MES.Utility.Core; +using Ursa.Controls; + +namespace 常用工具集.ViewModel._01PLC通信调试 +{ + public class 欧姆龙Fins调试ViewModel : ViewModelBase + { + private Omron omron; + public bool Enabled1 { get; set; } = true; + public bool Enabled2 { get; set; } = false; + public string IpAddress { get; set; } = "192.168.1.3"; + public int Port { get; set; } = 9600; + + public List AreaList { get; set; } + public List AreaTypeList { get; set; } + public int WriteAreaIndex { get; set; } = 0; + public int ReadAreaIndex { get; set; } = 0; + public int ReadAddress { get; set; } = 1042; + public int WriteAddress { get; set; } = 1042; + public int ReadCount { get; set; } = 1; + public string ReadResult { get; set; } = ""; + + public int StrLength { get; set; } = 50; + public string WriteValue { get; set; } = ""; + public DelegateCommand ConnectCmd { get; set; } + public DelegateCommand DisconnectCmd { get; set; } + public DelegateCommand ReadCmd { get; set; }//1读取 2字符串 3Float 4Short + public DelegateCommand WriteCmd { get; set; }//1写入 2.Float 3Short 4字符串 + + public 欧姆龙Fins调试ViewModel() + { + AreaList = new List + { + "CIO_Bit","WR_Bit","HR_Bit","AR_Bit", + "DM_Bit","CIO_Word","WR_Word","HR_Word","AR_Word","DM_Word" + }; + AreaTypeList = new List + { + Omron.AreaType.CIO_Bit,Omron.AreaType.WR_Bit,Omron.AreaType.HR_Bit,Omron.AreaType.AR_Bit, + Omron.AreaType.DM_Bit,Omron.AreaType.CIO_Word,Omron.AreaType.WR_Word,Omron.AreaType.HR_Word,Omron.AreaType.AR_Word,Omron.AreaType.DM_Word + }; + ReadAreaIndex = 9; + WriteAreaIndex = 9; + ConnectCmd = new DelegateCommand(ConnectCmdFunc); + DisconnectCmd = new DelegateCommand(DisconnectCmdFunc); + ReadCmd = new DelegateCommand(ReadCmdFunc); + WriteCmd = new DelegateCommand(WriteCmdFunc); + } + + private void ConnectCmdFunc(object obj) + { + try + { + omron = new Omron(); + omron.IPAddr = System.Net.IPAddress.Parse(IpAddress); + omron.Port = Port; + string info; + if (!omron.PlcConnect(out info)) + { + GlobalValues.Error($"连接失败"); + } + else + { + GlobalValues.Success("连接成功"); + Enabled1 = false; + Enabled2 = true; + } + } + catch (Exception ex) + { + GlobalValues.Error($"连接失败:{ex.Message}"); + } + } + + private void DisconnectCmdFunc(object obj) + { + omron.DisConnect(); + omron = null; + Enabled1 = true; + Enabled2 = false; + } + + private void ReadCmdFunc(object obj) + { + string cmd = obj.ToString(); + if (cmd == "1") + { + string[] res; + bool isSucess = omron.Read(AreaTypeList[ReadAreaIndex], ReadAddress, 0, ReadCount, out res); + if (!isSucess) + { + GlobalValues.Error("读取失败"); + DisconnectCmdFunc(obj); + return; + } + ReadResult = res.ToList().GetStrArray(" "); + } + else if (cmd == "2") + { + string res; + bool isSucess = omron.ReadString(ReadAddress, ReadCount, out res); + if (!isSucess) + { + GlobalValues.Error("读取失败"); + DisconnectCmdFunc(obj); + return; + } + ReadResult = res; + } + else if (cmd == "3") + { + float[] res; + bool isSucess = omron.ReadFloats(ReadAddress, ReadCount, out res); + if (!isSucess) + { + + GlobalValues.Error("读取失败"); + DisconnectCmdFunc(obj); + return; + } + ReadResult = res.Select(it => Convert.ToString(it)).ToList().GetStrArray(" "); + } + else if (cmd == "4") + { + short[] res; + bool isSucess = omron.ReadShorts(ReadAddress, ReadCount, out res); + if (!isSucess) + { + GlobalValues.Error("读取失败"); + DisconnectCmdFunc(obj); + return; + } + ReadResult = res.Select(it => Convert.ToString(it)).ToList().GetStrArray(" "); + } + } + + private void WriteCmdFunc(object obj) + { + try + { + //1写入 2.Float 3Short 4字符串 + string cmd = obj.ToString(); + if (cmd == "1") + { + int[] intValues = WriteValue.Split(' ').Where(it => !it.IsNullOrEmpty()).Select(it => Convert.ToInt32(it)).ToArray(); + if (WriteAreaIndex < 5) + { + bool[] values = intValues.Select(it => it == 1 ? true : false).ToArray(); + bool isSucess = omron.WriteBits(AreaTypeList[WriteAreaIndex], WriteAddress, 0, values.Length, values); + if (!isSucess) + { + GlobalValues.Error("写入失败"); + DisconnectCmdFunc(obj); + return; + } + GlobalValues.Success("写入成功"); + } + else + { + bool isSucess = omron.WriteWords(AreaTypeList[WriteAreaIndex], WriteAddress, intValues.Length, intValues); + if (!isSucess) + { + GlobalValues.Error("写入失败"); + DisconnectCmdFunc(obj); + return; + } + GlobalValues.Success("写入成功"); + } + + } + else if (cmd == "2") + { + float[] floatValues = WriteValue.Split(' ').Where(it => !it.IsNullOrEmpty()).Select(it => Convert.ToSingle(it)).ToArray(); + bool isSucess = omron.WriteFloat(WriteAddress, floatValues); + if (!isSucess) + { + GlobalValues.Error("写入失败"); + DisconnectCmdFunc(obj); + return; + } + GlobalValues.Success("写入成功"); + } + else if (cmd == "3") + { + short[] floatValues = WriteValue.Split(' ').Where(it => !it.IsNullOrEmpty()).Select(it => Convert.ToInt16(it)).ToArray(); + bool isSucess = omron.WriteShort(WriteAddress, floatValues); + if (!isSucess) + { + GlobalValues.Error("写入失败"); + DisconnectCmdFunc(obj); + return; + } + GlobalValues.Success("写入成功"); + } + else if (cmd == "4") + { + bool isSucess = omron.WriteString(WriteAddress, WriteValue, StrLength); + if (!isSucess) + { + GlobalValues.Error("写入失败"); + DisconnectCmdFunc(obj); + return; + } + GlobalValues.Success("写入成功"); + } + } + catch (Exception ex) + { + MessageBox.ShowAsync($"操作异常:{ex.Message}"); + } + } + } +} diff --git a/常用工具集/ViewModels/01PLC通信调试/西门子PLC调试ViewModel.cs b/常用工具集/ViewModels/01PLC通信调试/西门子PLC调试ViewModel.cs new file mode 100644 index 0000000..515caa1 --- /dev/null +++ b/常用工具集/ViewModels/01PLC通信调试/西门子PLC调试ViewModel.cs @@ -0,0 +1,264 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using 常用工具集.Base; +using MES.Utility.Network.S7netplus; +using S7.Net; + +namespace 常用工具集.ViewModel._01PLC通信调试 +{ + public class 西门子PLC调试ViewModel : ViewModelBase + { + public bool Enabled1 { get; set; } = true; + public bool Enabled2 { get; set; } = false; + public string IpAddress { get; set; } = "192.168.1.11"; + public string Address { get; set; } = "DB44.DBD80"; + + public List PlcTypeList { get; set; } + public List PlcTypeEnumList { get; set; } + public int PlcTypeIndex { get; set; } = 0; + public short R { get; set; } = 0; + public short S { get; set; } = 1; + + public int DBAddress2 { get; set; } = 40; + public int Address2 { get; set; } = 80; + public int StringLength { get; set; } = 50; + public string BoolValue { get; set; } = "true"; + public string Int16Value { get; set; } = ""; + public string Int32Value { get; set; } = ""; + public string RealValue { get; set; } = ""; + public string StringValue { get; set; } = ""; + public string StringValue1 { get; set; } = ""; + public int BytesCount { get; set; } = 50; + public string ReadedValue { get; set; } = ""; + public string Message { get; set; } = ""; + + public DelegateCommand ConnectCmd { get; set; } + public DelegateCommand DisconnectCmd { get; set; } + + public DelegateCommand ReadCmd { get; set; } + public DelegateCommand WriteCmd { get; set; } + private S7Helper plc; + public 西门子PLC调试ViewModel() + { + PlcTypeList = new List { "S7-1200", "S7-1500", "S7-200", "S7-300", "S7-400" }; + PlcTypeEnumList = new List { CpuType.S71200, CpuType.S71500, CpuType.S7200, CpuType.S7300, CpuType.S7400 }; + ConnectCmd = new DelegateCommand(ConnectCmdFunc); + DisconnectCmd = new DelegateCommand(DisconnectCmdFunc); + ReadCmd = new DelegateCommand(ReadCmdFunc); + WriteCmd = new DelegateCommand(WriteCmdFunc); + } + + + /// + /// 连接 + /// + /// + private void ConnectCmdFunc(object obj) + { + try + { + this.plc = new S7Helper(PlcTypeEnumList[PlcTypeIndex], IpAddress, R, S); + Enabled2 = true; + Enabled1 = false; + } + catch + { + GlobalValues.Error("PLC连接失败"); + } + } + /// + /// 断开连接 + /// + /// + private void DisconnectCmdFunc(object obj) + { + try { plc.Dispose(); } catch { } + plc = null; + Enabled1 = true; + Enabled2 = false; + } + + /// + /// 读取操作 + /// + /// + private void ReadCmdFunc(object obj) + { + ReadedValue = string.Empty; + Message = string.Empty; + string cmd = obj.ToString(); + if (cmd == "BOOL") + { + bool value; + bool flag = plc.ReadBool(Address, out value); + if (!flag) + { + ReadedValue = string.Empty; + Message = "读取失败"; + return; + } + ReadedValue = value.ToString(); + Message = "读取成功"; + } + else if (cmd == "INT16") + { + ushort value; + bool flag = plc.ReadInt16(Address, out value); + if (!flag) + { + ReadedValue = string.Empty; + Message = "读取失败"; + return; + } + ReadedValue = value.ToString(); + Message = "读取成功"; + } + else if (cmd == "INT32") + { + int value; + bool flag = plc.ReadInt32(Address, out value); + if (!flag) + { + ReadedValue = string.Empty; + Message = "读取失败"; + return; + } + ReadedValue = value.ToString(); + Message = "读取成功"; + } + else if (cmd == "REAL") + { + float value; + bool flag = plc.ReadSingle(Address, out value); + if (!flag) + { + ReadedValue = string.Empty; + Message = "读取失败"; + return; + } + ReadedValue = value.ToString(); + Message = "读取成功"; + } + else if (cmd == "STRING1") + { + string value; + bool flag = plc.ReadString(Address, StringLength, out value); + if (!flag) + { + ReadedValue = string.Empty; + Message = "读取失败"; + return; + } + ReadedValue = value.ToString(); + Message = "读取成功"; + } + else if (cmd == "STRING") + { + string value; + bool flag = plc.ReadBytesString(Address, StringLength, out value); + if (!flag) + { + ReadedValue = string.Empty; + Message = "读取失败"; + return; + } + ReadedValue = value.ToString(); + Message = "读取成功"; + } + else if (cmd == "Bytes") + { + byte[] value; + bool flag = plc.ReadBytes(DBAddress2, Address2, BytesCount, out value); + if (!flag) + { + Message = "读取失败"; + return; + } + ReadedValue = ToHexStrFromByte(value); + Message = "读取成功"; + } + } + + private void WriteCmdFunc(object obj) + { + ReadedValue = string.Empty; + Message = string.Empty; + string cmd = obj.ToString(); + if (cmd == "BOOL") + { + bool value; + bool flag = bool.TryParse(BoolValue, out value); + if (!flag) + { + Message = "请输入true或者false"; + return; + } + flag = plc.WriteBool(Address, value); + Message = flag ? "写入成功" : "写入失败"; + } + else if (cmd == "INT16") + { + ushort value; + bool flag = ushort.TryParse(Int16Value, out value); + if (!flag) + { + Message = "请输入正确的数据"; + return; + } + flag = plc.WriteInt16(Address, value); + Message = flag ? "写入成功" : "写入失败"; + } + else if (cmd == "INT32") + { + int value; + bool flag = int.TryParse(Int32Value, out value); + if (!flag) + { + Message = "请输入正确的数据"; + return; + } + flag = plc.WriteInt32(Address, value); + Message = flag ? "写入成功" : "写入失败"; + } + else if (cmd == "REAL") + { + float value; + bool flag = float.TryParse(RealValue, out value); + if (!flag) + { + Message = "请输入正确的数据"; + return; + } + Message = flag ? "写入成功" : "写入失败"; + } + else if (cmd == "STRING1") + { + bool flag = plc.WriteString(Address, StringValue1, StringLength); + Message = flag ? "写入成功" : "写入失败"; + } + else if (cmd == "STRING") + { + bool flag = plc.WriteBytesString(Address, StringValue, StringLength); + Message = flag ? "写入成功" : "写入失败"; + } + } + + /// + /// 字节数组转16进制字符串:空格分隔 + /// + /// + /// + private string ToHexStrFromByte(byte[] byteDatas) + { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < byteDatas.Length; i++) + { + builder.Append(string.Format("{0:X2} ", byteDatas[i])); + } + return builder.ToString().Trim(); + } + } +} diff --git a/常用工具集/ViewModels/02网络相关/FTP客户端ViewModel.cs b/常用工具集/ViewModels/02网络相关/FTP客户端ViewModel.cs new file mode 100644 index 0000000..9296be6 --- /dev/null +++ b/常用工具集/ViewModels/02网络相关/FTP客户端ViewModel.cs @@ -0,0 +1,766 @@ +using Avalonia.Threading; +using MES.Utility.Core; +using Microsoft.VisualBasic; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using Ursa.Controls; +using 常用工具集.Base; +using 常用工具集.Utility.Network; + +namespace 常用工具集.ViewModel._02网络相关 +{ + public class FTP客户端ViewModel : ViewModelBase + { + public List EncodingList { get; set; } = new List() { Encoding.UTF8 }; + + public List StrEncodingList { get; set; } = new List() { "UTF-8" }; + + public int EncodingIndex { get; set; } = 0; + + private object _lock = new object(); + private List msgList = new List(); + public string Message + { + get + { + lock (_lock) + { + return msgList.GetStrArray("\r\n"); + } + } + set + { + lock (_lock) + { + msgList.Add(value); + } + NotifyPropertyChanged(); + } + } + public string IpAddress { get; set; } = "127.0.0.1"; + public string UserName { get; set; } = "admin"; + public string Password { get; set; } = "123456"; + public int Port { get; set; } = 21; + public bool IsAnonymous { get; set; } = false; + public bool EnableFlag1 { get; set; } = true; + public bool EnableFlag2 { get; set; } = false; + private FtpHelper ftp; + public DelegateCommand ConnectCmd { get; set; } + public DelegateCommand DisconnectCmd { get; set; } + public DelegateCommand LocalDoubleClickCommand { get; set; } + public DelegateCommand LocalHomeClickCommand { get; set; } + + public DelegateCommand RemoteHomeClickCommand { get; set; } + public DelegateCommand RemoteDoubleClickCommand { get; set; } + + public DelegateCommand DownloadClickCommand { get; set; } + public DelegateCommand UploadClickCommand { get; set; } + public DelegateCommand RemoteRenameClickCommand { get; set; } + + public DelegateCommand RemoteDeleteClickCommand { get; set; } + public string LocalPath1 { get; set; } = "本地"; + public string CurrentLocalPath { get; set; } = ""; + + public ObservableCollection LocalTree { get; set; } + public MyDir LocalTreeSelectedItem { get; set; } = null; + + + + public string RemotePath1 { get; set; } = "远程"; + public string CurrentRemotePath { get; set; } = ""; + public ObservableCollection RemoteTree { get; set; } + public MyDir RemoteTreeSelectedItem { get; set; } = null; + + + public FTP客户端ViewModel() + { + LocalTree = GetFoldersAndFiles(); + ConnectCmd = new DelegateCommand(ConnectCmdFunc); + DisconnectCmd = new DelegateCommand(DisconnectCmdFunc); + RemoteHomeClickCommand = new DelegateCommand(RemoteHomeClickCommandFunc); + LocalHomeClickCommand = new DelegateCommand(LocalHomeClickCommandFunc); + LocalDoubleClickCommand = new DelegateCommand(LocalDoubleClickCommandFunc); + RemoteDoubleClickCommand = new DelegateCommand(RemoteDoubleClickCommandFunc); + DownloadClickCommand = new DelegateCommand(DownloadClickCommandFunc); + UploadClickCommand = new DelegateCommand(UploadClickCommandFunc); + RemoteDeleteClickCommand = new DelegateCommand(RemoteDeleteClickCommandFunc); + RemoteRenameClickCommand = new DelegateCommand(RemoteRenameClickCommandFunc); + } + + + + private void ConnectCmdFunc(object obj) + { + if (IsAnonymous) + { + try + { + ftp = new FtpHelper(IpAddress, Port, EncodingList[EncodingIndex]); + RemoteTree = GetRemoteFoldersAndFiles(); + } + catch + { + GlobalValues.Error("FTP连接失败"); + return; + } + } + else + { + try + { + ftp = new FtpHelper(IpAddress, Port, UserName, Password, EncodingList[EncodingIndex]); + RemoteTree = GetRemoteFoldersAndFiles(); + + } + catch + { + GlobalValues.Error("FTP连接失败"); + return; + } + } + EnableFlag1 = false; + EnableFlag2 = true; + } + + private void DisconnectCmdFunc(object obj) + { + ftp = null; + RemoteTree = new ObservableCollection(); + RemotePath1 = "远程"; + CurrentRemotePath = ""; + + EnableFlag1 = true; + EnableFlag2 = false; + } + + + + private void RemoteDoubleClickCommandFunc(object obj) + { + try + { + MyDir current = (MyDir)obj; + if (current == null) + return; + if (current.IsBack) + { + string path = CurrentRemotePath; + if (path.EndsWith("/")) + path = path.Substring(0, path.Length - 1); + int index = path.LastIndexOf("/"); + if (index == -1) + { + path = ""; + } + else + { + path = path.Substring(0, index + 1); + } + if (path == "") + { + CurrentRemotePath = ""; + RemotePath1 = "远程"; + } + else + { + CurrentRemotePath = path; + RemotePath1 = "远程:" + CurrentRemotePath; + } + RemoteTree = GetRemoteFoldersAndFiles(); + return; + } + //文件不能双击 + if (!current.IsDirectory) + { + return; + } + CurrentRemotePath = current.Path; + RemotePath1 = "远程:" + CurrentRemotePath; + RemoteTree = GetRemoteFoldersAndFiles(); + } + catch (Exception ex) + { + GlobalValues.Error("FTP连接失败"); + DisconnectCmdFunc(null); + } + } + + + private void LocalDoubleClickCommandFunc(object obj) + { + MyDir current = (MyDir)obj; + if (current == null) + return; + if (current.IsBack) + { + + string path = CurrentLocalPath; + if (path.EndsWith("/")) + path = path.Substring(0, path.Length - 1); + int index = path.LastIndexOf("/"); + if (index == -1) + { + path = ""; + } + else + { + path = path.Substring(0, index + 1); + } + if (path == "") + { + CurrentLocalPath = ""; + LocalPath1 = "本地"; + } + else + { + CurrentLocalPath = path.Replace("\\", "/"); + LocalPath1 = "本地:" + CurrentLocalPath; + } + LocalTree = GetFoldersAndFiles(); + return; + } + + //文件不能双击 + if (!current.IsDirectory) + { + return; + } + CurrentLocalPath = current.Path; + LocalPath1 = "本地:" + CurrentLocalPath; + LocalTree = GetFoldersAndFiles(); + } + + + private ObservableCollection GetRemoteFoldersAndFiles() + { + ObservableCollection list = new ObservableCollection(); + List list2 = ftp.GetList(CurrentRemotePath).OrderByDescending(it => it.IsDirectory).ToList(); + + if (CurrentRemotePath != "") + { + list.Add(new MyDir + { + Name = $"...", + Path = $"", + IsDirectory = true, + IsBack = true, + }); + } + foreach (var ftpFileInfo in list2) + { + if (ftpFileInfo.IsDirectory) + { + list.Add(new MyDir + { + Name = $"{ftpFileInfo.Name}", + Path = $"{CurrentRemotePath}{ftpFileInfo.Name}/", + IsDirectory = true + }); + } + else + { + list.Add(new MyDir + { + Name = $"{ftpFileInfo.Name}", + Path = $"{CurrentRemotePath}{ftpFileInfo.Name}", + IsDirectory = false + }); + } + } + return list; + } + + private ObservableCollection GetFoldersAndFiles() + { + ObservableCollection list = new ObservableCollection(); + if (CurrentLocalPath == "") + { + list.Add(new MyDir + { + Name = "桌面", + Path = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory).Replace("\\", "/"), + IsDirectory = true + }); + // 获取所有盘符 + DriveInfo[] allDrives = DriveInfo.GetDrives(); + // 过滤出固态硬盘(SSD)和机械硬盘(HDD) + List systemDrives = allDrives.Where(drive => drive.IsReady) // 确保驱动器已准备好访问 + .Where(drive => drive.DriveType == DriveType.Fixed) // 仅固定类型的驱动器 + .Select(drive => drive.Name.Substring(0, 1)) // 获取盘符 + .ToList(); + // 输出系统盘符 + foreach (var drive in systemDrives) + { + list.Add(new MyDir + { + Name = $"{drive}", + Path = $"{drive}:/", + IsDirectory = true + }); + } + } + else + { + list.Add(new MyDir + { + Name = "...", + Path = "", + IsDirectory = true, + IsBack = true, + }); + List dirList = Directory.GetDirectories(CurrentLocalPath).Select(it => it.Replace("\\", "/")).ToList(); + foreach (string dirPath in dirList) + { + list.Add(new MyDir + { + Name = System.IO.Path.GetFileName(dirPath), + Path = dirPath + "/", + IsDirectory = true, + }); + } + List fileList = Directory.GetFiles(CurrentLocalPath).Select(it => it.Replace("\\", "/")).ToList(); + foreach (string filePath in fileList) + { + list.Add(new MyDir + { + Name = System.IO.Path.GetFileName(filePath), + Path = filePath, + IsDirectory = false, + }); + } + } + return list; + } + + private void LocalHomeClickCommandFunc(object obj) + { + CurrentLocalPath = ""; + LocalPath1 = "本地"; + LocalTree = GetFoldersAndFiles(); + } + + private void RemoteHomeClickCommandFunc(object obj) + { + try + { + CurrentRemotePath = ""; + RemotePath1 = "远程"; + RemoteTree = GetRemoteFoldersAndFiles(); + } + catch (Exception ex) + { + GlobalValues.Error("FTP连接失败"); + DisconnectCmdFunc(null); + } + } + + + + /// + /// 上传文件 + /// + /// + private async void UploadClickCommandFunc(object obj) + { + if (LocalTreeSelectedItem == null) + { + await MessageBox.ShowAsync("请选择要上传的文件或文件夹"); + return; + } + if (LocalTreeSelectedItem.IsBack) + { + return; + } + bool cover = false; + + var result = await MessageBox.ShowOverlayAsync("上传过程中可能文件存在,是否覆盖上传", "提示", button: MessageBoxButton.YesNo); + if (result != MessageBoxResult.Yes) + { + cover = false; + } + else + { + cover = true; + } + //上传文件夹 + if (LocalTreeSelectedItem.IsDirectory) + { + new Thread(() => + { + UploadDir(LocalTreeSelectedItem, CurrentRemotePath, cover); + Message = $"文件夹上传完成:{LocalTreeSelectedItem.Name}"; + Dispatcher.UIThread.Invoke(() => + { + RemoteTree = GetRemoteFoldersAndFiles(); + }); + }).Start(); + } + //上传文件 + else + { + new Thread(() => + { + try + { + //判断远端是否存在文件,是否覆盖 + bool isExist = ftp.IsFileExist(CurrentRemotePath, LocalTreeSelectedItem.Name); + if (isExist && !cover) + { + return; + } + Message = $"准备上传:{LocalTreeSelectedItem.Path}"; + ftp.UploadFile(LocalTreeSelectedItem.Path, CurrentRemotePath + LocalTreeSelectedItem.Name); + Message = $"上传完成:{LocalTreeSelectedItem.Path}"; + } + catch (Exception ex) + { + Message = $"上传失败:{LocalTreeSelectedItem.Path}"; + } + Dispatcher.UIThread.Invoke(() => + { + RemoteTree = GetRemoteFoldersAndFiles(); + }); + }).Start(); + } + } + + /// + /// 下载文件 + /// + /// + private async void DownloadClickCommandFunc(object obj) + { + if (RemoteTreeSelectedItem == null) + { + await MessageBox.ShowAsync("请选择要下载的文件或文件夹"); + return; + } + if (CurrentLocalPath == "") + { + await MessageBox.ShowAsync("请选择本地路径"); + return; + } + if (RemoteTreeSelectedItem.IsBack) + { + return; + } + bool cover = false; + var result = await MessageBox.ShowOverlayAsync("下载过程中可能文件存在,是否覆盖下载", "提示", button: MessageBoxButton.YesNo); + if (result != MessageBoxResult.Yes) + { + cover = false; + } + else + { + cover = true; + } + //下载文件夹 + if (RemoteTreeSelectedItem.IsDirectory) + { + new Thread(() => + { + DownloadDir(RemoteTreeSelectedItem, CurrentLocalPath, cover); + Message = $"文件夹下载完成:{RemoteTreeSelectedItem.Name}"; + Dispatcher.UIThread.Invoke(() => + { + LocalTree = GetFoldersAndFiles(); + }); + }).Start(); + } + //下载文件 + else + { + new Thread(() => + { + + try + { + if (File.Exists(CurrentLocalPath + "/" + RemoteTreeSelectedItem.Name) && !cover) + { + return; + } + Message = $"准备下载:{RemoteTreeSelectedItem.Name}"; + ftp.DownloadFile(RemoteTreeSelectedItem.Path, CurrentLocalPath + "/" + RemoteTreeSelectedItem.Name); + Message = $"下载完成:{RemoteTreeSelectedItem.Name}"; + } + catch (Exception ex) + { + Message = $"下载失败:{ex.Message}"; + } + Dispatcher.UIThread.Invoke(() => + { + LocalTree = GetFoldersAndFiles(); + }); + }).Start(); + } + } + + /// + /// 删除FTP文件 + /// + /// + /// + private async void RemoteDeleteClickCommandFunc(object obj) + { + try + { + if (RemoteTreeSelectedItem == null) + { + await MessageBox.ShowAsync("请选择要删除的文件或文件夹"); + return; + } + if (RemoteTreeSelectedItem.IsBack) + { + return; + } + var result = await MessageBox.ShowOverlayAsync("是否确定删除", "提示", button: MessageBoxButton.YesNo); + if (result != MessageBoxResult.Yes) + { + return; + } + if (RemoteTreeSelectedItem.IsDirectory) + { + new Thread(() => + { + try + { + DeleteDirecory(RemoteTreeSelectedItem.Path); + Message = $"删除成功:{RemoteTreeSelectedItem.Path}"; + } + catch (Exception ex) + { + Message = $"删除失败:{ex.Message}"; + } + Dispatcher.UIThread.Invoke(() => + { + RemoteTree = GetRemoteFoldersAndFiles(); + }); + }).Start(); + } + else + { + new Thread(() => + { + Message = $"准备删除:{RemoteTreeSelectedItem.Path}"; + try + { + ftp.DeleteFile(RemoteTreeSelectedItem.Path); + Message = $"删除成功:{RemoteTreeSelectedItem.Path}"; + } + catch (Exception ex) + { + Message = $"删除失败:{ex.Message}"; + } + Dispatcher.UIThread.Invoke(() => + { + RemoteTree = GetRemoteFoldersAndFiles(); + }); + }).Start(); + } + + } + catch (Exception ex) + { + await MessageBox.ShowAsync(ex.Message); + } + } + + private void DeleteDirecory(string path) + { + List list = ftp.GetList(path); + foreach (FtpFileInfo info in list) + { + if (info.IsDirectory) + { + DeleteDirecory(path + info.Name + "/"); + } + else + { + Message = $"准备删除:{path + info.Name}"; + try + { + ftp.DeleteFile(path + info.Name); + Message = $"删除成功:{path + info.Name}"; + } + catch (Exception ex) + { + Message = $"删除失败:{ex.Message}"; + } + } + } + ftp.DeleteDirectory(path); + } + + + /// + /// 递归调用 + /// + /// + /// + /// + private void DownloadDir(MyDir remoteTreeSelectedItem, string currentLocalPath, bool cover) + { + //文件夹是否存在 + string localPath = currentLocalPath + "/" + remoteTreeSelectedItem.Name; + if (!Directory.Exists(localPath)) + { + Directory.CreateDirectory(localPath); + } + List myDirs = ftp.GetList(remoteTreeSelectedItem.Path); + foreach (FtpFileInfo f in myDirs) + { + if (f.IsDirectory) + { + MyDir dir = new MyDir { IsDirectory = true, Name = f.Name, Path = remoteTreeSelectedItem.Path + f.Name + "/" }; + DownloadDir(dir, localPath, cover); + } + else + { + + try + { + if (File.Exists(localPath + "/" + f.Name) && !cover) + { + return; + } + Message = $"准备下载:{remoteTreeSelectedItem.Path + f.Name}"; + ftp.DownloadFile(remoteTreeSelectedItem.Path + f.Name, localPath + "/" + f.Name); + Message = $"下载完成:{remoteTreeSelectedItem.Path + f.Name}"; + } + catch (Exception ex) + { + Message = $"下载失败:{ex.Message}"; + } + } + } + } + + + private void UploadDir(MyDir localTreeSelectedItem, string currentRemotePath, bool cover) + { + //文件夹是否存在 + string remotePath = currentRemotePath + localTreeSelectedItem.Name + "/"; + if (!ftp.FolderExists(currentRemotePath, localTreeSelectedItem.Name)) + { + ftp.CreateFolder(remotePath); + } + string[] dirList = Directory.GetDirectories(localTreeSelectedItem.Path).Select(it => it.Replace("\\", "/")).ToArray(); + string[] fileList = Directory.GetFiles(localTreeSelectedItem.Path).Select(it => it.Replace("\\", "/")).ToArray(); + foreach (string f in dirList) + { + MyDir dir = new MyDir { IsDirectory = true, Name = System.IO.Path.GetFileName(f), Path = f }; + UploadDir(dir, remotePath, cover); + } + foreach (string f in fileList) + { + try + { + bool isExist = ftp.IsFileExist(remotePath + "/", System.IO.Path.GetFileName(f)); + if (isExist) + { + continue; + } + Message = $"准备上传:{localTreeSelectedItem.Path + System.IO.Path.GetFileName(f)}"; + ftp.UploadFile(localTreeSelectedItem.Path + "/" + System.IO.Path.GetFileName(f), remotePath + "/" + System.IO.Path.GetFileName(f)); + Message = $"上传完成:{localTreeSelectedItem.Path + System.IO.Path.GetFileName(f)}"; + } + catch (Exception ex) + { + Message = $"上传失败:{ex.Message}"; + } + + } + } + + + + private void RemoteRenameClickCommandFunc(object obj) + { + if (RemoteTreeSelectedItem == null) + { + MessageBox.ShowAsync("请选择要重命名的文件或文件夹"); + return; + } + if (RemoteTreeSelectedItem.IsBack) + { + return; + } + string newName = Interaction.InputBox("请输入新名字", "", "", 200, 100); + if (newName.IsNullOrEmpty()) + { + MessageBox.ShowAsync("请输入新文件名"); + return; + } + string path = ""; + if (RemoteTreeSelectedItem.IsDirectory) + { + path = CurrentRemotePath; + if (path.EndsWith("/")) + path = path.Substring(0, path.Length - 1); + int index = path.LastIndexOf("/"); + if (index == -1) + { + path = ""; + } + else + { + path = path.Substring(0, index + 1); + } + path += newName + "/"; + } + else + { + path = CurrentRemotePath; + if (path.EndsWith("/")) + path = path.Substring(0, path.Length - 1); + int index = path.LastIndexOf("/"); + if (index == -1) + { + path = ""; + } + else + { + path = path.Substring(0, index + 1); + } + string ext = System.IO.Path.GetExtension(RemoteTreeSelectedItem.Path); + if (!newName.ToLower().EndsWith(ext.ToLower())) + { + path = path + newName + ext; + } + } + + new Thread(() => + { + try + { + ftp.Rename(RemoteTreeSelectedItem.Path, path); + + Dispatcher.UIThread.Invoke(() => + { + RemoteTree = GetRemoteFoldersAndFiles(); + }); + } + catch (Exception ex) + { + Message = $"重命名失败:{ex.Message}"; + } + + + }).Start(); + } + } + + public class MyDir + { + public string Path { get; set; } + public string Name { get; set; } + public bool IsDirectory { get; set; } + + public bool IsBack { get; set; } + } +} diff --git a/常用工具集/ViewModels/02网络相关/FTP服务ViewModel.cs b/常用工具集/ViewModels/02网络相关/FTP服务ViewModel.cs new file mode 100644 index 0000000..fc57c54 --- /dev/null +++ b/常用工具集/ViewModels/02网络相关/FTP服务ViewModel.cs @@ -0,0 +1,159 @@ +using Avalonia.Platform.Storage; +using FubarDev.FtpServer; +using FubarDev.FtpServer.AccountManagement; +using FubarDev.FtpServer.FileSystem.DotNet; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Ursa.Controls; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._02网络相关 +{ + public class FTP服务ViewModel : ViewModelBase + { + public bool Enabled1 { get; set; } = true; + public string ButtonName { get; set; } = "开启服务"; + public string Path { get; set; } = "d:/"; + public int Port { get; set; } = 21; + public string UserName { get; set; } = "admin"; + public string Password { get; set; } = "123456"; + public bool Anonymous { get; set; } = true; + public int MaxConnectCount { get; set; } = 100; + + public DelegateCommand SelectPathCmd { get; set; } + public DelegateCommand StartCmd { get; set; } + public FTP服务ViewModel() + { + SelectPathCmd = new DelegateCommand(SelectPathCmdFunc); + StartCmd = new DelegateCommand(StartCmdFunc); + } + + private void StartCmdFunc(object obj) + { + if (ButtonName == "开启服务") + { + bool flag = Start(UserName, Password, Path, Port, Anonymous, MaxConnectCount); + if (!flag) + { + return; + } + Enabled1 = false; + ButtonName = "停止服务"; + } + else + { + bool flag = Stop(); + if (!flag) + return; + Enabled1 = true; + ButtonName = "开启服务"; + } + } + + private async void SelectPathCmdFunc(object obj) + { + var sp = GlobalValues.StorageProvider; + if (sp is null) return; + var result = await sp.OpenFolderPickerAsync(new FolderPickerOpenOptions() + { + Title = "选择FTP主目录", + AllowMultiple = false, + }); + if (result == null || result.Count == 0) + { + await MessageBox.ShowAsync("文件夹路径不能为空"); + return; + } + Path = result.FirstOrDefault()?.Path.LocalPath; + } + + internal static string UserName1 { get; set; } + internal static string Password1 { get; set; } + public ServiceProvider serviceProvider; + public IFtpServerHost ftpServerHost; + private bool Start(string userName, string password, string path, int port, bool anonymous, int maxConnectCount) + { + try + { + UserName1 = userName; + Password1 = password; + // 设置依赖项注入 + var services = new ServiceCollection(); + // 使用%TEMP%/TestFtpServer作为根文件夹 + services.Configure(opt => + { + opt.RootPath = path; + }); + services.Configure(opt => opt.DefaultEncoding = Encoding.GetEncoding("GB2312")); + // 添加FTP服务器服务 + // DotNetFileSystemProvider = 使用.NET文件系统功能 + // AnonymousMembershipProvider = 仅允许匿名登录 + services.AddFtpServer(builder => + { + builder.UseDotNetFileSystem(); // 使用.NET文件系统功能 + if (anonymous) + { + builder.EnableAnonymousAuthentication();// 允许匿名登录 + } + builder.Services.AddSingleton();//用户登录 + }); + + // 配置FTP服务器 + services.Configure(opt => + { + opt.ServerAddress = "*"; + opt.Port = port; + opt.MaxActiveConnections = maxConnectCount; + }); + + serviceProvider = services.BuildServiceProvider(); + ftpServerHost = serviceProvider.GetRequiredService(); + ftpServerHost.StartAsync(CancellationToken.None); + return true; + } + catch (Exception ex) + { + return false; + } + } + + public class TestMembershipProvider : IMembershipProvider + { + public Task ValidateUserAsync(string name, string password) + { + if (UserName1 == name && password == Password1) + { + var identity = new ClaimsIdentity(); + identity.AddClaim(new Claim(ClaimTypes.Name, name)); + identity.AddClaim(new Claim(ClaimTypes.Role, "admin")); + return Task.FromResult(new MemberValidationResult(MemberValidationStatus.AuthenticatedUser, new ClaimsPrincipal(identity))); + } + return Task.FromResult(new MemberValidationResult(MemberValidationStatus.InvalidLogin)); + } + } + + private bool Stop() + { + try + { + // 停止FTP服务器 + ftpServerHost.StopAsync(CancellationToken.None).Wait(); + serviceProvider.Dispose(); + ftpServerHost = null; + serviceProvider = null; + return true; + } + catch (Exception ex) + { + return false; + } + } + } +} diff --git a/常用工具集/ViewModels/02网络相关/HTTP调试ViewModel.cs b/常用工具集/ViewModels/02网络相关/HTTP调试ViewModel.cs new file mode 100644 index 0000000..392dd99 --- /dev/null +++ b/常用工具集/ViewModels/02网络相关/HTTP调试ViewModel.cs @@ -0,0 +1,271 @@ +using MES.Utility.Core; +using System; +using System.Collections.Generic; +using 常用工具集.Base; +using System.Threading; +using 常用工具集.Utility.Network; +using System.IO; +using Ursa.Controls; +using Avalonia.Threading; +using Avalonia.Platform.Storage; + +namespace 常用工具集.ViewModel._02网络相关 +{ + public class HTTP调试ViewModel : ViewModelBase + { + private bool getChecked = true; + public bool GetChecked + { + get { return getChecked; } + set + { + getChecked = value; + if (value) + { + Visiable =false; + } + NotifyPropertyChanged(); + } + } + private bool downloadChecked = false; + public bool DownloadChecked + { + get { return downloadChecked; } + set + { + downloadChecked = value; + if (value) + { + Visiable = false; + } + NotifyPropertyChanged(); + } + } + private bool postChecked = false; + public bool PostChecked + { + get { return postChecked; } + set + { + postChecked = value; + if (value) + { + Visiable = true; + } + NotifyPropertyChanged(); + } + } + + public string Url { get; set; } = ""; + + public DelegateCommand ButtonCmd { get; set; } + + public bool JsonChecked { get; set; } = true; + public bool FormChecked { get; set; } = false; + + public int Timeout { get; set; } = 1000; + + public bool Visiable { get; set; } = false; + + public string Parms { get; set; } = ""; + public string Result { get; set; } = ""; + public bool ButtonEnabled { get; set; } = true; + public HTTP调试ViewModel() + { + ButtonCmd = new DelegateCommand(ButtonCmdFunc); + } + + private void ButtonCmdFunc(object obj) + { + if (Url.IsNullOrEmpty()) + { + MessageBox.ShowAsync("请输入URL"); + return; + } + string url = Url; + if (!url.ToLower().StartsWith("http")) + { + url = "http://" + url; + } + int timeout = Timeout; + if (GetChecked) + { + Dictionary dict = new Dictionary(); + if (url.Contains("?")) + { + int index = url.IndexOf("?"); + string parms = url.Substring(index + 1, url.Length - index - 1); + string[] parmsArray = parms.Split('&'); + foreach (string parm in parmsArray) + { + if (parm.Contains("=")) + { + string[] array = parm.Split('='); + dict.Add(array[0], array[1]); + } + } + parmsArray = Parms.Split('&'); + foreach (string parm in parmsArray) + { + if (parm.Contains("=")) + { + string[] array = parm.Split('='); + dict.Add(array[0], array[1]); + } + } + url = url.Substring(0, index); + } + else + { + string[] parmsArray = Parms.Split('&'); + foreach (string parm in parmsArray) + { + if (parm.Contains("=")) + { + string[] array = parm.Split('='); + dict.Add(array[0], array[1]); + } + } + } + ButtonEnabled = false; + Result = string.Empty; + new Thread(() => + { + string ret = HttpUtils.DoGet(url, dict, timeout); + if (!ret.IsNullOrEmpty()) + { + Result = ret; + } + else + { + Result = "网络或服务器异常"; + } + ButtonEnabled = true; + }).Start(); + } + else if (DownloadChecked) + { + Dictionary dict = new Dictionary(); + if (url.Contains("?")) + { + int index = url.IndexOf("?"); + string parms = url.Substring(index + 1, url.Length - index - 1); + string[] parmsArray = parms.Split('&'); + foreach (string parm in parmsArray) + { + if (parm.Contains("=")) + { + string[] array = parm.Split('='); + dict.Add(array[0], array[1]); + } + } + parmsArray = Parms.Split('&'); + foreach (string parm in parmsArray) + { + if (parm.Contains("=")) + { + string[] array = parm.Split('='); + dict.Add(array[0], array[1]); + } + } + url = url.Substring(0, index); + } + else + { + string[] parmsArray = Parms.Split('&'); + foreach (string parm in parmsArray) + { + if (parm.Contains("=")) + { + string[] array = parm.Split('='); + dict.Add(array[0], array[1]); + } + } + } + ButtonEnabled = false; + Result = string.Empty; + new Thread(() => + { + byte[] ret = HttpUtils.DoGetFile(url, timeout); + if (ret == null) + { + Result = "网络或服务器异常"; + } + else + { + Dispatcher.UIThread.Invoke(async () => + { + var sp = GlobalValues.StorageProvider; + if (sp is null) return; + var result = await sp.SaveFilePickerAsync(new FilePickerSaveOptions() + { + Title = "保存文件" + }); + if (result == null) return; + string filePath = result.Path.LocalPath; + using (FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite)) + { + fs.Write(ret, 0, ret.Length); + } + await MessageBox.ShowAsync("下载完成"); + }); + } + ButtonEnabled = true; + }).Start(); + } + else + { + //JSON + if (JsonChecked) + { + + ButtonEnabled = false; + Result = string.Empty; + new Thread(() => + { + string ret = HttpUtils.DoPostJson(url, Parms, timeout); + if (!ret.IsNullOrEmpty()) + { + Result = ret; + } + else + { + Result = "网络或服务器异常"; + } + ButtonEnabled = true; + }).Start(); + } + else + { + Dictionary dict = new Dictionary(); + + string[] parmsArray = Parms.Split('&'); + foreach (string parm in parmsArray) + { + if (parm.Contains("=")) + { + string[] array = parm.Split('='); + dict.Add(array[0], array[1]); + } + } + ButtonEnabled = false; + Result = string.Empty; + new Thread(() => + { + string ret = HttpUtils.DoPostForm(url, dict, timeout); + if (!ret.IsNullOrEmpty()) + { + Result = ret; + } + else + { + Result = "网络或服务器异常"; + } + ButtonEnabled = true; + }).Start(); + } + } + + } + } +} diff --git a/常用工具集/ViewModels/02网络相关/端口占用扫描ViewModel.cs b/常用工具集/ViewModels/02网络相关/端口占用扫描ViewModel.cs new file mode 100644 index 0000000..808eca0 --- /dev/null +++ b/常用工具集/ViewModels/02网络相关/端口占用扫描ViewModel.cs @@ -0,0 +1,200 @@ +using Avalonia.Threading; +using MES.Utility.Core; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._02网络相关 +{ + public class 端口占用扫描ViewModel : ViewModelBase + { + public bool Enabled1 { get; set; } = true; + public bool Enabled2 { get; set; } = false; + + public int SelectedIndex { get; set; } = -1; + + public ObservableCollection DataList { get; set; } = new ObservableCollection(); + + public int ProgressValue { get; set; } = 0; + public int ProgressMax { get; set; } = 100; + + public DelegateCommand StartScanCmd { get; set; } + public DelegateCommand CloseProcessCmd { get; set; } + + public 端口占用扫描ViewModel() + { + StartScanCmd = new DelegateCommand(StartScanCmdFunc); + CloseProcessCmd = new DelegateCommand(CloseProcessCmdFunc); + } + + private void CloseProcessCmdFunc(object obj) + { + if (SelectedIndex < 0) + { + return; + } + int pid = DataList[SelectedIndex].PID; + Process process = Process.GetProcessById(pid); + if (process == null) + { + GlobalValues.Error("获取进程相关信息失败,请尝试重新操作"); + return; + } + try + { + process.Kill(); + process.WaitForExit(); + process.Close(); + DataList.RemoveAt(SelectedIndex); + GlobalValues.Success("操作成功"); + } + catch (Exception ex) + { + GlobalValues.Error($"结束进程失败:{ex.Message}"); + } + } + + private void StartScanCmdFunc(object obj) + { + Enabled1 = false; + Enabled2 = false; + ProgressValue = 0; + DataList.Clear(); + new Thread(() => + { + ProcessStartInfo startInfo = new ProcessStartInfo("netstat", "-ano") + { + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true + }; + string result = ""; + using (Process process = Process.Start(startInfo)) + { + using (StreamReader reader = process.StandardOutput) + { + result = reader.ReadToEnd(); + } + } + List lines = result.Split('\n').Select(it => it.Trim('\r').Trim()).Where(it => !it.IsNullOrEmpty()).ToList(); + List results = new List(); + foreach (string line in lines) + { + string[] splitArray = line.Split(' ').Where(it => !it.IsNullOrEmpty()).ToArray(); + if (!line.StartsWith("TCP") && !line.StartsWith("UDP")) + continue; + if (splitArray.Length == 5) + { + int pid; + bool flag = int.TryParse(splitArray[4], out pid); + if (!flag) + continue; + if (!splitArray[1].Contains(":")) + { + continue; + } + string[] splitArray2 = splitArray[1].Split(':'); + int port; + flag = int.TryParse(splitArray2[1], out port); + if (!flag) + continue; + results.Add(new ScanResult + { + Protocol = splitArray[0], + LocalAddress = splitArray[1], + Port = port, + RemoteAddress = splitArray[2], + State = splitArray[3], + PID = pid, + ProcessName = "" + }); + } + else if (splitArray.Length == 4) + { + int pid; + bool flag = int.TryParse(splitArray[3], out pid); + if (!flag) + continue; + if (!splitArray[1].Contains(":")) + { + continue; + } + string[] splitArray2 = splitArray[1].Split(':'); + int port; + flag = int.TryParse(splitArray2[1], out port); + if (!flag) + continue; + results.Add(new ScanResult + { + Protocol = splitArray[0], + LocalAddress = splitArray[1], + Port = port, + RemoteAddress = splitArray[2], + State = "", + PID = pid, + ProcessName = "" + }); + } + } + + results = results.OrderBy(it => it.Port).ToList(); + + Dictionary pidName = new Dictionary(); + foreach (ScanResult scan in results) + { + try + { + if (pidName.ContainsKey(scan.PID)) + { + scan.ProcessName = pidName[scan.PID]; + } + else + { + Process process = Process.GetProcessById(scan.PID); + if (process != null) + { + pidName.Add(scan.PID, process.ProcessName); + scan.ProcessName = process.ProcessName; + } + } + + Dispatcher.UIThread.Invoke(new Action(() => + { + DataList.Add(scan); + })); + } + catch + { + Dispatcher.UIThread.Invoke(new Action(() => + { + DataList.Add(scan); + })); + } + } + Dispatcher.UIThread.Invoke(new Action(() => + { + Enabled1 = true; + Enabled2 = true; + })); + }).Start(); + } + } + + + public class ScanResult + { + public string Protocol { get; set; } + public string LocalAddress { get; set; } + + public int Port { get; set; } + public string RemoteAddress { get; set; } + public string State { get; set; } + public int PID { get; set; } + public string ProcessName { get; set; } + } +} diff --git a/常用工具集/ViewModels/02网络相关/端口扫描ViewModel.cs b/常用工具集/ViewModels/02网络相关/端口扫描ViewModel.cs new file mode 100644 index 0000000..27c05bd --- /dev/null +++ b/常用工具集/ViewModels/02网络相关/端口扫描ViewModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._02网络相关 +{ + public class 端口扫描ViewModel : ViewModelBase + { + } +} diff --git a/常用工具集/ViewModels/02网络相关/网络状态检测ViewModel.cs b/常用工具集/ViewModels/02网络相关/网络状态检测ViewModel.cs new file mode 100644 index 0000000..0dcb754 --- /dev/null +++ b/常用工具集/ViewModels/02网络相关/网络状态检测ViewModel.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Net.NetworkInformation; +using System.Threading; +using Ursa.Controls; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._02网络相关 +{ + public class 网络状态检测ViewModel : ViewModelBase + { + #region 数据 + public ObservableCollection PingStatus { get; set; } + + private int ip1 { get; set; } = 172; + public int IP1 { get { return ip1; } set { ip1 = value; SetTip(); NotifyPropertyChanged(); } } + private int ip2 { get; set; } = 16; + public int IP2 { get { return ip2; } set { ip2 = value; SetTip(); NotifyPropertyChanged(); } } + private int ip3 { get; set; } = 0; + public int IP3 { get { return ip3; } set { ip3 = value; SetTip(); NotifyPropertyChanged(); } } + #endregion + + public DelegateCommand Button1Cmd { get; set; } + public DelegateCommand Button2Cmd { get; set; } + public 网络状态检测ViewModel() + { + lock (_lock) + { + PingStatus = new ObservableCollection(); + for (int i = 1; i <= 255; i++) + { + PingStatus.Add(new MyPingStatus + { + Index = i, + IpAddress = $"{IP1}.{IP2}.{IP3}.{i}", + Color = "#FFFF80", + Tip = $"{IP1}.{IP2}.{IP3}.{i}", + Status = false + }); + } + } + threadList = new List(); + SetTip(); + Button1Cmd = new DelegateCommand(Button1CmdFunc); + Button2Cmd = new DelegateCommand(Button2CmdFunc); + } + + + private List threadList; + private void Button1CmdFunc(object obj) + { + lock (pingLock) + { + if (IPPool.Count > 0) + { + MessageBox.ShowAsync("上一次操作还没结束,请等待完成后继续"); + return; + } + } + lock (_lock) + { + SetColor("#FFFF80");//开始之前清除一下 + } + foreach (MyThread myThread in threadList) + { + myThread.Stop(); + myThread.OnSendResult -= Thread_OnSendResult; + } + threadList.Clear(); + lock (_lock) + { + for (int i = 1; i <= 255; i++) + { + IPPool.Add(i); + } + } + string ip = $"{IP1}.{IP2}.{IP3}."; + for (int i = 0; i < 10; i++) + { + MyThread thread = new MyThread(ip); + thread.OnSendResult += Thread_OnSendResult; + thread.Start(); + threadList.Add(thread); + } + } + + private void Thread_OnSendResult(int endIndex, bool state) + { + if (state) + { + SetColor(endIndex, "#00FF00"); + } + else + { + SetColor(endIndex, "#FF0000"); + } + } + + + private void Button2CmdFunc(object obj) + { + lock (pingLock) + { + if (IPPool.Count > 0) + { + MessageBox.ShowAsync("上一次操作还没结束,请等待完成后继续"); + return; + } + } + lock (_lock) + { + SetColor("#FFFF80"); + } + } + + + /// + /// 设置提示 + /// + private void SetTip() + { + foreach (MyPingStatus status in PingStatus) + { + status.Tip = $"{IP1}.{IP2}.{IP3}.{status.Index}"; + } + } + + /// + /// 设置提示 + /// + private void SetColor(string color) + { + foreach (MyPingStatus status in PingStatus) + { + status.Color = color; + } + } + internal static List IPPool = new List(); + internal static object pingLock = new object(); + internal object _lock = new object(); + private void SetColor(int index, string color) + { + lock (_lock) + { + foreach (MyPingStatus status in PingStatus) + { + if (status.Index == index) + { + status.Color = color; + break; + } + } + } + } + } + + + public class MyPingStatus : ViewModelBase + { + public int Index { get; set; } + public string Color { get; set; } + public string Tip { get; set; } + + public string IpAddress { get; set; } + + public bool Status { get; set; } + } + + + internal class MyThread + { + public delegate void SendResult(int endIndex, bool state); + public event SendResult OnSendResult; + private Thread thread; + private string ipAddress; + private bool stop; + public MyThread(string ip) + { + this.ipAddress = ip; + } + + public MyThread Start() + { + thread = new Thread(() => + { + while (true) + { + try + { + if (stop) + break; + //从IP池取出一个 + int ipIndex = 0; + lock (网络状态检测ViewModel.pingLock) + { + if (网络状态检测ViewModel.IPPool.Count > 0) + { + ipIndex = 网络状态检测ViewModel.IPPool[0]; + 网络状态检测ViewModel.IPPool.RemoveAt(0); + } + } + if (ipIndex == 0) + break; + bool flag = Ping($"{ipAddress}{ipIndex}"); + OnSendResult?.Invoke(ipIndex, flag); + } + catch (Exception ex) + { + if (ex is ThreadInterruptedException) + break; + } + } + }); + thread.Start(); + return this; + } + + public void Stop() + { + stop = true; + thread.Interrupt(); + } + + private bool Ping(string ip) + { + byte[] bytes = new byte[32]; + for (int i = 0; i < bytes.Length; i++) + { + bytes[i] = (byte)(i + 1); + } + + Ping ping = new Ping(); + if (ping.Send(ip, 300, bytes, new PingOptions { Ttl = 255 }).Status == IPStatus.Success) + { + return true; + } + return false; + } + } +} diff --git a/常用工具集/ViewModels/03图片相关/GIF分割ViewModel.cs b/常用工具集/ViewModels/03图片相关/GIF分割ViewModel.cs new file mode 100644 index 0000000..226e3c5 --- /dev/null +++ b/常用工具集/ViewModels/03图片相关/GIF分割ViewModel.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Threading; +using System.Windows.Forms; +using Avalonia; +using Avalonia.Platform.Storage; +using Avalonia.Threading; +using Ursa.Controls; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._03图片相关 +{ + public class GIF分割ViewModel : ViewModelBase + { + + public List Paths { get; set; } = new List(); + public string ButtonName { get; set; } = "导出文件"; + public string Path1 { get; set; } = ""; + public string Path2 { get; set; } = ""; + public DelegateCommand SelectGifCmd { get; set; } + public DelegateCommand SelectExportPathCmd { get; set; } + + public DelegateCommand ExportCmd { get; set; } + + public GIF分割ViewModel() + { + SelectGifCmd = new DelegateCommand(SelectGifCmdFunc); + SelectExportPathCmd = new DelegateCommand(SelectExportPathCmdFunc); + ExportCmd = new DelegateCommand(ExportCmdFunc); + } + + + private async void SelectGifCmdFunc(object obj) + { + var sp = GlobalValues.StorageProvider; + if (sp is null) return; + FilePickerOpenOptions options = new FilePickerOpenOptions(); + options.Title = "选择GIF文件"; + options.AllowMultiple = false; + options.FileTypeFilter = [ + new FilePickerFileType("GIF文件") + { + Patterns = new[] { "*.gif" }, + AppleUniformTypeIdentifiers = new[] { "public.gif" }, + MimeTypes = new[] { "image/gif" } + }]; + var result = await sp.OpenFilePickerAsync(options); + if (result == null || result.Count == 0) return; + Path1 = result.FirstOrDefault()?.Path.LocalPath; + } + + private async void SelectExportPathCmdFunc(object obj) + { + var sp = GlobalValues.StorageProvider; + if (sp is null) return; + var result = await sp.OpenFolderPickerAsync(new FolderPickerOpenOptions() + { + Title = "Select Folder", + AllowMultiple = false, + }); + if (result == null || result.Count == 0) return; + Path2 = result.FirstOrDefault()?.Path.LocalPath; + } + + private void ExportCmdFunc(object obj) + { + string gifPath = Path1.Trim(); + string folderPaht = Path2.Trim(); + if (string.IsNullOrEmpty(gifPath) || string.IsNullOrEmpty(folderPaht)) + { + MessageBox.ShowAsync("请选择gif路径或者导出路径!"); + return; + } + + if (ButtonName == "导出文件") + { + ButtonName = "导出中..."; + //开启线程导出图片 + Thread thread = new Thread(Export); + thread.Start(); + } + else + { + MessageBox.ShowAsync("正在导出中!"); + return; + } + } + + /// + /// 导出jpg图片 + /// + private void Export() + { + string gifPath = Path1.Trim(); + string folderPaht = Path2.Trim(); + Image img = Image.FromFile(gifPath); + FrameDimension fd = new FrameDimension(img.FrameDimensionsList[0]); + //获取gif帧的数量 + int count = img.GetFrameCount(fd); + //遍历保存图片 + for (int i = 0; i < count; i++) + { + img.SelectActiveFrame(fd, i); + string imgPath = folderPaht + "\\frame" + (i + 1) + ".png"; + //判断同名文件是否存在 + if (File.Exists(imgPath)) + { + File.Delete(imgPath); + } + //保存图片 一定要设置格式 否则保存出来的图片都是一张图片 + img.Save(imgPath, ImageFormat.Png); + } + Dispatcher.UIThread.Invoke(() => + { + GlobalValues.Success("文件导出成功!"); + ButtonName = "导出文件"; + }); + + } + } +} diff --git a/常用工具集/ViewModels/03图片相关/二维码条形码解析ViewModel.cs b/常用工具集/ViewModels/03图片相关/二维码条形码解析ViewModel.cs new file mode 100644 index 0000000..b8d07c4 --- /dev/null +++ b/常用工具集/ViewModels/03图片相关/二维码条形码解析ViewModel.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Avalonia.Media.Imaging; +using Avalonia.Platform.Storage; +using SkiaSharp; +using ZXing; +using ZXing.Common; +using ZXing.SkiaSharp; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._03图片相关 +{ + public class 二维码条形码解析ViewModel : ViewModelBase + { + public string ImagePath { get; set; } = ""; + public string Result { get; set; } = ""; + + public DelegateCommand SelectPathCmd { get; set; } + public DelegateCommand GetResultCmd { get; set; } + + public 二维码条形码解析ViewModel() + { + SelectPathCmd = new DelegateCommand(SelectPathCmdFunc); + GetResultCmd = new DelegateCommand(GetResultCmdFunc); + } + + + + /// + /// 选择文件 + /// + /// + private async void SelectPathCmdFunc(object obj) + { + var sp = GlobalValues.StorageProvider; + if (sp is null) return; + FilePickerOpenOptions options = new FilePickerOpenOptions(); + options.Title = "选择二维码图片"; + options.AllowMultiple = false; + options.FileTypeFilter = [ + FilePickerFileTypes.ImageAll + ]; + var result = await sp.OpenFilePickerAsync(options); + if (result == null || result.Count == 0) return; + ImagePath = result.FirstOrDefault()?.Path.LocalPath; + } + + + private void GetResultCmdFunc(object obj) + { + try + { + MultiFormatReader formatReader = new MultiFormatReader(); + Dictionary hints = new Dictionary(); + hints.Add(DecodeHintType.CHARACTER_SET, Encoding.UTF8.HeaderName); + // 优化精度 + hints.Add(DecodeHintType.TRY_HARDER, true); + // 复杂模式,开启PURE_BARCODE模式 + hints.Add(DecodeHintType.PURE_BARCODE, true); + formatReader.Hints = hints; + SKBitmap bitmap = SKBitmap.Decode(ImagePath); + LuminanceSource source = new SKBitmapLuminanceSource(bitmap); + Result result = formatReader.decode(new BinaryBitmap(new HybridBinarizer(source))); + if (null == result) + { + result = formatReader.decode(new BinaryBitmap(new GlobalHistogramBinarizer(source))); + } + bitmap.Dispose(); + if (result != null) + { + Result = result.Text; // 显示解析出的一维码文本内容 + } + else + { + Result = "未能解析条码"; + } + } + catch (Exception ex) + { + Result = ex.Message; + } + } + } +} diff --git a/常用工具集/ViewModels/03图片相关/图片转ICOViewModel.cs b/常用工具集/ViewModels/03图片相关/图片转ICOViewModel.cs new file mode 100644 index 0000000..2153767 --- /dev/null +++ b/常用工具集/ViewModels/03图片相关/图片转ICOViewModel.cs @@ -0,0 +1,118 @@ +using System; +using 常用工具集.Base; +using Avalonia.Media.Imaging; +using SkiaSharp; +using Ursa.Controls; +using Avalonia.Platform.Storage; +using System.Linq; +using System.IO; +using ImageMagick; +using static System.Runtime.InteropServices.JavaScript.JSType; + +namespace 常用工具集.ViewModel._03图片相关 +{ + public class 图片转ICOViewModel : ViewModelBase + { + public string ImagePath { get; set; } + + public Bitmap ImageSource { get; set; } + + public DelegateCommand SelectImageCmd { get; set; } + + public DelegateCommand ExportCmd { get; set; } + + public 图片转ICOViewModel() + { + SelectImageCmd = new DelegateCommand(SelectImageCmdFunc); + ExportCmd = new DelegateCommand(ExportCmdFunc); + } + + private async void SelectImageCmdFunc(object obj) + { + + var sp = GlobalValues.StorageProvider; + if (sp is null) return; + FilePickerOpenOptions options = new FilePickerOpenOptions(); + options.Title = "选择图片"; + options.AllowMultiple = false; + options.FileTypeFilter = [FilePickerFileTypes.ImageAll]; + var result = await sp.OpenFilePickerAsync(options); + if (result == null || result.Count == 0) return; + ImagePath = result.FirstOrDefault()?.Path.LocalPath; + ImageSource = new Bitmap(ImagePath); + } + + private async void ExportCmdFunc(object obj) + { + int size = Convert.ToInt32(obj); + SKBitmap sbitmap = SKBitmap.Decode(ImagePath); + SKBitmap bitmap = ResizeImage(sbitmap, size, size); + //Icon icon = ConvertToIcon(bitmap); + //IconDir icon = new IconDir(); + //icon.AddImage(bitmap, bitmap.Width, bitmap.Height); + + + var sp = GlobalValues.StorageProvider; + if (sp is null) return; + var result = await sp.OpenFolderPickerAsync(new FolderPickerOpenOptions() + { + Title = "请选择要生成的目录", + AllowMultiple = false, + }); + if (result == null || result.Count == 0) + { + await MessageBox.ShowAsync("文件夹路径不能为空"); + return; + } + string filePath = result.FirstOrDefault()?.Path.LocalPath; + if (!filePath.EndsWith("/")) + { + filePath += "/"; + } + string fileName = "favicon_" + size + "_" + DateTime.Now.ToString("yyyyMMddHHmmss"); + byte[] bytes = null; + using (MemoryStream stream = new MemoryStream()) + { + SKData data = bitmap.Encode(SKEncodedImageFormat.Png, 100); + data.SaveTo(stream); + bytes = stream.ToArray(); + } + using (MemoryStream stream = new MemoryStream(bytes)) + { + using (MagickImage image = new MagickImage(stream)) + { + image.Format = MagickFormat.Ico; + image.Write(filePath + fileName + ".ico"); + } + } + await MessageBox.ShowAsync("生成成功"); + //icon.Dispose(); + bitmap.Dispose(); + sbitmap.Dispose(); + } + + + /// + /// 图片缩放 + /// + /// + /// + /// + /// + public static SKBitmap ResizeImage(SKBitmap bmp, int newW, int newH) + { + try + { + SKBitmap b = new SKBitmap(newW, newH, SKColorType.Argb4444, SKAlphaType.Opaque); + SKCanvas g = new SKCanvas(b); + g.DrawBitmap(bmp, new SKRect(0, 0, newW, newH)); + g.Dispose(); + return b; + } + catch + { + return null; + } + } + } +} diff --git a/常用工具集/ViewModels/03图片相关/色卡包ViewModel.cs b/常用工具集/ViewModels/03图片相关/色卡包ViewModel.cs new file mode 100644 index 0000000..0cf1807 --- /dev/null +++ b/常用工具集/ViewModels/03图片相关/色卡包ViewModel.cs @@ -0,0 +1,25 @@ +using System; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._03图片相关 +{ + public class 色卡包ViewModel : ViewModelBase + { + public Bitmap ImageSource { get; set; } = null; + public DelegateCommand ButtonCmd { get; set; } + public 色卡包ViewModel() + { + ButtonCmd = new DelegateCommand(ButtonCmdFunc); + } + + private void ButtonCmdFunc(object obj) + { + string fileName = obj.ToString(); + // 获取Pack URI + string packUri = $"avares://常用工具集/Assets/ColorBag/{fileName}.gif"; + ImageSource = new Bitmap(AssetLoader.Open(new Uri(packUri))); + } + } +} diff --git a/常用工具集/ViewModels/04破解及系统相关/AdobeAcrobatXI破解ViewModel.cs b/常用工具集/ViewModels/04破解及系统相关/AdobeAcrobatXI破解ViewModel.cs new file mode 100644 index 0000000..9de1bc5 --- /dev/null +++ b/常用工具集/ViewModels/04破解及系统相关/AdobeAcrobatXI破解ViewModel.cs @@ -0,0 +1,108 @@ +using Avalonia; +using Avalonia.Markup.Xaml.MarkupExtensions; +using Avalonia.Platform; +using MES.Utility.Core; +using Microsoft.Win32; +using System; +using System.IO; +using Ursa.Controls; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._04破解及系统相关 +{ + public class AdobeAcrobatXI破解ViewModel : ViewModelBase + { + public string FilePath { get; set; } = ""; + public DelegateCommand FindPathCmd { get; set; } + public DelegateCommand CrackCmd { get; set; } + + public AdobeAcrobatXI破解ViewModel() + { + FindPathCmd = new DelegateCommand(FindPathCmdFunc); + CrackCmd = new DelegateCommand(CrackCmdFunc); + } + + + + private void FindPathCmdFunc(object obj) + { + RegistryKey registryKey = Registry.LocalMachine.OpenSubKey("Software"); + registryKey = registryKey.OpenSubKey("Adobe"); + if (registryKey == null) + { + MessageBox.ShowAsync("当前计算机未安装Adobe Acrobat XI"); + return; + } + registryKey = registryKey.OpenSubKey("Adobe Acrobat"); + if (registryKey == null) + { + MessageBox.ShowAsync("当前计算机未安装Adobe Acrobat XI"); + return; + } + registryKey = registryKey.OpenSubKey("11.0"); + if (registryKey == null) + { + MessageBox.ShowAsync("当前计算机未安装Adobe Acrobat XI"); + return; + } + registryKey = registryKey.OpenSubKey("InstallPath"); + if (registryKey == null) + { + MessageBox.ShowAsync("获得路径失败,请手动输入路径"); + return; + } + string path = registryKey.GetValue("").ToString(); + FilePath = path; + } + + + private void CrackCmdFunc(object obj) + { + try + { + string path = FilePath; + if (path.IsNullOrEmpty()) + { + MessageBox.ShowAsync("请查找或者输入Adobe Acrobat XI安装目录"); + return; + } + if (!Directory.Exists(path)) + { + MessageBox.ShowAsync("文件夹不存在,破解失败"); + return; + } + path = path.Replace("\\", "/"); + if (!path.EndsWith("/")) + path += "/"; + path += "amtlib.dll"; + + byte[] bytes = null; + // 获取Pack URI + string packUri = "avares://常用工具集/Assets/AcrobatXI/amtlib.dll"; + // 使用Pack URI打开文件并读取内容 + if (!File.Exists(path)) + { + MessageBox.ShowAsync("amtlib.dll文件不存在,请手动输入路径"); + return; + } + using (Stream stream = AssetLoader.Open(new Uri(packUri))) + { + bytes = new byte[stream.Length]; + stream.Read(bytes, 0, bytes.Length); + } + using (FileStream fs = new FileStream(path, FileMode.Create)) + { + using (BinaryWriter bw = new BinaryWriter(fs)) + { + bw.Write(bytes); + } + } + MessageBox.ShowAsync("破解完成"); + } + catch (Exception ex) + { + MessageBox.ShowAsync("破解失败:" + ex.Message); + } + } + } +} diff --git a/常用工具集/ViewModels/04破解及系统相关/MySQL定时备份ViewModel.cs b/常用工具集/ViewModels/04破解及系统相关/MySQL定时备份ViewModel.cs new file mode 100644 index 0000000..3e90447 --- /dev/null +++ b/常用工具集/ViewModels/04破解及系统相关/MySQL定时备份ViewModel.cs @@ -0,0 +1,92 @@ +using Microsoft.Win32.TaskScheduler; +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Ursa.Controls; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._04破解及系统相关 +{ + public class MySQL定时备份ViewModel : ViewModelBase + { + public string FilePath { get; set; } = ""; + public string UserName { get; set; } = "root"; + public string Password { get; set; } = "root"; + public string DatabaseName { get; set; } = "test"; + public TimeSpan SelectedTime { get; set; } = TimeSpan.Zero; + + public string BackPath { get; set; } = "d:/test.sql"; + + public DelegateCommand FindPathCmd { get; set; } + public DelegateCommand CreateBackPlanCmd { get; set; } + public MySQL定时备份ViewModel() + { + FindPathCmd = new DelegateCommand(FindPathCmdFunc); + CreateBackPlanCmd = new DelegateCommand(CreateBackPlanCmdFunc); + } + + private void FindPathCmdFunc(object obj) + { + try + { + Process process = Process.GetProcesses().Where(it => it.ProcessName.ToLower().Contains("mysql")).FirstOrDefault(); + if (process == null) + { + MessageBox.ShowAsync("找不到MySQL安装路径"); + return; + } + string path = process.MainModule.FileName; + path = Directory.GetParent(path).FullName; + FilePath = path; + } + catch + { + MessageBox.ShowAsync("找不到MySQL安装路径"); + } + } + + private void CreateBackPlanCmdFunc(object obj) + { + string binPath = FilePath.Replace("\\", "/"); + if (string.IsNullOrEmpty(binPath)) + { + MessageBox.ShowAsync("请输入mysqldump所在目录"); + return; + } + if (!binPath.EndsWith("/")) + { + binPath = binPath + "/"; + } + if (!Directory.Exists(binPath)) + { + MessageBox.ShowAsync("mysqldump所在目录不存在"); + return; + } + string userName = UserName; + string password = Password; + string dbName = DatabaseName; + string backupFile = BackPath; + + string cmd = $"mysqldump -u {userName} -p{password} {dbName} > {backupFile}"; + string batPath = $"{dbName}_back.bat"; + File.WriteAllText(binPath + batPath, cmd); + + using (TaskService ts = new TaskService()) + { + // 创建新的任务定义并指定任务的名称 + TaskDefinition td = ts.NewTask(); + td.RegistrationInfo.Description = "MySQL定时备份"; + // 创建触发器,设置为每天的特定时间 + DailyTrigger dailyTrigger = new DailyTrigger(); + dailyTrigger.StartBoundary = DateTime.Today.Add(SelectedTime); //指定时间 + td.Triggers.Add(dailyTrigger); + // 创建操作 - 运行一个程序 + td.Actions.Add(new ExecAction(batPath, "", binPath)); + // 注册任务到根文件夹下 + ts.RootFolder.RegisterTaskDefinition("MySQLBackup", td); + } + GlobalValues.Success("已创建备份计划"); + } + } +} diff --git a/常用工具集/ViewModels/04破解及系统相关/串口转键盘输入ViewModel.cs b/常用工具集/ViewModels/04破解及系统相关/串口转键盘输入ViewModel.cs new file mode 100644 index 0000000..7cce978 --- /dev/null +++ b/常用工具集/ViewModels/04破解及系统相关/串口转键盘输入ViewModel.cs @@ -0,0 +1,176 @@ +using System.Collections.Generic; +using System.IO.Ports; +using System.Linq; +using System.Runtime.InteropServices; +using Ursa.Controls; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._04破解及系统相关 +{ + public class 串口转键盘输入ViewModel : ViewModelBase + { + private SerialHelper helper; + public bool Enabled { get; set; } = true; + public string ButtonText { get; set; } = "打开"; + public DelegateCommand ButtonCmd { get; set; } + + //串口号下拉列表数据 + public List SerialList { get; set; } + public int SerialIndex { get; set; } = 0; + + /// + /// 波特率 + /// + public List BaudRateList { get; set; } + public int BaudRateIndex { get; set; } = 0; + //校验 + public List ParityList { get; set; } + public int ParityIndex { get; set; } = 0; + + //数据为 + public List DataBitList { get; set; } + public int DataBitIndex { get; set; } = 0; + //停止位 + public List StopBitList { get; set; } + public int StopBitIndex { get; set; } = 0; + + public int Timeout { get; set; } = 1000; + public 串口转键盘输入ViewModel() + { + //串口号 + string[] portNames = SerialPort.GetPortNames(); + if (portNames != null && portNames.Length > 0) + { + SerialList = portNames.ToList(); + } + //波特率 + BaudRateList = new List() { 110, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 56000, 57600, 115200, 128000 }; + BaudRateIndex = 6; + //校验 + ParityList = new List { "None", "Odd", "Even", "Mark", "Space" }; + ParityIndex = 0; + //数据位 + DataBitList = new List { 5, 6, 7, 8 }; + DataBitIndex = 3; + //停止位 + StopBitList = new List { "None", "One", "Two", "OnePointFive" }; + StopBitIndex = 1; + + ButtonCmd = new DelegateCommand(ButtonCmdFunc); + } + + private void ButtonCmdFunc(object obj) + { + if (ButtonText == "打开") + { + try + { + helper = new SerialHelper( + SerialList[SerialIndex], + BaudRateList[BaudRateIndex], + DataBitList[DataBitIndex], + (StopBits)StopBitIndex, + (Parity)ParityIndex, + Timeout + ); + helper.Open(); + } + catch + { + MessageBox.ShowAsync("串口打开失败"); + return; + } + Enabled = false; + ButtonText = "关闭"; + GlobalValues.MainWindow.WindowState = Avalonia.Controls.WindowState.Minimized; + } + else + { + helper.Close(); + helper = null; + Enabled = true; + ButtonText = "打开"; + } + } + + + + public class SerialHelper + { + [DllImport("user32")] + static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo); + const uint KEYEVENTF_EXTENDEDKEY = 0x1; + const uint KEYEVENTF_KEYUP = 0x2; + + + + + private SerialPort serialPort; + public SerialHelper(string portName, int baudRate, int dataBits, StopBits stopBits, Parity parity, int timeout) + { + serialPort = new SerialPort(); + serialPort.PortName = portName; + serialPort.BaudRate = baudRate; + serialPort.DataBits = dataBits; + serialPort.StopBits = stopBits; + serialPort.Parity = parity; + serialPort.ReadTimeout = timeout; + serialPort.DataReceived += SerialPort1_DataReceived; + } + + + /// + /// 打开串口 + /// + public void Open() + { + if (serialPort.IsOpen) + return; + serialPort.Open(); + } + + + /// + /// 关闭串口 + /// + public void Close() + { + if (!serialPort.IsOpen) + return; + serialPort.Close(); + } + + private void SerialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e) + { + int n = serialPort.BytesToRead; + byte[] buf = new byte[n]; + serialPort.Read(buf, 0, n); + foreach (byte b in buf) + { + if (b >= '0' && b <= '9') + { + keybd_event(b, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0); + keybd_event(b, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); + } + else if (b >= 'a' && b <= 'z') + { + keybd_event((byte)(b - 32), 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0); + keybd_event((byte)(b - 32), 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); + } + else if (b >= 'A' && b <= 'Z') + { + keybd_event(16, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0);//按下Shift键 + keybd_event(b, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0); + keybd_event(b, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); + keybd_event(16, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);//松开Shift键 + } + else if (b == 0x0D || b == 0x0A) + { + keybd_event(0x0D, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0); + keybd_event(0x0D, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); + } + } + } + } + } +} \ No newline at end of file diff --git a/常用工具集/ViewModels/04破解及系统相关/删除WPS图标ViewModel.cs b/常用工具集/ViewModels/04破解及系统相关/删除WPS图标ViewModel.cs new file mode 100644 index 0000000..b5021c3 --- /dev/null +++ b/常用工具集/ViewModels/04破解及系统相关/删除WPS图标ViewModel.cs @@ -0,0 +1,78 @@ +using Base.Utility; +using Microsoft.Win32; +using System.Collections.Generic; +using System.Linq; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._04破解及系统相关 +{ + public class 删除WPS图标ViewModel : ViewModelBase + { + private string name1 = "{7AE6DE87-C956-4B40-9C89-3D166C9841D3}"; + private string name2 = "{5FCD4425-CA3A-48F4-A57C-B8A75C32ACB1}"; + private string name3 = "{19ADA707-057F-45EF-8985-305FEE233FAB}"; + public DelegateCommand ClearCmd { get; set; } + public 删除WPS图标ViewModel() + { + ClearCmd = new DelegateCommand(ClearCmdFunc); + } + + private void ClearCmdFunc(object obj) + { + Delete(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace\", name2); + Delete(@"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\", name1); + Delete(@"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\", name2); + Delete(@"SOFTWARE\Classes\CLSID", name1); + Delete(@"SOFTWARE\Classes\CLSID", name2); + Delete(@"SOFTWARE\Classes\SOFTWARE\Classes\CLSID", name1); + Delete(@"SOFTWARE\Classes\SOFTWARE\Classes\CLSID", name2); + Delete(@"SOFTWARE\Classes\SOFTWARE\Classes\Wow6432Node\CLSID", name3); + Delete(@"SOFTWARE\Classes\SOFTWARE\Classes\Wow6432Node\CLSID", name2); + Delete(@"SOFTWARE\Classes\SOFTWARE\Classes\Wow6432Node\CLSID", name1); + Delete(@"SOFTWARE\Classes\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace", name1); + Delete(@"SOFTWARE\Classes\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace", name2); + Delete(@"SOFTWARE\Classes\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace", name1); + Delete(@"SOFTWARE\Classes\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace", name2); + DeleteKey(@"SOFTWARE\Classes\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved", name1); + DeleteKey(@"SOFTWARE\Classes\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved", name2); + DeleteKey(@"SOFTWARE\Classes\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved", name1); + DeleteKey(@"SOFTWARE\Classes\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved", name2); + DeleteKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved", name1); + DeleteKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved", name2); + DeleteKey(@"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved", name1); + DeleteKey(@"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved", name2); + Delete(@"SOFTWARE\Classes\WOW6432Node\CLSID", name3); + Delete(@"SOFTWARE\Classes\WOW6432Node\CLSID", name2); + Delete(@"SOFTWARE\Classes\WOW6432Node\CLSID", name1); + Delete(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace", name1); + //重新打开桌面 + DesktopRefurbish.DeskRef(); + } + + private void DeleteKey(string path, string name1) + { + try + { + RegistryKey user = Registry.CurrentUser; + RegistryKey key = user.OpenSubKey(path, true); + key.DeleteValue(name1); + } + catch { } + } + + private void Delete(string path, string name1) + { + try + { + RegistryKey user = Registry.CurrentUser; + RegistryKey key = user.OpenSubKey(path, true); + List names = key.GetSubKeyNames().ToList(); + if (names.Contains(name1)) + { + key.DeleteSubKey(name1); + } + } + catch { } + } + } +} diff --git a/常用工具集/ViewModels/04破解及系统相关/图标缓存清理ViewModel.cs b/常用工具集/ViewModels/04破解及系统相关/图标缓存清理ViewModel.cs new file mode 100644 index 0000000..06c59e2 --- /dev/null +++ b/常用工具集/ViewModels/04破解及系统相关/图标缓存清理ViewModel.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._04破解及系统相关 +{ + public class 图标缓存清理ViewModel : ViewModelBase + { + public DelegateCommand ClearCmd { get; set; } + + public 图标缓存清理ViewModel() + { + ClearCmd = new DelegateCommand(ClearCmdFunc); + } + + public void ClearCmdFunc(object obj) + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine("rem 关闭Windows外壳程序explorer"); + sb.AppendLine("taskkill /f /im explorer.exe"); + sb.AppendLine("rem 清理系统图标缓存数据库"); + sb.AppendLine("attrib -h -s -r \"%userprofile%\\AppData\\Local\\IconCache.db\""); + sb.AppendLine("del /f \"%userprofile%\\AppData\\Local\\IconCache.db\""); + sb.AppendLine("attrib /s /d -h -s -r \"%userprofile%\\AppData\\Local\\Microsoft\\Windows\\Explorer\\*\""); + sb.AppendLine("del /f \"%userprofile%\\AppData\\Local\\Microsoft\\Windows\\Explorer\\thumbcache_32.db\""); + sb.AppendLine("del /f \"%userprofile%\\AppData\\Local\\Microsoft\\Windows\\Explorer\\thumbcache_96.db\""); + sb.AppendLine("del /f \"%userprofile%\\AppData\\Local\\Microsoft\\Windows\\Explorer\\thumbcache_102.db\""); + sb.AppendLine("del /f \"%userprofile%\\AppData\\Local\\Microsoft\\Windows\\Explorer\\thumbcache_256.db\""); + sb.AppendLine("del /f \"%userprofile%\\AppData\\Local\\Microsoft\\Windows\\Explorer\\thumbcache_1024.db\""); + sb.AppendLine("del /f \"%userprofile%\\AppData\\Local\\Microsoft\\Windows\\Explorer\\thumbcache_idx.db\""); + sb.AppendLine("del /f \"%userprofile%\\AppData\\Local\\Microsoft\\Windows\\Explorer\\thumbcache_sr.db\""); + sb.AppendLine("rem 清理 系统托盘记忆的图标"); + sb.AppendLine("echo y|reg delete \"HKEY_CLASSES_ROOT\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\TrayNotify\" /v IconStreams"); + sb.AppendLine("echo y|reg delete \"HKEY_CLASSES_ROOT\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\TrayNotify\" /v PastIconsStream"); + sb.AppendLine("rem 重启Windows外壳程序explorer"); + sb.AppendLine("start explorer"); + string tempPath = Path.GetTempFileName(); + tempPath += ".bat"; + try + { + File.WriteAllText(tempPath, sb.ToString(), Encoding.GetEncoding("GB2312")); + } + catch + { + File.WriteAllText(tempPath, sb.ToString(), Encoding.UTF8); + } + ProcessStartInfo startInfo = new ProcessStartInfo + { + FileName = tempPath, + UseShellExecute = true, //不使用操作系统外壳程序启动 + CreateNoWindow = true + }; + using (Process process = Process.Start(startInfo)) + { + process.WaitForExit(); // 等待批处理执行完成 + File.Delete(tempPath); + } + } + } +} diff --git a/常用工具集/ViewModels/04破解及系统相关/快速打开网络和共享中心ViewModel.cs b/常用工具集/ViewModels/04破解及系统相关/快速打开网络和共享中心ViewModel.cs new file mode 100644 index 0000000..09a3217 --- /dev/null +++ b/常用工具集/ViewModels/04破解及系统相关/快速打开网络和共享中心ViewModel.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._04破解及系统相关 +{ + public class 快速打开网络和共享中心ViewModel : ViewModelBase + { + public DelegateCommand ButtonCmd { get; set; } + + public 快速打开网络和共享中心ViewModel() + { + ButtonCmd = new DelegateCommand(ButtonCmdFunc); + } + + private void ButtonCmdFunc(object obj) + { + var p = new Process(); + p.StartInfo = new ProcessStartInfo(@"ncpa.cpl") + { + UseShellExecute = true + }; + p.Start(); + } + } +} diff --git a/常用工具集/ViewModels/04破解及系统相关/服务器性能监控ViewModel.cs b/常用工具集/ViewModels/04破解及系统相关/服务器性能监控ViewModel.cs new file mode 100644 index 0000000..7fbd2c3 --- /dev/null +++ b/常用工具集/ViewModels/04破解及系统相关/服务器性能监控ViewModel.cs @@ -0,0 +1,133 @@ +using CZGL.SystemInfo; +using System; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._04破解及系统相关 +{ + public class 服务器性能监控ViewModel : ViewModelBase + { + public string OSArchitecture { get; set; } = ""; + public string OSPlatformID { get; set; } = ""; + public string OSVersion { get; set; } = ""; + public string OSDescription { get; set; } = ""; + public string ProcessArchitecture { get; set; } = ""; + public string ProcessorCount { get; set; } = ""; + public string MachineName { get; set; } = ""; + public string FrameworkVersion { get; set; } = ""; + public string FrameworkDescription { get; set; } = ""; + + public string CPURate { get; set; } = ""; + public string MemoryRate { get; set; } = ""; + public string UploadSpeed { get; set; } = ""; + public string DownloadSpeed { get; set; } = ""; + + public ObservableCollection DataList { get; set; } + + public 服务器性能监控ViewModel() + { + DataList = new ObservableCollection(); + DiskInfo[] diskInfo = DiskInfo.GetDisks(); + foreach (DiskInfo info in diskInfo) + { + MyDisk myDisk = new MyDisk(); + myDisk.Name = info.Name; + myDisk.SetValue(info.TotalSize, info.UsedSize, info.FreeSpace); + DataList.Add(myDisk); + } + OSArchitecture = SystemPlatformInfo.OSArchitecture; + OSPlatformID = SystemPlatformInfo.OSPlatformID; + OSVersion = SystemPlatformInfo.OSVersion; + OSDescription = SystemPlatformInfo.OSDescription; + ProcessArchitecture = SystemPlatformInfo.ProcessArchitecture; + ProcessorCount = SystemPlatformInfo.ProcessorCount.ToString(); + MachineName = SystemPlatformInfo.MachineName; + FrameworkVersion = SystemPlatformInfo.FrameworkVersion; + FrameworkDescription = SystemPlatformInfo.FrameworkDescription; + new Thread(() => + { + CPUTime v1 = CPUHelper.GetCPUTime(); + var network = NetworkInfo.TryGetRealNetworkInfo(); + var oldRate = network.GetIpv4Speed(); + while (true) + { + try + { + Thread.Sleep(1000); + var v2 = CPUHelper.GetCPUTime(); + var value = CPUHelper.CalculateCPULoad(v1, v2); + v1 = v2; + var memory = MemoryHelper.GetMemoryValue(); + var newRate = network.GetIpv4Speed(); + var speed = NetworkInfo.GetSpeed(oldRate, newRate); + oldRate = newRate; + CPURate = $"{(int)(value * 100)} %"; + MemoryRate = $"{memory.UsedPercentage}%"; + UploadSpeed = $"{speed.Sent.Size} {speed.Sent.SizeType}/S"; + DownloadSpeed = $"{speed.Received.Size} {speed.Received.SizeType}/S"; + DiskInfo[] diskInfos = DiskInfo.GetDisks(); + foreach (DiskInfo info1 in diskInfos) + { + MyDisk disk = DataList.Where(it => it.Name == info1.Name).FirstOrDefault(); + if (disk != null) + { + disk.SetValue(info1.TotalSize, info1.UsedSize, info1.FreeSpace); + } + } + + } + catch + { + Thread.Sleep(10); + } + } + }).Start(); + } + + } + + public class MyDisk : ViewModelBase + { + public void SetValue(long total, long used, long free) + { + Total = GetLength(total); + Used = GetLength(used); + Free = GetLength(free); + } + public string Name { get; set; } + public string Total { get; set; } + public string Used { get; set; } + public string Free { get; set; } + + + private string GetLength(long bytes) + { + double tb = Math.Pow(1024.0, 4); + if (bytes > tb) + { + return $"{((int)((bytes / tb) * 100)) / 100.0f}TB"; + } + double gb = Math.Pow(1024.0, 3); + if (bytes > gb) + { + return $"{((int)((bytes / gb) * 100)) / 100.0f}GB"; + } + double mb = Math.Pow(1024.0, 2); + if (bytes > mb) + { + return $"{((int)((bytes / mb) * 100)) / 100.0f}MB"; + } + else if (bytes > 1024) + { + return $"{((int)((bytes / 1024.0f) * 100)) / 100.0f}KB"; + } + else + { + return $"{bytes}Byte"; + } + } + } + +} diff --git a/常用工具集/ViewModels/04破解及系统相关/猫猫回收站ViewModel.cs b/常用工具集/ViewModels/04破解及系统相关/猫猫回收站ViewModel.cs new file mode 100644 index 0000000..41791a8 --- /dev/null +++ b/常用工具集/ViewModels/04破解及系统相关/猫猫回收站ViewModel.cs @@ -0,0 +1,105 @@ +using Avalonia.Platform; +using Base.Utility; +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._04破解及系统相关 +{ + public class 猫猫回收站ViewModel : ViewModelBase + { + public DelegateCommand Button1Cmd { get; set; } + public DelegateCommand Button2Cmd { get; set; } + public 猫猫回收站ViewModel() + { + Button1Cmd = new DelegateCommand(Button1CmdFunc); + Button2Cmd = new DelegateCommand(Button2CmdFunc); + } + + + /// + /// 编程猫猫回收站 + /// + /// + private void Button1CmdFunc(object obj) + { + RegistryKey user = Registry.CurrentUser; + RegistryKey CLSID = user.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\CLSID\\"); + RegistryKey icon = CLSID.OpenSubKey("{645FF040-5081-101B-9F08-00AA002F954E}", true); + RegistryKey defaultIcon = icon.CreateSubKey("DefaultIcon"); + //把dll复制到c:/System目录下 + byte[] fullDllBytes = null; + // 获取Pack URI + string packUri = "avares://常用工具集/Assets/Cat/full.dll"; + // 使用Pack URI打开文件并读取内容 + using (Stream stream = AssetLoader.Open(new Uri(packUri))) + { + fullDllBytes = new byte[stream.Length]; + stream.Read(fullDllBytes, 0, fullDllBytes.Length); + } + + + byte[] emptyDllBytes = null; + // 获取Pack URI + string packUri2 = "avares://常用工具集/Assets/Cat/empty.dll"; + // 使用Pack URI打开文件并读取内容 + using (Stream stream = AssetLoader.Open(new Uri(packUri2))) + { + emptyDllBytes = new byte[stream.Length]; + stream.Read(emptyDllBytes, 0, emptyDllBytes.Length); + } + + + string path = Environment.GetEnvironmentVariable("USERPROFILE"); + string appData = path + "\\AppData"; + DirectoryInfo directoryInfo = new DirectoryInfo(appData); + if (!directoryInfo.Exists) + directoryInfo.Create(); + string myDir = appData + "\\catIco"; + DirectoryInfo directoryInfo2 = new DirectoryInfo(myDir); + if (!directoryInfo2.Exists) + directoryInfo2.Create(); + string fullDllPath = myDir + "\\full.dll"; + string emptyDllPath = myDir + "\\empty.dll"; + if (File.Exists(fullDllPath)) + File.Delete(fullDllPath); + if (File.Exists(emptyDllPath)) + File.Delete(emptyDllPath); + using (FileStream stream = new FileStream(fullDllPath, FileMode.Create)) + stream.Write(fullDllBytes, 0, fullDllBytes.Length); + using (FileStream stream = new FileStream(emptyDllPath, FileMode.Create)) + stream.Write(emptyDllBytes, 0, emptyDllBytes.Length); + defaultIcon.SetValue("", @"%USERPROFILE%\AppData\catIco\empty.dll,0", RegistryValueKind.ExpandString); + defaultIcon.SetValue("Full", @"%USERPROFILE%\AppData\catIco\full.dll,0", RegistryValueKind.ExpandString); + defaultIcon.SetValue("Empty", @"%USERPROFILE%\AppData\catIco\empty.dll,0", RegistryValueKind.ExpandString); + //重新打开桌面 + DesktopRefurbish.DeskRef(); + } + + + /// + /// 还原 + /// + /// + private void Button2CmdFunc(object obj) + { + RegistryKey user = Registry.CurrentUser; + RegistryKey CLSID = user.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\CLSID\\"); + RegistryKey icon = CLSID.OpenSubKey("{645FF040-5081-101B-9F08-00AA002F954E}", true); + RegistryKey defaultIcon = icon.CreateSubKey("DefaultIcon"); + defaultIcon.SetValue("", @"%SystemRoot%\System32\imageres.dll,-55", RegistryValueKind.ExpandString); + defaultIcon.SetValue("Full", @"%SystemRoot%\System32\imageres.dll,-54", RegistryValueKind.ExpandString); + defaultIcon.SetValue("Empty", @"%SystemRoot%\System32\imageres.dll,-55", RegistryValueKind.ExpandString); + //重新打开桌面 + DesktopRefurbish.DeskRef(); + } + + } + +} diff --git a/常用工具集/ViewModels/04破解及系统相关/远程路径软链接ViewModel.cs b/常用工具集/ViewModels/04破解及系统相关/远程路径软链接ViewModel.cs new file mode 100644 index 0000000..66f142b --- /dev/null +++ b/常用工具集/ViewModels/04破解及系统相关/远程路径软链接ViewModel.cs @@ -0,0 +1,83 @@ +using System; +using System.Diagnostics; +using System.IO; +using Ursa.Controls; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._04破解及系统相关 +{ + public class 远程路径软链接ViewModel : ViewModelBase + { + public string RemotePath { get; set; } = "\\\\10.150.0.250\\运维软件"; + public string LocalPath { get; set; } = "d:\\运维软件"; + public DelegateCommand MakeLinkCmd { get; set; } + + public 远程路径软链接ViewModel() + { + MakeLinkCmd = new DelegateCommand(MakeLinkCmdFunc); + } + + private void MakeLinkCmdFunc(object obj) + { + if (string.IsNullOrEmpty(LocalPath) || string.IsNullOrEmpty(RemotePath)) + { + MessageBox.ShowAsync("请输入路径"); + return; + } + string remotePath = RemotePath; + string localPath = LocalPath.Replace("/", "\\"); + bool flag = CreateSymbolicLink(remotePath, localPath); + if (flag) + { + GlobalValues.Success("创建成功"); + } + else + { + GlobalValues.Error("创建失败"); + } + } + + private bool CreateSymbolicLink(string remotePath, string localPath) + { + try + { + Process process = new Process(); + ProcessStartInfo startInfo = new ProcessStartInfo + { + FileName = "cmd.exe", + CreateNoWindow = true, + UseShellExecute = false, + RedirectStandardError = true, + RedirectStandardInput = true, + RedirectStandardOutput = true + }; + process.StartInfo = startInfo; + process.Start(); + process.StandardInput.WriteLine($"mklink /d {localPath} {remotePath}"); + process.StandardInput.AutoFlush = true; + process.StandardInput.WriteLine("exit"); + StreamReader reader = process.StandardOutput; + string ret = ""; + while (true) + { + string curLine = reader.ReadLine(); + ret = ret + curLine + "\r\n"; + Console.WriteLine(curLine); + if (reader.EndOfStream) + { + break; + } + } + reader.Close(); + process.Close(); + if (ret.Contains("<<===>>")) + return true; + return false; + } + catch + { + return false; + } + } + } +} diff --git a/常用工具集/ViewModels/04破解及系统相关/键盘钩子ViewModel.cs b/常用工具集/ViewModels/04破解及系统相关/键盘钩子ViewModel.cs new file mode 100644 index 0000000..ce9b0b4 --- /dev/null +++ b/常用工具集/ViewModels/04破解及系统相关/键盘钩子ViewModel.cs @@ -0,0 +1,893 @@ +using Avalonia.Controls; +using MES.Utility.EventBus; +using System; +using System.Runtime.InteropServices; +using System.Windows; +using Weiz.EventBus.Core; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._04破解及系统相关 +{ + public class 键盘钩子ViewModel : ViewModelBase, IEventHandler + { + public string ButtonText { get; set; } = "开启键盘钩子"; + public string Message { get; set; } = ""; + public DelegateCommand StartStopCmd { get; set; } + public DelegateCommand CleanCmd { get; set; } + public DelegateCommand PageLoaedCommand { get; set; } + + + public 键盘钩子ViewModel() + { + EventBus.Instance.Subscribe(this); + PageLoaedCommand = new DelegateCommand(PageLoaded); + StartStopCmd = new DelegateCommand(StartStopCmdFunc); + CleanCmd = new DelegateCommand(CleanCmdFunc); + } + + private void PageLoaded(object obj) + { + + } + + private void StartStopCmdFunc(object obj) + { + if (ButtonText == "开启键盘钩子") + { + KeyboardHook.Start(); + ButtonText = "停止键盘钩子"; + GlobalValues.MainWindow.WindowState = WindowState.Minimized; + } + else + { + KeyboardHook.Stop(); + ButtonText = "开启键盘钩子"; + } + } + + private void CleanCmdFunc(object obj) + { + Message = ""; + } + + public void Handle(KeyBordHookEvent evt) + { + Message += evt.Message; + } + } + + + public class KeyboardHook + { + private const int WH_KEYBOARD_LL = 13; + private static LowLevelKeyboardProc _proc = HookCallback; + private static IntPtr _hookID = IntPtr.Zero; + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool UnhookWindowsHookEx(IntPtr hhk); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern IntPtr GetModuleHandle(string lpModuleName); + + private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam); + + public static void Start() + { + _hookID = SetWindowsHookEx(WH_KEYBOARD_LL, _proc, GetModuleHandle(null), 0); + if (_hookID == IntPtr.Zero) + { + throw new Exception("Could not set keyboard hook"); + } + } + + public static void Stop() + { + bool ret = UnhookWindowsHookEx(_hookID); + if (!ret) + { + throw new Exception("Could not unset keyboard hook"); + } + _hookID = IntPtr.Zero; + } + + private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) + { + if (nCode >= 0 && wParam == (IntPtr)0x100) + { + int vkCode = Marshal.ReadInt32(lParam); + Console.WriteLine((Keys)vkCode); + EventBus.Instance.Publish(new KeyBordHookEvent(((Keys)vkCode).ToString())); + } + return CallNextHookEx(_hookID, nCode, wParam, lParam); + } + } + + + public enum Keys + { + // + // 摘要: + // The bitmask to extract modifiers from a key value. + Modifiers = -65536, + // + // 摘要: + // No key pressed. + None = 0, + // + // 摘要: + // The left mouse button. + LButton = 1, + // + // 摘要: + // The right mouse button. + RButton = 2, + // + // 摘要: + // The CANCEL key. + Cancel = 3, + // + // 摘要: + // The middle mouse button (three-button mouse). + MButton = 4, + // + // 摘要: + // The first x mouse button (five-button mouse). + XButton1 = 5, + // + // 摘要: + // The second x mouse button (five-button mouse). + XButton2 = 6, + // + // 摘要: + // The BACKSPACE key. + Back = 8, + // + // 摘要: + // The TAB key. + Tab = 9, + // + // 摘要: + // The LINEFEED key. + LineFeed = 10, + // + // 摘要: + // The CLEAR key. + Clear = 12, + // + // 摘要: + // The RETURN key. + Return = 13, + // + // 摘要: + // The ENTER key. + Enter = 13, + // + // 摘要: + // The SHIFT key. + ShiftKey = 16, + // + // 摘要: + // The CTRL key. + ControlKey = 17, + // + // 摘要: + // The ALT key. + Menu = 18, + // + // 摘要: + // The PAUSE key. + Pause = 19, + // + // 摘要: + // The CAPS LOCK key. + Capital = 20, + // + // 摘要: + // The CAPS LOCK key. + CapsLock = 20, + // + // 摘要: + // The IME Kana mode key. + KanaMode = 21, + // + // 摘要: + // The IME Hanguel mode key. (maintained for compatibility; use HangulMode) + HanguelMode = 21, + // + // 摘要: + // The IME Hangul mode key. + HangulMode = 21, + // + // 摘要: + // The IME Junja mode key. + JunjaMode = 23, + // + // 摘要: + // The IME final mode key. + FinalMode = 24, + // + // 摘要: + // The IME Hanja mode key. + HanjaMode = 25, + // + // 摘要: + // The IME Kanji mode key. + KanjiMode = 25, + // + // 摘要: + // The ESC key. + Escape = 27, + // + // 摘要: + // The IME convert key. + IMEConvert = 28, + // + // 摘要: + // The IME nonconvert key. + IMENonconvert = 29, + // + // 摘要: + // The IME accept key, replaces System.Windows.Forms.Keys.IMEAceept. + IMEAccept = 30, + // + // 摘要: + // The IME accept key. Obsolete, use System.Windows.Forms.Keys.IMEAccept instead. + IMEAceept = 30, + // + // 摘要: + // The IME mode change key. + IMEModeChange = 31, + // + // 摘要: + // The SPACEBAR key. + Space = 32, + // + // 摘要: + // The PAGE UP key. + Prior = 33, + // + // 摘要: + // The PAGE UP key. + PageUp = 33, + // + // 摘要: + // The PAGE DOWN key. + Next = 34, + // + // 摘要: + // The PAGE DOWN key. + PageDown = 34, + // + // 摘要: + // The END key. + End = 35, + // + // 摘要: + // The HOME key. + Home = 36, + // + // 摘要: + // The LEFT ARROW key. + Left = 37, + // + // 摘要: + // The UP ARROW key. + Up = 38, + // + // 摘要: + // The RIGHT ARROW key. + Right = 39, + // + // 摘要: + // The DOWN ARROW key. + Down = 40, + // + // 摘要: + // The SELECT key. + Select = 41, + // + // 摘要: + // The PRINT key. + Print = 42, + // + // 摘要: + // The EXECUTE key. + Execute = 43, + // + // 摘要: + // The PRINT SCREEN key. + Snapshot = 44, + // + // 摘要: + // The PRINT SCREEN key. + PrintScreen = 44, + // + // 摘要: + // The INS key. + Insert = 45, + // + // 摘要: + // The DEL key. + Delete = 46, + // + // 摘要: + // The HELP key. + Help = 47, + // + // 摘要: + // The 0 key. + D0 = 48, + // + // 摘要: + // The 1 key. + D1 = 49, + // + // 摘要: + // The 2 key. + D2 = 50, + // + // 摘要: + // The 3 key. + D3 = 51, + // + // 摘要: + // The 4 key. + D4 = 52, + // + // 摘要: + // The 5 key. + D5 = 53, + // + // 摘要: + // The 6 key. + D6 = 54, + // + // 摘要: + // The 7 key. + D7 = 55, + // + // 摘要: + // The 8 key. + D8 = 56, + // + // 摘要: + // The 9 key. + D9 = 57, + // + // 摘要: + // The A key. + A = 65, + // + // 摘要: + // The B key. + B = 66, + // + // 摘要: + // The C key. + C = 67, + // + // 摘要: + // The D key. + D = 68, + // + // 摘要: + // The E key. + E = 69, + // + // 摘要: + // The F key. + F = 70, + // + // 摘要: + // The G key. + G = 71, + // + // 摘要: + // The H key. + H = 72, + // + // 摘要: + // The I key. + I = 73, + // + // 摘要: + // The J key. + J = 74, + // + // 摘要: + // The K key. + K = 75, + // + // 摘要: + // The L key. + L = 76, + // + // 摘要: + // The M key. + M = 77, + // + // 摘要: + // The N key. + N = 78, + // + // 摘要: + // The O key. + O = 79, + // + // 摘要: + // The P key. + P = 80, + // + // 摘要: + // The Q key. + Q = 81, + // + // 摘要: + // The R key. + R = 82, + // + // 摘要: + // The S key. + S = 83, + // + // 摘要: + // The T key. + T = 84, + // + // 摘要: + // The U key. + U = 85, + // + // 摘要: + // The V key. + V = 86, + // + // 摘要: + // The W key. + W = 87, + // + // 摘要: + // The X key. + X = 88, + // + // 摘要: + // The Y key. + Y = 89, + // + // 摘要: + // The Z key. + Z = 90, + // + // 摘要: + // The left Windows logo key (Microsoft Natural Keyboard). + LWin = 91, + // + // 摘要: + // The right Windows logo key (Microsoft Natural Keyboard). + RWin = 92, + // + // 摘要: + // The application key (Microsoft Natural Keyboard). + Apps = 93, + // + // 摘要: + // The computer sleep key. + Sleep = 95, + // + // 摘要: + // The 0 key on the numeric keypad. + NumPad0 = 96, + // + // 摘要: + // The 1 key on the numeric keypad. + NumPad1 = 97, + // + // 摘要: + // The 2 key on the numeric keypad. + NumPad2 = 98, + // + // 摘要: + // The 3 key on the numeric keypad. + NumPad3 = 99, + // + // 摘要: + // The 4 key on the numeric keypad. + NumPad4 = 100, + // + // 摘要: + // The 5 key on the numeric keypad. + NumPad5 = 101, + // + // 摘要: + // The 6 key on the numeric keypad. + NumPad6 = 102, + // + // 摘要: + // The 7 key on the numeric keypad. + NumPad7 = 103, + // + // 摘要: + // The 8 key on the numeric keypad. + NumPad8 = 104, + // + // 摘要: + // The 9 key on the numeric keypad. + NumPad9 = 105, + // + // 摘要: + // The multiply key. + Multiply = 106, + // + // 摘要: + // The add key. + Add = 107, + // + // 摘要: + // The separator key. + Separator = 108, + // + // 摘要: + // The subtract key. + Subtract = 109, + // + // 摘要: + // The decimal key. + Decimal = 110, + // + // 摘要: + // The divide key. + Divide = 111, + // + // 摘要: + // The F1 key. + F1 = 112, + // + // 摘要: + // The F2 key. + F2 = 113, + // + // 摘要: + // The F3 key. + F3 = 114, + // + // 摘要: + // The F4 key. + F4 = 115, + // + // 摘要: + // The F5 key. + F5 = 116, + // + // 摘要: + // The F6 key. + F6 = 117, + // + // 摘要: + // The F7 key. + F7 = 118, + // + // 摘要: + // The F8 key. + F8 = 119, + // + // 摘要: + // The F9 key. + F9 = 120, + // + // 摘要: + // The F10 key. + F10 = 121, + // + // 摘要: + // The F11 key. + F11 = 122, + // + // 摘要: + // The F12 key. + F12 = 123, + // + // 摘要: + // The F13 key. + F13 = 124, + // + // 摘要: + // The F14 key. + F14 = 125, + // + // 摘要: + // The F15 key. + F15 = 126, + // + // 摘要: + // The F16 key. + F16 = 127, + // + // 摘要: + // The F17 key. + F17 = 128, + // + // 摘要: + // The F18 key. + F18 = 129, + // + // 摘要: + // The F19 key. + F19 = 130, + // + // 摘要: + // The F20 key. + F20 = 131, + // + // 摘要: + // The F21 key. + F21 = 132, + // + // 摘要: + // The F22 key. + F22 = 133, + // + // 摘要: + // The F23 key. + F23 = 134, + // + // 摘要: + // The F24 key. + F24 = 135, + // + // 摘要: + // The NUM LOCK key. + NumLock = 144, + // + // 摘要: + // The SCROLL LOCK key. + Scroll = 145, + // + // 摘要: + // The left SHIFT key. + LShiftKey = 160, + // + // 摘要: + // The right SHIFT key. + RShiftKey = 161, + // + // 摘要: + // The left CTRL key. + LControlKey = 162, + // + // 摘要: + // The right CTRL key. + RControlKey = 163, + // + // 摘要: + // The left ALT key. + LMenu = 164, + // + // 摘要: + // The right ALT key. + RMenu = 165, + // + // 摘要: + // The browser back key. + BrowserBack = 166, + // + // 摘要: + // The browser forward key. + BrowserForward = 167, + // + // 摘要: + // The browser refresh key. + BrowserRefresh = 168, + // + // 摘要: + // The browser stop key. + BrowserStop = 169, + // + // 摘要: + // The browser search key. + BrowserSearch = 170, + // + // 摘要: + // The browser favorites key. + BrowserFavorites = 171, + // + // 摘要: + // The browser home key. + BrowserHome = 172, + // + // 摘要: + // The volume mute key. + VolumeMute = 173, + // + // 摘要: + // The volume down key. + VolumeDown = 174, + // + // 摘要: + // The volume up key. + VolumeUp = 175, + // + // 摘要: + // The media next track key. + MediaNextTrack = 176, + // + // 摘要: + // The media previous track key. + MediaPreviousTrack = 177, + // + // 摘要: + // The media Stop key. + MediaStop = 178, + // + // 摘要: + // The media play pause key. + MediaPlayPause = 179, + // + // 摘要: + // The launch mail key. + LaunchMail = 180, + // + // 摘要: + // The select media key. + SelectMedia = 181, + // + // 摘要: + // The start application one key. + LaunchApplication1 = 182, + // + // 摘要: + // The start application two key. + LaunchApplication2 = 183, + // + // 摘要: + // The OEM Semicolon key on a US standard keyboard. + OemSemicolon = 186, + // + // 摘要: + // The OEM 1 key. + Oem1 = 186, + // + // 摘要: + // The OEM plus key on any country/region keyboard. + Oemplus = 187, + // + // 摘要: + // The OEM comma key on any country/region keyboard. + Oemcomma = 188, + // + // 摘要: + // The OEM minus key on any country/region keyboard. + OemMinus = 189, + // + // 摘要: + // The OEM period key on any country/region keyboard. + OemPeriod = 190, + // + // 摘要: + // The OEM question mark key on a US standard keyboard. + OemQuestion = 191, + // + // 摘要: + // The OEM 2 key. + Oem2 = 191, + // + // 摘要: + // The OEM tilde key on a US standard keyboard. + Oemtilde = 192, + // + // 摘要: + // The OEM 3 key. + Oem3 = 192, + // + // 摘要: + // The OEM open bracket key on a US standard keyboard. + OemOpenBrackets = 219, + // + // 摘要: + // The OEM 4 key. + Oem4 = 219, + // + // 摘要: + // The OEM pipe key on a US standard keyboard. + OemPipe = 220, + // + // 摘要: + // The OEM 5 key. + Oem5 = 220, + // + // 摘要: + // The OEM close bracket key on a US standard keyboard. + OemCloseBrackets = 221, + // + // 摘要: + // The OEM 6 key. + Oem6 = 221, + // + // 摘要: + // The OEM singled/double quote key on a US standard keyboard. + OemQuotes = 222, + // + // 摘要: + // The OEM 7 key. + Oem7 = 222, + // + // 摘要: + // The OEM 8 key. + Oem8 = 223, + // + // 摘要: + // The OEM angle bracket or backslash key on the RT 102 key keyboard. + OemBackslash = 226, + // + // 摘要: + // The OEM 102 key. + Oem102 = 226, + // + // 摘要: + // The PROCESS KEY key. + ProcessKey = 229, + // + // 摘要: + // Used to pass Unicode characters as if they were keystrokes. The Packet key value + // is the low word of a 32-bit virtual-key value used for non-keyboard input methods. + Packet = 231, + // + // 摘要: + // The ATTN key. + Attn = 246, + // + // 摘要: + // The CRSEL key. + Crsel = 247, + // + // 摘要: + // The EXSEL key. + Exsel = 248, + // + // 摘要: + // The ERASE EOF key. + EraseEof = 249, + // + // 摘要: + // The PLAY key. + Play = 250, + // + // 摘要: + // The ZOOM key. + Zoom = 251, + // + // 摘要: + // A constant reserved for future use. + NoName = 252, + // + // 摘要: + // The PA1 key. + Pa1 = 253, + // + // 摘要: + // The CLEAR key. + OemClear = 254, + // + // 摘要: + // The bitmask to extract a key code from a key value. + KeyCode = 65535, + // + // 摘要: + // The SHIFT modifier key. + Shift = 65536, + // + // 摘要: + // The CTRL modifier key. + Control = 131072, + // + // 摘要: + // The ALT modifier key. + Alt = 262144 + } +} diff --git a/常用工具集/ViewModels/05其他/MD5DESViewModel.cs b/常用工具集/ViewModels/05其他/MD5DESViewModel.cs new file mode 100644 index 0000000..c8b7a6b --- /dev/null +++ b/常用工具集/ViewModels/05其他/MD5DESViewModel.cs @@ -0,0 +1,84 @@ +using CCS.Utility.Security; +using MES.Utility.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Ursa.Controls; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._05其他 +{ + public class MD5DESViewModel : ViewModelBase + { + public string DESKey { get; set; } = ""; + public string DESSource { get; set; } = ""; + public string DESDest { get; set; } = ""; + public string MD5Source { get; set; } = ""; + public string MD5Dest { get; set; } = ""; + public DelegateCommand CalcMD5Cmd { get; set; } + public DelegateCommand DESEncryptCmd { get; set; } + public DelegateCommand DESDecryptCmd { get; set; } + + public MD5DESViewModel() + { + CalcMD5Cmd = new DelegateCommand(CalcMD5CmdFunc); + + DESEncryptCmd = new DelegateCommand(DESEncryptCmdFunc); + DESDecryptCmd = new DelegateCommand(DESDecryptCmdFunc); + } + + /// + /// 解密 + /// + /// + private void DESDecryptCmdFunc(object obj) + { + try + { + if (DESKey.IsNullOrEmpty()) + { + DESDest = DESHelper.DESDecrypt(DESSource); + } + else + { + DESDest = DESHelper.DESDecrypt(DESSource, DESKey); + } + } + catch (Exception ex) + { + DESDest = ex.Message; + } + } + + private void DESEncryptCmdFunc(object obj) + { + try + { + if (DESKey.IsNullOrEmpty()) + { + DESDest = DESHelper.DESEncrypt(DESSource); + } + else + { + DESDest = DESHelper.DESEncrypt(DESSource, DESKey); + } + } + catch (Exception ex) + { + DESDest = ex.Message; + } + } + + private void CalcMD5CmdFunc(object obj) + { + if (MD5Source.IsNullOrEmpty()) + { + MessageBox.ShowAsync("请输入内容"); + return; + } + MD5Dest = MD5Source.MD5Encrypt(); + } + } +} diff --git a/常用工具集/ViewModels/05其他/SQLStringBuilder封装ViewModel.cs b/常用工具集/ViewModels/05其他/SQLStringBuilder封装ViewModel.cs new file mode 100644 index 0000000..f10d949 --- /dev/null +++ b/常用工具集/ViewModels/05其他/SQLStringBuilder封装ViewModel.cs @@ -0,0 +1,606 @@ +using MES.Utility.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using 常用工具集.Base; +using NHibernate.AdoNet.Util; +using System.Xml; +using System.IO; + +namespace 常用工具集.ViewModel._05其他 +{ + public class SQLStringBuilder封装ViewModel : ViewModelBase + { + public string Button1Text { get; set; } = "生成"; + public string TextBox1 { get; set; } = ""; + public string TextBox2 { get; set; } = ""; + public string TextBox3 { get; set; } = "strSql"; + public string Text3Title { get; set; } = "StringBuilder参数:"; + + public bool TextBox3Visiable { get; set; } = true; + public List FuncList { get; set; } + + public int funcIndex = 0; + public int FuncIndex + { + get + { + return funcIndex; + } + set + { + funcIndex = value; + SelectChanged(); + NotifyPropertyChanged(); + } + } + + + + public bool RadioButton1 { get; set; } = true; + public bool RadioButton2 { get; set; } = false; + public bool RadioButton3 { get; set; } = false; + public string RadioButton1Text { get; set; } = "Java"; + public string RadioButton2Text { get; set; } = "C#"; + public string RadioButton3Text { get; set; } = "JS"; + public bool RadioButton1Visiable { get; set; } = true; + public bool RadioButton2Visiable { get; set; } = true; + public bool RadioButton3Visiable { get; set; } = true; + + + + public bool CheckBox1 { get; set; } = false; + public bool CheckBox3 { get; set; } = true; + public bool CheckBox2 { get; set; } = true; + public bool CheckBox1Visiable { get; set; } = true; + public bool CheckBox3Visiable { get; set; } = true; + public bool CheckBox2Visiable { get; set; } = true; + + public DelegateCommand GenerateCmd { get; set; } + public DelegateCommand ClearCmd { get; set; } + + public SQLStringBuilder封装ViewModel() + { + FuncList = new List() { + "SQL转StringBuilder", + "XML格式化", + "SQL格式化", + "StringBuilder转SQL", + "大小写转换", + "JSON格式化" + }; + FuncIndex = 0; + GenerateCmd = new DelegateCommand(GenerateCmdFunc); + ClearCmd = new DelegateCommand(ClearCmdFunc); + } + + private void GenerateCmdFunc(object obj) + { + //SQL转StringBuilder + if (FuncIndex == 0) + { + if (CheckBox1) + { + string text2 = FormatStyle.Basic.Formatter.Format(TextBox1); + TextBox1 = text2; + } + List first = GetFirstContext(); + List second = new List(); + if (first.Count == 0) + { + TextBox2 = string.Empty; + return; + } + int maxLength = GetMaxLength(GetNewList(first)); + + if (RadioButton3) + { + second.Add("var " + GetParmName() + " = \"\";"); + } + else + { + second.Add("StringBuilder " + GetParmName() + " = new StringBuilder();"); + } + //循环处理每一行 + foreach (string str in first) + { + if (!CheckBox3) + { + if (str.Trim().StartsWith("--")) + { + second.Add(str.Trim().Replace("--", "//")); + continue; + } + if (str.Contains("--")) + { + //分割 + int index = str.IndexOf("--"); + string str1 = str.Substring(0, index).TrimEnd(); + string str2 = str.Substring(index, str.Length - index).Trim(); + if (!CheckBox2) + { + second.Add(GetString2(str1, 0, CheckBox3) + " " + str2.Replace("--", "//")); + } + else + { + string retStr = GetString(str1, maxLength - System.Text.Encoding.Default.GetByteCount(str1), CheckBox3) + " " + str2.Replace("--", "//"); + second.Add(retStr); + } + + continue; + } + } + if (!CheckBox2) + { + second.Add(GetString2(str, 0, CheckBox3)); + } + else + { + second.Add(GetString(str, maxLength - System.Text.Encoding.Default.GetByteCount(str), CheckBox3)); + } + } + + TextBox2 = second.GetStrArray("\r\n"); + } + //XML格式化 + else if (FuncIndex == 1) + { + if (TextBox1.IsNullOrEmpty()) + { + TextBox2 = string.Empty; + return; + } + TextBox2 = FormatXml(TextBox1); + } + //SQL格式化 + else if (FuncIndex == 2) + { + if (TextBox1.IsNullOrEmpty()) + { + TextBox2 = string.Empty; + return; + } + TextBox2 = FormatStyle.Basic.Formatter.Format(TextBox1); + } + //StringBuilder转SQL + else if (FuncIndex == 3) + { + List first = GetFirstContext(); + List second = new List(); + if (first.Count == 0) + { + return; + } + //判断是否每一行是否以StringBuilder开头 + string parmName = GetStringBuilderParmName(first[0]); + if (parmName.IsNullOrEmpty()) + { + return; + } + for (int i = 1; i < first.Count; i++) + { + string str = first[i]; + string handlerStr = HandlerString(str, parmName); + if (!String.Empty.Equals(handlerStr)) + { + second.Add(handlerStr); + } + } + if (second.Count != 0) + { + int spaceNum = second[0].IndexOf(second[0].Trim()); + for (int i = 0; i < second.Count; i++) + { + second[i] = QuSpace(second[i], spaceNum); + } + } + TextBox2 = second.GetStrArray("\r\n"); + } + //大小写转换 + else if (FuncIndex == 4) + { + if (TextBox1.IsNullOrEmpty()) + { + TextBox2 = string.Empty; + return; + } + List first = GetFirstContext(); + List second = new List(); + if (RadioButton1) + { + foreach (string str in first) + { + second.Add(str.ToUpper()); + } + } + else if (RadioButton2) + { + foreach (string str in first) + { + second.Add(str.ToLower()); + } + } + TextBox2 = second.GetStrArray("\r\n"); + + } + //JSON格式化 + else if (FuncIndex == 5) + { + if (TextBox1.IsNullOrEmpty()) + { + TextBox2 = string.Empty; + return; + } + TextBox2 = TextBox1.FormatJson(); + } + } + + private void ClearCmdFunc(object obj) + { + TextBox1 = string.Empty; + TextBox2 = string.Empty; + } + + private void SelectChanged() + { + //SQL转StringBuilder + if (FuncIndex == 0) + { + RadioButton1Visiable = true; + RadioButton2Visiable = true; + RadioButton3Visiable = true; + RadioButton1Text = "Java"; + RadioButton2Text = "C#"; + RadioButton3Text = "JS"; + CheckBox1Visiable = true; + CheckBox2Visiable = true; + CheckBox3Visiable = true; + TextBox3Visiable = true; + Button1Text = "生成"; + Text3Title = "StringBuilder参数"; + TextBox3 = "strSql"; + } + //StringBuilder转SQL + else if (FuncIndex == 3) + { + RadioButton1Visiable = false; + RadioButton2Visiable = false; + RadioButton3Visiable = false; + TextBox3Visiable = false; + CheckBox1Visiable =false; + CheckBox2Visiable =false; + CheckBox3Visiable = false; + Button1Text = "生成"; + } + //XML格式化 + else if (FuncIndex == 1) + { + RadioButton1Visiable = false; + RadioButton2Visiable = false; + RadioButton3Visiable = false; + TextBox3Visiable = false; + CheckBox1Visiable = false; + CheckBox2Visiable = false; + CheckBox3Visiable = false; + Button1Text = "格式化"; + } + //JSON格式化 + else if (FuncIndex == 5) + { + RadioButton1Visiable = false; + RadioButton2Visiable = false; + RadioButton3Visiable = false; + TextBox3Visiable = false; + CheckBox1Visiable = false; + CheckBox2Visiable = false; + CheckBox3Visiable = false; + Button1Text = "格式化"; + } + //SQL格式化 + else if (FuncIndex == 2) + { + RadioButton1Visiable = false; + RadioButton2Visiable = false; + RadioButton3Visiable = false; + TextBox3Visiable = false; + CheckBox1Visiable =false; + CheckBox2Visiable =false; + CheckBox3Visiable = false; + Button1Text = "格式化"; + } + //大小写转换 + else if (FuncIndex == 4) + { + RadioButton1Visiable = true; + RadioButton2Visiable = true; + RadioButton3Visiable = false; + RadioButton1Text = "转大写"; + RadioButton2Text = "转小写"; + RadioButton1 = true; + TextBox3Visiable = false; + CheckBox1Visiable = false; + CheckBox2Visiable = false; + CheckBox3Visiable = false; + Button1Text = "转换"; + } + } + + private string FormatXml(string sUnformattedXml) + { + XmlDocument xd = new XmlDocument(); + xd.LoadXml(sUnformattedXml); + StringBuilder sb = new StringBuilder(); + StringWriter sw = new StringWriter(sb); + XmlTextWriter xtw = null; + try + { + xtw = new XmlTextWriter(sw); + xtw.Formatting = Formatting.Indented; + xtw.Indentation = 1; + xtw.IndentChar = '\t'; + xd.WriteTo(xtw); + } + finally + { + if (xtw != null) + xtw.Close(); + } + return sb.ToString(); + } + + + public string GetParmName() + { + string parm = TextBox3.Trim(); + if (parm.IsNullOrEmpty()) + { + return "strSql"; + } + return parm; + } + + #region 给定一个集合得到去注释后的集合 + public List GetNewList(List list) + { + List list2 = new List(); + foreach (string str in list) + { + //if (str.Trim().StartsWith("--")) + //{ + // continue; + //} + //if (str.Contains("--")) + //{ + // int index = str.IndexOf("--"); + // list2.Add(str.Substring(0, index)); + // continue; + //} + list2.Add(str.TrimEnd()); + } + return list2; + } + #endregion + + #region 给定一个List得到其中最长的长度 + public int GetMaxLength(List list) + { + int maxLength = 0; + foreach (string str in list) + { + if (System.Text.Encoding.Default.GetByteCount(str) > maxLength) + { + maxLength = System.Text.Encoding.Default.GetByteCount(str); + } + } + return maxLength; + } + #endregion + + + #region 获得每一行值 + public string GetString(string strInfo, int spaceNum, bool appendLine) + { + string str = string.Empty; + if (RadioButton1) + { + if (appendLine) + { + str += GetParmName() + ".append(\" " + strInfo + GetSpace(spaceNum) + " \").append(\"\\n\");"; + } + else + { + str += GetParmName() + ".append(\" " + strInfo + GetSpace(spaceNum) + " \");"; + } + } + if (RadioButton2) + { + if (appendLine) + { + str += GetParmName() + ".Append(\" " + strInfo + GetSpace(spaceNum) + " \").AppendLine();"; + } + else + { + str += GetParmName() + ".Append(\" " + strInfo + GetSpace(spaceNum) + " \");"; + } + } + if (RadioButton3) + { + if (appendLine) + { + str += GetParmName() + " += \" " + strInfo + GetSpace(spaceNum) + " \\n\";"; + } + else + { + str += GetParmName() + " += \" " + strInfo + GetSpace(spaceNum) + " \";"; + } + } + return str; + } + + public string GetString2(string strInfo, int spaceNum, bool appendLine) + { + string str = string.Empty; + if (RadioButton1) + { + if (appendLine) + { + str += GetParmName() + ".append(\" " + strInfo + GetSpace(spaceNum) + "\").append(\"\\n\");"; + } + else + { + str += GetParmName() + ".append(\" " + strInfo + GetSpace(spaceNum) + "\");"; + } + } + if (RadioButton2) + { + if (appendLine) + { + str += GetParmName() + ".Append(\" " + strInfo + GetSpace(spaceNum) + "\").AppendLine();"; + } + else + { + str += GetParmName() + ".Append(\" " + strInfo + GetSpace(spaceNum) + "\");"; + } + } + if (RadioButton3) + { + if (appendLine) + { + str += GetParmName() + " += \" " + strInfo + GetSpace(spaceNum) + "\\n\";"; + } + else + { + str += GetParmName() + " += \" " + strInfo + GetSpace(spaceNum) + "\";"; + } + } + return str; + } + #endregion + + #region 补空格 + public string GetSpace(int num) + { + string str = string.Empty; + for (int i = 0; i < num; i++) + { + str += " "; + } + return str; + } + #endregion + + + #region 得到上面文本的每一行值 + public List GetFirstContext() + { + List list = new List(); + string content = TextBox1; + if (string.Empty.Equals(content.Trim())) + { + return list; + } + if (content.Contains("\t")) + { + content = content.Replace("\t", " "); + } + content = content.Replace("\r", ""); + string[] str = content.Split('\n'); + foreach (string strings in str) + { + if (!string.Empty.Equals(strings.Trim())) + { + list.Add(strings.TrimEnd()); + } + } + return list; + } + #endregion + + + + private string QuSpace(string str, int spaceNum) + { + string space = string.Empty; + for (int i = 0; i < spaceNum; i++) + { + space += " "; + } + if (!str.Contains(space)) + { + return str; + } + return str.Substring(str.IndexOf(space) + spaceNum, str.Length - str.IndexOf(space) - spaceNum); + } + + private string HandlerString(string str, string parmName) + { + if (str.Contains(parmName + ".Append")) + { + //得到StringBuilder中间的词 + string sqlStr = GetStringBuilderText(str, parmName + ".Append"); + //得到注释 + string zhushi = GetZhuShi(str); + //拼接 + if (string.Empty.Equals(zhushi)) + { + return sqlStr.TrimEnd(); + } + else + { + return sqlStr.TrimEnd() + " --" + zhushi; + } + } + if (str.Contains(parmName + ".append")) + { + //得到StringBuilder中间的词 + string sqlStr = GetStringBuilderText(str, parmName + ".append"); + //得到注释 + string zhushi = GetZhuShi(str); + //拼接 + if (string.Empty.Equals(zhushi)) + { + return sqlStr.TrimEnd(); + } + else + { + return sqlStr.TrimEnd() + " --" + zhushi; + } + } + return string.Empty; + } + + private string GetStringBuilderText(string str, string startStr) + { + startStr = startStr + "(\""; + str = str.Substring(str.IndexOf(startStr), str.IndexOf("\")") - str.IndexOf(startStr)); + str = str.Substring(startStr.Length, str.Length - startStr.Length); + return str; + } + + private string GetZhuShi(string str) + { + string retStr = string.Empty; + if (str.Contains(@"//")) + { + retStr = str.Substring(str.IndexOf("//") + 2, str.Length - str.IndexOf("//") - 2); + } + return retStr; + } + + private string GetStringBuilderParmName(string str) + { + string parmName = String.Empty; + if (!str.Contains("StringBuilder")) + { + return parmName; + } + str = str.Substring(str.IndexOf("StringBuilder"), str.IndexOf("=") - str.IndexOf("StringBuilder")); + str = str.Substring(str.IndexOf(" "), str.Length - str.IndexOf(" ")); + parmName = str.Trim(); + return parmName; + } + } +} diff --git a/常用工具集/ViewModels/05其他/人民币转大写ViewModel.cs b/常用工具集/ViewModels/05其他/人民币转大写ViewModel.cs new file mode 100644 index 0000000..b8dcfa9 --- /dev/null +++ b/常用工具集/ViewModels/05其他/人民币转大写ViewModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._05其他 +{ + public class 人民币转大写ViewModel : ViewModelBase + { + } +} diff --git a/常用工具集/ViewModels/05其他/地标写入ViewModel.cs b/常用工具集/ViewModels/05其他/地标写入ViewModel.cs new file mode 100644 index 0000000..d6563e5 --- /dev/null +++ b/常用工具集/ViewModels/05其他/地标写入ViewModel.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.IO.Ports; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Ursa.Controls; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._05其他 +{ + public class 地标写入ViewModel : ViewModelBase + { + public bool Enabled1 { get; set; } = true; + public bool Enabled2 { get; set; } = false; + //串口号下拉列表数据 + public List SerialList { get; set; } + public int SerialIndex { get; set; } = 0; + public string Data { get; set; } = ""; + + public DelegateCommand OpenCmd { get; set; } + public DelegateCommand CloseCmd { get; set; } + public DelegateCommand ReadCmd { get; set; } + public DelegateCommand WriteCmd { get; set; } + public DelegateCommand ClearCmd { get; set; } + + private SerialPort sp1; + public 地标写入ViewModel() + { + this.sp1 = new SerialPort(); + //串口号 + string[] portNames = SerialPort.GetPortNames(); + if (portNames != null && portNames.Length > 0) + { + SerialList = portNames.ToList(); + } + OpenCmd = new DelegateCommand(OpenCmdFunc); + CloseCmd = new DelegateCommand(CloseCmdFunc); + ReadCmd = new DelegateCommand(ReadCmdFunc); + WriteCmd = new DelegateCommand(WriteCmdFunc); + ClearCmd = new DelegateCommand(ClearCmdFunc); + } + + private void OpenCmdFunc(object obj) + { + try + { + this.sp1.PortName = SerialList[SerialIndex]; + this.sp1.BaudRate = 19200; + this.sp1.DataBits = 8; + this.sp1.StopBits = StopBits.One; + this.sp1.Parity = Parity.None; + if (this.sp1.IsOpen) + { + this.sp1.Close(); + } + this.sp1.Open(); + Enabled1 = false; + Enabled2 = true; + } + catch (Exception ex) + { + MessageBox.ShowAsync(ex.Message); + } + } + + private void CloseCmdFunc(object obj) + { + if (this.sp1.IsOpen) + { + this.sp1.Close(); + } + Enabled1 = true; + Enabled2 = false; + } + + private void ReadCmdFunc(object obj) + { + + List list = this.sendData(this.getReadData()); + if ((list != null) && (list.Count >= 15)) + { + int num = 0; + num = (((((((list[9] << 8) | list[10]) << 8) | list[11]) << 8) | list[12]) << 8) | list[13]; + Data = Convert.ToString(num); + this.sendData(this.getBeatData()); + } + } + + private void WriteCmdFunc(object obj) + { + if (this.sp1.IsOpen) + { + string str = Data; + int num = 0; + bool flag = int.TryParse(str, out num); + if (!flag) + { + MessageBox.ShowAsync("请输入数字"); + return; + } + if ((num < 0) || (num >= 999999999)) + { + MessageBox.ShowAsync("数字只能在0~999999999之间"); + return; + } + byte a = (byte)((num & 0xff00000000L) >> 32); + byte b = (byte)((num & 0xff000000L) >> 24); + byte c = (byte)((num & 0xff0000) >> 16); + byte d = (byte)((num & 0xff00) >> 8); + byte num8 = (byte)(num & 0xff); + this.sendData(this.getWriteData(a, b, c, d, num8)); + this.sendData(this.getBeatData()); + } + } + + private void ClearCmdFunc(object obj) + { + Data = ""; + } + + + private List getBeatData() + { + return new List { 0xAA, 0xBB, 0x05, 0x00, 0x00, 0x00, 0x06, 0x01, 0x07 }; + } + + private List getReadData() + { + return new List { 0xAA, 0xBB, 0x05, 0x00, 0x00, 0x00, 0x10, 0x20, 0x30 }; + } + + + private List getWriteData(byte a, byte b, byte c, byte d, byte e) + { + List list = new List(); + list.Add(0xAA); + list.Add(0xBB); + list.Add(0x0A); + list.Add(0x00); + list.Add(0x00); + list.Add(0x00); + list.Add(0x09); + list.Add(0x03); + list.Add(a); + list.Add(b); + list.Add(c); + list.Add(d); + list.Add(e); + byte item = list[6]; + for (int i = 7; i < 13; i++) + { + item ^= list[i]; + } + list.Add(item); + return list; + } + + + public List sendData(List sendData) + { + if (!this.sp1.IsOpen) + { + return null; + } + this.sp1.Write(sendData.ToArray(), 0, sendData.Count); + while (this.sp1.BytesToRead == 0) + { + Thread.Sleep(1); + } + Thread.Sleep(50); + byte[] buffer = new byte[this.sp1.BytesToRead]; + this.sp1.Read(buffer, 0, buffer.Length); + return buffer.ToList(); + } + + private void Sp1_DataReceived(object sender, SerialDataReceivedEventArgs e) + { + if (this.sp1.IsOpen) + { + DateTime now = DateTime.Now; + try + { + byte[] buffer = new byte[this.sp1.BytesToRead]; + this.sp1.Read(buffer, 0, buffer.Length); + } + catch (Exception exception) + { + MessageBox.ShowAsync(exception.Message, "出错提示!!!!!"); + } + } + else + { + MessageBox.ShowAsync("请打开某个串口", "错误提示"); + } + } + } +} diff --git a/常用工具集/ViewModels/05其他/角度弧度转换ViewModel.cs b/常用工具集/ViewModels/05其他/角度弧度转换ViewModel.cs new file mode 100644 index 0000000..3a5f929 --- /dev/null +++ b/常用工具集/ViewModels/05其他/角度弧度转换ViewModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._05其他 +{ + public class 角度弧度转换ViewModel : ViewModelBase + { + } +} diff --git a/常用工具集/ViewModels/05其他/进制转换及ASCII转换ViewModel.cs b/常用工具集/ViewModels/05其他/进制转换及ASCII转换ViewModel.cs new file mode 100644 index 0000000..fcbaf47 --- /dev/null +++ b/常用工具集/ViewModels/05其他/进制转换及ASCII转换ViewModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using 常用工具集.Base; + +namespace 常用工具集.ViewModel._05其他 +{ + public class 进制转换及ASCII转换ViewModel : ViewModelBase + { + } +} diff --git a/常用工具集/ViewModels/ApplicationViewModel.cs b/常用工具集/ViewModels/ApplicationViewModel.cs new file mode 100644 index 0000000..c06e7cb --- /dev/null +++ b/常用工具集/ViewModels/ApplicationViewModel.cs @@ -0,0 +1,25 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using System; +using 常用工具集.Base; + +namespace 常用工具集.ViewModels +{ + public class ApplicationViewModel : ViewModelBase + { + public DelegateCommand ExitCommand { get; set; } + public ApplicationViewModel() + { + ExitCommand = new DelegateCommand(Exit); + } + + private void Exit(object obj) + { + if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + Environment.Exit(0); + //desktop.Shutdown(); + } + } + } +} diff --git a/常用工具集/ViewModels/MainViewViewModel.cs b/常用工具集/ViewModels/MainViewViewModel.cs new file mode 100644 index 0000000..331e956 --- /dev/null +++ b/常用工具集/ViewModels/MainViewViewModel.cs @@ -0,0 +1,365 @@ +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Platform; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Ursa.Controls; +using 常用工具集.Base; + +namespace 常用工具集.ViewModels +{ + public partial class MainViewViewModel : ViewModelBase + { + public Dictionary PageDict { get; set; } = new Dictionary(); + public UserControl ContentPage { get; set; } + + + private MenuItem _selectedMenuItem; + public MenuItem SelectedMenuItem + { + get + { + return _selectedMenuItem; + } + set + { + if (_selectedMenuItem != value) + { + _selectedMenuItem = value; + NotifyPropertyChanged(); + //选的是哪个 + if (!MenuHeaderList.Contains(value.Header)) + { + return; + } + //是否已存在 + if (!PageDict.ContainsKey(value.Header)) + { + //这里用判断 + UserControl userControl = GetPage(value.Header); + PageDict.Add(value.Header, userControl); + ContentPage = PageDict[value.Header]; + return; + } + //已存在,判断当前页面是否为当前菜单项的页面 + if (ContentPage == PageDict[value.Header]) + { + return; + } + ContentPage = PageDict[value.Header]; + } + } + } + public ObservableCollection MenuItems { get; set; } = new ObservableCollection(); + private List MenuHeaderList = new List(); + public MainViewViewModel() + { + //二级菜单 + ObservableCollection plcCommList = new ObservableCollection(); + plcCommList.Add(new MenuItem { Header = "倍福ADS调试", IconPath = StreamGeometry.Parse("M156.7,1H43.3C19.4,1,0,20.4,0,44.3c0,0,0,0,0,0v112.3C0,180.6,19.4,200,43.3,200l0,0h113.3c23.9,0,43.3-19.4,43.3-43.3l0,0 V44.3C200,20.4,180.6,1,156.7,1C156.7,1,156.7,1,156.7,1z M67.4,118.5L63,105.7H42l-4.5,12.8H25.8l19.3-51.6h15.2l19.1,51.6 L67.4,118.5z M126.3,105.1c-5.2,17-27.3,12.9-41,13.3V66.8c10.6,0.7,27.4-2.5,36,4.8C129.3,78.7,129.3,95.4,126.3,105.1z M166.9,115.2c-5.8,5.4-22.1,4.4-30.3,2.1v-9.9c7.3,2.6,30.6,5.4,21.9-8.5c-5.2-3.6-14.4-3.1-18.9-7.8c-12-22.1,11.3-29.1,29.2-23.1 v9.3c-6.6-2.3-27-4.7-19.6,7.4c5.7,3.3,15.1,3.1,19.4,8.3C173.2,98.7,172.4,110.7,166.9,115.2z M52.1,75.7l-6.9,19.6h14.4L53,75.7H52.1z M112.9,78.5c-3.9-4.1-11-3-16.3-3.2v32.4c5.4-0.1,12.5,0.9,16.4-3.3C117.8,98.1,117.8,84.7,112.9,78.5z") }); + plcCommList.Add(new MenuItem { Header = "三菱MC协议", IconPath = StreamGeometry.Parse("M485.496 153.83L336.221 388.613a32 32 0 0 0 0 34.339l149.275 234.78c12.573 19.775 41.435 19.775 54.008 0l149.275-234.78a32 32 0 0 0 0-34.339L539.504 153.831c-12.573-19.775-41.435-19.775-54.008 0z m311.337 454.732H512.5c-25.552 0-40.798 28.474-26.633 49.74L628.035 871.74A32 32 0 0 0 654.667 886H939c25.552 0 40.798-28.473 26.633-49.74L823.465 622.823a32 32 0 0 0-26.632-14.26z m-284.333 0H228.167a32 32 0 0 0-26.632 14.26L59.367 836.26C45.202 857.527 60.447 886 86 886h284.333a32 32 0 0 0 26.632-14.26l142.168-213.438c14.165-21.266-1.08-49.74-26.633-49.74z") }); + plcCommList.Add(new MenuItem { Header = "MC-3E服务模拟", IconPath = StreamGeometry.Parse("M485.496 153.83L336.221 388.613a32 32 0 0 0 0 34.339l149.275 234.78c12.573 19.775 41.435 19.775 54.008 0l149.275-234.78a32 32 0 0 0 0-34.339L539.504 153.831c-12.573-19.775-41.435-19.775-54.008 0z m311.337 454.732H512.5c-25.552 0-40.798 28.474-26.633 49.74L628.035 871.74A32 32 0 0 0 654.667 886H939c25.552 0 40.798-28.473 26.633-49.74L823.465 622.823a32 32 0 0 0-26.632-14.26z m-284.333 0H228.167a32 32 0 0 0-26.632 14.26L59.367 836.26C45.202 857.527 60.447 886 86 886h284.333a32 32 0 0 0 26.632-14.26l142.168-213.438c14.165-21.266-1.08-49.74-26.633-49.74z") }); + plcCommList.Add(new MenuItem { Header = "OPCUA调试", IconPath = StreamGeometry.Parse("M981.952 688.128c13.44 0 24.32 10.88 24.32 24.32v171.328a23.744 23.744 0 0 1-23.68 23.808 23.488 23.488 0 0 0-23.552 23.488v0.256l0.256 24.704a24.384 24.384 0 0 1-24.32 24.704H105.6a24.384 24.384 0 0 1-24.32-24l-0.512-25.216a24.384 24.384 0 0 0-24.384-23.936H55.36a24.384 24.384 0 0 1-24.32-24.384v-170.688c0-13.44 10.88-24.384 24.32-24.384h926.592z m-585.216 48.768H128.64a24.384 24.384 0 0 0-24.32 21.504l-0.128 2.88v48.768c0 12.352 9.28 22.72 21.568 24.192l2.816 0.192H396.8a24.384 24.384 0 0 0 24.192-21.568l0.192-2.816v-48.768c0-13.44-10.88-24.384-24.32-24.384z m219.456 0a24.384 24.384 0 0 0-24.192 21.504l-0.192 2.88v48.768a24.384 24.384 0 0 0 48.64 2.816l0.128-2.816v-48.768c0-13.44-10.88-24.384-24.384-24.384z m97.536 0a24.384 24.384 0 0 0-24.192 21.504l-0.192 2.88v48.768a24.384 24.384 0 0 0 48.64 2.816l0.128-2.816v-48.768c0-13.44-10.88-24.384-24.384-24.384z m97.536 0a24.384 24.384 0 0 0-24.192 21.504l-0.192 2.88v48.768a24.384 24.384 0 0 0 48.64 2.816l0.128-2.816v-48.768c0-13.44-10.88-24.384-24.384-24.384z m97.536 0a24.384 24.384 0 0 0-24.192 21.504l-0.192 2.88v48.768a24.384 24.384 0 0 0 48.64 2.816l0.128-2.816v-48.768c0-13.44-10.88-24.384-24.384-24.384zM981.952 54.144c13.44 0 24.32 10.88 24.32 24.32v488.32a23.744 23.744 0 0 1-23.68 23.808 23.424 23.424 0 0 0-23.424 23.36v1.024a24 24 0 0 1-20.736 24.128l-2.816 0.256H105.408a24.384 24.384 0 0 1-24.384-23.744v-1.28a24.384 24.384 0 0 0-24.448-23.744H55.36a24.384 24.384 0 0 1-24.32-24.384v-487.68c0-13.44 10.88-24.384 24.32-24.384h926.592zM292.672 284.416c-66.88 0-112.96 47.808-112.96 130.048 0 81.92 46.08 132.096 112.96 132.096S405.76 496.384 405.76 414.464s-46.08-130.048-113.024-130.048z m494.976 0c-63.808 0-117.44 48.832-117.44 132.096 0 84.352 51.904 130.048 115.712 130.048 32.832 0 59.776-12.928 80.64-37.184l-26.688-31.36c-13.632 14.656-30.72 24.896-52.224 24.896-39.936 0-65.92-33.152-65.92-88.064 0-54.336 28.736-87.04 66.944-87.04 19.136 0 33.792 8.832 46.464 21.12l26.624-32.064a103.04 103.04 0 0 0-74.112-32.448z m-249.536 4.8H455.168v252.608h50.56V452.032h33.728c54.336 0 97.664-25.92 97.664-83.648 0-59.712-43.008-79.168-99.008-79.168z m-245.44 38.528c37.568 0 61.44 32.448 61.44 86.72 0 53.952-23.872 88.448-61.44 88.448s-61.44-34.496-61.44-88.448c0-54.272 23.872-86.72 61.44-86.72z m241.664 1.408c34.816 0 53.312 9.856 53.312 39.232 0 29.056-16.768 43.712-51.584 43.712h-30.4V329.152h28.672zM396.8 102.912H128.64a24.384 24.384 0 0 0-24.32 21.504l-0.128 2.88v48.768c0 12.352 9.28 22.784 21.568 24.192l2.816 0.192H396.8a24.384 24.384 0 0 0 24.192-21.568l0.192-2.816v-48.768a24.384 24.384 0 0 0-24.32-24.384z m219.456 0a24.384 24.384 0 0 0-24.192 21.504l-0.192 2.88v48.768a24.384 24.384 0 0 0 48.64 2.816l0.128-2.816v-48.768a24.384 24.384 0 0 0-24.384-24.384z m97.536 0a24.384 24.384 0 0 0-24.192 21.504l-0.192 2.88v48.768a24.384 24.384 0 0 0 48.64 2.816l0.128-2.816v-48.768a24.384 24.384 0 0 0-24.384-24.384z m97.536 0a24.384 24.384 0 0 0-24.192 21.504l-0.192 2.88v48.768a24.384 24.384 0 0 0 48.64 2.816l0.128-2.816v-48.768a24.384 24.384 0 0 0-24.384-24.384z m97.536 0a24.384 24.384 0 0 0-24.192 21.504l-0.192 2.88v48.768a24.384 24.384 0 0 0 48.64 2.816l0.128-2.816v-48.768a24.384 24.384 0 0 0-24.384-24.384z") }); + plcCommList.Add(new MenuItem { Header = "Modbus调试1", IconPath = StreamGeometry.Parse("M896 0H128C57.6 0 0 57.6 0 128v768c0 70.4 57.6 128 128 128h768c70.4 0 128-57.6 128-128V128c0-70.4-57.6-128-128-128z m44.8 940.8H83.2V83.2h851.2v857.6zM268.8 396.8c19.2 0 44.8-6.4 57.6-19.2l288 288c-12.8 19.2-19.2 38.4-19.2 57.6 0 57.6 44.8 102.4 102.4 102.4 57.6 0 102.4-44.8 102.4-102.4 0-57.6-44.8-102.4-102.4-102.4-19.2 0-44.8 6.4-57.6 19.2L352 358.4c12.8-19.2 19.2-38.4 19.2-57.6 0-57.6-44.8-102.4-102.4-102.4-57.6 0-102.4 44.8-102.4 102.4 6.4 51.2 51.2 96 102.4 96z m-38.4 396.8h172.8V627.2H230.4v166.4z m563.2-563.2H627.2v172.8h172.8V230.4z") }); + plcCommList.Add(new MenuItem { Header = "Modbus调试2", IconPath = StreamGeometry.Parse("M896 0H128C57.6 0 0 57.6 0 128v768c0 70.4 57.6 128 128 128h768c70.4 0 128-57.6 128-128V128c0-70.4-57.6-128-128-128z m44.8 940.8H83.2V83.2h851.2v857.6z m-672-544c19.2 0 44.8-6.4 57.6-19.2l288 288c-12.8 19.2-19.2 38.4-19.2 57.6 0 57.6 44.8 102.4 102.4 102.4 57.6 0 102.4-44.8 102.4-102.4 0-57.6-44.8-102.4-102.4-102.4-19.2 0-44.8 6.4-57.6 19.2L352 358.4c12.8-19.2 19.2-38.4 19.2-57.6 0-57.6-44.8-102.4-102.4-102.4-57.6 0-102.4 44.8-102.4 102.4 6.4 51.2 51.2 96 102.4 96z m-38.4 396.8h172.8V627.2H230.4v166.4z m563.2-563.2H627.2v172.8H800V230.4z") }); + plcCommList.Add(new MenuItem { Header = "ModbusTCP服务", IconPath = StreamGeometry.Parse("M224 160a64 64 0 0 0-64 64v192a64 64 0 0 0 64 64h576a64 64 0 0 0 64-64V224a64 64 0 0 0-64-64H224z m400 96a16 16 0 0 0-16 16v96a16 16 0 1 0 32 0v-96a16 16 0 0 0-16-16z m80 16a16 16 0 1 1 32 0v96a16 16 0 1 1-32 0v-96zM224 544a64 64 0 0 0-64 64v192a64 64 0 0 0 64 64h576a64 64 0 0 0 64-64V608a64 64 0 0 0-64-64H224z m80 96a16 16 0 0 0-16 16v96a16 16 0 1 0 32 0v-96a16 16 0 0 0-16-16z m80 16a16 16 0 1 1 32 0v96a16 16 0 1 1-32 0v-96z") }); + plcCommList.Add(new MenuItem { Header = "西门子PLC调试", IconPath = StreamGeometry.Parse("M955.733333 0H68.266667C30.72 0 0 30.72 0 68.266667v887.466666c0 37.546667 30.72 68.266667 68.266667 68.266667h887.466666c37.546667 0 68.266667-30.72 68.266667-68.266667V68.266667c0-37.546667-30.72-68.266667-68.266667-68.266667z m-320.853333 764.586667c-44.373333 27.306667-102.4 40.96-170.666667 40.96-58.026667 0-119.466667-6.826667-177.493333-20.48v-112.64c64.853333 20.48 122.88 30.72 174.08 30.72 68.266667 0 102.4-17.066667 102.4-54.613334 0-13.653333-3.413333-23.893333-13.653333-34.133333-10.24-10.24-37.546667-23.893333-78.506667-40.96-75.093333-30.72-126.293333-58.026667-150.186667-78.506667-34.133333-34.133333-47.786667-71.68-47.786666-116.053333 0-58.026667 20.48-98.986667 64.853333-129.706667 44.373333-30.72 98.986667-44.373333 167.253333-44.373333 37.546667 0 92.16 6.826667 163.84 20.48v109.226667c-54.613333-20.48-102.4-30.72-150.186666-30.72-64.853333 0-95.573333 17.066667-95.573334 54.613333 0 13.653333 6.826667 23.893333 20.48 34.133333 10.24 6.826667 40.96 20.48 88.746667 44.373334 71.68 30.72 116.053333 58.026667 139.946667 81.92 27.306667 27.306667 40.96 61.44 40.96 105.813333 3.413333 58.026667-23.893333 105.813333-78.506667 139.946667z") }); + plcCommList.Add(new MenuItem { Header = "欧姆龙Fins调试", IconPath = StreamGeometry.Parse("M421.4784 0h214.2208c232.2432 64.3072 416.1536 287.9488 398.1312 533.7088-9.4208 241.2544-201.9328 446.464-436.224 490.2912H462.848C222.8224 1000.2432 22.9376 800.768 0 560.3328v-133.5296C42.5984 217.088 207.2576 37.6832 421.4784 0m4.5056 231.0144c-199.8848 56.5248-274.432 341.6064-115.0976 482.0992 148.6848 165.4784 448.512 70.0416 487.8336-144.1792 53.6576-208.0768-170.3936-412.0576-372.736-337.92zM3537.7152 0h227.328c231.424 65.1264 410.4192 289.1776 394.8544 533.2992C4152.1152 778.24 3952.2304 987.5456 3712.6144 1024H3579.904c-214.6304-30.3104-405.504-194.1504-443.5968-410.8288C3071.1808 341.1968 3266.9696 54.4768 3537.7152 0m40.5504 224.0512c-211.7632 36.864-305.5616 331.3664-148.6848 480.4608 135.5776 163.0208 421.0688 98.7136 484.5568-99.9424 86.4256-202.3424-126.976-436.224-335.872-380.5184zM4803.7888 0h22.9376c110.1824 61.0304 160.1536 176.5376 211.3536 285.0816v711.0656c-77.4144-17.6128-196.608 24.1664-244.5312-53.6576-14.336-224.4608 43.4176-460.3904-18.0224-679.936-104.8576-58.9824-234.7008-26.624-350.208-34.4064-2.8672 256.4096-1.2288 513.2288-1.6384 769.6384-75.776 0.4096-151.552 0.8192-227.328 0.8192-1.6384-329.3184 0-658.6368-0.8192-987.5456 208.4864 0 417.3824-7.7824 625.4592 9.8304-4.5056-5.3248-13.1072-15.5648-17.2032-20.8896zM1066.5984 15.5648C1325.8752 3.6864 1585.9712 13.1072 1845.6576 9.8304c105.2672-6.5536 216.6784 28.2624 280.9856 115.9168 87.6544 102.4 68.4032 244.1216 70.4512 368.64-4.096 168.7552 7.3728 338.3296-7.3728 507.0848-74.1376 0.4096-148.2752-4.5056-222.0032-2.4576-3.6864-235.52 11.8784-471.8592-7.3728-706.1504-36.4544-83.5584-140.9024-50.7904-212.1728-61.0304 3.2768 253.5424 1.2288 507.0848 5.3248 760.6272-77.824 7.3728-156.0576 8.6016-234.2912 6.5536 2.048-255.1808 5.3248-510.3616 3.2768-765.5424-73.728 0.4096-147.456-0.4096-221.184-0.4096 0 253.5424-4.096 506.6752 2.4576 759.808-78.2336 7.3728-157.2864 9.8304-235.52 0.4096 2.048-326.0416 4.5056-651.6736-1.6384-977.7152z M2265.4976 10.6496c185.1392 2.8672 371.0976-7.7824 556.2368 4.096 172.4416 14.7456 314.9824 184.32 289.9968 357.9904-4.096 119.1936-78.2336 220.3648-175.3088 283.8528 72.9088 116.3264 156.0576 224.8704 231.424 339.5584-99.9424 2.8672-199.8848 2.8672-299.8272 2.048-113.0496-174.4896-242.8928-337.92-349.7984-516.096 97.8944-2.8672 197.4272 10.6496 294.0928-8.6016 97.8944-34.4064 106.9056-189.6448 7.7824-227.328-106.496-27.4432-217.9072-15.9744-326.4512-18.0224 0 256.4096-0.4096 512.8192 0.4096 769.6384-75.776-0.8192-151.9616 2.048-227.328 1.2288-2.4576-329.3184-0.4096-659.0464-1.2288-988.3648z") }); + plcCommList.Add(new MenuItem { Header = "串口调试工具", IconPath = StreamGeometry.Parse("M242.3 467c-17.5 0-31.7-14.3-31.7-31.8s14.2-31.7 31.7-31.7c17.2 0 31.8 14.5 31.8 31.7-0.1 17.3-14.6 31.8-31.8 31.8zM376.7 467c-17.5 0-31.7-14.3-31.7-31.8s14.2-31.7 31.7-31.7c17.2 0 31.8 14.5 31.8 31.7-0.1 17.3-14.6 31.8-31.8 31.8zM511 467c-17.5 0-31.7-14.3-31.7-31.8s14.2-31.7 31.7-31.7c17.2 0 31.8 14.5 31.8 31.7 0 17.3-14.5 31.8-31.8 31.8zM645.4 467c-17.5 0-31.7-14.3-31.7-31.8s14.2-31.7 31.7-31.7c17.2 0 31.8 14.5 31.8 31.7 0 17.3-14.6 31.8-31.8 31.8zM779.8 467c-17.5 0-31.7-14.3-31.7-31.8s14.2-31.7 31.7-31.7c17.2 0 31.8 14.5 31.8 31.7 0 17.3-14.6 31.8-31.8 31.8z M309.5 621.8c-17.5 0-31.7-14.3-31.7-31.8s14.2-31.7 31.7-31.7c17.2 0 31.8 14.5 31.8 31.7-0.1 17.2-14.6 31.8-31.8 31.8zM443.9 621.8c-17.5 0-31.7-14.3-31.7-31.8s14.2-31.7 31.7-31.7c17.2 0 31.8 14.5 31.8 31.7-0.1 17.2-14.6 31.8-31.8 31.8zM578.2 621.8c-17.5 0-31.7-14.3-31.7-31.8s14.2-31.7 31.7-31.7c17.2 0 31.8 14.5 31.8 31.7s-14.6 31.8-31.8 31.8zM712.6 621.8c-17.5 0-31.7-14.3-31.7-31.8s14.2-31.7 31.7-31.7c17.2 0 31.8 14.5 31.8 31.7s-14.6 31.8-31.8 31.8z M789.3 761H220.5C149.2 761 87.2 717.6 73 647.8L25 418.2c-7.8-38.2 1.9-77.4 26.6-107.6 24.7-30.2 61.2-47.5 100.2-47.5h720.5c38.9 0 75.3 17.2 100 47.2 24.7 30 34.5 69.1 27 107.2l-62.5 230.4C923 717.3 861 761 789.3 761zM151.8 294.1c-29.6 0-57.4 13.2-76.1 36.1-18.8 23-26.2 52.8-20.3 81.8l48.1 229.6c11.2 55.4 60.4 88.3 116.9 88.3h568.9c56.8 0 106.1-33.2 117.1-88.9l62.5-230.4c5.5-28-2-57.7-20.7-80.5-18.8-22.8-46.4-35.9-76-35.9H151.8z") }); + plcCommList.Add(new MenuItem { Header = "Socket调试", IconPath = StreamGeometry.Parse("M110.101333 457.130667h63.872c0-28.138667-6.677333-48.64-20.010666-61.482667-13.333333-12.842667-34.922667-19.264-64.768-19.264-28.885333 0-50.410667 6.976-64.597334 20.906667-14.208 13.952-21.290667 34.261333-21.290666 60.949333 0 15.402667 2.624 27.882667 7.893333 37.418667 5.248 9.557333 11.861333 17.258667 19.818667 23.125333 7.957333 5.888 16.576 10.666667 25.877333 14.314667 9.301333 3.669333 17.92 7.274667 25.877333 10.837333 7.936 3.541333 14.549333 7.829333 19.818667 12.842667 5.248 5.013333 7.893333 11.797333 7.893333 20.373333 0 7.082667-1.92 13.077333-5.696 17.984-3.797333 4.906667-9.237333 7.338667-16.341333 7.338667a20.928 20.928 0 0 1-16.149333-6.784c-4.16-4.544-6.229333-12.416-6.229334-23.68v-6.250667H0v9.173333c0 13.717333 1.962667 25.216 5.866667 34.517334 3.925333 9.301333 9.6 16.810667 17.066666 22.570666 7.466667 5.76 16.64 9.792 27.52 12.117334 10.901333 2.325333 23.445333 3.477333 37.632 3.477333 30.08 0 53.098667-6.293333 68.992-18.901333 15.914667-12.586667 23.872-32.853333 23.872-60.736 0-15.914667-2.773333-28.8-8.256-38.72a73.728 73.728 0 0 0-20.736-24.042667 119.658667 119.658667 0 0 0-26.794666-14.869333 782.762667 782.762667 0 0 1-26.794667-11.2 76.138667 76.138667 0 0 1-20.736-13.013334c-5.504-5.034667-8.256-11.818667-8.256-20.373333 0-5.888 1.706667-11.2 5.12-15.978667 3.434667-4.757333 8.810667-7.146667 16.149333-7.146666 6.613333 0 11.52 2.752 14.698667 8.256 3.178667 5.504 4.757333 12.16 4.757333 19.989333v6.250667z m152.32 87.36c0-12.48 0.256-22.762667 0.725334-30.848 0.490667-8.064 1.493333-14.421333 2.944-19.072 1.472-4.650667 3.370667-7.829333 5.696-9.557334 2.325333-1.706667 5.333333-2.56 8.981333-2.56 3.669333 0 6.741333 0.853333 9.173333 2.56 2.453333 1.706667 4.352 4.906667 5.696 9.557334 1.344 4.650667 2.261333 11.008 2.773334 19.072 0.469333 8.085333 0.725333 18.346667 0.725333 30.848 0 12.48-0.256 22.677333-0.746667 30.634666a90.752 90.752 0 0 1-2.752 18.901334c-1.344 4.650667-3.242667 7.829333-5.696 9.536a15.616 15.616 0 0 1-9.173333 2.56 14.784 14.784 0 0 1-8.981333-2.56c-2.346667-1.706667-4.224-4.885333-5.696-9.536a81.792 81.792 0 0 1-2.944-18.901334 517.888 517.888 0 0 1-0.725334-30.634666z m102.762667 0c0-14.442667-0.789333-27.946667-2.389333-40.554667a85.781333 85.781333 0 0 0-10.816-33.216c-5.632-9.557333-14.08-17.066667-25.322667-22.592-11.264-5.504-26.56-8.256-45.866667-8.256-18.602667 0-33.493333 2.986667-44.608 9.002667-11.136 5.973333-19.626667 13.866667-25.514666 23.68-5.866667 9.770667-9.728 20.906667-11.562667 33.386666a262.016 262.016 0 0 0-2.752 38.186667c0 14.421333 0.810667 27.946667 2.389333 40.533333 1.6 12.608 5.205333 23.616 10.837334 33.045334 5.610667 9.408 14.08 16.768 25.322666 22.016 11.242667 5.269333 26.538667 7.893333 45.866667 7.893333 18.602667 0 33.472-2.88 44.586667-8.618667 11.136-5.76 19.648-13.461333 25.514666-23.125333 5.888-9.664 9.728-20.736 11.562667-33.216 1.834667-12.48 2.773333-25.216 2.773333-38.186667z m119.296-21.653334h59.456c0-12.48-1.173333-23.808-3.498667-33.962666a64.106667 64.106667 0 0 0-12.096-26.048 56.874667 56.874667 0 0 0-23.125333-16.896c-9.664-4.032-21.845333-6.058667-36.522667-6.058667-12.48 0-24.106667 1.237333-34.88 3.669333-10.752 2.453333-20.117333 7.466667-28.074666 15.061334-7.936 7.573333-14.250667 18.474667-18.901334 32.661333-4.650667 14.186667-6.976 33.024-6.976 56.533333 0 21.525333 2.090667 38.826667 6.250667 51.925334 4.16 13.077333 9.962667 23.253333 17.429333 30.464 7.466667 7.210667 16.448 11.925333 26.986667 14.122666a170.517333 170.517333 0 0 0 34.858667 3.306667c28.16 0 48.448-6.613333 60.928-19.818667 6.357333-7.104 10.88-16.149333 13.589333-27.157333 2.688-11.008 4.032-24.341333 4.032-40H484.48v12.842667c0 12.48-1.664 21.034667-4.970667 25.685333-3.285333 4.650667-8 6.976-14.122666 6.976a15.68 15.68 0 0 1-9.365334-2.773333c-2.56-1.813333-4.586667-5.056-6.058666-9.706667a87.402667 87.402667 0 0 1-3.114667-18.901333 408.298667 408.298667 0 0 1-0.917333-30.293334c0-12.224 0.32-22.314667 0.917333-30.272 0.618667-7.957333 1.664-14.293333 3.114667-19.072 1.493333-4.778667 3.498667-8.085333 6.058666-9.92a15.68 15.68 0 0 1 9.386667-2.752c7.082667 0 11.968 2.816 14.656 8.448 1.472 3.178667 2.56 7.274667 3.306667 12.288 0.725333 5.013333 1.109333 11.562667 1.109333 19.626667z m339.84-82.965333c13.226667 0 24.789333 1.6 34.709333 4.778667 9.898667 3.2 18.218667 8.512 24.96 15.957333 6.72 7.466667 11.733333 17.258667 15.04 29.376 3.306667 12.096 4.949333 27.221333 4.949334 45.312v20.202667h-97.984v26.048c0 10.773333 1.834667 17.92 5.504 21.482666a17.493333 17.493333 0 0 0 12.48 5.333334c5.866667 0 10.453333-2.410667 13.76-7.168 3.306667-4.778667 4.949333-13.888 4.949333-27.349334h59.456c-0.490667 24.469333-6.72 42.88-18.709333 55.253334-11.989333 12.330667-31.701333 18.517333-59.093334 18.517333-15.914667 0-29.226667-1.770667-40-5.333333a54.485333 54.485333 0 0 1-26.069333-17.6c-6.613333-8.213333-11.306667-18.901333-14.122667-32.128-2.816-13.205333-4.224-29.226667-4.224-48.064 0-19.093333 1.578667-35.242667 4.778667-48.448 3.178667-13.226667 8.192-23.978667 15.04-32.298667 6.848-8.32 15.594667-14.378667 26.24-18.176 10.645333-3.797333 23.424-5.696 38.357333-5.696z m-197.802667-58.346667v132.117334h0.725334l36.693333-68.629334h70.485333l-55.04 81.856 62.016 116.693334h-70.485333l-33.386667-68.266667-11.008 17.28v50.986667H560.426667V381.546667h66.069333z m197.461334 98.730667c-6.848 0-11.562667 2.453333-14.144 7.338667-2.56 4.906667-3.84 12.586667-3.84 23.125333v8.810667h36.330666v-8.810667c0-10.517333-1.28-18.24-3.84-23.125333-2.581333-4.906667-7.424-7.338667-14.506666-7.338667z m85.866666-35.242667v44.416h22.037334v105.706667c0 11.008 1.024 19.882667 3.114666 26.602667 2.069333 6.72 5.376 11.797333 9.898667 15.232 4.544 3.413333 10.346667 5.568 17.450667 6.421333 7.082667 0.853333 15.658667 1.28 25.685333 1.28 5.632 0 11.498667-0.170667 17.621333-0.533333 6.122667-0.384 12.224-0.554667 18.346667-0.554667v-44.053333h-10.282667c-5.376 0-9.344-1.152-11.925333-3.477334-2.56-2.346667-3.84-6.314667-3.84-11.946666v-94.677334H1024V445.013333h-26.069333v-58.346666h-66.048v58.346666h-22.037334z") }); + plcCommList.Add(new MenuItem { Header = "ModbusRTU", IconPath = StreamGeometry.Parse("M130.628267 0C58.260724 0 0 58.260724 0 130.628267v762.743466C0 965.739276 58.260724 1024 130.628267 1024h762.743466c72.367543 0 130.628267-58.260724 130.628267-130.628267V130.628267C1024 58.260724 965.739276 0 893.371733 0H130.628267z m54.481676 73.142857h653.780114C900.920076 73.142857 950.857143 123.080411 950.857143 185.109943v653.780114c0 62.030019-49.937067 111.967086-111.967086 111.967086H185.109943C123.080899 950.857143 73.142857 900.920076 73.142857 838.890057V185.109943C73.142857 123.080899 123.080411 73.142857 185.109943 73.142857z M170.666667 475.428571h682.666666v73.142858H170.666667z M304.742888 851.8656h-45.452922v-140.309943q0-8.013531-7.84189-8.013531-8.002316 0-8.002316 7.711207v140.612267H197.992838v-140.612267q0-8.014019-7.841889-8.014019-8.002316 0-8.002317 7.711208v140.915078h-45.451946V676.327619h45.451946v6.199101q9.442743-7.408884 28.328229-7.408884 15.844693 0 28.808046 11.944716 12.002743-11.944716 32.80896-11.944716 11.843291 0 22.245668 6.803749 10.403352 6.652587 10.403353 18.59779z m9.603169-34.624366v-105.080929q0-37.042956 54.253958-37.042956 20.966156 0 36.810362 8.618179 16.00512 8.618179 16.00512 26.913158v108.104655q0 35.531337-52.815482 35.531337-54.253958 0-54.253958-37.043444z m61.457067 1.210271v-107.500983q0-8.617691-7.842865-8.617692-8.001829 0-8.001829 8.617692v107.500495q0 8.920503 8.001829 8.920503 7.842865 0 7.842865-8.920015z m161.484312 33.414095h-34.409326l-3.841462-5.897265q-11.683352 8.316343-26.567437 8.316343-17.445059 0-29.768167-7.71072-12.163657-7.711208-12.163657-21.016868V699.460267q0-11.189394 9.602682-17.690332 9.762621-6.652587 23.526643-6.652586 18.885486 0 28.167802 6.501424v-46.568594h45.452922z m-45.452922-32.809448v-109.013089q0-7.710232-7.841889-7.710233-8.002316 0-8.002316 8.013044v108.709791q0 8.013531 8.002316 8.013531 7.84189 0 7.841889-8.013044z m93.626271 26.912183l-3.841463 5.897265h-34.409326V635.050667h45.452434v46.568594q8.162743-6.501425 28.327741-6.501425 12.323596 0 22.566035 6.349775 10.40384 6.199101 10.40384 17.993143v126.096823q0 13.305661-12.323596 21.016381-12.323109 7.711208-29.608229 7.711208-13.443657 0-26.567436-8.316343z m23.046339-26.912183v-108.70979q0-8.013531-7.84189-8.013532-8.002804 0-8.002804 7.710233v109.013089q0 8.013044 8.002804 8.013044 7.84189 0 7.84189-8.013044z m162.124556 32.809448h-45.451947v-5.897265q-8.802499 8.316343-28.167802 8.316343-13.444632 0-23.366704-6.652587-9.763109-6.803749-9.763109-17.690819v-153.614628h45.452922v142.729508q0 8.013044 8.001829 8.013044 7.842865 0 7.842864-7.711207v-143.030858h45.451947z m9.923048-70.608701h45.452922v37.194606q0 8.920015 7.68195 8.920015 7.682438 0 7.682438-17.084709 0-22.225676-9.44323-28.727588-35.690301-17.689844-43.051886-24.645242-7.361585-7.105585-7.361585-21.166568v-23.587108q0-37.042956 53.133897-37.042956 51.694446 0 51.694446 35.531337v34.62339h-44.491825l0.959635-21.016381q0-21.922865-8.481646-21.922865-8.483109 0-8.483109 13.75866 0 14.212145 3.681524 21.469866 3.840975 7.257234 41.931337 24.947078 15.844206 10.734933 15.844206 28.425265v26.307535q0 37.043444-53.934568 37.043444-52.814507 0-52.814506-35.531337zM411.060419 410.376777h-88.444343q-14.319909-10.368244-14.319908-58.810758v-0.339383l0.560762-14.616868q0.848457-30.425478 0.848457-31.106195 0-12.407467-27.515368-12.407466V410.038857H193.744213V166.295162H302.967467q37.342354 0 64.859184 10.369219 27.796724 10.368244 27.796724 32.804571v29.235688q0 28.38528-54.751329 38.413653 55.03171 7.818971 55.03171 40.283673l-1.408731 26.685927q0 58.471375 16.565394 66.289859z m-101.361371-159.775695v-40.113006q0-12.7488-27.51488-12.7488v65.77981q27.515368 0 27.515367-12.918004z m297.905981-52.521448H558.187276v211.95776H469.742933V198.079634H420.325669v-31.785447h187.278872z m16.284525-31.785447h88.444343v204.648838q0 7.308922 14.882133 7.308922 14.599314 0 14.599315-7.308435V166.294187h88.445805v201.759207q0 21.076846-28.920685 32.63488-28.920198 11.558522-74.124922 11.558522-41.836251 0-72.721554-10.368244-30.604434-10.368244-30.604435-33.4848z") }); + + ObservableCollection netList = new ObservableCollection(); + netList.Add(new MenuItem { Header = "HTTP调试", IconPath = StreamGeometry.Parse("M946.939661 418.312678a23.170169 23.170169 0 0 0 23.100746-23.239593V116.197966c0-64.04339-51.781424-116.197966-115.486373-116.197966H115.477695C51.781424 0 0 52.154576 0 116.197966v790.154848c0 64.04339 51.781424 116.197966 115.477695 116.197966h739.076339c63.696271 0 115.477695-52.154576 115.477695-116.197966V766.915254a23.170169 23.170169 0 0 0-23.092068-23.239593 23.170169 23.170169 0 0 0-23.100746 23.239593v139.43756c0 38.44339-31.084475 69.71878-69.284881 69.718779H115.477695c-38.200407 0-69.284881-31.284068-69.284881-69.718779V232.395932h877.654779v162.677153a23.170169 23.170169 0 0 0 23.092068 23.239593zM46.192814 185.916746V116.197966c0-38.44339 31.084475-69.71878 69.284881-69.71878h739.076339c38.200407 0 69.293559 31.284068 69.293559 69.71878v69.71878H46.192814z m69.284881-46.479187a23.170169 23.170169 0 0 0 23.100746-23.239593 23.170169 23.170169 0 0 0-23.100746-23.239593 23.170169 23.170169 0 0 0-23.092068 23.239593 23.170169 23.170169 0 0 0 23.092068 23.239593z m92.385627 0a23.170169 23.170169 0 0 0 23.100746-23.239593 23.170169 23.170169 0 0 0-23.100746-23.239593 23.170169 23.170169 0 0 0-23.092068 23.239593 23.170169 23.170169 0 0 0 23.092068 23.239593z m92.385627 0a23.170169 23.170169 0 0 0 23.100746-23.239593 23.170169 23.170169 0 0 0-23.100746-23.239593 23.170169 23.170169 0 0 0-23.092068 23.239593 23.170169 23.170169 0 0 0 23.092068 23.239593zM254.056136 464.791864a23.170169 23.170169 0 0 1 23.100745 23.248272v185.916745a23.170169 23.170169 0 0 1-23.100745 23.239594 23.170169 23.170169 0 0 1-23.092068-23.239594v-69.718779H138.578441v69.718779a23.170169 23.170169 0 0 1-23.100746 23.239594 23.170169 23.170169 0 0 1-23.092068-23.239594V488.040136a23.170169 23.170169 0 0 1 23.092068-23.248272 23.170169 23.170169 0 0 1 23.100746 23.248272v69.718779h92.385627v-69.718779a23.170169 23.170169 0 0 1 23.092068-23.248272z m184.771254 0a23.170169 23.170169 0 0 1 23.100746 23.239594 23.170169 23.170169 0 0 1-23.100746 23.239593h-23.100746v162.68583a23.170169 23.170169 0 0 1-23.08339 23.239594 23.170169 23.170169 0 0 1-23.100746-23.239594V511.279729h-23.100745a23.170169 23.170169 0 0 1-23.100746-23.239593 23.170169 23.170169 0 0 1 23.100746-23.248272h92.385627z m184.771254 0a23.170169 23.170169 0 0 1 23.092068 23.248272 23.170169 23.170169 0 0 1-23.092068 23.239593h-23.100746v162.677152a23.170169 23.170169 0 0 1-23.092067 23.239594 23.170169 23.170169 0 0 1-23.100746-23.239594V511.279729h-23.092068a23.170169 23.170169 0 0 1-23.100746-23.239593 23.170169 23.170169 0 0 1 23.100746-23.248272H623.598644z m138.569763 0c38.209085 0 69.293559 31.284068 69.293559 69.71878 0 38.44339-31.084475 69.727458-69.293559 69.727458h-23.08339v69.718779a23.170169 23.170169 0 0 1-23.100746 23.239594 23.170169 23.170169 0 0 1-23.100746-23.239594V488.040136a23.170169 23.170169 0 0 1 23.100746-23.248272h46.184136z m0 92.958373c12.704542 0 23.100746-10.413559 23.100746-23.239593a23.204881 23.204881 0 0 0-23.100746-23.239593h-23.092068v46.479186h23.092068z m328.938305-92.255457a23.196203 23.196203 0 0 1 16.817898 28.168678l-46.192813 185.916745a23.048678 23.048678 0 0 1-27.995119 16.922034 23.196203 23.196203 0 0 1-16.80922-28.177356l46.184135-185.916745c3.054644-12.496271 15.429424-20.167593 27.995119-16.922034z m-92.385627 0a23.196203 23.196203 0 0 1 16.817898 28.168678l-46.192814 185.916745a23.048678 23.048678 0 0 1-27.995118 16.922034 23.196203 23.196203 0 0 1-16.80922-28.177356l46.192813-185.916745c3.037288-12.496271 15.377356-20.167593 27.986441-16.922034z m-97.974238 92.264135a23.170169 23.170169 0 0 1-23.092067-23.248271 23.170169 23.170169 0 0 1 23.092067-23.239593 23.170169 23.170169 0 0 1 23.100746 23.239593 23.170169 23.170169 0 0 1-23.100746 23.248271z m0 92.958373a23.170169 23.170169 0 0 1-23.092067-23.239593 23.170169 23.170169 0 0 1 23.092067-23.239593 23.170169 23.170169 0 0 1 23.100746 23.239593 23.170169 23.170169 0 0 1-23.100746 23.239593z") }); + netList.Add(new MenuItem { Header = "FTP客户端", IconPath = StreamGeometry.Parse("M170.666667 597.333333v85.333334h170.666666v85.333333H170.666667v170.666667H85.333333V512h256v85.333333z m469.333333-85.333333H384v85.333333h85.333333v341.333334h85.333334V597.333333h85.333333z m170.666667 85.333333a42.666667 42.666667 0 0 1 0 85.333334h-42.666667v-85.333334h42.666667m0-85.333333h-128v426.666667h85.333333v-170.666667h42.666667a128 128 0 0 0 128-128 128 128 0 0 0-128-128zM725.333333 85.333333l213.333334 213.333334v170.666666h-85.333334v-135.253333L689.92 170.666667H170.666667v298.666666H85.333333V85.333333z") }); + netList.Add(new MenuItem { Header = "FTP服务", IconPath = StreamGeometry.Parse("M718.116571 864.036571h115.053715a36.571429 36.571429 0 1 1 0 73.142858H191.268571a36.571429 36.571429 0 1 1 0-73.142858h134.802286l62.098286-146.285714h-203.337143a146.285714 146.285714 0 0 1-146.285714-146.285714V20.699429h800.548571a146.285714 146.285714 0 0 1 146.285714 146.285714v550.765714H450.121143a28.891429 28.891429 0 0 1-1.316572 3.949714l-60.416 142.336h256.950858l-53.101715-67.876571a28.672 28.672 0 1 1 45.202286-35.328l80.676571 103.204571z m-606.354285-770.194285v477.622857a73.142857 73.142857 0 0 0 73.142857 73.142857h727.332571v-477.622857a73.142857 73.142857 0 0 0-73.142857-73.142857H111.762286z m149.942857 410.624H206.994286V233.691429h146.285714v47.177142H261.632v69.632H345.965714v47.177143H261.632v106.788572zM516.973714 280.868571v223.597715h-54.857143V280.868571H389.12v-47.177142h201.142857v47.177142h-73.142857z m171.081143 129.243429v94.354286H633.417143V233.691429h88.283428c63.853714 0 95.817143 28.525714 95.817143 85.650285 0 27.794286-9.654857 50.029714-28.964571 66.706286-19.236571 16.749714-43.373714 24.722286-72.265143 24.064h-28.16z m0-132.754286v87.990857h27.940572c38.034286 0 57.051429-14.848 57.051428-44.544 0-28.964571-18.797714-43.446857-56.393143-43.446857h-28.598857z") }); + netList.Add(new MenuItem { Header = "端口扫描", IconPath = StreamGeometry.Parse("M832 106.666667H192C145.066667 106.666667 106.666667 145.066667 106.666667 192v640c0 46.933333 38.4 85.333333 85.333333 85.333333h640c46.933333 0 85.333333-38.4 85.333333-85.333333V192c0-46.933333-38.4-85.333333-85.333333-85.333333zM277.333333 778.666667c0 17.066667-14.933333 32-32 32s-32-14.933333-32-32v-106.666667c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v106.666667z m128 0c0 17.066667-14.933333 32-32 32s-32-14.933333-32-32v-106.666667c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v106.666667z m128 0c0 17.066667-14.933333 32-32 32s-32-14.933333-32-32v-106.666667c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v106.666667z m128 0c0 17.066667-14.933333 32-32 32s-32-14.933333-32-32v-106.666667c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v106.666667z m128 0c0 17.066667-14.933333 32-32 32s-32-14.933333-32-32v-106.666667c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v106.666667zM810.666667 426.666667c0 23.466667-19.2 42.666667-42.666667 42.666666h-85.333333c-23.466667 0-42.666667-19.2-42.666667-42.666666v-42.666667c0-23.466667-19.2-42.666667-42.666667-42.666667h-170.666666c-23.466667 0-42.666667 19.2-42.666667 42.666667v42.666667c0 23.466667-19.2 42.666667-42.666667 42.666666h-85.333333c-23.466667 0-42.666667-19.2-42.666667-42.666666v-170.666667c0-23.466667 19.2-42.666667 42.666667-42.666667h512c23.466667 0 42.666667 19.2 42.666667 42.666667v170.666667z") }); + if (OperatingSystem.IsWindows()) + { + netList.Add(new MenuItem { Header = "端口占用扫描", IconPath = StreamGeometry.Parse("M863.601206 859.27926H160.123797a153.572128 153.572128 0 0 1-153.293413-153.293414V153.293413A153.572128 153.572128 0 0 1 160.123797 0h703.477409a153.572128 153.572128 0 0 1 153.293413 153.293413v552.692433a153.572128 153.572128 0 0 1-153.293413 153.293414zM160.123797 83.614589A69.678824 69.678824 0 0 0 90.444973 153.293413v552.692433a69.678824 69.678824 0 0 0 69.678824 69.678825h703.477409a69.678824 69.678824 0 0 0 69.678824-69.678825V153.293413a69.678824 69.678824 0 0 0-69.678824-69.678824z M196.635501 165.556886m55.743059 0l518.967883 0q55.743059 0 55.743059 55.74306l0 416.400653q0 55.743059-55.743059 55.743059l-518.967883 0q-55.743059 0-55.743059-55.743059l0-416.400653q0-55.743059 55.743059-55.74306Z M303.94089 910.84159m48.775177 0l318.571584 0q48.775177 0 48.775177 48.775176l0 15.608057q0 48.775177-48.775177 48.775177l-318.571584 0q-48.775177 0-48.775177-48.775177l0-15.608057q0-48.775177 48.775177-48.775176Z") }); + } + netList.Add(new MenuItem { Header = "网络状态检测", IconPath = StreamGeometry.Parse("M998.4 262.4c0-38.4-32-70.4-70.4-70.4H518.4L448 134.4C441.6 128 435.2 128 435.2 128h-320C64 121.6 25.6 166.4 25.6 217.6v569.6c0 64 51.2 108.8 108.8 108.8h537.6c12.8 0 25.6-12.8 25.6-25.6s-12.8-25.6-25.6-25.6H134.4c-32 0-64-25.6-64-64V217.6c0-25.6 19.2-44.8 44.8-44.8h300.8l230.4 185.6h307.2v428.8c0 32-25.6 64-64 64h-32c-12.8 0-25.6 12.8-25.6 25.6s12.8 25.6 25.6 25.6h32c64 0 108.8-51.2 108.8-108.8V262.4z M249.6 460.8h-64v204.8h38.4V582.4h70.4c6.4 0 12.8-6.4 19.2-12.8 6.4 0 12.8-12.8 19.2-19.2s6.4-19.2 6.4-32c0-19.2-6.4-32-12.8-38.4-6.4-6.4-12.8-19.2-25.6-19.2h-51.2z m44.8 76.8c0 6.4-6.4 6.4-12.8 12.8s-19.2 0-32 0h-25.6v-57.6h51.2c6.4 0 12.8 6.4 19.2 6.4v38.4zM371.2 460.8h38.4v204.8h-38.4zM492.8 460.8h-38.4v198.4h38.4v-128l76.8 128h44.8V460.8H576v134.4zM838.4 550.4h-89.6v32h44.8v25.6c-6.4 6.4-12.8 6.4-19.2 12.8-6.4 6.4-19.2 6.4-25.6 6.4-19.2 0-32-6.4-44.8-19.2-12.8-12.8-19.2-32-19.2-51.2 0-19.2 6.4-38.4 12.8-51.2 12.8-12.8 25.6-19.2 44.8-19.2 12.8 0 19.2 0 32 6.4 6.4 6.4 12.8 12.8 19.2 25.6l44.8-6.4c-6.4-19.2-12.8-32-25.6-44.8-12.8-12.8-32-12.8-57.6-12.8-19.2 0-38.4 6.4-51.2 12.8-19.2 6.4-32 19.2-38.4 38.4-6.4 19.2-12.8 32-12.8 57.6 0 19.2 6.4 38.4 12.8 51.2 6.4 19.2 19.2 32 38.4 38.4 12.8 6.4 32 12.8 57.6 12.8 19.2 0 32 0 51.2-6.4s19.2-19.2 25.6-25.6V550.4z") }); + + ObservableCollection imageList = new ObservableCollection(); + imageList.Add(new MenuItem { Header = "二维码条形码生成", IconPath = StreamGeometry.Parse("M384 64l-249.6 0c-51.2 0-89.6 41.6-89.6 89.6l0 227.2c0 51.2 41.6 89.6 89.6 89.6l249.6 0c51.2 0 89.6-41.6 89.6-89.6l0-227.2C473.6 105.6 435.2 64 384 64zM428.8 380.8c0 25.6-19.2 44.8-44.8 44.8l-249.6 0c-25.6 0-44.8-19.2-44.8-44.8l0-227.2c0-25.6 19.2-44.8 44.8-44.8l249.6 0c25.6 0 44.8 19.2 44.8 44.8L428.8 380.8z M192 192l134.4 0 0 134.4-134.4 0 0-134.4Z M377.6 544l-243.2 0c-48 0-86.4 38.4-86.4 89.6l0 220.8c0 48 38.4 89.6 86.4 89.6l243.2 0c48 0 86.4-38.4 86.4-89.6l0-220.8C467.2 582.4 425.6 544 377.6 544zM422.4 851.2c0 25.6-19.2 44.8-44.8 44.8l-243.2 0c-25.6 0-44.8-19.2-44.8-44.8l0-220.8c0-25.6 19.2-44.8 44.8-44.8l243.2 0c25.6 0 44.8 19.2 44.8 44.8L422.4 851.2z M192 668.8l131.2 0 0 131.2-131.2 0 0-131.2Z M633.6 470.4l249.6 0c51.2 0 89.6-41.6 89.6-89.6l0-227.2c0-51.2-41.6-89.6-89.6-89.6l-249.6 0c-51.2 0-89.6 41.6-89.6 89.6l0 227.2C544 432 585.6 470.4 633.6 470.4zM588.8 153.6c0-25.6 19.2-44.8 44.8-44.8l249.6 0c25.6 0 44.8 19.2 44.8 44.8l0 227.2c0 25.6-19.2 44.8-44.8 44.8l-249.6 0c-25.6 0-44.8-19.2-44.8-44.8L588.8 153.6z M700.8 192l134.4 0 0 134.4-134.4 0 0-134.4Z M572.8 716.8l137.6 0c12.8 0 22.4-9.6 22.4-22.4l0-137.6c0-12.8-9.6-22.4-22.4-22.4l-137.6 0c-12.8 0-22.4 9.6-22.4 22.4l0 137.6C550.4 707.2 560 716.8 572.8 716.8z M886.4 563.2l0 38.4c0 12.8 12.8 25.6 25.6 25.6l38.4 0c12.8 0 25.6-12.8 25.6-25.6l0-38.4c0-12.8-12.8-25.6-25.6-25.6l-38.4 0C899.2 537.6 886.4 547.2 886.4 563.2z M582.4 944l48 0c12.8 0 22.4-9.6 22.4-22.4l0-48c0-12.8-9.6-22.4-22.4-22.4l-48 0c-12.8 0-22.4 9.6-22.4 22.4l0 48C560 934.4 569.6 944 582.4 944z M944 704l-99.2 0c-16 0-28.8 12.8-28.8 28.8l0 44.8-48 0c-19.2 0-32 12.8-32 32l0 99.2c0 16 12.8 28.8 28.8 28.8l179.2 3.2c16 0 28.8-12.8 28.8-28.8l0-179.2C972.8 716.8 960 704 944 704z") }); + imageList.Add(new MenuItem { Header = "导航二维码生成", IconPath = StreamGeometry.Parse("M512 22C241.38 22 22 241.38 22 512s219.38 490 490 490 490-219.38 490-490S782.62 22 512 22z m226.06 277.73L517.54 809.14c-3.18 7.28-13.72 7.08-17.21-0.41L410.4 616.57a5.966 5.966 0 0 0-2.97-2.97L215.6 523.58c-7.58-3.49-7.79-14.05-0.41-17.23l508.74-220.76c8.91-3.91 18.03 5.22 14.13 14.14z") }); + imageList.Add(new MenuItem { Header = "二维码条形码解析", IconPath = StreamGeometry.Parse("M170.666667 170.666667 426.666667 170.666667 426.666667 426.666667 170.666667 426.666667 170.666667 170.666667M853.333333 170.666667 853.333333 426.666667 597.333333 426.666667 597.333333 170.666667 853.333333 170.666667M597.333333 640 682.666667 640 682.666667 554.666667 597.333333 554.666667 597.333333 469.333333 682.666667 469.333333 682.666667 554.666667 768 554.666667 768 469.333333 853.333333 469.333333 853.333333 554.666667 768 554.666667 768 640 853.333333 640 853.333333 768 768 768 768 853.333333 682.666667 853.333333 682.666667 768 554.666667 768 554.666667 853.333333 469.333333 853.333333 469.333333 682.666667 597.333333 682.666667 597.333333 640M682.666667 640 682.666667 768 768 768 768 640 682.666667 640M170.666667 853.333333 170.666667 597.333333 426.666667 597.333333 426.666667 853.333333 170.666667 853.333333M256 256 256 341.333333 341.333333 341.333333 341.333333 256 256 256M682.666667 256 682.666667 341.333333 768 341.333333 768 256 682.666667 256M256 682.666667 256 768 341.333333 768 341.333333 682.666667 256 682.666667M170.666667 469.333333 256 469.333333 256 554.666667 170.666667 554.666667 170.666667 469.333333M384 469.333333 554.666667 469.333333 554.666667 640 469.333333 640 469.333333 554.666667 384 554.666667 384 469.333333M469.333333 256 554.666667 256 554.666667 426.666667 469.333333 426.666667 469.333333 256M85.333333 85.333333 85.333333 256 0 256 0 85.333333C0 38.4 38.4 0 85.333333 0L256 0 256 85.333333 85.333333 85.333333M938.666667 0C985.6 0 1024 38.4 1024 85.333333L1024 256 938.666667 256 938.666667 85.333333 768 85.333333 768 0 938.666667 0M85.333333 768 85.333333 938.666667 256 938.666667 256 1024 85.333333 1024C38.4 1024 0 985.6 0 938.666667L0 768 85.333333 768M938.666667 938.666667 938.666667 768 1024 768 1024 938.666667C1024 985.6 985.6 1024 938.666667 1024L768 1024 768 938.666667 938.666667 938.666667Z") }); + imageList.Add(new MenuItem { Header = "GIF分割", IconPath = StreamGeometry.Parse("M511.5 81C272.6 81 79 274.6 79 513.5S272.6 946 511.5 946 944 752.4 944 513.5 750.4 81 511.5 81z m-32.3 532.6c-24.7 23.8-57.1 35.8-97 35.8-45.9 0-82.7-14.1-110.3-42.3-27.6-28.2-41.4-61.4-41.4-99.4 0-38.1 13.7-71 41.2-98.9C299.2 381 335.2 367 379.9 367s85.1 17.2 121.4 51.7l-20.6 19.9c-15-14.5-31.4-25.6-49.3-33.3-17.9-7.7-36.3-11.6-55.2-11.6-19 0-38 5.1-56.9 15.4-19 10.2-33.8 24-44.4 41.4-10.6 17.4-15.9 36.1-15.9 56.4 0 31.5 11.8 58.9 35.4 82.2 23.6 23.4 53.1 35 88.6 35 27 0 50.1-7.5 69.5-22.7 19.3-15.1 30.9-35.6 34.6-61.6h-85.8V514h116.1c-0.8 42.5-13.5 75.8-38.2 99.6z m112 29h-27V373.7h27v268.9z m196.2-242.3H679.6v83.9h107.9v26.6H679.6v131.8h-27V373.7h134.8v26.6z") }); + imageList.Add(new MenuItem { Header = "图片转ICO", IconPath = StreamGeometry.Parse("M972.8 256v686.08c0 46.08-35.84 81.92-81.92 81.92H133.12c-46.08 0-81.92-35.84-81.92-81.92V81.92C51.2 35.84 87.04 0 133.12 0H716.8l256 256z M921.6 256v675.84c0 20.48-20.48 40.96-40.96 40.96H143.36c-20.48 0-40.96-15.36-40.96-40.96V92.16c0-25.6 20.48-40.96 40.96-40.96H716.8v204.8h204.8z M501.76 670.72h56.32c-15.36 56.32-61.44 97.28-122.88 97.28C363.52 768 307.2 711.68 307.2 640S363.52 512 435.2 512c61.44 0 112.64 40.96 122.88 97.28h-51.2c-15.36-25.6-40.96-46.08-71.68-46.08-40.96 0-76.8 35.84-76.8 76.8S394.24 716.8 435.2 716.8c30.72 0 56.32-20.48 66.56-46.08zM204.8 512h51.2v256H204.8v-256zM742.4 512c-71.68 0-128 56.32-128 128s56.32 128 128 128 128-56.32 128-128-56.32-128-128-128z m0 204.8c-40.96 0-76.8-35.84-76.8-76.8s35.84-76.8 76.8-76.8 76.8 35.84 76.8 76.8-35.84 76.8-76.8 76.8z") }); + //if (OperatingSystem.IsWindows()) + //{ + // imageList.Add(new MenuItem { Header = "取颜色工具", IconPath = StreamGeometry.Parse("M963.145143 41.340343l11.819886 11.0592a147.573029 147.573029 0 0 1-2.4576 206.145828l-127.3856 127.3856 103.336228 105.706058-84.553143 77.824-91.282285-92.745143-433.649372 418.728228-17.466514 16.296229c-30.427429 26.565486-71.504457 53.394286-123.991772 81.451886A130.750171 130.750171 0 0 1 22.996114 812.763429c33.850514-58.9824 63.868343-101.346743 91.867429-128.380343L552.521143 255.619657 457.581714 154.799543l86.074515-80.0768 98.128457 107.022628 131.861943-131.774171a140.6976 140.6976 0 0 1 189.469257-8.630857z m-330.839772 293.156571L201.610971 756.384914l-15.389257 15.272229c-17.905371 19.485257-40.199314 51.902171-65.887085 96.636343a18.666057 18.666057 0 0 0 24.663771 25.892571c51.258514-27.384686 88.327314-52.4288 110.943086-74.313143L694.857143 395.995429l-62.551772-61.498515z") }); + //} + imageList.Add(new MenuItem { Header = "色卡包", IconPath = StreamGeometry.Parse("M192 0a128 128 0 0 1 128 128v381.568L638.4 191.36a128 128 0 0 1 180.992 0l45.248 45.248a128 128 0 0 1 0 180.992L578.304 704 896 704a128 128 0 0 1 128 128v64a128 128 0 0 1-128 128H128a128 128 0 0 1-128-128V128a128 128 0 0 1 128-128h64z m128 768l2.176 192.064L896 960a64 64 0 0 0 63.552-56.512L960 896v-64a64 64 0 0 0-56.512-63.552L896 768H320zM192 64H128a64 64 0 0 0-63.552 56.512L64 128v768a64 64 0 0 0 56.512 63.552L128 960h64a64 64 0 0 0 63.552-56.512L256 896V128a64 64 0 0 0-56.512-63.552L192 64z m-32 768a32 32 0 1 1 0 64 32 32 0 0 1 0-64z m529.28-600.384l-5.632 4.992L320 600.064V704l167.744-0.064 331.648-331.584a64 64 0 0 0 4.992-84.928l-4.992-5.568-45.248-45.248a64 64 0 0 0-84.928-4.992z") }); + + ObservableCollection crackList = new ObservableCollection(); + if (OperatingSystem.IsWindows()) + { + crackList.Add(new MenuItem { Header = "远程路径软链接", IconPath = StreamGeometry.Parse("M512 1024C229.228 1024 0 794.772 0 512S229.228 0 512 0s512 229.228 512 512-229.228 512-512 512z m54.545-565.02l-1.798-1.764a102.207 102.207 0 0 0-11.002-9.523l-35.453 35.442c4.176 2.446 8.078 5.393 11.605 8.92l1.866 1.763a58.277 58.277 0 0 1 0 82.341l-96.904 96.882a58.334 58.334 0 0 1-82.341 0l-1.832-1.798a58.243 58.243 0 0 1 0-82.306l43.816-43.828a149.675 149.675 0 0 1-10.866-58.732l-67.812 67.72c-41.836 41.825-41.836 110.251 0 152.053l1.787 1.798c41.836 41.79 110.228 41.79 152.052 0l96.882-96.916c41.757-41.825 41.757-110.25 0-152.052z m141.38-141.37l-1.82-1.797c-41.802-41.825-110.228-41.825-152.053 0l-96.882 96.916c-41.824 41.79-41.824 110.216 0 152.053l1.798 1.763c3.505 3.425 7.225 6.576 11.002 9.523l35.454-35.476a57.845 57.845 0 0 1-11.583-8.92l-1.798-1.763a58.311 58.311 0 0 1 0-82.375l96.905-96.882a58.197 58.197 0 0 1 82.284 0l1.798 1.797a58.277 58.277 0 0 1 0 82.341l-43.76 43.828c7.612 18.796 11.196 38.81 10.844 58.766l67.789-67.755c41.824-41.79 41.824-110.216 0.022-152.007z") }); + crackList.Add(new MenuItem { Header = "AdobeAcrobatXI破解", IconPath = StreamGeometry.Parse("M169.64 18.36c13.96-2.8 28.24-2.56 42.4-2.4C418.72 16 625.36 15.92 832 16c68.16 2.24 131.04 51.04 151.84 115.72 10.92 29.6 8 61.48 8.32 92.4-0.08 189.32 0.12 378.6-0.12 567.92-2.28 83.76-76 157.64-159.76 160.04-212.12 0.2-424.2 0.04-636.32 0.12-59.4-0.56-116.32-36.44-144.2-88.68-14.6-26.68-21-57.2-20.84-87.48 0-194.68-0.04-389.4 0.04-584.08-0.68-42.92 13.72-86.36 42.52-118.52 24.72-28.44 59.2-47.96 96.16-55.08m285.16 192.16c-17.12 18.56-18.08 45.68-17.4 69.56 0.56 42.64 16.64 82.76 30.32 122.36-22.16 66.76-48.44 132.64-82.8 194.16-47.2 19.16-94.16 41.56-133.12 74.96-18.96 16.56-36.84 41.36-28.8 67.84 8.28 26.56 38.84 38.36 64.56 34.84 32.68-4.32 58.92-26.96 80.96-49.84 30.12-32.2 54.16-69.52 76.08-107.64 2.32-5.16 8.56-6 13.28-7.88 45.48-14.28 91.56-27.08 138.6-35.2 40.88 36.2 92.16 63.28 147.4 66.68 20.8 0.92 44.44-1.32 59.84-16.96 18.04-17.48 16.72-49.08-0.56-66.64-14.48-14.8-35-21.6-54.84-25.6-45.44-7.6-91.88-3.8-137.2 2.8-4.6 0.48-7.24-4.04-10.36-6.56-29.04-29.88-52.84-64.28-74.56-99.68 14.2-49.36 29.36-99.8 27.52-151.72-1.04-24.4-7.96-50.56-27.04-67.16-19.56-17.72-53.76-18.68-71.88 1.68z M469.4 248.28c1.56-11.16 10.08-26.32 23.48-22 16.04 8.8 13.52 29.88 13.2 45.36-3.12 29.48-8.8 59.04-18.12 87.16l-3.72 1.56c-11.04-36.08-21.88-74.16-14.84-112.08zM508.52 475.6c3.96 1.8 8.48 3.24 10.24 7.72 11.08 19.6 26.8 35.96 40.24 53.84 0.28 1.72 0.88 5.12 1.16 6.8-29.72 4.24-58 14.88-87.36 20.68 12.4-29.44 26.6-58.32 35.72-89.04zM636.08 559.92c33.2-2.48 66.88-5.68 99.92-0.08 12 2.08 25.88 10.04 25.28 23.88-0.32 16.56-18.84 25.2-33.48 23.36-31.52-3.32-60.2-19-86.2-36.2-4.04-2.4-5.88-6.08-5.52-10.96zM296.04 675.8c21.8-14.2 44.44-27.64 68.88-36.72-0.96 2.56-2.96 7.76-3.92 10.32-19.92 29.92-41.68 59.24-69.96 81.76-8.08 5.32-17.04 10.92-27.08 10.32-7.92-3.12-15.6-11.64-12.12-20.6 8.12-20.28 27.04-33 44.2-45.08z") }); + crackList.Add(new MenuItem { Header = "猫猫回收站", IconPath = StreamGeometry.Parse("M512 341.333333l-56.746667 3.84C418.56 301.653333 315.733333 192 213.333333 192c0 0-84.053333 126.293333-1.706666 294.826667-23.466667 35.413333-37.973333 53.76-40.96 96l-82.346667 12.373333 8.96 41.813333 75.093333-11.093333 5.973334 30.293333-66.986667 40.106667 20.053333 37.973333 61.866667-37.973333C242.346667 800.426667 366.506667 853.333333 512 853.333333s269.653333-52.906667 318.72-157.013333l61.866667 37.973333 20.053333-37.973333-66.986667-40.106667 5.973334-30.293333 75.093333 11.093333 8.96-41.813333-82.346667-12.373333c-2.986667-42.24-17.493333-60.586667-40.96-96C894.72 318.293333 810.666667 192 810.666667 192c-102.4 0-205.226667 109.653333-241.92 153.173333L512 341.333333m-128 128a42.666667 42.666667 0 0 1 42.666667 42.666667 42.666667 42.666667 0 0 1-42.666667 42.666667 42.666667 42.666667 0 0 1-42.666667-42.666667 42.666667 42.666667 0 0 1 42.666667-42.666667m256 0a42.666667 42.666667 0 0 1 42.666667 42.666667 42.666667 42.666667 0 0 1-42.666667 42.666667 42.666667 42.666667 0 0 1-42.666667-42.666667 42.666667 42.666667 0 0 1 42.666667-42.666667m-170.666667 128h85.333334l-29.866667 59.306667c8.533333 27.306667 32.426667 47.36 61.866667 47.36a64 64 0 0 0 64-64h21.333333a85.333333 85.333333 0 0 1-85.333333 85.333333c-32 0-59.733333-17.493333-74.666667-42.666666-14.933333 25.173333-42.666667 42.666667-74.666667 42.666666a85.333333 85.333333 0 0 1-85.333333-85.333333h21.333333a64 64 0 0 0 64 64c29.44 0 53.333333-20.053333 61.866667-47.36L469.333333 597.333333z") }); + crackList.Add(new MenuItem { Header = "删除WPS图标", IconPath = StreamGeometry.Parse("M558.6 404.2c-9.5-7.1-23.6-10.7-42.6-10.7h-36.3v101.3H516c18.9 0 33.1-3.6 42.6-10.7s14.2-20.4 14.2-39.9c0-19.6-4.8-32.9-14.2-40z m-44.7-338c-239.1 0-433 193.9-433 433s193.9 433 433 433 433-193.9 433-433-193.9-433-433-433zM380.4 629.7h-31.5l-28.4-196.4h-3.2L289 629.7h-31.5l-34.7-265.4h34.7l17.3 167.2h3.2l23.6-167.2h34.7l23.6 167.2h3.2l17.3-167.2h34.7l-34.7 265.4zM587 502.4c-14.7 14.3-35.2 21.5-61.5 21.5h-45.7v102.8h-36.3V364.3h82c26.3 0 46.8 7.2 61.5 21.5 14.7 14.3 22.1 33.8 22.1 58.3-0.1 24.5-7.4 43.9-22.1 58.3zM784.8 609c-15.2 13.8-36 20.7-62.3 20.7-26.3 0-46.8-7.1-61.5-21.5-14.7-14.3-22-32.2-22-53.7v-10.7h36.3v9.2c0 15.3 4.7 27.1 14.2 35.3 9.5 8.2 20.5 12.3 33.1 12.3 16.8 0 29.2-4.3 37-13 7.9-8.7 11.8-19.2 11.8-31.5 0-10.2-4.7-19.7-14.2-28.4s-23.1-16.6-41-23.8c-25.2-9.2-43.4-19.4-54.4-30.7-11-11.2-16.6-24.5-16.6-39.9 0-21.5 7.6-38.9 22.9-52.2 15.2-13.3 33.4-19.9 54.4-19.9 27.3 0 47 7.9 59.1 23.8 12.1 15.9 18.1 33 18.1 51.4h-36.3c1-11.2-2.1-21.5-9.5-30.7s-17.9-13.8-31.5-13.8c-12.6 0-22.6 3.3-30 10-7.4 6.7-11 15.6-11 26.9 0 9.2 2.9 17.1 8.7 23.8 5.8 6.7 20.7 14.6 44.9 23.8 23.1 9.2 41 20.2 53.6 33 12.6 12.8 18.9 27.4 18.9 43.7 0.2 23.4-7.5 42.1-22.7 55.9z") }); + crackList.Add(new MenuItem { Header = "快速打开网络和共享中心", IconPath = StreamGeometry.Parse("M426.666667 170.666667v170.666666h170.666666V170.666667h-170.666666m256 0v170.666666h170.666666V170.666667h-170.666666m0 256v170.666666h170.666666v-170.666666h-170.666666m0 256v170.666666h170.666666v-170.666666h-170.666666m-85.333334 170.666666v-170.666666h-170.666666v170.666666h170.666666m-256 0v-170.666666H170.666667v170.666666h170.666666m0-256v-170.666666H170.666667v170.666666h170.666666m0-256V170.666667H170.666667v170.666666h170.666666m85.333334 256h170.666666v-170.666666h-170.666666v170.666666M170.666667 85.333333h682.666666c46.933333 0 85.333333 38.4 85.333334 85.333334v682.666666c0 46.933333-38.4 85.333333-85.333334 85.333334H170.666667c-46.933333 0-85.333333-38.4-85.333334-85.333334V170.666667c0-46.933333 38.4-85.333333 85.333334-85.333334z") }); + crackList.Add(new MenuItem { Header = "图标缓存清理", IconPath = StreamGeometry.Parse("M512 34.133333C248.149333 34.133333 34.133333 248.149333 34.133333 512s214.016 477.866667 477.866667 477.866667 477.866667-214.016 477.866667-477.866667S775.850667 34.133333 512 34.133333z m264.533333 422.912c0 22.357333-18.090667 40.448-40.448 40.448h-1.365333v166.741334c0 44.714667-36.181333 80.896-80.896 80.896h-14.336c0.341333-2.048 0.682667-4.096 0.682667-6.144v-70.314667c0-18.773333-15.36-34.133333-34.133334-34.133333s-34.133333 15.36-34.133333 34.133333v70.314667c0 2.048 0.170667 4.096 0.682667 6.144h-26.965334c0.341333-1.877333 0.512-3.925333 0.512-6.144v-70.485334c0-18.773333-15.36-34.133333-34.133333-34.133333s-34.133333 15.36-34.133333 34.133333v70.485334c0 2.048 0.170667 4.096 0.682666 6.144h-26.965333c0.341333-2.048 0.682667-4.096 0.682667-6.144v-70.314667c0-18.773333-15.36-34.133333-34.133334-34.133333s-34.133333 15.36-34.133333 34.133333v70.314667c0 2.048 0.170667 4.096 0.682667 6.144h-14.336c-44.714667 0-80.896-36.181333-80.896-80.896v-166.741334h-1.365334c-22.357333 0-40.448-18.090667-40.448-40.448v-30.549333c0-22.357333 18.090667-40.448 40.448-40.448h148.138667c11.093333 0 20.138667-9.045333 20.138667-20.309333v-70.997334c0-11.093333 9.045333-20.138667 20.138666-20.138666h70.997334c11.093333 0 20.138667 9.045333 20.138666 20.138666v70.997334c0 11.093333 9.045333 20.309333 20.138667 20.309333h148.138667c22.357333 0 40.448 18.090667 40.448 40.448v30.549333z") }); + crackList.Add(new MenuItem { Header = "MySQL定时备份", IconPath = StreamGeometry.Parse("M915.2 844.8l-6.4-8.533333c-6.4-10.666667-12.8-19.2-21.333333-27.733334-6.4-6.4-21.333333-14.933333-23.466667-23.466666 6.4 0 10.666667 0 14.933333-2.133334 8.533333-2.133333 14.933333 0 23.466667-2.133333 4.266667 0 6.4-2.133333 10.666667-2.133333v-2.133334c-4.266667-4.266667-6.4-10.666667-10.666667-14.933333-10.666667-12.8-21.333333-23.466667-34.133333-34.133333-6.4-6.4-14.933333-10.666667-21.333334-14.933334-2.133333-2.133333-6.4-2.133333-8.533333-6.4-2.133333-6.4-6.4-12.8-6.4-19.2-4.266667-14.933333-10.666667-29.866667-14.933333-44.8-2.133333-10.666667-4.266667-19.2-8.533334-29.866666-17.066667-40.533333-42.666667-74.666667-78.933333-100.266667-8.533333-6.4-17.066667-10.666667-27.733333-12.8-6.4 0-10.666667-2.133333-17.066667-2.133333-4.266667-2.133333-6.4-6.4-8.533333-8.533334-10.666667-8.533333-23.466667-14.933333-36.266667-19.2-8.533333-2.133333-17.066667 2.133333-19.2 10.666667-8.533333 14.933333 6.4 32 10.666667 40.533333 4.266667 6.4 6.4 12.8 8.533333 19.2 2.133333 4.266667 2.133333 8.533333 2.133333 12.8 2.133333 10.666667 4.266667 23.466667 8.533334 34.133334 2.133333 6.4 4.266667 10.666667 6.4 14.933333 2.133333 2.133333 4.266667 6.4 4.266666 8.533333-2.133333 6.4-6.4 10.666667-6.4 17.066667-10.666667 25.6-10.666667 55.466667-2.133333 83.2 2.133333 6.4 10.666667 21.333333 21.333333 17.066667 10.666667-4.266667 10.666667-17.066667 14.933334-29.866667 0-2.133333 2.133333-4.266667 2.133333-6.4 2.133333 6.4 4.266667 14.933333 6.4 21.333333 6.4 12.8 14.933333 25.6 25.6 36.266667 4.266667 6.4 10.666667 10.666667 14.933333 17.066667 10.666667 8.533333 4.266667 10.666667 83.2 49.066666 0 0 32 14.933333 46.933334 21.333334 14.933333 6.4 29.866667 10.666667 44.8 14.933333 2.133333 0 2.133333-4.266667 2.133333-6.4zM693.333333 550.4c-2.133333-8.533333-6.4-19.2-10.666666-25.6-6.4-6.4 2.133333-12.8 10.666666-10.666667h2.133334c4.266667 0 6.4 4.266667 8.533333 6.4 0 2.133333 2.133333 2.133333 2.133333 4.266667 2.133333 10.666667-2.133333 23.466667-12.8 25.6z M46.933333 290.133333a371.2 119.466667 0 1 0 742.4 0 371.2 119.466667 0 1 0-742.4 0Z M678.4 793.6h-6.4c-14.933333 0-53.333333-6.4-76.8-55.466667v-4.266666c-6.4-17.066667-10.666667-36.266667-10.666667-55.466667-49.066667 8.533333-106.666667 12.8-166.4 12.8-164.266667 0-302.933333-34.133333-352-81.066667-8.533333-6.4-19.2-2.133333-19.2 8.533334v113.066666c0 66.133333 166.4 119.466667 371.2 119.466667 110.933333 0 211.2-14.933333 277.333334-40.533333-6.4-4.266667-10.666667-10.666667-17.066667-17.066667zM418.133333 631.466667c64 0 123.733333-4.266667 174.933334-14.933334v-2.133333c0-2.133333-2.133333-4.266667-2.133334-6.4-4.266667-12.8-6.4-25.6-10.666666-40.533333 0-2.133333-2.133333-6.4-2.133334-8.533334 0-2.133333-2.133333-2.133333-2.133333-4.266666v-4.266667l-2.133333-2.133333c-6.4-12.8-27.733333-46.933333-14.933334-87.466667-44.8 6.4-91.733333 8.533333-142.933333 8.533333-164.266667 0-302.933333-34.133333-352-81.066666-8.533333-6.4-19.2-2.133333-19.2 8.533333V512c0 66.133333 166.4 119.466667 373.333333 119.466667z") }); + crackList.Add(new MenuItem { Header = "键盘钩子", IconPath = StreamGeometry.Parse("M896 819.2h-819.2C34.4576 819.2 0 784.7424 0 742.4v-409.6C0 290.4576 34.4576 256 76.8 256h819.2c42.3424 0 76.8 34.4576 76.8 76.8v409.6c0 42.3424-34.4576 76.8-76.8 76.8zM76.8 307.2a25.6 25.6 0 0 0-25.6 25.6v409.6a25.6 25.6 0 0 0 25.6 25.6h819.2a25.6 25.6 0 0 0 25.6-25.6v-409.6a25.6 25.6 0 0 0-25.6-25.6h-819.2z M179.2 409.6h-51.2a25.6 25.6 0 0 1 0-51.2h51.2a25.6 25.6 0 0 1 0 51.2zM844.8 409.6h-51.2a25.6 25.6 0 0 1 0-51.2h51.2a25.6 25.6 0 0 1 0 51.2zM230.4 512h-102.4a25.6 25.6 0 0 1 0-51.2h102.4a25.6 25.6 0 0 1 0 51.2zM179.2 716.8h-51.2a25.6 25.6 0 0 1 0-51.2h51.2a25.6 25.6 0 0 1 0 51.2zM588.8 716.8h-307.2a25.6 25.6 0 0 1 0-51.2h307.2a25.6 25.6 0 0 1 0 51.2zM742.4 716.8h-51.2a25.6 25.6 0 0 1 0-51.2h51.2a25.6 25.6 0 0 1 0 51.2zM844.8 614.4h-153.6a25.6 25.6 0 0 1 0-51.2H819.2V486.4a25.6 25.6 0 0 1 51.2 0v102.4a25.6 25.6 0 0 1-25.6 25.6zM281.6 614.4h-153.6a25.6 25.6 0 0 1 0-51.2h153.6a25.6 25.6 0 0 1 0 51.2zM307.2 384a25.6 25.6 0 1 1-51.2 0 25.6 25.6 0 0 1 51.2 0zM409.6 384a25.6 25.6 0 1 1-51.2 0 25.6 25.6 0 0 1 51.2 0zM512 384a25.6 25.6 0 1 1-51.2 0 25.6 25.6 0 0 1 51.2 0zM614.4 384a25.6 25.6 0 1 1-51.2 0 25.6 25.6 0 0 1 51.2 0zM716.8 384a25.6 25.6 0 1 1-51.2 0 25.6 25.6 0 0 1 51.2 0zM358.4 486.4a25.6 25.6 0 1 1-51.2 0 25.6 25.6 0 0 1 51.2 0zM460.8 486.4a25.6 25.6 0 1 1-51.2 0 25.6 25.6 0 0 1 51.2 0zM563.2 486.4a25.6 25.6 0 1 1-51.2 0 25.6 25.6 0 0 1 51.2 0zM665.6 486.4a25.6 25.6 0 1 1-51.2 0 25.6 25.6 0 0 1 51.2 0z M768 486.4a25.6 25.6 0 1 1-51.2 0 25.6 25.6 0 0 1 51.2 0zM409.6 588.8a25.6 25.6 0 1 1-51.2 0 25.6 25.6 0 0 1 51.2 0zM512 588.8a25.6 25.6 0 1 1-51.2 0 25.6 25.6 0 0 1 51.2 0zM614.4 588.8a25.6 25.6 0 1 1-51.2 0 25.6 25.6 0 0 1 51.2 0z") }); + crackList.Add(new MenuItem { Header = "串口转键盘输入", IconPath = StreamGeometry.Parse("M1153.728 32c22.592 0 36.8 8.448 52.736 22.72 20.8 18.752 30.016 30.464 30.016 60.672v599.04c0 22.784-7.552 45.504-22.592 60.672a81.024 81.024 0 0 1-52.672 22.784H114.816c-22.592 0-37.632-7.616-52.672-22.784C39.552 759.936 32 737.28 32 714.432v-599.04c0-22.72 7.552-45.44 22.592-60.672C69.632 39.68 92.16 32 107.264 32z m-82.816 515.648h-60.288c-7.552 0-7.552 0-7.552 7.68v60.608c0 7.616 0 7.616 7.552 7.616h60.224c7.552 0 7.552 0 7.552-7.616v-60.672c0-7.552 0-7.68-7.488-7.68z m-316.224-106.112c0 7.616 0 7.616 7.488 7.616h67.776c7.552 0 7.552 0 7.552-7.616V380.8c0-7.488 0.128-7.488-7.488-7.552h-67.84c-7.552 0-7.552 0-7.552 7.552v60.8z m90.304-159.232h60.224c7.552 0 7.552 0 7.552-7.552V206.464c0-7.552 0-7.552-7.552-7.552h-60.16c-7.68 0-7.68 0-7.68 7.552v68.288c0 7.552 0 7.552 7.68 7.552z m82.816 166.912h143.04c7.552 0 7.552 0 7.552-7.616V206.464c0-7.552 0.128-7.552-7.488-7.68h-67.84c-7.488 0-7.488 0-7.488 7.68v166.784h-67.776c-7.552 0-7.552 0-7.552 7.552v60.8c0 7.616 0 7.616 7.552 7.616z m-301.12 546.368l112.896-136.512H513.728l112.96 136.512z m-361.408-448h-60.16c-7.552 0-7.552 0-7.552 7.68v60.672c0 7.616 0 7.616 7.488 7.616h60.352c7.488 0 7.488 0 7.488-7.616v-60.672c0-7.552 0.384-7.68-7.616-7.68z m640 0h-542.08c-7.552 0-7.552 0-7.552 7.68v60.672c0 7.616 0 7.616 7.552 7.616h542.08c7.488 0 7.488 0 7.488-7.616v-60.672c0-7.552 0-7.68-7.552-7.68zM445.952 449.216h60.16c7.552 0 7.552 0 7.552-7.616V380.8c0-7.488 0-7.488-7.488-7.552h-60.224c-7.552 0-7.552 0-7.552 7.552v60.8c0 7.552 0 7.552 7.552 7.552zM197.568 274.752c0 7.552 0 7.552 7.488 7.552h60.352c7.488 0 7.488 0 7.488-7.552V206.464c0-7.552 0-7.552-7.616-7.552h-60.16c-7.552 0-15.104 0-7.552 7.552v68.288z m489.344 7.552h60.288c7.552 0 7.552 0 7.552-7.552V206.464c0-7.552 0-7.552-7.552-7.552h-60.288c-7.552 0-7.552 0-7.552 7.552v68.288c0 7.552 0 7.552 7.552 7.552z m-489.344 98.56v60.672c0 7.616 0 7.616 7.488 7.616H348.16c7.552 0 7.552 0 7.552-7.616V380.8c0-7.488 0-7.552-7.552-7.552H205.056c-7.488 0-7.488 0-7.488 7.552z m165.632-98.56h60.16c7.552 0 7.552 0 7.552-7.552V206.464c0-7.552 0-7.552-7.488-7.552h-60.224c-7.552 0-7.552 0-7.552 7.552v68.288c0 7.552 0 7.552 7.552 7.552zM596.48 441.6c0 7.616 0 7.616 7.616 7.616h60.16c7.68 0 7.68 0 7.68-7.616V380.8c0-7.488 0-7.488-7.68-7.552h-60.16c-7.616 0-7.616 0-7.616 7.552v60.8zM521.28 282.304h67.776c7.488 0 7.488 0 7.488-7.552V206.464c0-7.552-7.488-7.552-14.976-7.552h-60.288s-7.552 0-7.552 7.552v68.288c0 7.552 0 7.552 7.552 7.552z") }); + crackList.Add(new MenuItem { Header = "服务器性能监控", IconPath = StreamGeometry.Parse("M0 728v136c0 35.3 28.7 64 64 64h896c35.3 0 64-28.7 64-64V728c0-4.4-3.6-8-8-8H8c-4.4 0-8 3.6-8 8z m774 96c0 4.4-3.6 8-8 8H424c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h342c4.4 0 8 3.6 8 8v48z m122 0c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v48zM0 536v144c0 4.4 3.6 8 8 8h1008c4.4 0 8-3.6 8-8V536c0-4.4-3.6-8-8-8H8c-4.4 0-8 3.6-8 8z m774 96c0 4.4-3.6 8-8 8H424c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h342c4.4 0 8 3.6 8 8v48z m122 0c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v48zM0 344v144c0 4.4 3.6 8 8 8h1008c4.4 0 8-3.6 8-8V344c0-4.4-3.6-8-8-8H8c-4.4 0-8 3.6-8 8z m774 96c0 4.4-3.6 8-8 8H424c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h342c4.4 0 8 3.6 8 8v48z m122 0c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v48zM960 96H64c-35.3 0-64 28.7-64 64v136c0 4.4 3.6 8 8 8h1008c4.4 0 8-3.6 8-8V160c0-35.3-28.7-64-64-64zM774 248c0 4.4-3.6 8-8 8H424c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h342c4.4 0 8 3.6 8 8v48z m122 0c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v48z") }); + } + //else if (OperatingSystem.IsMacOS()) + //{ + //} + //else if (OperatingSystem.IsLinux()) + //{ + //} + + ObservableCollection otherList = new ObservableCollection(); + otherList.Add(new MenuItem { Header = "人民币转大写", IconPath = StreamGeometry.Parse("M447.488 764.928l-197.632 0q-27.648-2.048-42.496-23.04t-17.92-46.592q3.072-25.6 17.92-41.984t42.496-18.432l197.632 0 0-64.512-197.632-1.024q-27.648-2.048-42.496-19.456t-17.92-44.032q3.072-25.6 17.92-41.984t42.496-18.432l147.456 0-137.216-241.664q-10.24-12.288-19.456-31.232t-8.192-41.472q5.12-28.672 20.48-46.592t57.344-23.04q24.576 2.048 44.032 16.896t31.744 32.256l155.648 284.672 171.008-286.72q12.288-17.408 31.744-30.72t44.032-16.384q15.36 1.024 28.16 4.096t22.528 10.24 16.384 20.48 9.728 34.816q0 29.696-20.48 56.32l-158.72 258.048 150.528 0q26.624 2.048 41.984 18.432t17.408 41.984q-2.048 26.624-17.92 45.056t-42.496 20.48l-195.584 1.024 0 62.464 196.608 0q26.624 2.048 41.984 20.48t17.408 44.032q-2.048 26.624-17.408 44.544t-41.984 19.968l-196.608-1.024 0 108.544q-4.096 87.04-95.232 87.04-45.056 0-70.144-21.504t-27.136-65.536l0-106.496z") }); + otherList.Add(new MenuItem { Header = "进制转换及ASCII转换", IconPath = StreamGeometry.Parse("M624.042667 336.042667a64 64 0 0 0 0 128h192a64 64 0 0 0 0-128h-32V112.042667a64 64 0 0 0-64-64H624.042667a64 64 0 0 0 0 128h32v160h-32zM400.042667 848.042667h-32V624.042667a64 64 0 0 0-64-64H208.042667a64 64 0 0 0 0 128h32v160h-32a64 64 0 0 0 0 128h192a64 64 0 0 0 0-128zM400.042667 48.042667h-192a64 64 0 0 0-64 64v288c0 35.328 28.672 64 64 64h192a64 64 0 0 0 64-64V112.042667a64 64 0 0 0-64-64z m-64 288h-64V176.042667h64v160zM816.042667 560.042667h-192a64 64 0 0 0-64 64v288c0 35.328 28.672 64 64 64h192a64 64 0 0 0 64-64V624.042667a64 64 0 0 0-64-64z m-64 288h-64V688.042667h64v160z") }); + otherList.Add(new MenuItem { Header = "SQL StringBuilder封装", IconPath = StreamGeometry.Parse("M271.1 327.5 208.6 382.7c-22-30.6-44.3-45.8-67.1-45.8-11.1 0-20.1 3-27.2 8.9-7 5.9-10.6 12.6-10.6 20.1 0 7.4 2.5 14.5 7.6 21.1 6.8 8.9 27.5 27.9 61.9 57 32.2 26.9 51.8 43.9 58.6 51 17.1 17.3 29.3 33.8 36.4 49.6 7.1 15.8 10.7 33 10.7 51.7 0 36.4-12.6 66.5-37.7 90.2-25.2 23.7-58 35.6-98.4 35.6-31.6 0-59.1-7.7-82.6-23.2-23.4-15.5-43.5-39.8-60.2-73l71-42.8c21.3 39.2 45.9 58.8 73.7 58.8 14.5 0 26.7-4.2 36.6-12.7 9.9-8.4 14.8-18.2 14.8-29.3 0-10.1-3.7-20.1-11.2-30.2-7.5-10.1-23.9-25.4-49.2-46.1-48.3-39.4-79.6-69.8-93.7-91.2-14.1-21.4-21.1-42.8-21.1-64.1 0-30.8 11.7-57.2 35.2-79.2 23.4-22 52.4-33 86.8-33 22.1 0 43.2 5.1 63.3 15.4C226.1 281.6 247.8 300.3 271.1 327.5z M709.2 646l77.2 99.8-100 0-39.2-50.5c-32.4 17.8-68.6 26.6-108.4 26.6-66.6 0-122-23-166.1-68.9-44.1-45.9-66.1-100.7-66.1-164.2 0-42.4 10.3-81.4 30.8-116.9 20.5-35.5 48.7-63.7 84.7-84.6 35.9-20.9 74.5-31.4 115.7-31.4 63 0 117 22.7 162.2 68.2 45.2 45.4 67.8 100.8 67.8 166.2C767.8 550.5 748.2 602.3 709.2 646zM656.5 577.8c17.9-26.5 26.8-55.9 26.8-88.1 0-42-14.2-77.7-42.6-107.1-28.4-29.3-62.7-44-103-44-41.5 0-76.2 14.3-104.2 42.8-28 28.6-42 64.8-42 108.9 0 49.1 17.6 87.9 52.9 116.4 27.6 22.3 58.9 33.5 94 33.5 20.1 0 39.1-3.9 56.8-11.8l-79.4-102.2 100.7 0L656.5 577.8z M816.5 267.1l84.4 0 0 363.1L1024 630.2l0 80.5L816.5 710.7 816.5 267.1z") }); + otherList.Add(new MenuItem { Header = "角度弧度转换", IconPath = StreamGeometry.Parse("M54 86c0-28.509 34.469-42.786 54.627-22.627l853 853C981.787 936.53 967.51 971 939 971H86c-17.673 0-32-14.327-32-32z m64 77.255l-0.001 189.295h31.976c17.673 0 32 14.327 32 32 0 17.496-14.042 31.713-31.47 31.996l-0.53 0.004h-31.976v63.95h31.976c17.673 0 32 14.327 32 32 0 17.496-14.042 31.713-31.47 31.996l-0.53 0.004h-31.976v63.95h31.976c17.673 0 32 14.327 32 32 0 17.496-14.042 31.713-31.47 31.996l-0.53 0.004h-31.976v63.95h31.976c17.673 0 32 14.327 32 32 0 17.496-14.042 31.713-31.47 31.996l-0.53 0.004h-31.976L118 907h106.599l0.001-31.975c0-17.673 14.327-32 32-32 17.496 0 31.713 14.042 31.996 31.47l0.004 0.53-0.001 31.975h63.95l0.001-31.975c0-17.673 14.327-32 32-32 17.496 0 31.713 14.042 31.996 31.47l0.004 0.53-0.001 31.975H480.5v-31.975c0-17.673 14.327-32 32-32 17.496 0 31.713 14.042 31.996 31.47l0.004 0.53V907h63.949l0.001-31.975c0-17.673 14.327-32 32-32 17.496 0 31.713 14.042 31.996 31.47l0.004 0.53-0.001 31.975h189.296L118 163.255z m127.925 327.92c0-28.509 34.469-42.786 54.627-22.627l255.9 255.9c20.16 20.158 5.882 54.627-22.627 54.627h-255.9c-17.673 0-32-14.327-32-32z m63.999 77.254v146.645h146.645L309.924 568.429z") }); + otherList.Add(new MenuItem { Header = "MD5、DES", IconPath = StreamGeometry.Parse("M862.435556 200.248889h-34.133334s-79.644444-2.275556-159.288889-34.133333c-81.92-34.133333-136.533333-72.817778-136.533333-72.817778l-20.48-15.928889-20.48 13.653333s-54.613333 38.684444-136.533333 72.817778c-79.644444 34.133333-159.288889 34.133333-159.288889 34.133333H161.564444v359.537778c0 179.768889 227.555556 370.915556 350.435556 370.915556s350.435556-191.146667 350.435556-370.915556V200.248889z m-100.124445 159.288889L509.724444 614.4c-6.826667 6.826667-15.928889 9.102222-25.031111 9.102222-6.826667 0-15.928889-2.275556-20.48-6.826666l-170.666666-136.533334c-13.653333-11.377778-18.204444-34.133333-4.551111-47.786666 11.377778-13.653333 34.133333-18.204444 47.786666-4.551112l147.911111 118.328889 232.106667-232.106666c13.653333-13.653333 34.133333-13.653333 47.786667 0s11.377778 31.857778-2.275556 45.511111z") }); + otherList.Add(new MenuItem { Header = "地标写入", IconPath = StreamGeometry.Parse("M782.8 112.2C713.5 42.9 617.7 0 512 0 300.5 0 129 171.5 129 383c0 84 27.1 161.8 73.2 225.3L512 1024l302.6-406.5c50.2-65 80.4-145.9 80.4-234.5 0-105.7-42.9-201.5-112.2-270.8zM512 563.2c-99.3 0-180.2-80.4-180.2-180.2 0-99.8 80.9-179.7 180.2-179.7s180.2 80.4 180.2 180.2S611.3 563.2 512 563.2z") }); + + MenuItems.Add(new MenuItem { Header = "PLC通信调试", IconPath = StreamGeometry.Parse("M224.619355 171.767742v85.883871h26.425806v231.225806h13.212904c1.123097 0 2.219768 0.066065 3.303225 0.198194V254.348387l128.825807-0.006606V171.767742h404.645161v82.574039l127.174194 0.006606v568.154839h-660.645162V594.382452l-1.6384 0.145342L264.258065 594.580645h-13.212904v227.922581H132.129032V594.574039L118.916129 594.580645a26.425806 26.425806 0 0 1-26.392774-25.104516L92.490323 568.154839v-52.851613a26.425806 26.425806 0 0 1 25.104516-26.392774L118.916129 488.877419h13.212903V257.651613l26.4192-0.006607L158.554839 171.767742h66.064516z m0 436.025806h-59.458065v184.980646h59.458065V607.793548z m-13.212903 151.948387v13.212904h-33.032258v-13.212904h33.032258z m668.176516-424.081341h-13.212903v18.498064L343.535484 354.152052V336.929032h-13.212903v30.442529h536.047484V396.387097h-168.953394v284.077419h168.953394v26.425807H330.322581v30.442529h13.212903V720.103226h522.834581v25.203613h13.212903V335.660594zM211.406452 726.709677v13.212904h-33.032258v-13.212904h33.032258z m0-33.032258v13.212904h-33.032258v-13.212904h33.032258z m363.354838-34.287484h-46.245161v19.819355h46.245161v-19.819355z m-171.767742 0h-46.245161v19.819355h46.245161v-19.819355z m85.883871 0h-46.245161v19.819355h46.245161v-19.819355z m171.767742 0h-46.245161v19.819355h46.245161v-19.819355zM211.406452 660.645161v13.212904h-33.032258v-13.212904h33.032258z m654.963613-251.045161v257.651613h-155.740491V409.6h155.740491zM211.406452 627.612903v13.212903h-33.032258v-13.212903h33.032258z m277.470967-14.468129h-46.245161v19.819355h46.245161v-19.819355z m171.767742 0h-46.245161v19.819355h46.245161v-19.819355z m-257.651613 0h-46.245161v19.819355h46.245161v-19.819355z m171.767742 0h-46.245161v19.819355h46.245161v-19.819355zM231.225806 442.632258H158.554839v13.212903h72.670967v-13.212903z m171.767742-46.245161h-46.245161v39.638709h46.245161v-39.638709z m85.883871 0h-46.245161v39.638709h46.245161v-39.638709z m85.883871 0h-46.245161v39.638709h46.245161v-39.638709z m85.883871 0h-46.245161v39.638709h46.245161v-39.638709z m-449.238709 13.212903h-52.851613v13.212903h52.851613v-13.212903z m-19.819355-33.032258h-33.032258v13.212903h33.032258v-13.212903z m556.593548-151.948387H449.23871v29.722426h298.941935V224.619355zM191.587097 198.193548a19.819355 19.819355 0 1 0 0 39.63871 19.819355 19.819355 0 0 0 0-39.63871z"), Children = plcCommList }); + MenuItems.Add(new MenuItem { Header = "网络相关", IconPath = StreamGeometry.Parse("M512 96c229.76 0 416 186.24 416 416S741.76 928 512 928 96 741.76 96 512 282.24 96 512 96z m-32 448l-127.317333 0.021333c0.896 20.48 2.624 40.405333 5.12 59.669334l1.984 14.293333 2.474666 15.253333c19.754667 112.896 65.728 197.738667 117.76 222.997334L480 544z m191.317333 0.021333L544 544v312.234667c50.858667-24.725333 95.936-106.368 116.373333-215.509334l1.365334-7.488 2.474666-15.232a701.013333 701.013333 0 0 0 7.104-73.984z m-382.698666 0H161.429333c11.648 129.066667 92.992 238.08 206.101334 289.066667-22.122667-34.282667-40.362667-76.416-53.76-124.032l-3.029334-11.093333-3.52-14.165334-3.242666-14.464a744.490667 744.490667 0 0 1-15.36-125.312z m573.952 0H735.36a752.661333 752.661333 0 0 1-12.672 112.128l-2.688 13.184-3.242667 14.464-3.52 14.186667c-13.653333 52.138667-32.96 98.197333-56.789333 135.104 113.109333-50.986667 194.453333-160 206.08-289.066667zM367.530667 190.890667l-2.858667 1.301333C253.013333 243.733333 172.970667 352 161.429333 480h127.189334c1.536-39.04 5.866667-76.693333 12.672-112.149333l2.688-13.184 3.242666-14.464 3.52-14.186667c13.653333-52.138667 32.96-98.197333 56.789334-135.104zM480 167.765333c-50.709333 24.618667-95.68 105.898667-116.202667 214.592l-1.536 8.405334-2.474666 15.232a701.034667 701.034667 0 0 0-7.104 74.005333H480V167.765333z m176.469333 23.146667l2.56 4.053333c20.906667 33.429333 38.229333 73.984 51.093334 119.552l3.136 11.52 3.52 14.165334 3.242666 14.464c8.362667 39.253333 13.632 81.408 15.36 125.333333h127.189334c-11.626667-129.088-92.970667-238.101333-206.101334-289.066667zM544 167.765333L544 480h127.317333a707.136 707.136 0 0 0-5.333333-61.376l-1.770667-12.629333-2.474666-15.232c-19.754667-112.874667-65.706667-197.717333-117.717334-222.997334z"), Children = netList }); + MenuItems.Add(new MenuItem { Header = "图片相关", IconPath = StreamGeometry.Parse("M852.3 189h-682c-30.2 0-54.6 24.4-54.6 54.6v550.1c0 30.2 24.4 54.6 54.6 54.6h682c30.2 0 54.6-24.5 54.6-54.6V243.6c0.1-30.2-24.4-54.6-54.6-54.6zM713.5 338.8c17.7 0 32.1 14.4 32.1 32.1 0 17.7-14.4 32.1-32.1 32.1s-32.1-14.4-32.1-32.1c0-17.7 14.4-32.1 32.1-32.1z m168.1 238.5c-2.8 5.4-7.5 9.4-13.4 11.3-5.8 1.9-12 1.3-17.4-1.5L724 521.6c-10.5-5.4-23.3-4.3-32.7 3L620.1 580c-10.5 8.2-23.1 12.3-35.8 12.3-12.5 0-25-4-35.4-12l-196-149.9c-12.5-9.5-30.2-7.6-40.3 4.4L189 582c-8.1 9.6-22.5 10.9-32.2 2.8-4.7-3.9-7.5-9.4-8.1-15.5-0.5-6.1 1.3-12 5.3-16.7l134-159.3c20.1-23.9 55.2-27.8 80-8.8l197.8 151.2c11 8.4 26.3 8.3 37.2-0.2l69.4-54c17.9-13.9 42.4-16.1 62.5-5.8l136.9 70.7c11.2 5.9 15.6 19.7 9.8 30.9z"), Children = imageList }); + if (OperatingSystem.IsWindows()) + { + MenuItems.Add(new MenuItem { Header = "破解及系统相关", IconPath = StreamGeometry.Parse("M91.83 661.86c1.87 34.09 30.28 60.65 64.43 60.22h331.23v91.74H360.74c-13.34 0-24.16 10.82-24.16 24.16s10.82 24.16 24.16 24.16h302.53c13.34 0 24.16-10.82 24.16-24.16s-10.82-24.16-24.16-24.16H536.51v-91.74h331.24c34.14 0.43 62.55-26.13 64.43-60.22v-55.32H91.83v55.32zM867.74 161.86H156.26c-34.93-0.39-63.66 27.41-64.43 62.33v328.43h840.34V224.19c-0.77-34.93-29.5-62.72-64.43-62.33z"), Children = crackList }); + } + MenuItems.Add(new MenuItem { Header = "其他", IconPath = StreamGeometry.Parse("M512 65.311495c-246.699682 0-446.688505 199.989847-446.688505 446.688505s199.989847 446.688505 446.688505 446.688505S958.688505 758.699682 958.688505 512 758.698658 65.311495 512 65.311495zM309.953308 567.255465c-30.517037 0-55.255465-24.738427-55.255465-55.255465s24.738427-55.255465 55.255465-55.255465 55.255465 24.738427 55.255465 55.255465S340.470345 567.255465 309.953308 567.255465zM512 567.255465c-30.517037 0-55.255465-24.738427-55.255465-55.255465s24.738427-55.255465 55.255465-55.255465c30.517037 0 55.255465 24.738427 55.255465 55.255465S542.517037 567.255465 512 567.255465zM714.046692 567.255465c-30.517037 0-55.255465-24.738427-55.255465-55.255465s24.738427-55.255465 55.255465-55.255465 55.255465 24.738427 55.255465 55.255465S744.56373 567.255465 714.046692 567.255465z"), Children = otherList }); + + + //所有菜单页面的标题集合 + foreach (MenuItem item in MenuItems) + { + MenuHeaderList.AddRange(item.Children?.Select(it => it.Header).ToList()); + } + //页面字典初始化 + SelectedMenuItem = MenuItems.Where(it => it.Header == "PLC通信调试").FirstOrDefault()?.Children?.Where(it => it.Header == "Modbus调试2").FirstOrDefault(); + ContentPage = PageDict.FirstOrDefault().Value; + } + + + private UserControl GetPage(string pageName) + { + UserControl userControl1 = null; + if (pageName == "倍福ADS调试") + { + userControl1 = new 倍福ADS调试(); + } + else if (pageName == "三菱MC协议") + { + userControl1 = new 三菱MC协议l(); + } + else if (pageName == "MC-3E服务模拟") + { + userControl1 = new MC3E服务模拟(); + } + else if (pageName == "OPCUA调试") + { + userControl1 = new OPCUA调试(); + } + else if (pageName == "Modbus调试1") + { + userControl1 = new Modbus调试1(); + } + else if (pageName == "Modbus调试2") + { + userControl1 = new Modbus调试2(); + } + else if (pageName == "ModbusTCP服务") + { + userControl1 = new Modbus服务(); + } + else if (pageName == "西门子PLC调试") + { + userControl1 = new 西门子PLC调试(); + } + else if (pageName == "欧姆龙Fins调试") + { + userControl1 = new 欧姆龙Fins调试(); + } + else if (pageName == "串口调试工具") + { + userControl1 = new 串口调试工具(); + } + else if (pageName == "Socket调试") + { + userControl1 = new Socket调试(); + } + else if (pageName == "ModbusRTU") + { + userControl1 = new ModbusRTU(); + } + + else if (pageName == "HTTP调试") + { + userControl1 = new HTTP调试(); + } + else if (pageName == "FTP客户端") + { + userControl1 = new FTP客户端(); + } + else if (pageName == "FTP服务") + { + userControl1 = new FTP服务(); + } + else if (pageName == "端口扫描") + { + userControl1 = new 端口扫描(); + } + else if (pageName == "端口占用扫描") + { + userControl1 = new 端口占用扫描(); + } + else if (pageName == "网络状态检测") + { + userControl1 = new 网络状态检测(); + } + else if (pageName == "二维码条形码生成") + { + userControl1 = new 二维码条形码生成(); + } + else if (pageName == "导航二维码生成") + { + userControl1 = new 导航二维码生成(); + } + else if (pageName == "二维码条形码解析") + { + userControl1 = new 二维码条形码解析(); + } + else if (pageName == "GIF分割") + { + userControl1 = new GIF分割(); + } + else if (pageName == "图片转ICO") + { + userControl1 = new 图片转ICO(); + + } + //else if (pageName == "取颜色工具") + //{ + // userControl1 = new 取颜色工具(); + //} + else if (pageName == "色卡包") + { + userControl1 = new 色卡包(); + } + else if (pageName == "远程路径软链接") + { + userControl1 = new 远程路径软链接(); + } + else if (pageName == "AdobeAcrobatXI破解") + { + userControl1 = new AdobeAcrobatXI破解(); + } + else if (pageName == "猫猫回收站") + { + userControl1 = new 猫猫回收站(); + } + else if (pageName == "删除WPS图标") + { + userControl1 = new 删除WPS图标(); + } + else if (pageName == "快速打开网络和共享中心") + { + userControl1 = new 快速打开网络和共享中心(); + } + else if (pageName == "图标缓存清理") + { + userControl1 = new 图标缓存清理(); + } + else if (pageName == "MySQL定时备份") + { + userControl1 = new MySQL定时备份(); + } + else if (pageName == "键盘钩子") + { + userControl1 = new 键盘钩子(); + } + else if (pageName == "串口转键盘输入") + { + userControl1 = new 串口转键盘输入(); + } + else if (pageName == "服务器性能监控") + { + userControl1 = new 服务器性能监控(); + } + else if (pageName == "人民币转大写") + { + userControl1 = new 人民币转大写(); + } + else if (pageName == "进制转换及ASCII转换") + { + userControl1 = new 进制转换及ASCII转换(); + } + else if (pageName == "SQL StringBuilder封装") + { + userControl1 = new SQLStringBuilder封装(); + } + else if (pageName == "角度弧度转换") + { + userControl1 = new 角度弧度转换(); + } + if (pageName == "MD5、DES") + { + userControl1 = new MD5DES(); + } + else if (pageName == "地标写入") + { + userControl1 = new 地标写入(); + } + if (userControl1 == null) + { + userControl1 = new UserControl1(); + ((UserControl1)userControl1).SetText(pageName); + } + return userControl1; + + } + } + + + public class MenuItem + { + public string Header { get; set; } + public StreamGeometry IconPath { get; set; } + public bool IsSeparator { get; set; } + public DelegateCommand NavigationCommand { get; set; } + + public MenuItem() + { + NavigationCommand = new DelegateCommand(OnNavigate); + } + + private void OnNavigate(object obj) + { + MessageBox.ShowOverlayAsync(Header ?? string.Empty, "Navigation Result"); + } + + public ObservableCollection Children { get; set; } = new ObservableCollection(); + + public IEnumerable GetLeaves() + { + if (this.Children.Count == 0) + { + yield return this; + yield break; + } + + foreach (var child in Children) + { + var items = child.GetLeaves(); + foreach (var item in items) + { + yield return item; + } + } + } + } +} diff --git a/常用工具集/Views/01PLC通信调试/MC3E服务模拟.axaml b/常用工具集/Views/01PLC通信调试/MC3E服务模拟.axaml new file mode 100644 index 0000000..5a7db4e --- /dev/null +++ b/常用工具集/Views/01PLC通信调试/MC3E服务模拟.axaml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/常用工具集/Views/02网络相关/FTP客户端.axaml.cs b/常用工具集/Views/02网络相关/FTP客户端.axaml.cs new file mode 100644 index 0000000..b32a3bc --- /dev/null +++ b/常用工具集/Views/02网络相关/FTP客户端.axaml.cs @@ -0,0 +1,13 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace 常用工具集; + +public partial class FTP客户端 : UserControl +{ + public FTP客户端() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/常用工具集/Views/02网络相关/FTP服务.axaml b/常用工具集/Views/02网络相关/FTP服务.axaml new file mode 100644 index 0000000..57fd57a --- /dev/null +++ b/常用工具集/Views/02网络相关/FTP服务.axaml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +