diff --git a/src/HidApi.Net.Tests/StringTests.cs b/src/HidApi.Net.Tests/StringTests.cs
index a092ce3..9a8b6f1 100644
--- a/src/HidApi.Net.Tests/StringTests.cs
+++ b/src/HidApi.Net.Tests/StringTests.cs
@@ -53,4 +53,17 @@ public void TestUtf32()
}
}
}
+
+
+ [TestMethod]
+ public unsafe void CanCreateNullTerminatedString()
+ {
+ var result = "MyString";
+ var nullTerminatedString = WCharT.CreateNullTerminatedString(result);
+ fixed (byte* ptr = nullTerminatedString)
+ {
+ var r = WCharT.GetString(ptr);
+ r.Should().Be(result);
+ }
+ }
}
diff --git a/src/HidApi.Net/Device.cs b/src/HidApi.Net/Device.cs
index 97718c9..16ebdf7 100644
--- a/src/HidApi.Net/Device.cs
+++ b/src/HidApi.Net/Device.cs
@@ -15,7 +15,21 @@ public class Device : IDisposable
/// product id of target device
public Device(ushort vendorId, ushort productId)
{
- handle = NativeMethods.Open(vendorId, productId, string.Empty);
+ handle = NativeMethods.Open(vendorId, productId, NullTerminatedString.Empty);
+
+ if (handle.IsInvalid)
+ HidException.Throw(handle);
+ }
+
+ ///
+ /// Connects to a given device.
+ ///
+ /// Vendor id of target device
+ /// Product id of target device
+ /// Serial number of target device
+ public Device(ushort vendorId, ushort productId, string serialNumber)
+ {
+ handle = NativeMethods.Open(vendorId, productId, WCharT.CreateNullTerminatedString(serialNumber));
if (handle.IsInvalid)
HidException.Throw(handle);
diff --git a/src/HidApi.Net/HidApi.Net.csproj b/src/HidApi.Net/HidApi.Net.csproj
index 2453527..b0c8e30 100644
--- a/src/HidApi.Net/HidApi.Net.csproj
+++ b/src/HidApi.Net/HidApi.Net.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/src/HidApi.Net/Internal/NativeMethods.cs b/src/HidApi.Net/Internal/NativeMethods.cs
index 1018f98..480af2f 100644
--- a/src/HidApi.Net/Internal/NativeMethods.cs
+++ b/src/HidApi.Net/Internal/NativeMethods.cs
@@ -36,6 +36,17 @@ private static IntPtr Resolve(string libraryName, Assembly assembly, DllImportSe
throw new Exception($"Could not find hidapi library tried: {string.Join(", ", libraries)}");
}
+ public static DeviceSafeHandle Open(ushort vendorId, ushort productId, NullTerminatedString serialNumber)
+ {
+ unsafe
+ {
+ fixed(byte* ptr = serialNumber)
+ {
+ return Open(vendorId, productId, ptr);
+ }
+ }
+ }
+
public static int Write(DeviceSafeHandle device, ReadOnlySpan data)
{
return Write(device, ref MemoryMarshal.GetReference(data), (nuint) data.Length);
@@ -99,7 +110,7 @@ public static int GetInputReport(DeviceSafeHandle device, ReadOnlySpan dat
public static extern unsafe void FreeEnumeration(NativeDeviceInfo* devices);
[DllImport(Library, EntryPoint = "hid_open")]
- public static extern DeviceSafeHandle Open(ushort vendorId, ushort productId, [MarshalAs(UnmanagedType.LPWStr)] string serialNumber);
+ private static extern unsafe DeviceSafeHandle Open(ushort vendorId, ushort productId, byte* serialNumber);
[DllImport(Library, EntryPoint = "hid_open_path")]
public static extern DeviceSafeHandle OpenPath([MarshalAs(UnmanagedType.LPStr)] string path);
diff --git a/src/HidApi.Net/Internal/NullTerminatedString.cs b/src/HidApi.Net/Internal/NullTerminatedString.cs
new file mode 100644
index 0000000..023a3f1
--- /dev/null
+++ b/src/HidApi.Net/Internal/NullTerminatedString.cs
@@ -0,0 +1,20 @@
+namespace HidApi;
+
+internal readonly ref struct NullTerminatedString
+{
+ public static NullTerminatedString Empty => new ();
+
+ private readonly ReadOnlySpan data;
+
+ public NullTerminatedString()
+ {
+ data = ReadOnlySpan.Empty;
+ }
+
+ internal NullTerminatedString(ref byte[] str)
+ {
+ data = str;
+ }
+
+ public ref readonly byte GetPinnableReference() => ref data.GetPinnableReference();
+}
diff --git a/src/HidApi.Net/Internal/Unicode.cs b/src/HidApi.Net/Internal/Unicode.cs
index a2f0ba7..e4f8db1 100644
--- a/src/HidApi.Net/Internal/Unicode.cs
+++ b/src/HidApi.Net/Internal/Unicode.cs
@@ -4,6 +4,15 @@ namespace HidApi;
internal static class Unicode
{
+ public static NullTerminatedString CreateNullTerminatedString(string str)
+ {
+ var src = Encoding.Unicode.GetBytes(str);
+ var dest = new byte[src.Length + sizeof(ushort)];
+ Array.Copy(src, dest, src.Length);
+
+ return new NullTerminatedString(ref dest);
+ }
+
public static ReadOnlySpan CreateBuffer(int size)
{
return new byte[size * sizeof(ushort)];
diff --git a/src/HidApi.Net/Internal/Utf32.cs b/src/HidApi.Net/Internal/Utf32.cs
index 23b4846..7a03487 100644
--- a/src/HidApi.Net/Internal/Utf32.cs
+++ b/src/HidApi.Net/Internal/Utf32.cs
@@ -4,6 +4,15 @@ namespace HidApi;
internal static class Utf32
{
+ public static NullTerminatedString CreateNullTerminatedString(string str)
+ {
+ var src = Encoding.UTF32.GetBytes(str);
+ var dest = new byte[src.Length + sizeof(uint)];
+ Array.Copy(src, dest, src.Length);
+
+ return new NullTerminatedString(ref dest);
+ }
+
public static ReadOnlySpan CreateBuffer(int size)
{
return new byte[size * sizeof(uint)];
diff --git a/src/HidApi.Net/Internal/WCharT.cs b/src/HidApi.Net/Internal/WCharT.cs
index 55d4c76..777bdc5 100644
--- a/src/HidApi.Net/Internal/WCharT.cs
+++ b/src/HidApi.Net/Internal/WCharT.cs
@@ -46,4 +46,18 @@ public static ReadOnlySpan CreateBuffer(int size)
throw new Exception("Unsupported platform to create a buffer");
}
+
+ public static NullTerminatedString CreateNullTerminatedString(string str)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ return Utf32.CreateNullTerminatedString(str);
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ return Unicode.CreateNullTerminatedString(str);
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ return Utf32.CreateNullTerminatedString(str);
+
+ throw new Exception("Unsupported platform to create a null terminated string");
+ }
}