1

I have been working with HIDAPI with C and trying to get Mono to communicate with the HIDAPI using interop. I have done a lot of searching and have not been able to find anyone who has gotten HIDAPI to work with Mono on OS X.

Does anyone know if I can redirect the output from a HID device using HIDAPI to a local virtual serial port and then have Mono just read from the serial port?

Another option, would anyone know if I could use something like Arduino leonardo or Circuits@Home USB Host Shield?

At least until I can sort out PInvoke on Mono.

Thanks

Joe Pitz
  • 2,434
  • 3
  • 25
  • 30

2 Answers2

2

I found and adapted this code from elsewhere, though I can't for the life of me find the source. If someone knows, please let me know so I can properly attribute it and link to it. It's been working well for me on both Windows and OS X. You do have to build hidapi for each platform, obviously.

using System;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

namespace HidApiCommunicationLayer
{
    internal class HidApiInteropCommLayer
    {
        #region Interop

#if WINDOWS
        private const string HIDAPI_DLL = "hidapi.dll";
#else
        private const string HIDAPI_DLL = "hidapi.dylib";
#endif

        protected IntPtr _device;

        private Object _locker = new object();

        public bool IsOpen()
        {
            return _device != IntPtr.Zero;
        }

        public void Open(ushort vid, ushort hid, string serial)
        {
            if (_device != IntPtr.Zero) throw new Exception("a device is already opened; close it first.");
            IntPtr ret = hid_open(vid, hid, serial);
            _device = ret;
            //if (_device != IntPtr.Zero)
            //    hid_set_nonblocking(_device, true);
        }

        public int Read(byte[] buffer, int length)
        {
            lock (_locker)
            {
                AssertValidDev();
                int ret = hid_read_timeout(_device, buffer, (uint)length, 1);
                if (ret < 0)
                    throw new Exception("Failed to Read.");

                return ret;
            }
        }

        public void Close()
        {
            AssertValidDev();
            hid_close(_device);
            _device = IntPtr.Zero;
        }

        public int ExitHidAPI()
        {
            return hid_exit();
        }

        public String GetProductString()
        {
            AssertValidDev();
            byte[] buf = new byte[1000];
            int ret = HidApiInteropCommLayer.hid_get_product_string(_device, buf, (uint)(buf.Length / 4) - 1);
            if (ret < 0)
                throw new Exception("failed to receive product string");
            return EncodeBuffer(buf);
        }

        public String GetManufacturerString()
        {
            AssertValidDev();
            byte[] buf = new byte[1000];
            int ret = HidApiInteropCommLayer.hid_get_manufacturer_string(_device, buf, (uint)(buf.Length / 4) - 1);
            if (ret < 0)
                throw new Exception("failed to receive manufacturer string");
            return EncodeBuffer(buf);
        }

        public int GetFeatureReport(byte[] buffer, int length)
        {
            AssertValidDev();
            int ret = hid_get_feature_report(_device, buffer, (uint)length);
            if (ret < 0)
                throw new Exception("failed to get feature report");
            return ret;
        }

        public int SendFeatureReport(byte[] buffer)
        {
            int ret = hid_send_feature_report(_device, buffer, (uint)buffer.Length);
            //if (ret < 0)
            //  throw new Exception ("failed to send feature report");
            return ret;
        }

        public int Write(byte[] buffer)
        {
            lock (_locker)
            {
                AssertValidDev();
                int ret = hid_write(_device, buffer, HID_MAX_PACKET_SIZE + 1);
                //if (ret < 0)
                //    Custom logging
                return ret;
            }
        }

        public String Error()
        {
            AssertValidDev();
            IntPtr ret = hid_error(_device);
            return Marshal.PtrToStringAuto(ret);
        }

        public string GetIndexedString(int index)
        {
            AssertValidDev();
            byte[] buf = new byte[1000];
            int ret = HidApiInteropCommLayer.hid_get_indexed_string(_device, index, buf, (uint)(buf.Length / 4) - 1);
            if (ret < 0)
                throw new Exception("failed to receive indexed string");
            return EncodeBuffer(buf);
        }

        public string GetSerialNumberString()
        {
            AssertValidDev();
            byte[] buf = new byte[1000];
            int ret = HidApiInteropCommLayer.hid_get_serial_number_string(_device, buf, (uint)(buf.Length / 4) - 1);
            if (ret < 0)
                throw new Exception("failed to receive serial number string");
            return EncodeBuffer(buf);
        }

        private string EncodeBuffer(byte[] buffer)
        {
            return Encoding.Unicode.GetString(buffer).Trim('\0');
        }

        private void AssertValidDev()
        {
            if (_device == IntPtr.Zero) throw new Exception("No device opened");
        }

        #region DllImports

        [DllImport(HIDAPI_DLL)]
        private static extern int hid_read(IntPtr device, [Out, MarshalAs(UnmanagedType.LPArray)] byte[] data, uint length);

        [DllImport(HIDAPI_DLL)]
        private static extern int hid_read_timeout(IntPtr device, [Out, MarshalAs(UnmanagedType.LPArray)] byte[] data, uint length, int timeout);

        [DllImport(HIDAPI_DLL)]
        private static extern IntPtr hid_open(ushort vid, ushort pid, [MarshalAs(UnmanagedType.LPWStr)] string serial);

        [DllImport(HIDAPI_DLL)]
        private static extern void hid_close(IntPtr device);

        [DllImport(HIDAPI_DLL)]
        private static extern int hid_init();

        [DllImport(HIDAPI_DLL)]
        private static extern int hid_exit();

        [DllImport(HIDAPI_DLL)]
        private static extern int hid_get_product_string(IntPtr device, [Out] byte[] _string, uint length);

        [DllImport(HIDAPI_DLL)]
        private static extern int hid_get_manufacturer_string(IntPtr device, [Out] byte[] _string, uint length);

        [DllImport(HIDAPI_DLL)]
        private static extern int hid_get_feature_report(IntPtr device, [Out, MarshalAs(UnmanagedType.LPArray)] byte[] data, uint length);

        [DllImport(HIDAPI_DLL)]
        private static extern int hid_get_serial_number_string(IntPtr device, [Out] byte[] serial, uint maxlen);

        [DllImport(HIDAPI_DLL)]
        private static extern int hid_get_indexed_string(IntPtr device, int string_index, [Out] byte[] _string, uint maxlen);

        [DllImport(HIDAPI_DLL)]
        private static extern IntPtr hid_error(IntPtr device);

        [DllImport(HIDAPI_DLL)]
        private static extern int hid_send_feature_report(IntPtr device, [In, MarshalAs(UnmanagedType.LPArray)] byte[] data, uint length);

        [DllImport(HIDAPI_DLL)]
        private static extern int hid_set_nonblocking(IntPtr device, [In, MarshalAs(UnmanagedType.SysInt)] bool nonblock);

        [DllImport(HIDAPI_DLL)]
        private static extern int hid_write(IntPtr device, [In, MarshalAs(UnmanagedType.LPArray)] byte[] data, uint length);

        [DllImport(HIDAPI_DLL)]
        private static extern IntPtr hid_open_path([In, MarshalAs(UnmanagedType.LPStr)] string path);

        #endregion DllImports

        #endregion Interop

        #region Constructors

        public static HidApiInteropCommLayer GetDevice(ushort vid, ushort pid)
        {
            try
            {
                HidApiInteropCommLayer layer = new HidApiInteropCommLayer();
                layer.Open(vid, pid, null);
                return layer._device == IntPtr.Zero ? null : layer;
            }
            catch (System.BadImageFormatException fx)
            {
                //Custom logging
                return null;
            }
            catch (Exception ex)
            {
                //Custom logging
                return null;
            }
        }

        #endregion Constructors

        private const int HID_MAX_PACKET_SIZE = 1024;

        #region ICommunicationLayer

        public void Init()
        {
            try
            {
                if (IsOpen())
                {
                    ContinueReadProcessing = true;
                    ReadThread = new Thread(new ThreadStart(ReadLoop));
                    ReadThread.Name = "HidApiReadThread";
                    ReadThread.Start();
                }
                else
                {
                    Disconnect();
                }
            }
            catch (Exception ex)
            {
                //Custom logging
                throw;
            }
        }

        public bool SendData(byte[] data)
        {
            try
            {
                MemoryStream stream = new MemoryStream(HID_MAX_PACKET_SIZE + 1);
                stream.WriteByte(0);
                stream.Write(data, 0, HID_MAX_PACKET_SIZE);
                int ret = Write(stream.ToArray());
                if (ret >= 0)
                    return true;
                else
                {
                    return false;
                }
            }
            catch (Exception ex)
            {
                //Custom logging
                return false;
            }
        }

        public event EventHandler<DataEventArgs> DataReceived;

        public event EventHandler Disconnected;

        public void Start()
        {
            ContinueReadProcessing = true;
        }

        public void Stop()
        {
            Disconnect();
        }

        #endregion ICommunicationLayer

        private Thread ReadThread = null;

        protected volatile bool ContinueReadProcessing = true;

        private void ReadLoop()
        {
            var culture = CultureInfo.InvariantCulture;
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = culture;
            Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;

            while (ContinueReadProcessing)
            {
                try
                {
                    byte[] report = new byte[HID_MAX_PACKET_SIZE];

                    var result = Read(report, HID_MAX_PACKET_SIZE);

                    if (result > 0)
                    {
                        DataReceived(this, new DataEventArgs(report));
                    }
                    else if (result < 0)
                    {
                        Disconnect();
                    }
                }
                catch (Exception ex)
                {
                    Disconnect();
                }

                Thread.Sleep(1);
            }
        }

        private void Disconnect()
        {
            ContinueReadProcessing = false;
            Disconnected(this, EventArgs.Empty);
        }

        #region IDisposable Members

        public void Dispose()
        {
            ContinueReadProcessing = false;
            ReadThread.Join(500);
            if (ReadThread.IsAlive)
            {
                ReadThread.Abort();
            }

            if (IsOpen())
                Close();
            int res = ExitHidAPI();
        }

        #endregion IDisposable Members
    }

    internal class Utf32Marshaler : ICustomMarshaler
    {
        private static Utf32Marshaler instance = new Utf32Marshaler();

        public static ICustomMarshaler GetInstance(string s)
        {
            return instance;
        }

        public void CleanUpManagedData(object o)
        {
        }

        public void CleanUpNativeData(IntPtr pNativeData)
        {
            Marshal.FreeHGlobal(pNativeData);
            //UnixMarshal.FreeHeap(pNativeData);
        }

        public int GetNativeDataSize()
        {
            return IntPtr.Size;
        }

        public IntPtr MarshalManagedToNative(object obj)
        {
            string s = obj as string;
            if (s == null)
                return IntPtr.Zero;
            return Marshal.StringToHGlobalAuto(s);
        }

        public object MarshalNativeToManaged(IntPtr pNativeData)
        {
            return Marshal.PtrToStringAuto(pNativeData);
        }
    }

    public class DataEventArgs : EventArgs
    {
        public DataEventArgs(byte[] data)
        {
            Data = data;
        }

        public byte[] Data { get; private set; }
    }
}
Chris
  • 236
  • 3
  • 13
0

Arduino Leonardo can't act as a USB Host.

In principle though, yes, you could make a device act as a CDC Serial Port on the device side, and have it act as Host USB to a HID device.

Alternatively, you could skip the HIDAPI step entirely and use HidSharp. :) It'll P/Invoke directly into the MacOS X native APIs.

Hope this helps

James

James
  • 1,874
  • 1
  • 16
  • 18