After flailing around with HDF.PInvoke for a while, I tried the HDF.PInvoke.NETStandard Nuget package. Visual Studio was able to resolve my namespace reference (using HDF.PInvoke).
For a basic idea of how to open files, groups, and datasets, I read through the HDF5 User Guide, specifically sections
- The HDF5 File
- HDF5 Groups
- HDF5 Datasets
I used the Object Browser in Visual Studio to browse the API for methods, enums, etc. referenced in the User Guide above. The names match closely. You can get this info from the HDF.PInvoke.NETStandard API reference, but there’s no additional info there, so you might as well use the IDE.
Finally, I wanted to read an array, but I couldn’t figure out how to pass in a pointer to an array in c#. Eventually, it occurred to me to check the unit tests for the API. There, I found you need to get a pinned GCHandle, and pass the AddrOfPinnedObject.
Here’s an example,
using HDF.PInvoke;
using System.Runtime.InteropServices;
namespace HDF5PInvokeHell
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("application start");
            // Build file path
            var folder = AppDomain.CurrentDomain.BaseDirectory;
            string fileName = Path.Combine(folder, ".//resources//sample.h5");
            // Open file
            Console.WriteLine("opening file");
            var fileId = H5F.open(fileName, H5F.ACC_RDONLY);
            WriteStatusMessage(fileId, "open file");
            // Open group
            var groupName = "Group0/GroupA";
            Console.WriteLine($"opening group: {groupName}");
            var groupId = H5G.open(fileId, groupName);
            WriteStatusMessage(groupId, "open group");
            // open dataset
            Console.WriteLine("opening dataset");
            var datasetId = H5D.open(groupId, "Dataset0", H5F.ACC_RDONLY);
            WriteStatusMessage(datasetId, "open dataset");
            // get dataset type
            Console.WriteLine("getting datatype id");
            var datatypeId = H5D.get_type(datasetId);
            WriteStatusMessage(datatypeId, "get datatype id");
            // get datatype class id
            Console.WriteLine("getting datatype class id");
            var datatypeClassId = H5T.get_class(datatypeId);
            WriteStatusMessage((int)datatypeClassId, "get datatype class id");
            // read dataset
            Console.WriteLine("reading dataset");
            var myArray = new float[512, 512];
            var myArrayHandle = GCHandle.Alloc(myArray, GCHandleType.Pinned);
            WriteStatusMessage(H5D.read(datasetId, H5T.NATIVE_FLOAT, H5S.ALL, H5S.ALL, H5P.DEFAULT, myArrayHandle.AddrOfPinnedObject()), "read dataset");
            // close dataset
            Console.WriteLine("closing dataset");
            WriteStatusMessage(H5D.close(datasetId), "close dataset");
            // close group
            Console.WriteLine($"closing group: {groupName}");
            WriteStatusMessage(H5G.close(groupId), "close group");
            // close file
            Console.WriteLine("closing file");
            WriteStatusMessage(H5F.close(fileId), "close file");
            Console.WriteLine("Application end");
        }
        private static void WriteStatusMessage(long status, string message)
        {
            // H5* methods that return a status code follow the convention:
            //  success = 0
            //  failure = -1
            // H5* methods that return a value follow a similar convention:
            //  readSuccess => value = value
            //  readFailure => value < 0
            if (status >= 0)
                Console.WriteLine($"{message}: OK");
            else
                Console.WriteLine($"{message}: FAIL");
        }
    }
}