From 3ef7b8e83883ee4d4464c5026d35c10993b2b2fb Mon Sep 17 00:00:00 2001 From: Michael Helfenstein Date: Sun, 4 Jan 2026 20:04:03 +0100 Subject: [PATCH] - Support for .NET 10.0. - Removed Support for .NET Framework 4.7.1 (But still .NET Standard 2.0.) - Removed Dependency to Mbc.Hdf5Utils Nuget package. The code is now part of Mbc.Pcs.Net.DataRecorder. --- NET/AdsMapperCli/AdsMapperCli.csproj | 5 +- NET/Changelog.md | 18 +- NET/Directory.Build.props | 5 +- .../Mbc.Ads.Helper.ReadType.csproj | 2 +- .../Mbc.Ads.Mapper.Test.csproj | 2 +- NET/Mbc.Ads.Mapper/Mbc.Ads.Mapper.csproj | 4 +- .../Mbc.Ads.Utils.Test.csproj | 2 +- NET/Mbc.Ads.Utils/Mbc.Ads.Utils.csproj | 4 +- .../Mbc.Pcs.Net.Alarm.Mediator.csproj | 7 +- .../Mbc.Pcs.Net.Alarm.Service.Test.csproj | 4 +- .../Mbc.Pcs.Net.Alarm.Service.csproj | 7 +- .../Mbc.Pcs.Net.Alarm.Test.csproj | 2 +- .../Mbc.Pcs.Net.Alarm.csproj | 4 +- .../Mbc.Pcs.Net.Command.Test.csproj | 2 +- .../Mbc.Pcs.Net.Command.csproj | 4 +- .../Hdf5RingBuffer/RingBufferTest.cs | 2 +- .../Hdf5Utils/H5AttributeTest.cs | 127 ++++ .../Hdf5Utils/H5DataSetTest.cs | 83 +++ .../Hdf5Utils/H5FileTest.cs | 140 ++++ .../Hdf5Utils/H5GlobalLockTest.cs | 35 + .../Hdf5Utils/H5GroupTest.cs | 71 ++ .../Hdf5Utils/Hdf5LibCollection.cs | 10 + .../Hdf5Utils/Prototype/SingleChannelWrite.cs | 94 +++ .../Hdf5Utils/UseCases/WriteData.cs | 211 ++++++ .../Hdf5Utils/Utils/Hdf5LibFixture.cs | 42 ++ .../Mbc.Pcs.Net.DataRecorder.Test.csproj | 7 +- .../Hdf5RingBuffer/RingBuffer.cs | 2 +- .../Hdf5Utils/H5Attribute.cs | 294 ++++++++ .../Hdf5Utils/H5DataSet.cs | 636 ++++++++++++++++++ .../Hdf5Utils/H5DataSpace.cs | 193 ++++++ .../Hdf5Utils/H5Error.cs | 113 ++++ .../Hdf5Utils/H5File.cs | 115 ++++ .../Hdf5Utils/H5GlobalLock.cs | 28 + .../Hdf5Utils/H5Group.cs | 117 ++++ .../Hdf5Utils/H5Id.cs | 54 ++ .../Hdf5Utils/H5LinkIterator.cs | 47 ++ .../Hdf5Utils/H5Type.cs | 59 ++ .../Hdf5Utils/H5Utils.cs | 66 ++ .../Hdf5Utils/Nativ/H5IdAccesor.cs | 16 + .../Hdf5Utils/UnmanagedUtils.cs | 93 +++ .../Mbc.Pcs.Net.DataRecorder.csproj | 25 +- .../Mbc.Pcs.Net.Test.Util.csproj | 4 +- NET/Mbc.Pcs.Net.Test/Mbc.Pcs.Net.Test.csproj | 2 +- NET/Mbc.Pcs.Net.sln | 17 +- NET/Mbc.Pcs.Net/Mbc.Pcs.Net.csproj | 4 +- NET/Profiling/App.config | 6 - ....GeneratedMSBuildEditorConfig.editorconfig | 29 - NET/Profiling/Profiling.csproj | 59 -- NET/Profiling/Properties/AssemblyInfo.cs | 36 - NET/Samples/Cli/Cli.csproj | 5 +- NET/Samples/Profiling/Profiling.csproj | 15 + NET/{ => Samples}/Profiling/Program.cs | 0 52 files changed, 2738 insertions(+), 191 deletions(-) create mode 100644 NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5AttributeTest.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5DataSetTest.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5FileTest.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5GlobalLockTest.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5GroupTest.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/Hdf5LibCollection.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/Prototype/SingleChannelWrite.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/UseCases/WriteData.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/Utils/Hdf5LibFixture.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Attribute.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5DataSet.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5DataSpace.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Error.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5File.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5GlobalLock.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Group.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Id.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5LinkIterator.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Type.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Utils.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/Nativ/H5IdAccesor.cs create mode 100644 NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/UnmanagedUtils.cs delete mode 100644 NET/Profiling/App.config delete mode 100644 NET/Profiling/Profiling.GeneratedMSBuildEditorConfig.editorconfig delete mode 100644 NET/Profiling/Profiling.csproj delete mode 100644 NET/Profiling/Properties/AssemblyInfo.cs create mode 100644 NET/Samples/Profiling/Profiling.csproj rename NET/{ => Samples}/Profiling/Program.cs (100%) diff --git a/NET/AdsMapperCli/AdsMapperCli.csproj b/NET/AdsMapperCli/AdsMapperCli.csproj index eb3888d..7d1a7bd 100644 --- a/NET/AdsMapperCli/AdsMapperCli.csproj +++ b/NET/AdsMapperCli/AdsMapperCli.csproj @@ -2,14 +2,13 @@ Exe - net8.0 + net10.0 AdsMapperCli.Program - + - diff --git a/NET/Changelog.md b/NET/Changelog.md index 7f2a63b..ea7fab8 100644 --- a/NET/Changelog.md +++ b/NET/Changelog.md @@ -12,7 +12,23 @@ The following types of changes exist: - **Fixed** for any bug fixes. - **Security** in case of vulnerabilities. -## 2025 Summer, Version 5.0.1 +## 2026 New Year, Version 5.2.0 +### Removed +- Removed Support for .NET Framework 4.7.1 (But still .NET Standard 2.0.) +- Removed Dependency to Mbc.Hdf5Utils Nuget package. The code is now part of Mbc.Pcs.Net.DataRecorder. + +### Added +- Support for .NET 10.0. +- Add Automatic recconection behavior in PlcAdsConnectionService + +### Changed +- Update to Beckhoff.TwinCAT.Ads version 6.2.521 + +### Fixed +- fixed deadlock in PlcAdsConnectionService +- fixed handling PlcAdsStateReader when stop is called before start + +## 2025 Summer, Version 5.1.0 ### Feature - It is now possible to write Structs with the `CommandInputBuilder` and the `PrimitiveCommandArgumentHandler`. Actually it results in multiple write commands to the PLC. So each struct and it fields will be recursively written to the PLC also in recursive structs. The fields in the struct has to be flagged with the `{attribute 'PlcCommandInput'}`. The Struct values must in c# be of type of `CommandInputBuilder`. ```C# diff --git a/NET/Directory.Build.props b/NET/Directory.Build.props index 91d6247..9d116c2 100644 --- a/NET/Directory.Build.props +++ b/NET/Directory.Build.props @@ -56,12 +56,15 @@ limitations under the License. $(MSBuildProjectDirectory)\..\Build\Mbc.CodeAnalysis.ruleset true latest + <_SkipUpgradeNetAnalyzersNuGetWarning>true all - + + all + \ No newline at end of file diff --git a/NET/Mbc.Ads.Helper.ReadType/Mbc.Ads.Helper.ReadType.csproj b/NET/Mbc.Ads.Helper.ReadType/Mbc.Ads.Helper.ReadType.csproj index 1d6b8d2..a83a8d3 100644 --- a/NET/Mbc.Ads.Helper.ReadType/Mbc.Ads.Helper.ReadType.csproj +++ b/NET/Mbc.Ads.Helper.ReadType/Mbc.Ads.Helper.ReadType.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 diff --git a/NET/Mbc.Ads.Mapper.Test/Mbc.Ads.Mapper.Test.csproj b/NET/Mbc.Ads.Mapper.Test/Mbc.Ads.Mapper.Test.csproj index 1041eea..efceb11 100644 --- a/NET/Mbc.Ads.Mapper.Test/Mbc.Ads.Mapper.Test.csproj +++ b/NET/Mbc.Ads.Mapper.Test/Mbc.Ads.Mapper.Test.csproj @@ -2,7 +2,7 @@ - net471;net8.0 + net10.0;net8.0 Mbc.Ads.Mapper.Test false diff --git a/NET/Mbc.Ads.Mapper/Mbc.Ads.Mapper.csproj b/NET/Mbc.Ads.Mapper/Mbc.Ads.Mapper.csproj index ea32b5e..cce3993 100644 --- a/NET/Mbc.Ads.Mapper/Mbc.Ads.Mapper.csproj +++ b/NET/Mbc.Ads.Mapper/Mbc.Ads.Mapper.csproj @@ -2,8 +2,8 @@ - netstandard2.0;net471;net8.0 - 5.0.0.0 + net10.0;net8.0;netstandard2.0 + 5.2.0 Mbc.Ads.Mapper diff --git a/NET/Mbc.Ads.Utils.Test/Mbc.Ads.Utils.Test.csproj b/NET/Mbc.Ads.Utils.Test/Mbc.Ads.Utils.Test.csproj index 8e7efc5..bc68d0a 100644 --- a/NET/Mbc.Ads.Utils.Test/Mbc.Ads.Utils.Test.csproj +++ b/NET/Mbc.Ads.Utils.Test/Mbc.Ads.Utils.Test.csproj @@ -2,7 +2,7 @@ - net471;net8.0 + net10.0;net8.0 Mbc.Utils.Test false diff --git a/NET/Mbc.Ads.Utils/Mbc.Ads.Utils.csproj b/NET/Mbc.Ads.Utils/Mbc.Ads.Utils.csproj index c97d116..c1bab97 100644 --- a/NET/Mbc.Ads.Utils/Mbc.Ads.Utils.csproj +++ b/NET/Mbc.Ads.Utils/Mbc.Ads.Utils.csproj @@ -2,8 +2,8 @@ - netstandard2.0;net471;net8.0 - 5.1.0.0 + net10.0;net8.0;netstandard2.0 + 5.2.0 Mbc.Ads.Utils diff --git a/NET/Mbc.Pcs.Net.Alarm.Mediator/Mbc.Pcs.Net.Alarm.Mediator.csproj b/NET/Mbc.Pcs.Net.Alarm.Mediator/Mbc.Pcs.Net.Alarm.Mediator.csproj index 2d1de50..f5d18f1 100644 --- a/NET/Mbc.Pcs.Net.Alarm.Mediator/Mbc.Pcs.Net.Alarm.Mediator.csproj +++ b/NET/Mbc.Pcs.Net.Alarm.Mediator/Mbc.Pcs.Net.Alarm.Mediator.csproj @@ -1,8 +1,8 @@  - net471;net8.0-windows - 5.0.0.0 + net10.0-windows;net8.0-windows + 5.2.0 Exe tceventbar.ico @@ -19,9 +19,6 @@ - - - diff --git a/NET/Mbc.Pcs.Net.Alarm.Service.Test/Mbc.Pcs.Net.Alarm.Service.Test.csproj b/NET/Mbc.Pcs.Net.Alarm.Service.Test/Mbc.Pcs.Net.Alarm.Service.Test.csproj index 9a78413..8ba1b09 100644 --- a/NET/Mbc.Pcs.Net.Alarm.Service.Test/Mbc.Pcs.Net.Alarm.Service.Test.csproj +++ b/NET/Mbc.Pcs.Net.Alarm.Service.Test/Mbc.Pcs.Net.Alarm.Service.Test.csproj @@ -2,7 +2,7 @@ - net471;net8.0-windows + net10.0-windows;net8.0-windows Mbc.Pcs.Net.Alarm.Service.Test false @@ -15,8 +15,6 @@ - - 2.8.2 diff --git a/NET/Mbc.Pcs.Net.Alarm.Service/Mbc.Pcs.Net.Alarm.Service.csproj b/NET/Mbc.Pcs.Net.Alarm.Service/Mbc.Pcs.Net.Alarm.Service.csproj index 545c699..9ea9352 100644 --- a/NET/Mbc.Pcs.Net.Alarm.Service/Mbc.Pcs.Net.Alarm.Service.csproj +++ b/NET/Mbc.Pcs.Net.Alarm.Service/Mbc.Pcs.Net.Alarm.Service.csproj @@ -1,8 +1,8 @@  - net471;net8.0-windows - 5.0.0.0 + net10.0-windows;net8.0-windows + 5.2.0 Mbc.Pcs.Net.Alarm.Service @@ -13,8 +13,8 @@ - + @@ -22,7 +22,6 @@ - diff --git a/NET/Mbc.Pcs.Net.Alarm.Test/Mbc.Pcs.Net.Alarm.Test.csproj b/NET/Mbc.Pcs.Net.Alarm.Test/Mbc.Pcs.Net.Alarm.Test.csproj index 7ba384a..e080a11 100644 --- a/NET/Mbc.Pcs.Net.Alarm.Test/Mbc.Pcs.Net.Alarm.Test.csproj +++ b/NET/Mbc.Pcs.Net.Alarm.Test/Mbc.Pcs.Net.Alarm.Test.csproj @@ -1,7 +1,7 @@  - net471;net8.0 + net10.0;net8.0 Mbc.Pcs.Net.Alarm.Test false diff --git a/NET/Mbc.Pcs.Net.Alarm/Mbc.Pcs.Net.Alarm.csproj b/NET/Mbc.Pcs.Net.Alarm/Mbc.Pcs.Net.Alarm.csproj index 11bb9e0..421b327 100644 --- a/NET/Mbc.Pcs.Net.Alarm/Mbc.Pcs.Net.Alarm.csproj +++ b/NET/Mbc.Pcs.Net.Alarm/Mbc.Pcs.Net.Alarm.csproj @@ -1,8 +1,8 @@  - netstandard2.0;net471;net8.0 - 5.1.0.0 + net10.0;net8.0;netstandard2.0 + 5.2.0 Mbc.Pcs.Net.Alarm diff --git a/NET/Mbc.Pcs.Net.Command.Test/Mbc.Pcs.Net.Command.Test.csproj b/NET/Mbc.Pcs.Net.Command.Test/Mbc.Pcs.Net.Command.Test.csproj index 0f213df..9493df2 100644 --- a/NET/Mbc.Pcs.Net.Command.Test/Mbc.Pcs.Net.Command.Test.csproj +++ b/NET/Mbc.Pcs.Net.Command.Test/Mbc.Pcs.Net.Command.Test.csproj @@ -1,7 +1,7 @@  - net471;net8.0 + net10.0;net8.0 Mbc.Pcs.Net.Command.Test false diff --git a/NET/Mbc.Pcs.Net.Command/Mbc.Pcs.Net.Command.csproj b/NET/Mbc.Pcs.Net.Command/Mbc.Pcs.Net.Command.csproj index 8dd505b..b88ac55 100644 --- a/NET/Mbc.Pcs.Net.Command/Mbc.Pcs.Net.Command.csproj +++ b/NET/Mbc.Pcs.Net.Command/Mbc.Pcs.Net.Command.csproj @@ -1,8 +1,8 @@  - netstandard2.0;net471;net8.0 - 5.0.0.0 + net10.0;net8.0;netstandard2.0 + 5.2.0 Mbc.Pcs.Net.Command diff --git a/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5RingBuffer/RingBufferTest.cs b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5RingBuffer/RingBufferTest.cs index bb92074..9c07b08 100644 --- a/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5RingBuffer/RingBufferTest.cs +++ b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5RingBuffer/RingBufferTest.cs @@ -1,6 +1,6 @@ using AwesomeAssertions; -using Mbc.Hdf5Utils; using Mbc.Pcs.Net.DataRecorder.Hdf5RingBuffer; +using Mbc.Pcs.Net.DataRecorder.Hdf5Utils; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using System.IO; diff --git a/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5AttributeTest.cs b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5AttributeTest.cs new file mode 100644 index 0000000..a2dc523 --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5AttributeTest.cs @@ -0,0 +1,127 @@ +using System; +using System.IO; +using System.Linq; +using AwesomeAssertions; +using Mbc.Pcs.Net.DataRecorder.Hdf5Utils; +using Mbc.Pcs.Net.DataRecorder.Test.Hdf5Utils.Utils; +using Xunit; + +namespace Mbc.Pcs.Net.DataRecorder.Test.Hdf5Utils +{ + [Collection("HDF5")] + public class H5AttributeTest : IDisposable + { + private readonly H5File _h5File; + + public H5AttributeTest(Hdf5LibFixture hdf5Lib) + { + var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + File.Delete(file); + _h5File = new H5File(file, H5File.Flags.CreateOnly); + } + + public void Dispose() + { + _h5File.Dispose(); + } + + [Fact] + public void WriteAndReadStringAttribute() + { + // Arrange + var attribute = new H5Attribute(_h5File); + + // Act + attribute.Write("foo", "bar"); + var attrValue = attribute.ReadString("foo"); + + // Assert + attrValue.Should().Be("bar"); + } + + [Fact] + public void WriteAndReadIntAttribute() + { + // Arrange + var attribute = new H5Attribute(_h5File); + + // Act + attribute.Write("foo", 42); + var attrValue = attribute.ReadInt("foo"); + + // Assert + attrValue.Should().Be(42); + } + + [Fact] + public void WriteAndReadDoubleAttribute() + { + // Arrange + var attribute = new H5Attribute(_h5File); + + // Act + attribute.Write("foo", 42d); + var attrValue = attribute.ReadDouble("foo"); + + // Assert + attrValue.Should().Be(42d); + } + + [Fact] + public void WriteShortAndReadIntAttribute() + { + // Arrange + var attribute = new H5Attribute(_h5File); + + // Act + attribute.Write("foo", (ushort)60000); + var attrValue = attribute.ReadInt("foo"); + + // Assert + attrValue.Should().Be(60000); + } + + [Fact] + public void ReadObjectAttribute() + { + // Arrange + var attribute = new H5Attribute(_h5File); + attribute.Write("foo", "bar"); + + // Act + var attrValue = attribute.Read("foo"); + + // Assert + attrValue.Should().BeAssignableTo().Which.Should().Be("bar"); + } + + [Fact] + public void ListAttributeNames() + { + // Arrange + var attribute = new H5Attribute(_h5File); + attribute.Write("foo", "bar"); + attribute.Write("baz", "bar"); + + // Act + var names = attribute.GetAttributeNames().ToList(); + + // Assert + names.Should().BeEquivalentTo("foo", "baz"); + } + + [Fact] + public void ReWriteAttribute() + { + // Arrange + var attribute = new H5Attribute(_h5File); + attribute.Write("foo", 1); + + // Act + attribute.Write("foo", 2); + + // Assert + attribute.ReadInt("foo").Should().Be(2); + } + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5DataSetTest.cs b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5DataSetTest.cs new file mode 100644 index 0000000..bd0b740 --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5DataSetTest.cs @@ -0,0 +1,83 @@ +using AwesomeAssertions; +using Mbc.Pcs.Net.DataRecorder.Hdf5Utils; +using System; +using System.IO; +using System.Linq; +using Xunit; + +namespace Mbc.Pcs.Net.DataRecorder.Test.Hdf5Utils +{ + public class H5DataSetTest + { + public H5DataSetTest() + { + } + + [Fact] + public void OpenDataSet() + { + // Arrange + var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + File.Delete(file); + using (var h5file = new H5File(file, H5File.Flags.CreateOnly)) + { + new H5DataSet.Builder() + .WithName("foo") + .WithType() + .WithDimension(100) + .WithChunking(10) + .Create(h5file); + } + + // Act + using (var h5file = new H5File(file, H5File.Flags.ReadOnly)) + { + var dataSet = H5DataSet.Open(h5file, "foo"); + + // Assert + dataSet.Should().NotBeNull(); + dataSet.ValueType.Should().Be(typeof(float)); + dataSet.GetDimensions().Should().BeEquivalentTo(new ulong[] { 100 }); + dataSet.GetMaxDimensions().Should().BeEquivalentTo(new ulong[] { 100 }); + dataSet.GetChunkSize().Should().BeEquivalentTo(new ulong[] { 10 }); + } + } + + [Fact] + public void WriteAndReadAgain() + { + // Arrange + var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + File.Delete(file); + using (var h5file = new H5File(file, H5File.Flags.CreateOnly)) + { + var dataSet = new H5DataSet.Builder() + .WithName("foo") + .WithType() + .WithDimension(100) + .WithChunking(10) + .Create(h5file); + + Array values = Enumerable.Range(0, 100).Select(i => (float)i).ToArray(); + dataSet.Write(values); + } + + // Act + Array readValues = new float[50]; + using (var h5file = new H5File(file, H5File.Flags.ReadOnly)) + { + var dataSet = H5DataSet.Open(h5file, "foo"); + using (var fileDataSpace = dataSet.GetSpace()) + { + H5DataSpace.CreateSelectionBuilder() + .Start(10).Count(50).ApplyTo(fileDataSpace); + + dataSet.Read(readValues, fileDataSpace); + } + } + + // Assert + readValues.Should().BeEquivalentTo(Enumerable.Range(10, 50).Select(i => (float)i)); + } + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5FileTest.cs b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5FileTest.cs new file mode 100644 index 0000000..72999e2 --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5FileTest.cs @@ -0,0 +1,140 @@ +using System; +using System.IO; +using AwesomeAssertions; +using Mbc.Pcs.Net.DataRecorder.Hdf5Utils; +using Mbc.Pcs.Net.DataRecorder.Test.Hdf5Utils.Utils; +using Xunit; + +namespace Mbc.Pcs.Net.DataRecorder.Test.Hdf5Utils +{ + [Collection("HDF5")] + public class H5FileTest + { + public H5FileTest(Hdf5LibFixture hdf5Lib) + { + } + + [Fact] + public void NewHdf5IsCreated() + { + var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + File.Delete(file); + var h5file = new H5File(file, H5File.Flags.CreateOnly); + var name = h5file.GetName(); + h5file.Dispose(); + + name.Should().Be(file); + File.Exists(file).Should().BeTrue(); + } + + [Fact] + public void OpenFailsIfExist() + { + var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + File.Create(file).Dispose(); + + try + { +#pragma warning disable CA1806 // Do not ignore method results + Action act = () => new H5File(file, H5File.Flags.CreateOnly); +#pragma warning restore CA1806 // Do not ignore method results + + act.Should().Throw() + .WithMessage("H5Fcreate: unable to create file -> File accessibilty - Unable to open file"); + } + finally + { + File.Delete(file); + } + } + + [Fact] + public void OpenCurrentName() + { + var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + File.Delete(file); + using (var h5file = new H5File(file, H5File.Flags.CreateOnly)) + { + using (var group = h5file.OpenGroup(".")) + { + group.Should().NotBeNull(); + } + } + } + + [Fact] + public void OpenRootName() + { + var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + File.Delete(file); + using (var h5file = new H5File(file, H5File.Flags.CreateOnly)) + { + using (var group = h5file.OpenGroup("/")) + { + group.Should().NotBeNull(); + } + } + } + + [Fact] + public void OpenNotExistingName() + { + var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + File.Delete(file); + using (var h5file = new H5File(file, H5File.Flags.CreateOnly)) + { + Action act = () => h5file.OpenGroup("foo"); + + act.Should().Throw() + .WithMessage("H5Gopen2: unable to open group -> Symbol table - Can't open object"); + } + } + + [Fact] + public void CreateNotExistingName() + { + var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + File.Delete(file); + using (var h5file = new H5File(file, H5File.Flags.CreateOnly)) + { + using (var group = h5file.CreateGroup("/foo")) + { + group.Should().NotBeNull(); + } + } + } + + [Fact] + public void CreateAndOpen() + { + var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + File.Delete(file); + using (var h5file = new H5File(file, H5File.Flags.CreateOnly)) + { + using (var group = h5file.CreateGroup("/foo/bar")) + { + group.Should().NotBeNull(); + } + + using (var group = h5file.OpenGroup("foo/bar")) + { + group.Should().NotBeNull(); + } + } + } + + [Fact] + public void ListAllGroupNames() + { + var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + File.Delete(file); + using (var h5file = new H5File(file, H5File.Flags.CreateOnly)) + { + h5file.CreateGroup("/foo").Dispose(); + h5file.CreateGroup("/bar").Dispose(); + + h5file.GetNames().Should().BeEquivalentTo("foo", "bar"); + } + } + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5GlobalLockTest.cs b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5GlobalLockTest.cs new file mode 100644 index 0000000..3132656 --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5GlobalLockTest.cs @@ -0,0 +1,35 @@ +using AwesomeAssertions; +using Mbc.Pcs.Net.DataRecorder.Hdf5Utils; +using Xunit; + +namespace Mbc.Pcs.Net.DataRecorder.Test.Hdf5Utils +{ + public class H5GlobalLockTest + { + public H5GlobalLockTest() + { + } + + [Fact] + public void HasLockWithInstance() + { + using (new H5GlobalLock()) + { + H5GlobalLock.HasLock.Should().BeTrue(); + } + + H5GlobalLock.HasLock.Should().BeFalse(); + } + + [Fact] + public void HasLockWithLock() + { + lock (H5GlobalLock.Sync) + { + H5GlobalLock.HasLock.Should().BeTrue(); + } + + H5GlobalLock.HasLock.Should().BeFalse(); + } + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5GroupTest.cs b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5GroupTest.cs new file mode 100644 index 0000000..b2a541e --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/H5GroupTest.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using AwesomeAssertions; +using Mbc.Pcs.Net.DataRecorder.Hdf5Utils; +using Mbc.Pcs.Net.DataRecorder.Test.Hdf5Utils.Utils; +using Xunit; + +namespace Mbc.Pcs.Net.DataRecorder.Test.Hdf5Utils +{ + [Collection("HDF5")] + public class H5GroupTest : IDisposable + { + private readonly H5File _h5File; + + public H5GroupTest(Hdf5LibFixture hdf5Lib) + { + var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + File.Delete(file); + _h5File = new H5File(file, H5File.Flags.CreateOnly); + } + + public void Dispose() + { + _h5File.Dispose(); + } + + [Fact] + public void CreateGroupRelativ() + { + // Arrange + var fooGroup = _h5File.CreateGroup("/foo"); + + // Act + var subGroup = fooGroup.CreateGroup("bar"); + + // Assert + subGroup.Should().NotBeNull(); + fooGroup.OpenGroup("bar").Should().NotBeNull("because group bar should exist relativ to foo"); + _h5File.OpenGroup("/foo/bar").Should().NotBeNull("because /foo/bar should exist"); + } + + [Fact] + public void CreateGroupAbsolute() + { + // Arrange + var fooGroup = _h5File.CreateGroup("/foo"); + + // Act + var subGroup = fooGroup.CreateGroup("/bar"); + + // Assert + subGroup.Should().NotBeNull(); + _h5File.OpenGroup("/bar").Should().NotBeNull("because /bar should exist"); + } + + [Fact] + public void ListAllNamesInGroup() + { + // Arrange + var fooGroup = _h5File.CreateGroup("/foo"); + fooGroup.CreateGroup("bar").Dispose(); + fooGroup.CreateGroup("baz").Dispose(); + + // Act + var names = fooGroup.GetNames(); + + // Assert + names.Should().BeEquivalentTo("bar", "baz"); + } + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/Hdf5LibCollection.cs b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/Hdf5LibCollection.cs new file mode 100644 index 0000000..70de440 --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/Hdf5LibCollection.cs @@ -0,0 +1,10 @@ +using Mbc.Pcs.Net.DataRecorder.Test.Hdf5Utils.Utils; +using Xunit; + +namespace Mbc.Pcs.Net.DataRecorder.Test.Hdf5Utils +{ + [CollectionDefinition("HDF5")] + public class Hdf5LibCollection : ICollectionFixture + { + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/Prototype/SingleChannelWrite.cs b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/Prototype/SingleChannelWrite.cs new file mode 100644 index 0000000..e0dc618 --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/Prototype/SingleChannelWrite.cs @@ -0,0 +1,94 @@ +using Mbc.Pcs.Net.DataRecorder.Hdf5Utils; +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Xunit; +using Xunit.Abstractions; + +namespace Mbc.Pcs.Net.DataRecorder.Test.Hdf5Utils.Prototype +{ + [Collection("HDF5")] + public class SingleChannelWrite + { + private readonly ITestOutputHelper _testOutputHelper; + + public SingleChannelWrite(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + + [Fact] + public void WritePerformanceTest() + { + var file = GetFile(); + using (var h5File = new H5File(file, H5File.Flags.CreateOnly)) + { + var fileAttr = new H5Attribute(h5File); + fileAttr.Write("testCycleName", "Abnahmezyklus"); + fileAttr.Write("measurementName", "Warmup 50%"); + + var channelNames = new[] + { + "ActualSpeed", "SetSpeed", "MotorCurrent", "MotorTorque", "OutputVoltage", + "BearingTemp[1]", "BearingTemp[2]", "BearingTemp[3]", "BearingTemp[4]", "BearingTemp[5]", + "MotorTemp[3]", "MotorTemp[4]", + }; + + var startTime = DateTime.UtcNow; + + var dataSets = new H5DataSet[channelNames.Length]; + for (int i = 0; i < channelNames.Length; i++) + { + var dataSet = new H5DataSet.Builder() + .WithName($"/{channelNames[i]}") + .WithType() + .WithDimension(0) + .WithChunking(300) + .WithDeflate(5) + .Create(h5File); + + var attr = new H5Attribute(dataSet); + attr.Write("sampleRate", 5); + attr.Write("startTimeTc", startTime.ToString("o")); + + dataSets[i] = dataSet; + } + + // Simuliertes Logging + var rnd = new Random(); + var watch = Stopwatch.StartNew(); + + for (var sample = 0; sample < 1500; sample++) + { + var sampleValues = Enumerable.Range(0, channelNames.Length) + .Select(x => (float)rnd.NextDouble()) + .ToArray(); + + for (int i = 0; i < channelNames.Length; i++) + { + dataSets[i].Write(new[] { sampleValues[i] }); + } + } + + watch.Stop(); + + foreach (var dataSet in dataSets) + { + dataSet.Dispose(); + } + + _testOutputHelper.WriteLine($"Laufzeit: {watch.Elapsed}"); + } + + _testOutputHelper.WriteLine(file); + } + + private string GetFile() + { + var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + File.Delete(file); + return file; + } + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/UseCases/WriteData.cs b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/UseCases/WriteData.cs new file mode 100644 index 0000000..07d426d --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/UseCases/WriteData.cs @@ -0,0 +1,211 @@ +using Mbc.Pcs.Net.DataRecorder.Hdf5Utils; +using System; +using System.IO; +using Xunit; +using Xunit.Abstractions; + +namespace Mbc.Pcs.Net.DataRecorder.Test.Hdf5Utils.UseCases +{ + [Collection("HDF5")] + public class WriteData + { + private readonly ITestOutputHelper _testOutputHelper; + + public WriteData(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + + private string GetFile() + { + var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + File.Delete(file); + return file; + } + + [Fact] + public void WriteOneDimensionalDataFull() + { + var file = GetFile(); + using (var h5File = new H5File(file, H5File.Flags.CreateOnly)) + { + var dataSetBuilder = new H5DataSet.Builder() + .WithName("/data/set") + .WithType() + .WithDimension(10); + + using (var dataSet = dataSetBuilder.Create(h5File)) + { + dataSet.Attributes().Write("Timestamp", DateTime.UtcNow.ToString("o")); + + var data = new double[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + dataSet.Write(data); + } + } + + _testOutputHelper.WriteLine(file); + } + + + [Fact] + public void WriteOneDimensionalDataPartial() + { + var file = GetFile(); + using (var h5File = new H5File(file, H5File.Flags.CreateOnly)) + { + var dataSetBuilder = new H5DataSet.Builder() + .WithName("/data/set") + .WithType() + .WithDimension(10); + + using (var dataSet = dataSetBuilder.Create(h5File)) + { + dataSet.Attributes().Write("Timestamp", DateTime.UtcNow.ToString("o")); + + Array data = new double[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + + using (var srcDataSpace = H5DataSpace.CreateSimpleFixed(new [] { (ulong) data.Length })) + using (var fileDataSpace = dataSet.GetSpace()) + { + H5DataSpace.CreateSelectionBuilder() + .Start(5).Count(5).ApplyTo(srcDataSpace); + H5DataSpace.CreateSelectionBuilder() + .Start(0).Count(5).ApplyTo(fileDataSpace); + dataSet.Write(typeof(double), data, fileDataSpace, srcDataSpace); + } + } + } + + _testOutputHelper.WriteLine(file); + } + + [Fact] + public void WriteOneDimensionalDataFullWithExplicitType() + { + var file = GetFile(); + using (var h5File = new H5File(file, H5File.Flags.CreateOnly)) + { + var dataSetBuilder = new H5DataSet.Builder() + .WithName("/data/set") + .WithType() + .WithDimension(10); + + using (var dataSet = dataSetBuilder.Create(h5File)) + { + dataSet.Attributes().Write("Timestamp", DateTime.UtcNow.ToString("o")); + + Array data = new double[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + dataSet.Write(typeof(double), data); + } + } + + _testOutputHelper.WriteLine(file); + } + + [Fact] + public void WriteOneDimensionalDataIncrementelFixed() + { + var file = GetFile(); + using (var h5File = new H5File(file, H5File.Flags.CreateOnly)) + { + var dataSetBuilder = new H5DataSet.Builder() + .WithName("/data/set") + .WithType() + .WithDimension(10); + + using (var dataSet = dataSetBuilder.Create(h5File)) + { + dataSet.Attributes().Write("Timestamp", DateTime.UtcNow.ToString("o")); + + using (var space = dataSet.GetSpace()) + { + H5DataSpace.CreateSelectionBuilder() + .Start(0).Count(5).ApplyTo(space); + dataSet.Write(new double[] { 1, 2, 3, 4, 5 }, space); + } + + using (var space = dataSet.GetSpace()) + { + H5DataSpace.CreateSelectionBuilder() + .Start(5).Count(5).ApplyTo(space); + dataSet.Write(new double[] { 6, 7, 8, 9, 10 }, space); + } + } + } + + _testOutputHelper.WriteLine(file); + } + + [Fact] + public void WriteOneDimensionalDataIncrementelGrow() + { + var file = GetFile(); + using (var h5File = new H5File(file, H5File.Flags.CreateOnly)) + { + var dataSetBuilder = new H5DataSet.Builder() + .WithName("/data/set") + .WithType() + .WithDimension(0) + .WithChunking(5); + + using (var dataSet = dataSetBuilder.Create(h5File)) + { + dataSet.Attributes().Write("Timestamp", DateTime.UtcNow.ToString("o")); + + dataSet.Write(new double[] { 1, 2, 3, 4, 5 }); + dataSet.Write(new double[] { 6, 7, 8, 9, 10 }); + } + } + + _testOutputHelper.WriteLine(file); + } + + [Fact] + public void WriteTwoDimensionalDataIncrementelGrow() + { + var file = GetFile(); + using (var h5File = new H5File(file, H5File.Flags.CreateOnly)) + { + var dataSetBuilder = new H5DataSet.Builder() + .WithName("/data/set") + .WithType() + .WithDimension(0, 5) + .WithChunking(2, 5); + + using (var dataSet = dataSetBuilder.Create(h5File)) + { + dataSet.Attributes().Write("Timestamp", DateTime.UtcNow.ToString("o")); + + dataSet.Write(new[,] { { 1, 2, 3, 4, 5 }, { 11, 12, 13, 14, 15 } }); + dataSet.Write(new[,] { { 6, 7, 8, 9, 10 }, { 16, 17, 18, 19, 20 } }); + } + } + + _testOutputHelper.WriteLine(file); + } + + [Fact] + public void AppendOneDimensionalData() + { + var file = GetFile(); + using (var h5File = new H5File(file, H5File.Flags.CreateOnly)) + { + var dataSetBuilder = new H5DataSet.Builder() + .WithName("/data/set") + .WithType() + .WithDimension(0) + .WithChunking(5); + + using (var dataSet = dataSetBuilder.Create(h5File)) + { + dataSet.Attributes().Write("Timestamp", DateTime.UtcNow.ToString("o")); + + dataSet.AppendAll(new[] { 1, 2, 3, 4, 5 }); + dataSet.AppendAll(new[] { 6, 7, 8, 9, 10 }); + } + } + + _testOutputHelper.WriteLine(file); + } + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/Utils/Hdf5LibFixture.cs b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/Utils/Hdf5LibFixture.cs new file mode 100644 index 0000000..8657016 --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder.Test/Hdf5Utils/Utils/Hdf5LibFixture.cs @@ -0,0 +1,42 @@ +using Mbc.Pcs.Net.DataRecorder.Hdf5Utils; +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Mbc.Pcs.Net.DataRecorder.Test.Hdf5Utils.Utils +{ + /// + /// Hilfsklasse um bei Testende die geladene HDF5-DLL zu entladen. + /// + public class Hdf5LibFixture : IDisposable + { + public Hdf5LibFixture() + { + H5Utils.OpenH5(); + } + + public void Dispose() + { + H5Utils.CloseH5(); + + var hdf5Dll = FindHdf5DllModule(); + if (hdf5Dll != null) + { + FreeLibrary(hdf5Dll.BaseAddress); + } + } + + [DllImport("kernel32", SetLastError = true)] + private static extern bool FreeLibrary(IntPtr hModule); + + private ProcessModule FindHdf5DllModule() + { + return Process.GetCurrentProcess() + .Modules + .OfType() + .Where(x => x.ModuleName == "hdf5.dll") + .FirstOrDefault(); + } + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder.Test/Mbc.Pcs.Net.DataRecorder.Test.csproj b/NET/Mbc.Pcs.Net.DataRecorder.Test/Mbc.Pcs.Net.DataRecorder.Test.csproj index 4426045..636ec50 100644 --- a/NET/Mbc.Pcs.Net.DataRecorder.Test/Mbc.Pcs.Net.DataRecorder.Test.csproj +++ b/NET/Mbc.Pcs.Net.DataRecorder.Test/Mbc.Pcs.Net.DataRecorder.Test.csproj @@ -1,7 +1,7 @@  - net471;net8.0 + net10.0;net8.0 Mbc.Pcs.Net.DataRecorder.Test false @@ -12,17 +12,12 @@ - all runtime; build; native; contentfiles; analyzers - - - - diff --git a/NET/Mbc.Pcs.Net.DataRecorder/Hdf5RingBuffer/RingBuffer.cs b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5RingBuffer/RingBuffer.cs index a5e0704..11eb90b 100644 --- a/NET/Mbc.Pcs.Net.DataRecorder/Hdf5RingBuffer/RingBuffer.cs +++ b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5RingBuffer/RingBuffer.cs @@ -1,5 +1,5 @@ using EnsureThat; -using Mbc.Hdf5Utils; +using Mbc.Pcs.Net.DataRecorder.Hdf5Utils; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; diff --git a/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Attribute.cs b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Attribute.cs new file mode 100644 index 0000000..137b788 --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Attribute.cs @@ -0,0 +1,294 @@ +using HDF.PInvoke; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace Mbc.Pcs.Net.DataRecorder.Hdf5Utils +{ + /// + /// Stellt den Zugriff auf HDF5-Attribute zur Verfügung. + /// + public class H5Attribute + { + private readonly long _locId; + + public H5Attribute(H5File file) + { + _locId = file.Id; + } + + public H5Attribute(H5DataSet dataSet) + { + _locId = dataSet.Id; + } + + /// + /// Liefert alle Attribut-Namen zurück. + /// + public IEnumerable GetAttributeNames() + { + var objectInfo = default(H5O.info_t); + + lock (H5GlobalLock.Sync) + { + var ret = H5O.get_info(_locId, ref objectInfo); + H5Error.CheckH5Result(ret); + } + + for (ulong index = 0; index < objectInfo.num_attrs; index++) + { + var name = new StringBuilder(); + + lock (H5GlobalLock.Sync) + { + var ret = H5A.get_name_by_idx(_locId, ".", H5.index_t.NAME, H5.iter_order_t.NATIVE, index, name, new IntPtr(1024)).ToInt32(); + H5Error.CheckH5Result(ret); + } + + yield return name.ToString(); + } + } + + /// + /// Schreibt ein Attribut mit dem Namen und + /// den Wert . + /// + public void Write(string name, string value) + { + var valueBuffer = Encoding.UTF8.GetBytes(value); + + var valuePtr = Marshal.AllocHGlobal(valueBuffer.Length); + try + { + Marshal.Copy(valueBuffer, 0, valuePtr, valueBuffer.Length); + + lock (H5GlobalLock.Sync) + { + using (H5Id type = new H5Id(H5T.create(H5T.class_t.STRING, new IntPtr(valueBuffer.Length)), H5T.close), + dspace = new H5Id(H5S.create(H5S.class_t.SCALAR), H5S.close)) + { + var ret = H5T.set_cset(type, H5T.cset_t.UTF8); + H5Error.CheckH5Result(ret); + + using (var attribute = new H5Id(H5A.create(_locId, name, type, dspace), H5A.close)) + { + ret = H5A.write(attribute, type, valuePtr); + H5Error.CheckH5Result(ret); + } + } + } + } + finally + { + Marshal.FreeHGlobal(valuePtr); + } + } + + public void Write(string name, sbyte value) => + WriteScalar(H5T.NATIVE_INT8, name, 1, ptr => Marshal.WriteByte(ptr, (byte)value)); + + public void Write(string name, short value) => + WriteScalar(H5T.NATIVE_INT16, name, 2, ptr => Marshal.WriteInt16(ptr, value)); + + public void Write(string name, int value) => + WriteScalar(H5T.NATIVE_INT32, name, 4, ptr => Marshal.WriteInt32(ptr, value)); + + public void Write(string name, long value) => + WriteScalar(H5T.NATIVE_INT64, name, 8, ptr => Marshal.WriteInt64(ptr, value)); + + public void Write(string name, byte value) => + WriteScalar(H5T.NATIVE_UINT8, name, 1, ptr => Marshal.WriteByte(ptr, value)); + + public void Write(string name, ushort value) => + WriteScalar(H5T.NATIVE_UINT16, name, 2, ptr => Marshal.WriteInt16(ptr, (short)value)); + + public void Write(string name, uint value) => + WriteScalar(H5T.NATIVE_UINT32, name, 4, ptr => Marshal.WriteInt32(ptr, (int)value)); + + public void Write(string name, ulong value) => + WriteScalar(H5T.NATIVE_UINT64, name, 8, ptr => Marshal.WriteInt64(ptr, (long)value)); + + public void Write(string name, float value) => + WriteScalar(H5T.NATIVE_FLOAT, name, sizeof(float), ptr => UnmanagedUtils.SingleToPtr(value, ptr)); + + public void Write(string name, double value) => + WriteScalar(H5T.NATIVE_DOUBLE, name, sizeof(double), ptr => UnmanagedUtils.DoubleToPtr(value, ptr)); + + private void WriteScalar(long h5NativeTypeId, string name, int size, Action writePtr) + { + var valuePtr = Marshal.AllocHGlobal(size); + try + { + writePtr(valuePtr); + + lock (H5GlobalLock.Sync) + { + using (H5Id dspace = new H5Id(H5S.create(H5S.class_t.SCALAR), H5S.close)) + { + H5Id attribute = null; + try + { + if (H5Error.CheckH5Result(H5A.exists(_locId, name)) > 0) + { + attribute = new H5Id(H5A.open(_locId, name), H5A.close); + } + else + { + attribute = new H5Id(H5A.create(_locId, name, h5NativeTypeId, dspace), H5A.close); + } + + var ret = H5A.write(attribute, h5NativeTypeId, valuePtr); + H5Error.CheckH5Result(ret); + } + finally + { + attribute?.Dispose(); + } + } + } + } + finally + { + Marshal.FreeHGlobal(valuePtr); + } + } + + /// + /// Liest das Attribut mit dem Namen als + /// String. + /// + public string ReadString(string name) + { + return (string)Read(name); + } + + public byte ReadByte(string name) => + ReadScalar(H5T.NATIVE_UINT8, name, sizeof(byte), (ptr) => Marshal.ReadByte(ptr)); + public ushort ReadUShort(string name) => + ReadScalar(H5T.NATIVE_UINT16, name, sizeof(ushort), (ptr) => (ushort)Marshal.ReadInt16(ptr)); + public uint ReadUInt(string name) => + ReadScalar(H5T.NATIVE_UINT32, name, sizeof(uint), (ptr) => (uint)Marshal.ReadInt32(ptr)); + public ulong ReadULong(string name) => + ReadScalar(H5T.NATIVE_UINT64, name, sizeof(ulong), (ptr) => (ulong)Marshal.ReadInt64(ptr)); + public sbyte ReadSByte(string name) => + ReadScalar(H5T.NATIVE_INT8, name, sizeof(sbyte), (ptr) => (sbyte)Marshal.ReadByte(ptr)); + public short ReadShort(string name) => + ReadScalar(H5T.NATIVE_INT16, name, sizeof(short), (ptr) => Marshal.ReadInt16(ptr)); + public int ReadInt(string name) => + ReadScalar(H5T.NATIVE_INT32, name, sizeof(int), (ptr) => Marshal.ReadInt32(ptr)); + public long ReadLong(string name) => + ReadScalar(H5T.NATIVE_INT64, name, sizeof(long), (ptr) => Marshal.ReadInt64(ptr)); + public float ReadFloat(string name) => + ReadScalar(H5T.NATIVE_FLOAT, name, sizeof(float), (ptr) => UnmanagedUtils.PtrToSingle(ptr)); + public double ReadDouble(string name) => + ReadScalar(H5T.NATIVE_DOUBLE, name, sizeof(double), (ptr) => UnmanagedUtils.PtrToDouble(ptr)); + + private T ReadScalar(long h5NativeTypeId, string name, int size, Func readPtr) + { + var bufferPtr = Marshal.AllocHGlobal(size); + try + { + lock (H5GlobalLock.Sync) + { + using (H5Id attribute = new H5Id(H5A.open(_locId, name), H5A.close)) + { + var ret = H5A.read(attribute, h5NativeTypeId, bufferPtr); + H5Error.CheckH5Result(ret); + } + } + + return readPtr(bufferPtr); + } + finally + { + Marshal.FreeHGlobal(bufferPtr); + } + } + + public object Read(string name) + { + lock (H5GlobalLock.Sync) + { + using (H5Id attribute = new H5Id(H5A.open(_locId, name), H5A.close), + type = new H5Id(H5A.get_type(attribute), H5T.close), + typeMem = new H5Id(H5T.get_native_type(type, H5T.direction_t.ASCEND), H5T.close)) + { + var size = H5T.get_size(type).ToInt32(); + + var bufferPtr = Marshal.AllocHGlobal(size); + try + { + var ret = H5A.read(attribute, type, bufferPtr); + H5Error.CheckH5Result(ret); + return ConvertData(bufferPtr, type, size); + } + finally + { + Marshal.FreeHGlobal(bufferPtr); + } + } + } + } + + private object ConvertData(IntPtr bufferPtr, long typeId, int size) + { + H5T.class_t typeClass; + lock (H5GlobalLock.Sync) + { + typeClass = H5T.get_class(typeId); + } + + switch (typeClass) + { + case H5T.class_t.STRING: + H5T.cset_t cset; + lock (H5GlobalLock.Sync) + { + cset = H5T.get_cset(typeId); + } + + switch (cset) + { + case H5T.cset_t.ASCII: + return UnmanagedUtils.PtrToStringASCII(bufferPtr, size); + case H5T.cset_t.UTF8: + return UnmanagedUtils.PtrToStringUTF8(bufferPtr, size); + default: + throw new H5Error($"Invalid character set {cset}."); + } + + case H5T.class_t.INTEGER: + if (typeId == H5T.NATIVE_CHAR || typeId == H5T.NATIVE_B8 || typeId == H5T.NATIVE_INT8) + return (sbyte)Marshal.ReadByte(bufferPtr); + if (typeId == H5T.NATIVE_SHORT || typeId == H5T.NATIVE_B16 || typeId == H5T.NATIVE_INT16) + return Marshal.ReadInt16(bufferPtr); + if (typeId == H5T.NATIVE_INT || typeId == H5T.NATIVE_B32 || typeId == H5T.NATIVE_INT32) + return Marshal.ReadInt32(bufferPtr); + if (typeId == H5T.NATIVE_LONG || typeId == H5T.NATIVE_B64 || typeId == H5T.NATIVE_INT64) + return Marshal.ReadInt64(bufferPtr); + if (typeId == H5T.NATIVE_UCHAR || typeId == H5T.NATIVE_UINT8) + return Marshal.ReadByte(bufferPtr); + if (typeId == H5T.NATIVE_USHORT || typeId == H5T.NATIVE_UINT16) + return (ushort)Marshal.ReadInt16(bufferPtr); + if (typeId == H5T.NATIVE_UINT || typeId == H5T.NATIVE_UINT32) + return (uint)Marshal.ReadInt32(bufferPtr); + if (typeId == H5T.NATIVE_ULONG || typeId == H5T.NATIVE_UINT64) + return (ulong)Marshal.ReadInt64(bufferPtr); + + throw new H5Error($"Unknown type id: {typeId}"); + + case H5T.class_t.FLOAT: + if (typeId == H5T.NATIVE_FLOAT) + return UnmanagedUtils.PtrToSingle(bufferPtr); + if (typeId == H5T.NATIVE_DOUBLE) + return UnmanagedUtils.PtrToDouble(bufferPtr); + + throw new H5Error($"Unknown type id: {typeId}"); + + default: + throw new H5Error($"Unhandled class: {typeClass}."); + } + } + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5DataSet.cs b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5DataSet.cs new file mode 100644 index 0000000..5e7ca2d --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5DataSet.cs @@ -0,0 +1,636 @@ +using HDF.PInvoke; +using System; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Mbc.Pcs.Net.DataRecorder.Hdf5Utils +{ + public class H5DataSet : IDisposable + { + private readonly bool[] _growing; + private bool _disposed; + + public class CreateOptions + { + public ulong[] Chunks { get; set; } + + public uint? DeflateLevel { get; set; } + } + + /// + /// Öffnet ein bestehendes Dataset mit dem angegeben Namen. + /// + public static H5DataSet Open(H5File file, string name) + { + return Open(file.Id, name); + } + + /// + /// Öffnet ein bestehendes Dataset mit dem angegeben Namen. + /// + public static H5DataSet Open(H5Group group, string name) + { + return Open(group.Id, name); + } + + private static H5DataSet Open(long locId, string name) + { + lock (H5GlobalLock.Sync) + { + var dataSetId = H5Error.CheckH5Result(H5D.open(locId, name)); + + bool[] growing; + using (var dataSpaceId = new H5Id(H5D.get_space(dataSetId), id => H5S.close(id))) + { + var rank = H5Error.CheckH5Result(H5S.get_simple_extent_ndims(dataSpaceId)); + + var maxDims = new ulong[rank]; + H5Error.CheckH5Result(H5S.get_simple_extent_dims(dataSpaceId, null, maxDims)); + + growing = maxDims.Select(x => x == H5S.UNLIMITED).ToArray(); + } + + return new H5DataSet(dataSetId, growing); + } + } + + public static H5DataSet Create(H5File file, string name, H5Type type, H5DataSpace space, CreateOptions options = null) + { + return Create(file.Id, name, type.Id, space.Id, options); + } + + public static H5DataSet Create(H5Group group, string name, H5Type type, H5DataSpace space, CreateOptions options = null) + { + return Create(group.Id, name, type.Id, space.Id, options); + } + + private static H5DataSet Create(long locId, string name, long typeId, long spaceId, CreateOptions options) + { + lock (H5GlobalLock.Sync) + { + var rank = H5S.get_simple_extent_ndims(spaceId); + if (rank < 0) + throw H5Error.GetExceptionFromHdf5Stack(); + + var maxDimensions = new ulong[rank]; + var ret = H5S.get_simple_extent_dims(spaceId, null, maxDimensions); + if (ret < 0) + throw H5Error.GetExceptionFromHdf5Stack(); + + var growing = maxDimensions.Select(x => x == H5S.UNLIMITED).ToArray(); + + if (growing.Contains(true) && options?.Chunks == null) + throw new ArgumentException("Growing data sets require chunking.", nameof(options)); + + using (var propList = new H5Id(H5P.create(H5P.DATASET_CREATE), (id) => H5P.close(id))) + { + if (options?.Chunks != null) + { + ret = H5P.set_chunk(propList.Id, options.Chunks.Length, options.Chunks); + H5Error.CheckH5Result(ret); + } + + if (options != null && options.DeflateLevel.HasValue) + { + ret = H5P.set_deflate(propList.Id, options.DeflateLevel.Value); + H5Error.CheckH5Result(ret); + } + + var dataSetId = H5D.create(locId, name, typeId, spaceId, dcpl_id: propList.Id); + if (dataSetId < 0) + throw H5Error.GetExceptionFromHdf5Stack(); + + return new H5DataSet(dataSetId, growing); + } + } + } + + private H5DataSet(long dataSetId, bool[] growing) + { + Id = dataSetId; + _growing = growing; + } + + ~H5DataSet() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + lock (H5GlobalLock.Sync) + { + var ret = H5D.close(Id); + if (disposing) + H5Error.CheckH5Result(ret); + } + } + + internal long Id { get; } + + public bool IsGrowing => _growing.Contains(true); + + /// + /// Liefert den Typ des Datasets zurück (sofern möglich -> ComboundType, CustomTypes). + /// + public Type ValueType + { + get + { + lock (H5GlobalLock.Sync) + { + using (var typeId = new H5Id(H5D.get_type(Id), id => H5T.close(id))) + { + return H5Type.H5ToNative(typeId); + } + } + } + } + + /// + /// Liefert die Dimension des Datasets zurück. + /// + public ulong[] GetDimensions() + { + lock (H5GlobalLock.Sync) + { + using (var dataSpaceId = new H5Id(H5D.get_space(Id), id => H5S.close(id))) + { + var rank = H5Error.CheckH5Result(H5S.get_simple_extent_ndims(dataSpaceId)); + + var dims = new ulong[rank]; + H5Error.CheckH5Result(H5S.get_simple_extent_dims(dataSpaceId, dims, null)); + + return dims; + } + } + } + + /// + /// Liefert die max. Dimension des Datasets zurück. + /// + public ulong[] GetMaxDimensions() + { + lock (H5GlobalLock.Sync) + { + using (var dataSpaceId = new H5Id(H5D.get_space(Id), id => H5S.close(id))) + { + var rank = H5Error.CheckH5Result(H5S.get_simple_extent_ndims(dataSpaceId)); + + var maxDims = new ulong[rank]; + H5Error.CheckH5Result(H5S.get_simple_extent_dims(dataSpaceId, null, maxDims)); + + return maxDims; + } + } + } + + /// + /// Liefert die konfigurierte Chunk-Size zurück. + /// + public ulong[] GetChunkSize() + { + lock (H5GlobalLock.Sync) + { + using (var propListId = new H5Id(H5D.get_create_plist(Id), id => H5P.close(id))) + { + int rank; + using (var dataSpaceId = new H5Id(H5D.get_space(Id), id => H5S.close(id))) + { + rank = H5Error.CheckH5Result(H5S.get_simple_extent_ndims(dataSpaceId)); + } + + var dims = new ulong[rank]; + H5Error.CheckH5Result(H5P.get_chunk(propListId, rank, dims)); + return dims; + } + } + } + + public void ExtendDimensions(ulong[] newDimensions) + { + lock (H5GlobalLock.Sync) + { + var ret = H5D.set_extent(Id, newDimensions); + H5Error.CheckH5Result(ret); + } + } + + public H5DataSpace GetSpace() + { + lock (H5GlobalLock.Sync) + { + var spaceId = H5D.get_space(Id); + if (spaceId < 0) + throw H5Error.GetExceptionFromHdf5Stack(); + + return new H5DataSpace(spaceId); + } + } + + public void Write(T[,] data, H5DataSpace fileDataSpace = null) + { + Write(typeof(T), data, fileDataSpace); + } + + public void Write(T[] data, H5DataSpace fileDataSpace = null) + { + Write(typeof(T), data, fileDataSpace); + } + + public void Write(Array data, H5DataSpace fileDataSpace = null, H5DataSpace memoryDataSpace = null) + { + Write(data.GetType().GetElementType(), data, fileDataSpace, memoryDataSpace); + } + + public void Write(Type type, Array data, H5DataSpace fileDataSpace = null, H5DataSpace memoryDataSpace = null) + { + var memoryType = H5Type.NativeToH5(type); + var memoryRank = data.Rank; + var memoryDimension = Enumerable.Range(0, memoryRank).Select(i => (ulong)data.GetLength(i)).ToArray(); + + lock (H5GlobalLock.Sync) + { + var memorySpace = memoryDataSpace ?? H5DataSpace.CreateSimpleFixed(memoryDimension); + try + { + if (IsGrowing && fileDataSpace == null) + { + if (memoryDataSpace != null) + throw new NotImplementedException("Currently sourceDataSpace is not supported with growing data sets."); + + ulong[] start; + using (var space = GetSpace()) + { + var cur = space.CurrentDimensions; + start = new ulong[cur.Length]; + var end = new ulong[cur.Length]; + for (var i = 0; i < cur.Length; i++) + { + if (_growing[i]) + { + start[i] = cur[i]; + end[i] = cur[i] + (ulong)data.GetLength(i); + } + else + { + start[i] = 0; + end[i] = cur[i]; + } + } + + ExtendDimensions(end); + } + + using (var space = GetSpace()) + { + space.Select(start, memoryDimension); + Write(memorySpace, memoryType, space, data); + } + } + else + { + Write(memorySpace, memoryType, fileDataSpace, data); + } + } + finally + { + // nur Dispose, wenn der DataSpace auch erzeugt wurde + if (memorySpace != memoryDataSpace) + { + memorySpace.Dispose(); + } + } + } + } + + public void Read(Array data, H5DataSpace fileDataSpace = null, H5DataSpace memoryDataSpace = null) + { + Read(data.GetType().GetElementType(), data, fileDataSpace, memoryDataSpace); + } + + public void Read(Type type, Array data, H5DataSpace fileDataSpace = null, H5DataSpace memoryDataSpace = null) + { + var memoryType = H5Type.NativeToH5(type); + + lock (H5GlobalLock.Sync) + { + var memorySpace = memoryDataSpace ?? H5DataSpace.CreateSimpleFixed(Enumerable.Range(0, data.Rank).Select(i => (ulong)data.GetLength(i)).ToArray()); + try + { + // checks + if (data.Rank != memorySpace.Rank) + throw new ArgumentException($"Rank of memoryDataSpace ({memorySpace.Rank}) does not match data ({data.Rank})"); + + Read(memorySpace, memoryType, fileDataSpace, data); + } + finally + { + // nur Dispose, wenn der DataSpace auch erzeugt wurde + if (memorySpace != memoryDataSpace) + { + memorySpace.Dispose(); + } + } + } + } + + public void Append(T data) + { + Append(typeof(T), data); + } + + public void Append(Type type, object data) + { + if (!IsGrowing) + throw new InvalidOperationException("Append can only be used on growing data sets."); + + var memoryType = H5Type.NativeToH5(type); + + lock (H5GlobalLock.Sync) + { + using (var memorySpace = H5DataSpace.CreateSimpleFixed(new[] { 1UL })) + { + ulong[] start; + using (var space = GetSpace()) + { + start = space.CurrentDimensions; + if (start.Length != 1) + throw new InvalidOperationException("Append can only be used on one dimensional data sets."); + + var end = new[] { start[0] + 1 }; + ExtendDimensions(end); + } + + using (var space = GetSpace()) + { + space.Select(start, new[] { 1UL }); + Write(memorySpace, memoryType, space, data); + } + } + } + } + + public void Append(Type type, Array data) + { + if (!IsGrowing) + throw new InvalidOperationException("Append can only be used on growing data sets."); + + var memoryType = H5Type.NativeToH5(type); + + var dataSpaceDim = new ulong[data.Rank + 1]; + dataSpaceDim[0] = 1UL; + for (var i = 0; i < data.Rank; i++) + { + dataSpaceDim[i + 1] = (ulong)data.GetLength(i); + } + + lock (H5GlobalLock.Sync) + { + using (var memorySpace = H5DataSpace.CreateSimpleFixed(dataSpaceDim)) + { + ulong[] start; + using (var space = GetSpace()) + { + start = space.CurrentDimensions; + if ((start.Length - 1) != data.Rank) + throw new InvalidOperationException("Append can only be used if dimension is one less."); + + var end = (ulong[])start.Clone(); + end[0]++; + ExtendDimensions(end); + } + + using (var space = GetSpace()) + { + for (int i = 0; i < data.Rank; i++) + { + start[i + 1] = 0; + } + + var count = (ulong[])dataSpaceDim.Clone(); + space.Select(start, count); + Write(memorySpace, memoryType, space, data); + } + } + } + } + + /// + /// Fügt mehrere Werte auf einmal hinzu. Das DataSet darf keine fixe Grösse + /// besitzen und darf nur aus einer Dimension bestehen. + /// + /// der Datentyp der zu schreibenden Werte + public void AppendAll(T[] data) + { + AppendAll(typeof(T), data, data.Length); + } + + public void AppendAll(Type type, Array data, int count) + { + /* + * Implementierungshinweis: Die hinzufügenden Daten werden als Array mit primitiven + * Datentyp erwartet, da diese dann ohne Kopiervorgang direkt geschrieben werden + * können, in dem der HDF5-Lib ein unmanaged Pointer auf die Daten übergeben wird. + */ + + if (!IsGrowing) + throw new InvalidOperationException("Append can only be used on growing data sets."); + + if (data.GetLength(0) == 0) + throw new ArgumentException("No data to append.", nameof(data)); + + if (data.GetLength(0) < count) + throw new ArgumentOutOfRangeException(nameof(count)); + + var memoryType = H5Type.NativeToH5(type); + + // 1. Dimension ist vorgegeben durch count + var dataSpaceDim = Enumerable.Range(0, data.Rank) + .Select(x => (ulong)(x == 0 ? count : data.GetLength(x))) + .ToArray(); + + lock (H5GlobalLock.Sync) + { + using (var memorySpace = H5DataSpace.CreateSimpleFixed(dataSpaceDim)) + { + ulong[] start; + using (var space = GetSpace()) + { + start = space.CurrentDimensions; + if (start.Length != data.Rank) + throw new InvalidOperationException("AppendAll can only be used if dimension matches."); + + // 1. Dimension wird erweitert (growing) + var end = (ulong[])start.Clone(); + end[0] += (ulong)count; + ExtendDimensions(end); + } + + using (var space = GetSpace()) + { + // Start: alle ausser 1. Dimension auf 0 setzen + for (int i = 1; i < data.Rank; i++) + { + start[i] = 0; + } + + space.Select(start, dataSpaceDim); + Write(memorySpace, memoryType, space, data); + } + } + } + } + + /// + /// Schreibt die übergebenen Daten in den DataSet. + /// + /// Der DataSpace der übergebenen Daten . + /// Der Typ der übergebenen Daten . + /// Der DataSpace wo die Daten geschrieben werden sollen. + /// Die zu schreibenden Daten. Es muss sich dabei entweder um + /// primitive Datentypen oder um 1-Dimensionale Arrays mit primitive Datentypen handeln. + private void Write(H5DataSpace memoryDataSpace, H5Type memoryDataType, H5DataSpace fileDataSpace, object data) + { + GCHandle gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned); + try + { + var ret = H5D.write(Id, memoryDataType.Id, memoryDataSpace.Id, fileDataSpace != null ? fileDataSpace.Id : H5S.ALL, H5P.DEFAULT, gcHandle.AddrOfPinnedObject()); + H5Error.CheckH5Result(ret); + } + finally + { + gcHandle.Free(); + } + } + + /// + /// Liest Daten vom DataSet in die übergebene Struktur. + /// + private void Read(H5DataSpace memoryDataSpace, H5Type memoryDataType, H5DataSpace fileDataSpace, object data) + { + GCHandle gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned); + try + { + H5Error.CheckH5Result(H5D.read(Id, memoryDataType.Id, memoryDataSpace.Id, fileDataSpace != null ? fileDataSpace.Id : H5S.ALL, H5P.DEFAULT, gcHandle.AddrOfPinnedObject())); + } + finally + { + gcHandle.Free(); + } + } + + public class Builder + { + private H5Type _type; + private string _name; + private ulong[] _dims; + private CreateOptions _options = new CreateOptions(); + + public Builder() + { + } + + public Builder WithType(H5Type type) + { + _type = type; + return this; + } + + public Builder WithType(Type type) + { + _type = H5Type.NativeToH5(type); + return this; + } + + public Builder WithType() + { + return WithType(typeof(T)); + } + + public Builder WithName(string name) + { + _name = name; + return this; + } + + public Builder WithDimension(int dim0, params int[] dimx) + { + _dims = new ulong[1 + dimx.Length]; + _dims[0] = (ulong)dim0; + for (var i = 1; i < _dims.Length; i++) + { + _dims[i] = (ulong)dimx[i - 1]; + } + + return this; + } + + public Builder WithDeflate(int deflateLevel) + { + _options.DeflateLevel = (uint)deflateLevel; + return this; + } + + public Builder WithChunking(int dim0, params int[] dimx) + { + _options.Chunks = new ulong[1 + dimx.Length]; + _options.Chunks[0] = (ulong)dim0; + for (var i = 1; i < _options.Chunks.Length; i++) + { + _options.Chunks[i] = (ulong)dimx[i - 1]; + } + + return this; + } + + public H5DataSet Create(H5File file) + { + if (_type == null) + throw new InvalidOperationException($"Type must be set ({nameof(WithType)})."); + if (_name == null) + throw new InvalidOperationException($"Name must be set ({nameof(WithName)})."); + if (_dims == null) + throw new InvalidOperationException($"At least one dimension must be set ({nameof(WithDimension)})."); + if (_options.Chunks != null && _options.Chunks.Length != _dims.Length) + throw new InvalidOperationException("Dimension size must match chunk size."); + + file.MakeGroups(H5Utils.GetGroups(_name)); + + var current = new ulong[_dims.Length]; + var max = new ulong[_dims.Length]; + for (var i = 0; i < _dims.Length; i++) + { + if (_dims[i] == 0) + { + current[i] = 0; + max[i] = H5S.UNLIMITED; + } + else + { + current[i] = max[i] = _dims[i]; + } + } + + lock (H5GlobalLock.Sync) + { + using (var space = H5DataSpace.CreateSimple(current, max)) + { + return H5DataSet.Create(file, _name, _type, space, _options); + } + } + } + } + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5DataSpace.cs b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5DataSpace.cs new file mode 100644 index 0000000..b0c6115 --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5DataSpace.cs @@ -0,0 +1,193 @@ +using System; +using System.Linq; +using HDF.PInvoke; + +namespace Mbc.Pcs.Net.DataRecorder.Hdf5Utils +{ + public class H5DataSpace : IDisposable + { + public const ulong UNLIMITED = H5S.UNLIMITED; + + private bool _disposed; + + public static H5DataSpace CreateSimpleFixed(ulong[] dimension) + { + return CreateSimple(dimension, dimension); + } + + public static H5DataSpace CreateSimple(ulong[] dimension, ulong[] maxDimension = null) + { + if (maxDimension != null && dimension.Length != maxDimension.Length) + throw new ArgumentException("max must have the same length as current", nameof(maxDimension)); + + var rank = dimension.Length; + + if (maxDimension == null) + { + maxDimension = Enumerable.Repeat(UNLIMITED, rank).ToArray(); + } + + lock (H5GlobalLock.Sync) + { + var dataSpaceId = H5S.create_simple(rank, dimension, maxDimension); + if (dataSpaceId < 0) + throw H5Error.GetExceptionFromHdf5Stack(); + + return new H5DataSpace(dataSpaceId); + } + } + + public static HyperslabSelectionBuilder CreateSelectionBuilder() => new HyperslabSelectionBuilder(); + + internal H5DataSpace(long dataSpaceId) + { + Id = dataSpaceId; + } + + ~H5DataSpace() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + lock (H5GlobalLock.Sync) + { + var ret = H5S.close(Id); + if (disposing) + H5Error.CheckH5Result(ret); + } + } + + internal long Id { get; } + + public int Rank + { + get + { + lock (H5GlobalLock.Sync) + { + var rank = H5S.get_simple_extent_ndims(Id); + if (rank < 0) + throw H5Error.GetExceptionFromHdf5Stack(); + + return rank; + } + } + } + + public ulong[] CurrentDimensions + { + get + { + var rank = Rank; + var dims = new ulong[rank]; + + lock (H5GlobalLock.Sync) + { + var ret = H5S.get_simple_extent_dims(Id, dims, null); + H5Error.CheckH5Result(ret); + return dims; + } + } + } + + public long ElementCount + { + get + { + lock (H5GlobalLock.Sync) + { + return H5Error.CheckH5Result(H5S.get_simple_extent_npoints(Id)); + } + } + } + + public HyperslabSelector Select(ulong[] start, ulong[] count, ulong[] stride = null, ulong[] block = null) + { + return new HyperslabSelector(Id, start, stride, count, block); + } + + public class HyperslabSelector + { + private readonly long _dataSetId; + + internal HyperslabSelector(long dataSetId, ulong[] start, ulong[] stride, ulong[] count, ulong[] block) + { + _dataSetId = dataSetId; + AddSelect(H5S.seloper_t.SET, start, stride, count, block); + } + + private void AddSelect(H5S.seloper_t op, ulong[] start, ulong[] stride, ulong[] count, ulong[] block) + { + lock (H5GlobalLock.Sync) + { + var ret = H5S.select_hyperslab(_dataSetId, op, start, stride, count, block); + H5Error.CheckH5Result(ret); + } + } + + public HyperslabSelector Or(ulong[] start, ulong[] count, ulong[] stride = null, ulong[] block = null) + { + AddSelect(H5S.seloper_t.OR, start, stride, count, block); + return this; + } + + public HyperslabSelector And(ulong[] start, ulong[] count, ulong[] stride = null, ulong[] block = null) + { + AddSelect(H5S.seloper_t.AND, start, stride, count, block); + return this; + } + } + + public class HyperslabSelectionBuilder + { + private ulong[] _start; + private ulong[] _count; + + internal HyperslabSelectionBuilder() + { + } + + public HyperslabSelectionBuilder Start(ulong dim0, params ulong[] dimx) + { + _start = new ulong[dimx.Length + 1]; + _start[0] = dim0; + for (var i = 1; i < _start.Length; i++) + { + _start[i] = dimx[i - 1]; + } + + return this; + } + + public HyperslabSelectionBuilder Count(ulong dim0, params ulong[] dimx) + { + _count = new ulong[dimx.Length + 1]; + _count[0] = dim0; + for (var i = 1; i < _count.Length; i++) + { + _count[i] = dimx[i - 1]; + } + + return this; + } + + public void ApplyTo(H5DataSpace dataSpace) + { + new HyperslabSelector(dataSpace.Id, _start, null, _count, null); + } + } + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Error.cs b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Error.cs new file mode 100644 index 0000000..187920b --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Error.cs @@ -0,0 +1,113 @@ +using HDF.PInvoke; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Mbc.Pcs.Net.DataRecorder.Hdf5Utils +{ + [Serializable] + public class H5Error : Exception + { + public H5Error() + { + } + + public H5Error(string message) + : base(message) + { + } + + public H5Error(string message, Exception innerException) + : base(message, innerException) + { + } + + public static H5Error GetExceptionFromHdf5Stack() + { + return new ErrorWalker().Error; + } + + public static int CheckH5Result(int result) + { + if (result < 0) + throw GetExceptionFromHdf5Stack(); + + return result; + } + + public static long CheckH5Result(long result) + { + if (result < 0) + throw GetExceptionFromHdf5Stack(); + + return result; + } + + private class ErrorWalker + { + private readonly List _errors = new List(); + private readonly List _walkedHdf5Errors = new List(); + + public ErrorWalker() + { + lock (H5GlobalLock.Sync) + { + var res = H5E.walk(H5E.DEFAULT, H5E.direction_t.H5E_WALK_UPWARD, Walker, IntPtr.Zero); + if (res < 0) + throw new InvalidOperationException("Could not walk HDF5 error."); + } + + /* + * Aus einen unbekannten Grund können die Error-Messages nicht im Callback des Walkers + * erstellt werden (Program crashed ohne erkennbare Ursache). Daher werden im Callback + * nur die Error-Strukturen gesammelt und dann anschliessend hier ausgewertet. + */ + + foreach (var foo in _walkedHdf5Errors) + { + { + H5E.type_t type = default(H5E.type_t); // wird eigentlich nicht benötigt + + var majorMsg = new StringBuilder(1024); + var minorMsg = new StringBuilder(1024); + + lock (H5GlobalLock.Sync) + { + H5E.get_msg(foo.maj_num, ref type, majorMsg, new IntPtr(majorMsg.Capacity)); + H5E.get_msg(foo.min_num, ref type, minorMsg, new IntPtr(majorMsg.Capacity)); + } + + var msg = $"{foo.func_name}: {foo.desc} -> {majorMsg} - {minorMsg}"; + + H5Error exc; + if (_errors.Count == 0) + exc = new H5Error(msg); + else + exc = new H5Error(msg, _errors.Last()); + + /* + TODO hier könnten noch deutlich mehr Informationen herausgeholt werden, wenn notwendig. + Siehe auch http://davis.lbl.gov/Manuals/HDF5-1.8.7/UG/13_ErrorHandling.html + */ + + _errors.Add(exc); + } + } + } + + private int Walker(uint n, ref H5E.error_t err_desc, IntPtr client_data) + { + _walkedHdf5Errors.Add(err_desc); + return 0; + } + + public H5Error Error => _errors.Last(); + } + + protected H5Error(System.Runtime.Serialization.SerializationInfo serializationInfo, System.Runtime.Serialization.StreamingContext streamingContext) + { + throw new NotImplementedException(); + } + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5File.cs b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5File.cs new file mode 100644 index 0000000..bde4901 --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5File.cs @@ -0,0 +1,115 @@ +using HDF.PInvoke; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mbc.Pcs.Net.DataRecorder.Hdf5Utils +{ + public sealed class H5File : IDisposable + { + [Flags] + public enum Flags : uint + { + ReadOnly = H5F.ACC_RDONLY, + ReadWrite = H5F.ACC_RDWR, + Overwrite = H5F.ACC_TRUNC, + CreateOnly = H5F.ACC_EXCL, + OpenOrCreate = H5F.ACC_CREAT, + } + + private bool _disposed; + + public H5File(string filename, Flags flags) + { + lock (H5GlobalLock.Sync) + { + if ((flags & (Flags.CreateOnly | Flags.OpenOrCreate)) != 0) + { + Id = H5Error.CheckH5Result(H5F.create(filename, (uint)flags)); + } + else + { + Id = H5Error.CheckH5Result(H5F.open(filename, (uint)flags)); + } + } + } + + ~H5File() + { + Dispose(false); + } + + public string GetName() + { + lock (H5GlobalLock.Sync) + { + var len = H5Error.CheckH5Result(H5F.get_name(Id, null, IntPtr.Zero).ToInt32()); + var name = new StringBuilder(len + 1); + var ret = H5F.get_name(Id, name, new IntPtr(name.Capacity)); + H5Error.CheckH5Result(ret.ToInt32()); + + return name.ToString(); + } + } + + internal long Id { get; } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + lock (H5GlobalLock.Sync) + { + var ret = H5F.close(Id); + + if (disposing) + { + H5Error.CheckH5Result(ret); + } + } + } + + public void Flush() + { + lock (H5GlobalLock.Sync) + { + var ret = H5F.flush(Id, H5F.scope_t.GLOBAL); + H5Error.CheckH5Result(ret); + } + } + + public H5Group OpenGroup(string name) + { + lock (H5GlobalLock.Sync) + { + var groupId = H5Error.CheckH5Result(H5G.open(Id, name)); + return new H5Group(groupId); + } + } + + public H5Group CreateGroup(string name) + { + H5Group.CreateMissingGroups(Id, name); + return OpenGroup(name); + } + + public void MakeGroups(string name) + { + H5Group.CreateMissingGroups(Id, name); + } + + public IEnumerable GetNames() + { + return new H5LinkIterator(Id).Names; + } + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5GlobalLock.cs b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5GlobalLock.cs new file mode 100644 index 0000000..f257d3e --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5GlobalLock.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading; + +namespace Mbc.Pcs.Net.DataRecorder.Hdf5Utils +{ + /// + /// Stellt einen anwendungsweiten Lock für den Zugriff auf HDF5 + /// Funktionen zur Verfügung. + ///

Alle public-Methoden dieses Assembly, die auf HDF5 zugreifen + /// benutzen intern diesen Lock.

+ ///
+ public class H5GlobalLock : IDisposable + { + public static readonly object Sync = new object(); + + public static bool HasLock => Monitor.IsEntered(Sync); + + public H5GlobalLock() + { + Monitor.Enter(Sync); + } + + public void Dispose() + { + Monitor.Exit(Sync); + } + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Group.cs b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Group.cs new file mode 100644 index 0000000..c15f728 --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Group.cs @@ -0,0 +1,117 @@ +using HDF.PInvoke; +using System; +using System.Collections.Generic; + +namespace Mbc.Pcs.Net.DataRecorder.Hdf5Utils +{ + public class H5Group : IDisposable + { + private bool _disposed; + + internal H5Group(long groupId) + { + Id = groupId; + } + + ~H5Group() + { + Dispose(false); + } + + internal long Id { get; } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (_disposed) + return; + _disposed = true; + + lock (H5GlobalLock.Sync) + { + var ret = H5G.close(Id); + if (disposing) + { + H5Error.CheckH5Result(ret); + } + } + } + + public H5Group OpenGroup(string name) + { + lock (H5GlobalLock.Sync) + { + var groupId = H5Error.CheckH5Result(H5G.open(Id, name)); + return new H5Group(groupId); + } + } + + public H5Group CreateGroup(string name) + { + H5Group.CreateMissingGroups(Id, name); + return OpenGroup(name); + } + + public IEnumerable GetNames() + { + return new H5LinkIterator(Id).Names; + } + + internal static void CreateMissingGroups(long id, string name) + { + // Mögliche Namen: + // / -> file + // /foo/bar -> file -> group(foo) -> group(bar) + // foo/bar -> current -> group(foo) -> group(bar) + // ./foo/bar -> current -> group(foo) -> group(bar) + + // Startpunkt festlegen + var curName = string.Empty; + var remainingName = name; + if (remainingName.StartsWith("/")) + { + remainingName = remainingName.Substring(1); + } + else if (remainingName.StartsWith(".")) + { + remainingName = remainingName.Substring(1); + curName = "."; + } + else + { + curName = "."; + } + + while (remainingName.Length > 0) + { + var idx = remainingName.IndexOf('/'); + if (idx > 0) + { + curName += $"/{remainingName.Substring(0, idx)}"; + remainingName = remainingName.Substring(idx + 1); + } + else + { + curName += $"/{remainingName}"; + remainingName = string.Empty; + } + + lock (H5GlobalLock.Sync) + { + if (H5Error.CheckH5Result(H5L.exists(id, curName)) == 0) + { + using (var newid = new H5Id(H5G.create(id, curName), H5G.close)) + { + // nothing to do, only create + } + } + } + } + } + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Id.cs b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Id.cs new file mode 100644 index 0000000..9d45c80 --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Id.cs @@ -0,0 +1,54 @@ +using System; + +namespace Mbc.Pcs.Net.DataRecorder.Hdf5Utils +{ + public class H5Id : IDisposable + { + private readonly Func _closer; + private bool _disposed; + + public H5Id(long id, Func closer) + { + Id = id; + _closer = closer; + + lock (H5GlobalLock.Sync) + { + if (Id < 0) + throw H5Error.GetExceptionFromHdf5Stack(); + } + } + + ~H5Id() + { + Dispose(false); + } + + public long Id { get; } + + public static implicit operator long(H5Id h5Id) + { + return h5Id.Id; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (_disposed) + return; + _disposed = true; + + lock (H5GlobalLock.Sync) + { + var ret = _closer(Id); + if (disposing) + H5Error.CheckH5Result(ret); + } + } + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5LinkIterator.cs b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5LinkIterator.cs new file mode 100644 index 0000000..8512d28 --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5LinkIterator.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using HDF.PInvoke; + +namespace Mbc.Pcs.Net.DataRecorder.Hdf5Utils +{ + /// + /// Implementiert die H5Literate-Funktion, die alle Links einer Gruppe (oder File) + /// auflistet. + /// + internal class H5LinkIterator + { + private readonly List _names = new List(); + + public H5LinkIterator(long groupId) + { + ulong idx = 0; + lock (H5GlobalLock.Sync) + { + var ret = H5L.iterate(groupId, H5.index_t.NAME, H5.iter_order_t.NATIVE, ref idx, IterateCallback, IntPtr.Zero); + H5Error.CheckH5Result(ret); + } + } + + private int IterateCallback(long group, IntPtr name, ref H5L.info_t info, IntPtr op_data) + { + string linkName; + switch (info.cset) + { + case H5T.cset_t.ASCII: + linkName = UnmanagedUtils.PtrToStringASCII(name, -1); + break; + case H5T.cset_t.UTF8: + linkName = UnmanagedUtils.PtrToStringUTF8(name, -1); + break; + default: + throw new H5Error($"Invalid character set {info.cset}."); + } + + _names.Add(linkName); + + return 0; + } + + public IReadOnlyList Names => _names.AsReadOnly(); + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Type.cs b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Type.cs new file mode 100644 index 0000000..631b8a3 --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Type.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using HDF.PInvoke; + +namespace Mbc.Pcs.Net.DataRecorder.Hdf5Utils +{ + public class H5Type + { + public static readonly H5Type DoubleType = new H5Type(H5T.NATIVE_DOUBLE); + public static readonly H5Type FloatType = new H5Type(H5T.NATIVE_FLOAT); + public static readonly H5Type SByteType = new H5Type(H5T.NATIVE_SCHAR); + public static readonly H5Type ShortType = new H5Type(H5T.NATIVE_SHORT); + public static readonly H5Type IntType = new H5Type(H5T.NATIVE_INT); + public static readonly H5Type LongType = new H5Type(H5T.NATIVE_INT64); + public static readonly H5Type ByteType = new H5Type(H5T.NATIVE_UCHAR); + public static readonly H5Type UShortType = new H5Type(H5T.NATIVE_USHORT); + public static readonly H5Type UIntType = new H5Type(H5T.NATIVE_UINT); + public static readonly H5Type ULongType = new H5Type(H5T.NATIVE_UINT64); + + private static Dictionary _nativeTypes = new Dictionary + { + [typeof(double)] = DoubleType, + [typeof(float)] = FloatType, + [typeof(sbyte)] = SByteType, + [typeof(short)] = ShortType, + [typeof(int)] = IntType, + [typeof(long)] = LongType, + [typeof(byte)] = ByteType, + [typeof(ushort)] = UShortType, + [typeof(uint)] = UIntType, + [typeof(ulong)] = ULongType, + }; + + private readonly long _typeId; + + public static H5Type NativeToH5(Type type) + { + if (!_nativeTypes.ContainsKey(type)) + throw new ArgumentException($"Invalid type: {type}.", nameof(type)); + return _nativeTypes[type]; + } + + public static Type H5ToNative(long typeId) + { + lock (H5GlobalLock.Sync) + { + return _nativeTypes.First(x => H5Error.CheckH5Result(H5T.equal(x.Value.Id, typeId)) > 0).Key; + } + } + + private H5Type(long typeId) + { + _typeId = typeId; + } + + internal long Id => _typeId; + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Utils.cs b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Utils.cs new file mode 100644 index 0000000..8850a1f --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/H5Utils.cs @@ -0,0 +1,66 @@ +using HDF.PInvoke; +using System; +using System.Threading; + +namespace Mbc.Pcs.Net.DataRecorder.Hdf5Utils +{ + public static class H5Utils + { + private static object _initLock = new object(); + private static bool _initialized; + private static int _destroyed; + + public static void OpenH5(bool autoClose = true) + { + lock (_initLock) + { + if (_initialized) + return; + _initialized = true; + + H5Error.CheckH5Result(H5.open()); + if (autoClose) + { + AppDomain.CurrentDomain.ProcessExit += (sender, e) => + { + CloseH5(); + }; + AppDomain.CurrentDomain.DomainUnload += (sender, e) => + { + CloseH5(); + }; + } + } + } + + public static void CloseH5() + { + if (Interlocked.Exchange(ref _destroyed, 1) == 1) + return; + H5Error.CheckH5Result(H5.close()); + } + + /// + /// Liefert den Gruppennamen eines Location-Strings zurück, + /// sofern einer existiert. + /// + public static string GetGroups(string name) + { + var idx = name.LastIndexOf('/'); + if (idx < 0) + return string.Empty; + + return name.Substring(0, idx); + } + + /// + /// Erzeugt eine -Instanz für eine . + /// + public static H5Attribute Attributes(this H5File file) => new H5Attribute(file); + + /// + /// Erzeugt eine -Instanz für eine . + /// + public static H5Attribute Attributes(this H5DataSet dataSet) => new H5Attribute(dataSet); + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/Nativ/H5IdAccesor.cs b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/Nativ/H5IdAccesor.cs new file mode 100644 index 0000000..fe3fce5 --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/Nativ/H5IdAccesor.cs @@ -0,0 +1,16 @@ +namespace Mbc.Pcs.Net.DataRecorder.Hdf5Utils.Nativ +{ + /// + /// Liefert für spezielle Anwendungen die interne ID von verschiedenen + /// HDF5-Objekten zurück. + /// + public static class H5IdAccesor + { + public static long GetId(H5File h5File) => h5File.Id; + public static long GetId(H5DataSet h5DataSet) => h5DataSet.Id; + public static long GetId(H5DataSpace h5DataSpace) => h5DataSpace.Id; + public static long GetId(H5Group h5Group) => h5Group.Id; + public static long GetId(H5Type h5Type) => h5Type.Id; + + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/UnmanagedUtils.cs b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/UnmanagedUtils.cs new file mode 100644 index 0000000..8defe9d --- /dev/null +++ b/NET/Mbc.Pcs.Net.DataRecorder/Hdf5Utils/UnmanagedUtils.cs @@ -0,0 +1,93 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Mbc.Pcs.Net.DataRecorder.Hdf5Utils +{ + /// + /// Hilfsfunktionen für unmanaged Daten. + /// + internal static class UnmanagedUtils + { + /// + /// Liefert einen Single-Wert zurück, der sich an der + /// Stelle befindet. + /// + public static unsafe float PtrToSingle(IntPtr ptr) + { + return *((float*)ptr.ToPointer()); + } + + /// + /// Liefert einen Double-Wert zurück, der sich an der + /// Stelle befindet. + /// + public static unsafe double PtrToDouble(IntPtr ptr) + { + return *((double*)ptr.ToPointer()); + } + + /// + /// Schreibt einen Single-Wert an die Stelle + /// von . + /// + public static unsafe void SingleToPtr(float value, IntPtr ptr) + { + *((float*)ptr.ToPointer()) = value; + } + + /// + /// Schreibt einen Double-Wert an die Stelle + /// von . + /// + public static unsafe void DoubleToPtr(double value, IntPtr ptr) + { + *((double*)ptr.ToPointer()) = value; + } + + /// + /// Liefert einen ASCII-String, der an der Stelle + /// liegt zurück. Die Grösse wird entweder explizit angegeben oder wenn + /// -1 ist wird sie automatisch ermittelt. + /// + public static unsafe string PtrToStringASCII(IntPtr ptr, int size) + { + if (size == -1) + { + size = EndOfString(ptr); + } + + var buffer = new byte[size]; + Marshal.Copy(ptr, buffer, 0, size); + return Encoding.ASCII.GetString(buffer); + } + + /// + /// Liefert einen UTF8-String, der an der Stelle + /// liegt zurück. Die Grösse wird entweder explizit angegeben oder wenn + /// -1 ist wird sie automatisch ermittelt. + /// + public static string PtrToStringUTF8(IntPtr ptr, int size) + { + if (size == -1) + { + size = EndOfString(ptr); + } + + var buffer = new byte[size]; + Marshal.Copy(ptr, buffer, 0, size); + return Encoding.UTF8.GetString(buffer); + } + + private static unsafe int EndOfString(IntPtr ptr) + { + var size = 0; + while (*((byte*)(ptr + size).ToPointer()) != 0) + { + size++; + } + + return size; + } + } +} diff --git a/NET/Mbc.Pcs.Net.DataRecorder/Mbc.Pcs.Net.DataRecorder.csproj b/NET/Mbc.Pcs.Net.DataRecorder/Mbc.Pcs.Net.DataRecorder.csproj index ba3b0f9..bc5b20a 100644 --- a/NET/Mbc.Pcs.Net.DataRecorder/Mbc.Pcs.Net.DataRecorder.csproj +++ b/NET/Mbc.Pcs.Net.DataRecorder/Mbc.Pcs.Net.DataRecorder.csproj @@ -1,20 +1,31 @@  - netstandard2.0;net471;net8.0 - 5.0.0.0 + net10.0;net8.0;netstandard2.0 + 5.2.0 Mbc.Pcs.Net.DataRecorder - - true + - NU1702 + NU1702 + + true + + + + true + + + + + - + + - + \ No newline at end of file diff --git a/NET/Mbc.Pcs.Net.Test.Util/Mbc.Pcs.Net.Test.Util.csproj b/NET/Mbc.Pcs.Net.Test.Util/Mbc.Pcs.Net.Test.Util.csproj index 6471e89..8a7e5f7 100644 --- a/NET/Mbc.Pcs.Net.Test.Util/Mbc.Pcs.Net.Test.Util.csproj +++ b/NET/Mbc.Pcs.Net.Test.Util/Mbc.Pcs.Net.Test.Util.csproj @@ -1,8 +1,8 @@  - netstandard2.0;net471;net8.0 - 5.0.0.0 + net10.0;net8.0;netstandard2.0 + 5.2.0 Mbc.Pcs.Net diff --git a/NET/Mbc.Pcs.Net.Test/Mbc.Pcs.Net.Test.csproj b/NET/Mbc.Pcs.Net.Test/Mbc.Pcs.Net.Test.csproj index f9b8360..5cc77e2 100644 --- a/NET/Mbc.Pcs.Net.Test/Mbc.Pcs.Net.Test.csproj +++ b/NET/Mbc.Pcs.Net.Test/Mbc.Pcs.Net.Test.csproj @@ -1,7 +1,7 @@  - net471;net8.0 + net10.0;net8.0 Mbc.Pcs.Net.Test false diff --git a/NET/Mbc.Pcs.Net.sln b/NET/Mbc.Pcs.Net.sln index c953b9f..b9c6620 100644 --- a/NET/Mbc.Pcs.Net.sln +++ b/NET/Mbc.Pcs.Net.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.10.34928.147 +# Visual Studio Version 18 +VisualStudioVersion = 18.1.11312.151 d18.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cli", "Samples\Cli\Cli.csproj", "{08056A54-605C-4ACC-ADD7-3DB109298C28}" EndProject @@ -51,8 +51,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mbc.Pcs.Net.Command", "Mbc. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mbc.Pcs.Net.Command.Test", "Mbc.Pcs.Net.Command.Test\Mbc.Pcs.Net.Command.Test.csproj", "{DCD8BB64-82B9-4D75-A5B6-FFAB83EC9A59}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Profiling", "Profiling\Profiling.csproj", "{6718F565-F3E0-4F0A-8A0B-FE85F9355E10}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdsMapperCli", "AdsMapperCli\AdsMapperCli.csproj", "{6C610A13-BD3D-4B55-A134-E5D5416B8569}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mbc.Ads.Helper.ReadType", "Mbc.Ads.Helper.ReadType\Mbc.Ads.Helper.ReadType.csproj", "{C1F08399-B1F2-4691-B483-DCEE779DD0D9}" @@ -67,6 +65,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mbc.Pcs.Net.DataRecorder", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mbc.Pcs.Net.DataRecorder.Test", "Mbc.Pcs.Net.DataRecorder.Test\Mbc.Pcs.Net.DataRecorder.Test.csproj", "{1EEC6272-7D89-48C4-A09B-C33C3C774BE0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Profiling", "Samples\Profiling\Profiling.csproj", "{CD03558C-DF23-C19C-6FE7-FCED1AA13E2E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -132,9 +132,6 @@ Global {DCD8BB64-82B9-4D75-A5B6-FFAB83EC9A59}.Debug|Any CPU.Build.0 = Debug|Any CPU {DCD8BB64-82B9-4D75-A5B6-FFAB83EC9A59}.Release|Any CPU.ActiveCfg = Release|Any CPU {DCD8BB64-82B9-4D75-A5B6-FFAB83EC9A59}.Release|Any CPU.Build.0 = Release|Any CPU - {6718F565-F3E0-4F0A-8A0B-FE85F9355E10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6718F565-F3E0-4F0A-8A0B-FE85F9355E10}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6718F565-F3E0-4F0A-8A0B-FE85F9355E10}.Release|Any CPU.ActiveCfg = Release|Any CPU {6C610A13-BD3D-4B55-A134-E5D5416B8569}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6C610A13-BD3D-4B55-A134-E5D5416B8569}.Debug|Any CPU.Build.0 = Debug|Any CPU {6C610A13-BD3D-4B55-A134-E5D5416B8569}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -151,15 +148,19 @@ Global {1EEC6272-7D89-48C4-A09B-C33C3C774BE0}.Debug|Any CPU.Build.0 = Debug|Any CPU {1EEC6272-7D89-48C4-A09B-C33C3C774BE0}.Release|Any CPU.ActiveCfg = Release|Any CPU {1EEC6272-7D89-48C4-A09B-C33C3C774BE0}.Release|Any CPU.Build.0 = Release|Any CPU + {CD03558C-DF23-C19C-6FE7-FCED1AA13E2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD03558C-DF23-C19C-6FE7-FCED1AA13E2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD03558C-DF23-C19C-6FE7-FCED1AA13E2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD03558C-DF23-C19C-6FE7-FCED1AA13E2E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {08056A54-605C-4ACC-ADD7-3DB109298C28} = {7FC136A2-182C-44F9-AF35-0AA0013BEB23} - {6718F565-F3E0-4F0A-8A0B-FE85F9355E10} = {7FC136A2-182C-44F9-AF35-0AA0013BEB23} {6C610A13-BD3D-4B55-A134-E5D5416B8569} = {7FC136A2-182C-44F9-AF35-0AA0013BEB23} {C1F08399-B1F2-4691-B483-DCEE779DD0D9} = {7FC136A2-182C-44F9-AF35-0AA0013BEB23} + {CD03558C-DF23-C19C-6FE7-FCED1AA13E2E} = {7FC136A2-182C-44F9-AF35-0AA0013BEB23} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {41EBCC31-1BB3-4F43-BB31-8BD9166775A7} diff --git a/NET/Mbc.Pcs.Net/Mbc.Pcs.Net.csproj b/NET/Mbc.Pcs.Net/Mbc.Pcs.Net.csproj index 821ae3d..cc7a272 100644 --- a/NET/Mbc.Pcs.Net/Mbc.Pcs.Net.csproj +++ b/NET/Mbc.Pcs.Net/Mbc.Pcs.Net.csproj @@ -1,8 +1,8 @@  - netstandard2.0;net471;net8.0 - 5.1.0.0 + net10.0;net8.0;netstandard2.0 + 5.2.0 Mbc.Pcs.Net diff --git a/NET/Profiling/App.config b/NET/Profiling/App.config deleted file mode 100644 index 787dcbe..0000000 --- a/NET/Profiling/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/NET/Profiling/Profiling.GeneratedMSBuildEditorConfig.editorconfig b/NET/Profiling/Profiling.GeneratedMSBuildEditorConfig.editorconfig deleted file mode 100644 index 460911c..0000000 --- a/NET/Profiling/Profiling.GeneratedMSBuildEditorConfig.editorconfig +++ /dev/null @@ -1,29 +0,0 @@ -is_global = true -build_property.TargetFramework = net471 -build_property.TargetFramework = net471 -build_property.TargetFramework = net471 -build_property.TargetFramework = net471 -build_property.TargetPlatformMinVersion = -build_property.TargetPlatformMinVersion = -build_property.TargetPlatformMinVersion = -build_property.TargetPlatformMinVersion = -build_property.UsingMicrosoftNETSdkWeb = -build_property.UsingMicrosoftNETSdkWeb = -build_property.UsingMicrosoftNETSdkWeb = -build_property.UsingMicrosoftNETSdkWeb = -build_property.ProjectTypeGuids = -build_property.ProjectTypeGuids = -build_property.ProjectTypeGuids = -build_property.ProjectTypeGuids = -build_property.PublishSingleFile = -build_property.PublishSingleFile = -build_property.PublishSingleFile = -build_property.PublishSingleFile = -build_property.IncludeAllContentForSelfExtract = -build_property.IncludeAllContentForSelfExtract = -build_property.IncludeAllContentForSelfExtract = -build_property.IncludeAllContentForSelfExtract = -build_property._SupportedPlatformList = -build_property._SupportedPlatformList = -build_property._SupportedPlatformList = -build_property._SupportedPlatformList = diff --git a/NET/Profiling/Profiling.csproj b/NET/Profiling/Profiling.csproj deleted file mode 100644 index 81398bb..0000000 --- a/NET/Profiling/Profiling.csproj +++ /dev/null @@ -1,59 +0,0 @@ - - - - - Debug - AnyCPU - {6718F565-F3E0-4F0A-8A0B-FE85F9355E10} - Exe - Profiling - Profiling - v4.7.1 - 512 - true - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - {c050d1b2-47bf-4212-a770-63ac481c2ad5} - Mbc.Pcs.Net.DataRecorder - - - - \ No newline at end of file diff --git a/NET/Profiling/Properties/AssemblyInfo.cs b/NET/Profiling/Properties/AssemblyInfo.cs deleted file mode 100644 index 9cc5e9f..0000000 --- a/NET/Profiling/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Profiling")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("mbc engineering")] -[assembly: AssemblyProduct("Profiling")] -[assembly: AssemblyCopyright("Copyright (c) 2020 by mbc engineering, CH-6015 Luzern")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("6718f565-f3e0-4f0a-8a0b-fe85f9355e10")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/NET/Samples/Cli/Cli.csproj b/NET/Samples/Cli/Cli.csproj index 8a48334..8cdd128 100644 --- a/NET/Samples/Cli/Cli.csproj +++ b/NET/Samples/Cli/Cli.csproj @@ -2,16 +2,15 @@ Exe - net8.0 + net10.0 Cli.Program $(MSBuildProjectDirectory)\..\..\Build\Mbc.CodeAnalysis.ruleset - + - diff --git a/NET/Samples/Profiling/Profiling.csproj b/NET/Samples/Profiling/Profiling.csproj new file mode 100644 index 0000000..1f991c1 --- /dev/null +++ b/NET/Samples/Profiling/Profiling.csproj @@ -0,0 +1,15 @@ + + + + Exe + net10.0 + Profiling.Program + + $(MSBuildProjectDirectory)\..\..\Build\Mbc.CodeAnalysis.ruleset + + + + + + + diff --git a/NET/Profiling/Program.cs b/NET/Samples/Profiling/Program.cs similarity index 100% rename from NET/Profiling/Program.cs rename to NET/Samples/Profiling/Program.cs