Jump to content
Vitor Domingos

Difficulty with XKeys PIEHid32.dll in Delphi 10 Seattle

Recommended Posts

I am having trouble using the XKeys PIEHid32.dll in Delphi 10 Seattle. The issue occurs when trying to send and receive information, and I keep getting the following error:

"read of address 0x00000000'. Process Stopped. Use Step or Run to continue."

I am not sure what else to try to resolve this. Below is the code I am currently using:


 
delphi
 """ 
 

unit Unit4;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Vcl.ExtCtrls;

const
  PI_VID = $05F3;
  MAX_XKEY_DEVICES = 128;

{$ALIGN 1}

type
  TDevicePath = array[0..255] of AnsiChar;
  TString128 = array[0..127] of AnsiChar;

  TEnumHIDInfo = packed record
    PID: DWORD;
    Usage: DWORD;
    UP: DWORD;
    readSize: LongInt;
    writeSize: LongInt;
    DevicePath: TDevicePath;
    Handle: DWORD;
    Version: DWORD;
    ManufacturerString: TString128;
    ProductString: TString128;
    SerialNumberString: TString128;
  end;

{$ALIGN ON}

type
  // Declaração das funções
  TEnumeratePIE = function(VID: LongInt; info: PEnumHIDInfo; var count: LongInt): DWORD; stdcall;
  TSetupInterfaceEx = function(hnd: LongInt): DWORD; stdcall;
  TWriteData = function(hnd: LongInt; data: PByte): DWORD; stdcall;
  TBlockingReadData = function(hnd: LongInt; data: PByte; maxMillis: Integer): DWORD; stdcall;
  TSetDataCallback = function(hnd: LongInt; pDataEvent: PHIDDataEvent): DWORD; stdcall;
  TSetErrorCallback = function(hnd: LongInt; pErrorCall: PHIDErrorEvent): DWORD; stdcall;
  TGetWriteLength = function(hnd: LongInt): DWORD; stdcall;

  TForm4 = class(TForm)
    ButtonEnumerate: TButton;
    Memo2: TMemo;
    ButtonChangeColor: TButton;
    procedure ButtonEnumerateClick(Sender: TObject);
    procedure ButtonChangeColorClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    procedure EnumerateAndSetupDevices;
    procedure SetCallback;
    function GetErrorMessage(ErrorCode: DWORD): string;
  public
  end;

var
  Form4: TForm4;
  MainForm: TForm4;
  // Variáveis globais
  DLLHandle: THandle;
  // Funções da DLL
  EnumeratePIE: TEnumeratePIE;
  SetupInterfaceEx: TSetupInterfaceEx;
  WriteData: TWriteData;
  BlockingReadData: TBlockingReadData;
  SetDataCallback: TSetDataCallback;
  SetErrorCallback: TSetErrorCallback;
  GetWriteLength: TGetWriteLength;
  DeviceHandle: LongInt;
  CurrentColor: Integer = 6;

implementation

{$R *.dfm}

// Callback de dados
function HandleDataEvent(pData: PByte; deviceID: DWORD; error: DWORD): DWORD; stdcall;
begin
  Result := 0; // Retorna 0 indicando sucesso
  if Assigned(MainForm) then
  begin
    TThread.Synchronize(nil,
      procedure
      var
        DataStr: string;
        i: Integer;
        KeyStates: array[0..31] of Byte;
        KeyIndex: Integer;
      begin
        if error = 0 then
        begin
          // Copiar os dados recebidos para um array
          Move(pData^, KeyStates, 32);

          // Analisar as teclas pressionadas
          for i := 0 to 31 do
          begin
            if KeyStates <> 0 then
            begin
              for KeyIndex := 0 to 7 do
              begin
                if (KeyStates and (1 shl KeyIndex)) <> 0 then
                begin
                  // Uma tecla foi pressionada
                  DataStr := Format('Tecla %d pressionada', [(i * 😎 + KeyIndex + 1]);
                  MainForm.Memo2.Lines.Add(DataStr);
                end;
              end;
            end;
          end;
        end
        else
        begin
          MainForm.Memo2.Lines.Add('Error occurred: ' + MainForm.GetErrorMessage(error));
        end;
      end);
  end;
end;

// Callback de erro
function HandleErrorEvent(deviceID: DWORD; status: DWORD): DWORD; stdcall;
begin
  Result := 0; // Retorna 0 indicando sucesso
  if Assigned(MainForm) then
  begin
    TThread.Synchronize(nil,
      procedure
      begin
        MainForm.Memo2.Lines.Add('Error callback from device ' + IntToStr(deviceID) + ' with status ' + MainForm.GetErrorMessage(status));
      end);
  end;
end;

procedure TForm4.FormCreate(Sender: TObject);
begin
  MainForm := Self;

  // Carregar a DLL
  DLLHandle := LoadLibrary('PIEHid.dll');
  if DLLHandle = 0 then
  begin
    Memo2.Lines.Add('Não foi possível carregar a DLL PIEHid.dll.');
    Exit;
  end
  else
  begin
    Memo2.Lines.Add('DLL PIEHid.dll carregada com sucesso.');
  end;

  // Obter as funções da DLL
  @EnumeratePIE := GetProcAddress(DLLHandle, 'EnumeratePIE');
  @SetupInterfaceEx := GetProcAddress(DLLHandle, 'SetupInterfaceEx');
  @WriteData := GetProcAddress(DLLHandle, 'WriteData');
  @BlockingReadData := GetProcAddress(DLLHandle, 'BlockingReadData');
  @SetDataCallback := GetProcAddress(DLLHandle, 'SetDataCallback');
  @SetErrorCallback := GetProcAddress(DLLHandle, 'SetErrorCallback');
  @GetWriteLength := GetProcAddress(DLLHandle, 'GetWriteLength');

  // Verificar se todas as funções foram carregadas corretamente
  if not Assigned(EnumeratePIE) or not Assigned(SetupInterfaceEx) or not Assigned(WriteData) or
     not Assigned(BlockingReadData) or not Assigned(SetDataCallback) or not Assigned(SetErrorCallback) or
     not Assigned(GetWriteLength) then
  begin
    Memo2.Lines.Add('Erro ao carregar funções da DLL.');
    Exit;
  end;
end;

procedure TForm4.FormDestroy(Sender: TObject);
begin
  // Liberar a DLL
  if DLLHandle <> 0 then
  begin
    FreeLibrary(DLLHandle);
    DLLHandle := 0;
  end;

  MainForm := nil;
end;

procedure TForm4.ButtonEnumerateClick(Sender: TObject);
begin
  EnumerateAndSetupDevices;
end;

procedure TForm4.EnumerateAndSetupDevices;
var
  Info: array[0..MAX_XKEY_DEVICES - 1] of TEnumHIDInfo;
  Count: LongInt;
  VID: Word;
  ResultCode, SetupResult: DWORD;
  I: Integer;
  WriteLength: DWORD;
begin
  if DLLHandle = 0 then
  begin
    Memo2.Lines.Add('A DLL não foi carregada. Não é possível continuar.');
    Exit;
  end;

  Memo2.Lines.Add('Chamando EnumeratePIE...');
  VID := PI_VID;
  Count := 0;

  ResultCode := EnumeratePIE(VID, @Info[0], Count);
  Memo2.Lines.Add('ResultCode de EnumeratePIE: ' + IntToStr(ResultCode));
  Memo2.Lines.Add('Count de dispositivos encontrados: ' + IntToStr(Count));

  if ResultCode = 0 then
  begin
    if Count > 0 then
    begin
      for I := 0 to Count - 1 do
      begin
        // Obter o Write Length
        WriteLength := GetWriteLength(Info.Handle);

        Memo2.Lines.Add(Format('Dispositivo %d - PID: %d, Handle: %d, Versão: %d, UsagePage: %d, WriteLength: %d',
          [I, Info.PID, Info.Handle, Info.Version, Info.UP, WriteLength]));

        if Info.PID = $0419 then
        begin
          Memo2.Lines.Add('Tentando configurar dispositivo com Handle: ' + IntToStr(Info.Handle));

          SetupResult := SetupInterfaceEx(Info.Handle);
          Memo2.Lines.Add('Resultado de SetupInterfaceEx: ' + IntToStr(SetupResult));

          if SetupResult = 0 then
          begin
            DeviceHandle := Info.Handle;
            Memo2.Lines.Add('DeviceHandle atribuído: ' + IntToStr(DeviceHandle));
            SetCallback;
            Break; // Sair do loop após configurar o dispositivo
          end
          else
          begin
            Memo2.Lines.Add('Falha ao configurar o dispositivo. SetupResult: ' + IntToStr(SetupResult));
          end;
        end
        else
        begin
          Memo2.Lines.Add('Dispositivo ignorado (PID não corresponde).');
        end;
      end;
    end
    else
    begin
      Memo2.Lines.Add('Nenhum dispositivo encontrado.');
    end;
  end
  else
  begin
    Memo2.Lines.Add('Erro ao enumerar dispositivos. Código de erro: ' + IntToStr(ResultCode));
  end;
end;

procedure TForm4.SetCallback;
var
  ResultCode: DWORD;
begin
  if DeviceHandle = 0 then
  begin
    Memo2.Lines.Add('DeviceHandle é zero. Não é possível configurar callbacks.');
    Exit;
  end;

  ResultCode := SetDataCallback(DeviceHandle, @HandleDataEvent);
  if ResultCode = 0 then
    Memo2.Lines.Add('Callback de dados configurado com sucesso.')
  else
    Memo2.Lines.Add('Erro ao configurar o callback de dados. Código de erro: ' + IntToStr(ResultCode));

  ResultCode := SetErrorCallback(DeviceHandle, @HandleErrorEvent);
  if ResultCode = 0 then
    Memo2.Lines.Add('Callback de erro configurado com sucesso.')
  else
    Memo2.Lines.Add('Erro ao configurar o callback de erro. Código de erro: ' + IntToStr(ResultCode));
end;

function TForm4.GetErrorMessage(ErrorCode: DWORD): string;
begin
  case ErrorCode of
    0: Result := 'Success';
    // Adicione outros códigos de erro conforme necessário
    else
      Result := 'Unknown error (' + IntToStr(ErrorCode) + ')';
  end;
end;

procedure TForm4.ButtonChangeColorClick(Sender: TObject);
var
  Buffer: array[0..35] of Byte;
  ResultWriteData: DWORD;
begin
  if DeviceHandle = 0 then
  begin
    Memo2.Lines.Add('DeviceHandle é zero. O dispositivo não foi configurado corretamente.');
    Exit;
  end;

  FillChar(Buffer, SizeOf(Buffer), 0);
  Buffer[0] := 0;    // Report ID
  Buffer[1] := $B3;  // Comando para controlar o LED

  // Alternar entre verde (6) e vermelho (7)
  if CurrentColor = 6 then
    CurrentColor := 7
  else
    CurrentColor := 6;

  Buffer[2] := CurrentColor;  // 6 = verde, 7 = vermelho
  Buffer[3] := 1;  // 0 = desligado, 1 = ligado, 2 = piscar

  ResultWriteData := WriteData(DeviceHandle, @Buffer[0]);

  if ResultWriteData = 0 then
  begin
    if CurrentColor = 6 then
      Memo2.Lines.Add('Cor alterada para verde com sucesso.')
    else
      Memo2.Lines.Add('Cor alterada para vermelho com sucesso.');
  end
  else
  begin
    Memo2.Lines.Add('Erro ao alterar a cor. Código de erro: ' + IntToStr(ResultWriteData) + ' - ' + GetErrorMessage(ResultWriteData));
  end;
end;

end.

 
"""

I would greatly appreciate any help or guidance on how to fix this error and get the communication working properly.

Thank you in advance for your attention.

Edited by Vitor Domingos

Share this post


Link to post

There could be a number of reasons. The first question is where does the AV occur in the code? 

Did you create the interface translation of the DLL functions and types yourself?

Share this post


Link to post

The error occurs after exiting the procedure "EnumerateAndSetupDevices". I got the function information from the XKeys documentation and the open-source C++ code. There is a file called PIEHid32.h, from which I copied the functions and adapted them to Delphi.

Share this post


Link to post

Before the AV occurs, does your memo contain the expected results?

 

I suspect there is a problem in the translation of the PIEHid32.h header file. I'll look further this evening, if someone else doesn't find an issue first.

 

 

 

 

Share this post


Link to post
I will share a new code, as the one I previously sent was an old version. Here is the updated code: ---- cod.txt
----.

 

Here’s what’s happening:

In the "EnumeratePIE" function, it returns the values correctly, but when trying to connect using "SetupInterface" with the handle, the connection doesn’t happen. I noticed that the Usage Page (UP) value was 0, so I changed it, and then the connection worked. I was able to make the X-Keys switch between red and green, but when I tried to receive information using "BlockingReadData", it didn’t work at all.

cod.txt

Share this post


Link to post

Just a thought: Are you sure that the calling convention is stdcall? Maybe it is cdecl.

Share this post


Link to post

I have already tried both methods, and the result was the same. I noticed that you have done something similar before. Would you mind sharing it with me?

Share this post


Link to post

I just had a look at my import unit. It also uses stdcall, so that's not the problem. I apparently wrote a test tool, but I don't recall the specifics. As I already wrote on Mastodon: I ended up just using the keyboard emulation. It might have been because I didn't get the API to work. Or maybe the emulation was easier and good enough. I'll try to look into it tomorrow, if I find some time.

Share this post


Link to post

 

Thank you very much, I've been at this for a long time, I've already done this code 5 times and I don't understand why it doesn't work

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×