0
votes

We are getting a weird behaviour while attempting to PInvoke read() method of linux/libc with Mono.

[16:05:17.258][UNHANDLED EXCEPTION][BEGIN]
[16:05:18.463]System.NullReferenceException: Object reference not set to an instance of an object
  at (wrapper unknown) PI.SDK.UI.HW.PAX.ProlinKeyboardManager+InputEvent:PtrToStructure (intptr,object)
  at (wrapper managed-to-native) System.Runtime.InteropServices.Marshal:PtrToStructure (intptr,System.Type)
  at System.Runtime.InteropServices.Marshal.PtrToStructure[T] (IntPtr ptr)  in :0 
  at PI.SDK.UI.HW.PAX.ProlinKeyboardManager.StartListening ()  in :0 
  at PI.SDK.PAX.Playground.Program+c.b__37_0 ()  in :0 
  at System.Threading.ThreadHelper.ThreadStart_Context (System.Object state)  in :0 
  at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, Boolean preserveSyncCtx)  in :0 
  at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, Boolean preserveSyncCtx)  in :0 
  at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state)  in :0 
  at System.Threading.ThreadHelper.ThreadStart ()  in :0 

The problem looks to be on Marshal.PtrToStructure<T>(IntPtr) but I can't see where it is...

The managed/C# part of the repro case is as follow:

[StructLayout(LayoutKind.Sequential)]
private struct InputEvent
{
    public int Seconds;
    public int Microseconds;
    public ushort type;
    public ushort code;
    public int value;
}

[DllImport(LIBC, EntryPoint = "read", CallingConvention = CallingConvention.Cdecl)]
private static extern int NativeRead(int fd, ref IntPtr buf, int nbytes);

public void StartListening()
{
    var kbDeviceName = "/dev/keypad";
    int fd = -1;
    fd = NativeOpen(kbDeviceName, 2);
    Console.WriteLine($"[KBD] {fd}");

    if (fd < 0)
        throw new InvalidOperationException($"Unable to open Keyboard device {kbDeviceName}. Return code {fd}.");

    var ev = new InputEvent[64];
    var size = Marshal.SizeOf<InputEvent>();
    Console.WriteLine($"[EventSize] {size}");
    var totalSize = size * 64;
    Console.WriteLine($"[TotalEventSize] {totalSize}");

    var ptr = IntPtr.Zero;

    while (true)
    {
        var rd = NativeRead(fd, ref ptr, totalSize);
        Console.WriteLine($"[KBD][Read] {rd}");
        Console.WriteLine($"[PTR] {ptr}");
        if (rd > size)
        {
            var eventNum = rd / size;
            Console.WriteLine($"[Events] {eventNum}");
            for (int i = 0; i < eventNum; i++)
            {
                var entry = Marshal.PtrToStructure<InputEvent>(ptr);
                Console.WriteLine($"code: {entry.code} | type: {entry.type} | value: {entry.value}");
                ptr = new IntPtr(ptr.ToInt32() + size);
            }
        }
    }
}

The output of those Console.Writeline is:

[KBD] 5
[EventSize] 16
[TotalEventSize] 1024
[KBD][Read] 32
[PTR] 1460304317
[Events] 2
[16:05:17.258][UNHANDLED EXCEPTION][BEGIN]
... THE EXCEPTION GOES HERE ...

This is a C native sample we are trying to replicate:

int i;
int eventNum = 0;
struct input_event ev[64];

int size = sizeof(struct input_event);
int rd = 0;

memset(ev, 0, sizeof(ev));
rd = read(gfd, ev, sizeof(ev)); 
eventNum = rd / size;
for (i = 0; i < eventNum; ++i)
{
    /* EV_KEY means type is key (not mouse, etc) */
    if (ev[i].type == EV_KEY && ev[i].value == 1)
    {
        return ev[i].code;
    }
}

Can someone point me to where should be our error?

Thanks! Really appreciate any help. Gutemberg

1
where is the code of PI.SDK.UI.HW.PAX.ProlinKeyboardManager? - knocte
the C# part is it for now. Just removed the non-related rest for the sake of simplicity but it does not affect this case. - Gutemberg Ribeiro
you're not adding the class, so we have no clue where exactly it's failing - knocte
also, you should run mono with the --debug flag so you can see line numbers - knocte
@knocte there is no need for the rest of the class. This is the single method on it and everything related to the PInvoke is there which is actually my problem. Also, the --debug isn't suitable. Its an ARM device which has mono embedded inside a C application. - Gutemberg Ribeiro

1 Answers

2
votes

Two problems:

1) The second parameter of NativeRead shouldn't be a ref to pointer. Instead it should be the pointer itself. Please verify the PInvoke declaration and change "ref IntPtr" to "IntPtr". Example:

private static extern int NativeRead(int fd, IntPtr buf, int nbytes);

2) read expects the pointer to the buffer, and this code passes 0. You should allocate the native memory for the buffer instead. Example:

var ptr = Marshal.AllocHGlobal(totalSize);

And one more problem in the loop: the pointer incremented in the loop is the same used for further reads. Instead, the reads should be done using the buffer pointer always. This should fix it:

  var buffer_ptr = Marshal.AllocHGlobal(totalSize);

  while (true)
  {
    var rd = NativeRead(fd, buffer_ptr, totalSize);
    Console.WriteLine($"[KBD][Read] {rd}");
    Console.WriteLine($"[PTR] {buffer_ptr}");
    if (rd > size)
    {
      var eventNum = rd / size;
      Console.WriteLine($"[Events] {eventNum}");
      var event_ptr = buffer_ptr;
      for (int i = 0; i < eventNum; i++)
      {
        var entry = Marshal.PtrToStructure<InputEvent>(event_ptr);
        Console.WriteLine($"code: {entry.code} | type: {entry.type} | value: {entry.value}");
        event_ptr = IntPtr.Add(event_ptr, size);
      }
    }
  }

Best regards!