2

EDIT

Oddly enough, I've worked around this issue, but it's still annoying me. I worked around it by sending too-long writes, with padded zeroes; the code works but sends a few hundred unnecessary bytes. Specifically, I need to send exactly 992-byte packets instead of 7 or 19-byte packets. My question still stands however, why is the Logitech code able to make 7- or 19-byte writes when I cannot.


I'm running into an issue where a specific block of code fails or succeeds apparently depending on the length of the data passed to it. This makes no sense to me; I'm obviously doing something wrong but I can't tell what.

I have three cases where I'm trying to use the following block of code, which writes a byte stream to a USB device (one of two Logitech G-Series keyboards):

Friend Function WriteData(ByVal Keyboard As GSeriesKeyboard, ByVal Data() As Byte) As Integer
   Dim BytesWritten As Integer = Data.Length
   Dim Success As Boolean = Win32.WriteFile(Keyboard.ExternalIOHandle, Data, Data.Length, BytesWritten, Nothing)
   Dim ErrorCode As Integer = GetLastError()
   Return ErrorCode
End Function

<DllImport("kernel32.dll", SetlastError:=True)> Friend Shared Function WriteFile( _
    ByVal File As SafeFileHandle, _
    ByVal Buffer() As Byte, _
    ByVal NumberOfBytesToWrite As Integer, _
    ByRef NumberOfBytesWritten As Integer, _
    ByRef Overlapped As System.Threading.NativeOverlapped) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function

In the first case, I'm passing a 992-byte stream and the write completes successfully. In the second and third cases, I'm writing either 7 or 19 bytes, and WriteFile generates the errors ERROR_INVALID_USER_BUFFER for 7 bytes, or ERROR_INVALID_PARAMETER for 19 bytes. I've opened the handle for reading and writing, non-overlapped.

When using USBTrace, I can see that the default Logitech program is able to write all three cases without trouble, but my own code can only write the 992-byte case. This behavior is the same whether I compile my code as x86 or x64.

The code I'm using to open the handle is this:

    Friend Function OpenInterface(ByVal KeyboardPath As String) As SafeFileHandle

        Dim SecurityData As New SECURITY_ATTRIBUTES()
        Dim security As New DirectorySecurity()
        Dim DescriptorBinary As Byte() = security.GetSecurityDescriptorBinaryForm()
        Dim SecurityDescriptorPtr As IntPtr = Marshal.AllocHGlobal(DescriptorBinary.Length)

        SecurityData.nLength = Marshal.SizeOf(SecurityData)
        Marshal.Copy(DescriptorBinary, 0, SecurityDescriptorPtr, DescriptorBinary.Length)
        SecurityData.lpSecurityDescriptor = SecurityDescriptorPtr

        Dim Handle As SafeFileHandle = Win32.CreateFile(KeyboardPath, GENERIC_READ Or GENERIC_WRITE, _
                                                        FILE_SHARE_READ Or FILE_SHARE_WRITE, SecurityData, _
                                                        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)

        If Handle.IsInvalid Then
            Dim ErrorInfo As New ComponentModel.Win32Exception()
            Debug.Print("Failed to get device IO handle: " & ErrorInfo.Message)
        End If

        Return Handle
    End Function

    Friend Overridable Function BitmapToLcdFormat(ByVal SourceBitmap As Bitmap) As Byte()
        'Base class is for the generic B&W 160x43 LCD.  Override for G19 if I ever get my hands on one
        Dim Data As Byte() = New Byte(991) {}
        ' USBTrace says 992 bytes
        Dim Image(640 * 48) As Byte 'Temp array for image conversion.  Adds additional 5 lines of unused pixels to avoid an out-of-bounds error
        Dim BitmapData As BitmapData = SourceBitmap.LockBits(New Rectangle(0, 0, 160, 43), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb)
        Marshal.Copy(BitmapData.Scan0, Image, 0, (640 * 43))

        Data(0) = &H3 'Set-LCD
        Dim output As Integer = 32 'First byte of image data starts at byte 32; 960 bytes of image data
        Dim ImageOffset As Integer = 0
        For Row As Integer = 0 To 5
            For Column As Integer = 0 To (SourceBitmap.Width << 2) - 1 Step 4

                Dim r As Integer = _
                ((Image(ImageOffset + Column + BitmapData.Stride * 0) And &H80) >> 7) Or _
                ((Image(ImageOffset + Column + BitmapData.Stride * 1) And &H80) >> 6) Or _
                ((Image(ImageOffset + Column + BitmapData.Stride * 2) And &H80) >> 5) Or _
                ((Image(ImageOffset + Column + BitmapData.Stride * 3) And &H80) >> 4) Or _
                ((Image(ImageOffset + Column + BitmapData.Stride * 4) And &H80) >> 3) Or _
                ((Image(ImageOffset + Column + BitmapData.Stride * 5) And &H80) >> 2) Or _
                ((Image(ImageOffset + Column + BitmapData.Stride * 6) And &H80) >> 1) Or _
                ((Image(ImageOffset + Column + BitmapData.Stride * 7) And &H80) >> 0)

                Data(output) = CByte(r)

                output += 1
            Next
            ImageOffset += BitmapData.Stride * 8
        Next
        SourceBitmap.UnlockBits(BitmapData)
        Return Data
    End Function

The exact code blocks that call WriteData() are the following:

(Logitech G15)
    HardwareInterface.WriteData(Me, New Byte() {2, 0, 0, 0, 0, 0, 0})

(Logitech G510)
    HardwareInterface.WriteData(Me, New Byte() {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})

(Both; the one that works)
    HardwareInterface.WriteData(Me, BitmapToLcdFormat(NewImage)) 'Build 992-byte LCD-format bitmap from source iumage

These specific commands are used to remap the macro ("G") keys on the boards to different character codes, in this case 00 disables the key. Anyone with a G15/G19/G510 keyboard can see this by disabling their Logitech gamepanel software and re-plugging the keyboard. G3 will act like F3 in notepad (find next), but after restarting the gamepanel software it will no longer do so.

Sukasa
  • 1,700
  • 4
  • 22
  • 40
  • What happens if you construct the 7 and 19 byte buffers external to the actual WriteData() call? – ribram Jun 06 '11 at 22:26
  • The result is the same either way – Sukasa Jun 06 '11 at 22:32
  • can you set a breakpoint in WriteData and verify that the buffer you think is being passed is valid? Also, BytesWritten needs to be a reference since WriteFile wants to place the number of bytes actually written into this location. Is it? Sorry I don't know VB. – ribram Jun 06 '11 at 22:44
  • I've set the breakpoint and can confirm that the data is passing to that function properly, and is valid before and after the call to WriteFile. – Sukasa Jun 06 '11 at 22:48
  • What library GSeriesKeyboard class comes from? I'm assuming it's from some SDK that comes with the keyboard, where one can download it? – Andrew Savinykh Jun 09 '11 at 06:14
  • I'm rolling it myself, this library will have -no- external dependencies (save for .net/winapi). The different G-series keyboard classes inherit from it and simply implement keyboard-specific logic. – Sukasa Jun 09 '11 at 06:23
  • What happens if you copy the 7 or 19 bytes into a large buffer (say 2K in size) within your `WriteData` function, and then only ask for 7 or 19 of those bytes to be written? Also, could you show `BitmapToLcdFormat`? – Damien_The_Unbeliever Jun 09 '11 at 06:31
  • @Damien_The_Unbeliever added the function, and I did try using a larger buffer to WriteFile (1024 bytes, even larger than the 992 the LCD data needs), but it didn't work. – Sukasa Jun 09 '11 at 06:39

2 Answers2

1

I'm sorry this is not a Really Useful Answer, as I'm quite sure that what you are seeing is specific to your particular scenario, and I don't know this scenario in its entirety.

But the most likely reason to what you are seeing in my opinion is this. What you are writing to is not a real file (as you undoubtedly know) it's just an interface to your keyboard, that is surfaced as a file. Because of this, it is not a surprise, that you see completely different set of behaviour from a normal file. You can write to a file in file system most of the time. With the keyboard there could be a protocol that you have to follow to make it work.

What I think is happening here, is that things you are trying to write that fails, they are not being attempted to be written at the right state. The keyboard for whatever reason does not expect them when you are writing them.

You are saying that you can see that they are accepted ok with the USBTrace. This means that there is a correct state where they could be written there properly.

I'd start investigation at looking at the sequence of interactions that you are doing with the keyboard and thinking what could affect the ability of the keyboard to process your input.

Andrew Savinykh
  • 25,351
  • 17
  • 103
  • 158
  • @zespri The problem I have is that these two particular writes are the only ones _not_ working; I can get/set all the feature reports fine as well as write to the LCDs on the keyboards (WriteFile) and read special-key data like the macro or LCD buttons (ReadFile) without problem. – Sukasa Jun 09 '11 at 06:27
  • Can you put up a repro for me that I could actually run? I have a G15. May be this way we'll get somewhere. – Andrew Savinykh Jun 09 '11 at 06:44
  • Is that a blue G15 (v1) or an orange one (v2)? If it's a v1 I haven't been able to get ahold of the info I need to actually add support for it to the library (namely, the normal device path for it) – Sukasa Jun 09 '11 at 06:46
  • Right. It's blue (v1). I did some programming for it several years ago but I always used the SDK, not this CreateFile / WriteFile approach. Is there any documentation on the API you are using? In particular what does {2, 0, 0, 0, 0, 0, 0} mean? – Andrew Savinykh Jun 09 '11 at 07:12
  • New Byte() {2, 0, 0, 0, 0, 0, 0} means a new array of bytes pre-filled with the values in the {}. The API I'm using is just the Windows API, and the "documentation" came from a lot of my own work, mainly monitoring traffic and figuring out which bits corresponded to which keypresses and such. – Sukasa Jun 09 '11 at 07:15
  • And specifically, 2 0 0 0 0 0 0 means "remap all G keys to send the keypress for character 0," I think. It's the only difference in traffic between what my code sends and what logitech's does, so I'm pretty sure that that is indeed its effect. It helps that the number of 0s matches the number of G keys. – Sukasa Jun 09 '11 at 07:19
  • Ok. I see what you mean, now. Good luck =) – Andrew Savinykh Jun 09 '11 at 07:31
  • looking back, I realize I never did make this code available. As it has heavily matured and does work (sans g15v1 since I still have not managed to get ahold of one), please let me know if you would like to get a copy of this library plus an example application. – Sukasa Jan 07 '12 at 02:37
  • @Sukasa, well I certainly don't mind having it, thank you for consideration. – Andrew Savinykh Jan 07 '12 at 08:20
  • http://einherjar.rustedlogic.net/glib_zespri.rar There's a short readme about the individual projects, they should all work whether compiled as x86 or x64. – Sukasa Jan 07 '12 at 19:20
1

Try writing an even number of bytes?

SSS
  • 4,807
  • 1
  • 23
  • 44