KbdLayerDescriptor pVkToWcharTable возвращает NULL в Win64

Здесь у меня заканчиваются идеи. У меня есть фрагмент кода, адаптированный из http://thetechnofreak.com/technofreak/keylogger-visual-c/ для преобразования кодов клавиш в символы Unicode. Он отлично работает во всех ситуациях, кроме случаев, когда вы пытаетесь запустить 32-битную версию из 64-битной Windows. По какой-то причине pKbd-> pVkToWcharTable продолжает возвращать NULL. Я пробовал __ptr64, а также явно указывал SysWOW64 и System32 для пути к kbd dll. Я нашел в Интернете несколько элементов, относящихся к этой точной или очень похожей проблеме, но я не могу заставить какое-либо из решений работать (см. KbdLayerDescriptor возвращает NULL на 64-битной архитектуре) Ниже приведен мой тестовый код, который был скомпилирован с помощью mingw-32 в Windows XP (gcc -std = c99 Wow64Test.c), а затем выполняется в 64-битной Windows 7. В Windows XP я получаю действительный указатель, однако в Windows 7 я получаю NULL.

*** Обновление: похоже, что проблемы, которые у меня возникают, связаны с тем, что mingw неправильно реализует __ptr64, поскольку операция sizeof дает 4 байта вместо 8 байтов, предоставленных Visual Studio. Таким образом, реальным решением было бы найти способ сделать размер KBD_LONG_POINTER динамическим или хотя бы 64-битным, но я не уверен, возможно ли это. Любые идеи?

#include <windows.h>
#include <stdio.h>

#define KBD_LONG_POINTER __ptr64
//#define KBD_LONG_POINTER

typedef struct {
    BYTE Vk;
    BYTE ModBits;
} VK_TO_BIT, *KBD_LONG_POINTER PVK_TO_BIT;

typedef struct {
    PVK_TO_BIT pVkToBit;
    WORD wMaxModBits;
    BYTE ModNumber[];
} MODIFIERS, *KBD_LONG_POINTER PMODIFIERS;

typedef struct _VK_TO_WCHARS1 {
    BYTE VirtualKey;
    BYTE Attributes;
    WCHAR wch[1];
} VK_TO_WCHARS1, *KBD_LONG_POINTER PVK_TO_WCHARS1;

typedef struct _VK_TO_WCHAR_TABLE {
    PVK_TO_WCHARS1 pVkToWchars;
    BYTE nModifications;
    BYTE cbSize;
} VK_TO_WCHAR_TABLE, *KBD_LONG_POINTER PVK_TO_WCHAR_TABLE;

typedef struct {
    DWORD dwBoth;
    WCHAR wchComposed;
    USHORT uFlags;
} DEADKEY, *KBD_LONG_POINTER PDEADKEY;

typedef struct {
    BYTE vsc;
    WCHAR *KBD_LONG_POINTER pwsz;
} VSC_LPWSTR, *KBD_LONG_POINTER PVSC_LPWSTR;

typedef struct _VSC_VK {
    BYTE Vsc;
    USHORT Vk;
} VSC_VK, *KBD_LONG_POINTER PVSC_VK;

typedef struct _LIGATURE1 {
    BYTE VirtualKey;
    WORD ModificationNumber;
    WCHAR wch[1];
} LIGATURE1, *KBD_LONG_POINTER PLIGATURE1;

typedef struct tagKbdLayer {
    PMODIFIERS pCharModifiers;
    PVK_TO_WCHAR_TABLE pVkToWcharTable;
    PDEADKEY pDeadKey;
    PVSC_LPWSTR pKeyNames;
    PVSC_LPWSTR pKeyNamesExt;
    WCHAR *KBD_LONG_POINTER *KBD_LONG_POINTER pKeyNamesDead;
    USHORT *KBD_LONG_POINTER pusVSCtoVK;
    BYTE bMaxVSCtoVK;
    PVSC_VK pVSCtoVK_E0;
    PVSC_VK pVSCtoVK_E1;
    DWORD fLocaleFlags;
    BYTE nLgMax;
    BYTE cbLgEntry;
    PLIGATURE1 pLigature;
    DWORD dwType;
    DWORD dwSubType;
} KBDTABLES, *KBD_LONG_POINTER PKBDTABLES;


typedef PKBDTABLES(CALLBACK *KbdLayerDescriptor) (VOID);

int main() {
    PKBDTABLES pKbd;
    HINSTANCE kbdLibrary = NULL;

    //"C:\\WINDOWS\\SysWOW64\\KBDUS.DLL"
    //"C:\\WINDOWS\\System32\\KBDUS.DLL"
    kbdLibrary = LoadLibrary("C:\\WINDOWS\\SysWOW64\\KBDUS.DLL");

    KbdLayerDescriptor pKbdLayerDescriptor = (KbdLayerDescriptor) GetProcAddress(kbdLibrary, "KbdLayerDescriptor");

    if(pKbdLayerDescriptor != NULL) {
        pKbd = pKbdLayerDescriptor();

        printf("Is Null? %d 0x%X\n", sizeof(pKbd->pVkToWcharTable), pKbd->pVkToWcharTable);
    }

    FreeLibrary(kbdLibrary);
    kbdLibrary = NULL;
}

person Alex Barker    schedule 31.07.2012    source источник


Ответы (1)


Возможно, для вас уже поздно, но вот решение для тех, у кого такая же проблема. Эта демонстрация и неполное объяснение помогают, но работают только в Visual Studio: http://www.codeproject.com/Articles/439275/Loading-keyboard-layout-KbdLayerDescriptor-in-32-6

Все указатели в структурах в kbd.h имеют макрос KBD_LONG_POINTER, который определен как *__ptr64* в 64-битных операционных системах. В Visual Studio это заставляет указатели занимать 8 байтов вместо обычных 4 из 32-битных программ. К сожалению, в MinGW *__ptr64* определен, чтобы ничего не делать.

Как написано в связанном объяснении, функция KbdLayerDescriptor по-разному возвращает указатели в 32-битной и 64-битной Windows. Размер указателей, похоже, зависит от операционной системы, а не от запущенной программы. На самом деле указатели по-прежнему имеют размер 4 байта в 64-битной операционной системе для 32-битной программы, но в VS ключевое слово __ptr64 говорит о том, что это не так.

Например, некоторые структуры в kbd.h выглядят так:

typedef struct {
    BYTE Vk;
    BYTE ModBits;
} VK_TO_BIT, *KBD_LONG_POINTER PVK_TO_BIT;

typedef struct {
    PVK_TO_BIT pVkToBit;
    WORD wMaxModBits;
    BYTE ModNumber[];
} MODIFIERS, *KBD_LONG_POINTER PMODIFIERS;

Это не может работать ни в MinGW, ни в VS для 32-битных программ в 64-битной Windows. Поскольку элемент pVkToBit в MODIFIERS составляет всего 4 байта без __ptr64. Решение состоит в том, чтобы забыть о KBD_LONG_POINTER (вы даже можете удалить их все) и определить структуры, подобные приведенным выше. то есть:

struct VK_TO_BIT64
{
    BYTE Vk;
    BYTE ModBits;
};

struct MODIFIERS64
{
    VK_TO_BIT64 *pVkToBit;
    int _align1;
    WORD wMaxModBits;
    BYTE ModNumber[];
};

(Вы можете использовать VK_TO_BIT, а не определять свои собственные VK_TO_BIT64, поскольку они одинаковы, но наличие отдельных определений помогает понять, что происходит.)

Элемент pVkToBit по-прежнему занимает 4 байта, но KbdLayerDescriptor дополняет указатели до 8 байтов в 64-битной ОС, поэтому нам нужно вставить некоторые отступы (int _align1).

Вы должны проделать то же самое с другими структурами в kbd.h. Например, это заменит KBDTABLES:

struct WCHARARRAY64
{
    WCHAR *str;
    int _align1;
};

struct KBDTABLES64
{
    MODIFIERS64 *pCharModifiers;
    int _align1;
    VK_TO_WCHAR_TABLE64 *pVkToWcharTable;
    int _align2;
    DEADKEY64 *pDeadKey;
    int _align3;
    VSC_LPWSTR64 *pKeyNames;
    int _align4;
    VSC_LPWSTR64 *pKeyNamesExt;
    int _align5;
    WCHARARRAY64 *pKeyNamesDead;
    int _align6;
    USHORT *pusVSCtoVK;
    int _align7;
    BYTE bMaxVSCtoVK;
    int _align8;
    VSC_VK64 *pVSCtoVK_E0;
    int _align9;
    VSC_VK64 *pVSCtoVK_E1;
    int _align10;
    DWORD fLocaleFlags;
    byte nLgMax;
    byte cbLgEntry;
    LIGATURE64_1 *pLigature;
    int _align11;
    DWORD dwType;
    DWORD dwSubType;
};

(Обратите внимание, что член _align8 не идет после указателя.)

Чтобы использовать все это, вы должны проверить, работаете ли вы в 64-битных окнах, с помощью этого: http://msdn.microsoft.com/en-us/library/ms684139%28v=vs.85%29.aspx

Если нет, используйте исходные структуры из kbd.h, потому что указатели работают правильно. Они занимают 4 байта. Если программа работает в 64-битной ОС, используйте созданные вами структуры. Вы можете добиться этого с помощью этого:

typedef __int64 (CALLBACK *LayerDescriptor64)(); // Result should be cast to KBDTABLES64.
typedef PKBDTABLES (CALLBACK *LayerDescriptor)(); // This is used on 32 bit OS.

static PKBDTABLES kbdtables = NULL;
static KBDTABLES64 *kbdtables64 = NULL;

И в какой-то функции инициализации:

if (WindowsIs64Bit()) // Your function that checks the OS version.
{
    LayerDescriptor64 KbdLayerDescriptor = (LayerDescriptor64)GetProcAddress(kbdLibrary, "KbdLayerDescriptor");
    if (KbdLayerDescriptor != NULL)
        kbdtables64 = (KBDTABLES64*)KbdLayerDescriptor();
    else
        kbdtables64 = NULL;
}
else
{
    LayerDescriptor KbdLayerDescriptor = (LayerDescriptor)GetProcAddress(kbdLibrary, "KbdLayerDescriptor");
    if (KbdLayerDescriptor != NULL)
        kbdtables = KbdLayerDescriptor();
    else
        kbdtables = NULL;
}

Это решение вообще не использует __ptr64 и работает как в VS, так и в MinGW. Вам следует остерегаться:

  1. Структуры должны быть выровнены по 8-байтовым границам. (Это значение по умолчанию в текущих VS или MinGW, по крайней мере, для C ++.)
  2. Не определяйте KBD_LONG_POINTER как __ptr64 и не удаляйте его отовсюду. Хотя лучше kbd.h не менять.
  3. Понять, как работает выравнивание элементов конструкции. (Я скомпилировал это как C ++, а не C. Я не уверен, будут ли другие правила выравнивания для C.)
  4. Используйте правильную переменную (kbdtables или kbdtables64) в зависимости от ОС.
  5. Очевидно, что это решение не требуется при компиляции 64-битной программы.
person Zoltán Sólyom    schedule 19.05.2013
comment
Во-первых, спасибо, что нашли время все это объяснить! В итоге я выбрал аналогичный, но более динамичный и, следовательно, более сложный путь для достижения того же результата. Я в основном рассчитал заполнение указателя во время выполнения на основе простой проверки Wow64. Исходный код полностью доступен в открытом доступе: github.com/ kwhat / libuiohook / blob / master / src / windows / - person Alex Barker; 18.04.2014