Ошибка SetWindowsHookEx в .NET 4.0 на 32-разрядной машине с "модулем не найден"?

Я нашел похожие вопросы на этой странице, но я не могу понять, как интерпретировать ответы или выяснить, действительно ли они дублируются.

Вот возможные дубликаты, которые я нашел, с комментариями:

  • SetWindowsHookEx возвращает 0 при компиляции для платформы .NET 4.0 на 32-битных машинах

    Кажется, он не возвращает 0 на мой, но я заметил, что дескриптор, сообщаемый при сбое (.NET 4.0 на 32-разрядной версии), отличается от дескриптора, указанного при его запуске (.NET 3.5 на 32- бит), например, ручка аварийной остановки = 523727, и рабочая рукоятка = 172738378.

  • Вызов SetWindowsHookEx внутри отладчика VS2008 всегда возвращает NULL

    Я могу воспроизвести свою проблему при работе за пределами Visual Studio

  • Модуль не найден

    Это кажется наиболее перспективным, за исключением того, что в комментариях к удаленному ответу я должен использовать LoadLibrary и GetProcAddress для загрузки user32.dll в .NET 4.0, так как что-то о загрузке сборок изменилось. Я уверен, однако, что это мой собственный модуль, который он не может найти, но я не знаю, применимо ли это.

Комментарии, о которых идет речь по поводу удалённого ответа на этот последний, Ханса Пассанта, гласят:

Используете ли вы .NET 4.0? Его среда CLR изменила способ загрузки сборок, больше нет вызова LoadLibrary, для них не будет дескриптора модуля. Использование GetEntryAssembly() вместо этого было бы другим исправлением. - Ханс Пассант 5 мая в 19:43

Итак, что это за слово? Используете ли вы .NET 4.0? Вы пытались использовать LoadLibrary ( "user32.dll" ), чтобы получить полезную ручку DLL? - Ханс Пассант 6 мая в 15:43

Я уверен, что мне не нужно это делать, но, очевидно, я не уверен на 100%. Вопрос, который мне оставил, если мне нужно это изменить, - это то, почему он работает на 64-битной ОС, когда скомпилирован для Any CPU, но не работает на 32-битной в любой конфигурации.

Если что-то изменилось в отношении загрузки сборков .NET, так что я не получаю надлежащего дескриптора для библиотеки классов, у меня есть следующие вопросы:

  • Можно ли каким-либо образом обмануть это в том, что я хочу, без необходимости понижать до .NET 3.5 или изменить библиотеку крюков на неуправляемые?
  • Почему это работает при работе в 64-разрядной ОС, но не на 32-разрядной версии?

Фон

Я создал программу в .NET 4.0, которая использует SetWindowsHookEx с типом hook WH_KEYBOARD_LL для захвата нажатия клавиш. Это хорошо работает на моей 64-битной Windows 7, но с ошибкой "модуль не найден", когда крючок клавиатуры установлен на 32-разрядной Windows 7.

Вот что я пробовал:

  • Компиляция для x86, работающая на 64-разрядной ОС, сбой с "не найденным модулем"
  • Компиляция для x86, запуск на 32-разрядной ОС, сбои
  • Компиляция для любого процессора, работающая на 64-разрядной ОС, работает красиво
  • Компиляция для любого процессора, работающая на 32-разрядной ОС, сбой
  • Переключитесь на .NET 3.5 и повторите описанные выше четыре случая, все они работают

Я бы предпочел не переключать свой код на .NET 3.5, так как я использую несколько своих классов для облегчения работы, а последний код - только в .NET 4.0.

Вы можете загрузить .ZIP файл со всем как проект Visual Studio 2010, если хотите, или можете вставить следующие два файла.

Чтобы воссоздать, если вы хотите спуститься по этому маршруту:

  • Создайте новый консольный проект,.NET 4.0
  • Добавить еще один проект библиотеки классов, а также .NET 4.0
  • Добавить ссылку на проект библиотеки классов из проекта консоли-программы
  • Вставьте содержимое Program.cs ниже в файл Program.cs, который у вас есть в проекте консоли.
  • Вставьте содержимое Hook.cs ниже в файл в проекте библиотеки классов. Вы можете вставить его в файл по умолчанию Class1.cs или добавить другой файл. Вы не можете поместить это в проект консоли

Затем создайте и запустите, проверьте различные конфигурации.

Program.cs

using System;
using HookLib;

namespace HookTest
{
    class Program
    {
        static void Main()
        {
            var hook = new Hook();

            Console.Out.WriteLine("hooking");
            hook.Enable();
            Console.Out.WriteLine("hooked");

            Console.Out.WriteLine("unhooking");
            hook.Disable();
            Console.Out.WriteLine("unhooked");
        }
    }
}

Hook.cs

using System;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.InteropServices;

namespace HookLib
{
    public class Hook
    {
        private IntPtr _Handle;
        private HookProcDelegate _Hook;

        public void Enable()
        {
            Module module = Assembly.GetExecutingAssembly().GetModules()[0];
            if (module != null)
                Console.Out.WriteLine("found module");
            IntPtr moduleHandle = Marshal.GetHINSTANCE(module);
            if (moduleHandle != IntPtr.Zero)
                Console.Out.WriteLine("got module handle: " +
                    moduleHandle.ToString());
            _Hook = HookProc;
            _Handle = SetWindowsHookEx(WH_KEYBOARD_LL, _Hook, moduleHandle, 0);
            if (_Handle == IntPtr.Zero)
                throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        public void Disable()
        {
            bool ok = UnhookWindowsHookEx(_Handle);
            _Handle = IntPtr.Zero;
            if (!ok)
                throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        private delegate int HookProcDelegate(
            int code, IntPtr wParam, IntPtr lParam);

        private int HookProc(int code, IntPtr wParam, IntPtr lParam)
        {
            return CallNextHookEx(_Handle, code, wParam, lParam);
        }

        private const int WH_KEYBOARD_LL = 13;

        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(
            int hookType, HookProcDelegate lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern int CallNextHookEx(
            IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
    }
}

Ответы

Ответ 1

Да, я думаю, вы понимаете, что происходит. SetWindowsHookEx() требует действительного дескриптора модуля и проверяет его, но на самом деле он не используется, когда вы устанавливаете хук низкого уровня. Вам просто нужен действительный дескриптор, неважно, какой именно. Вызов LoadLibrary ( "user32.dll" ) - это хороший способ получить дескриптор, что DLL всегда будет загружаться, так как вы P/Invoke его методы. И он всегда загружается загрузчиком CLR (mscoree.dll). Не беспокойтесь, звоните в FreeLibrary(), это не имеет значения.

Более поздние версии Windows больше не выполняют эту проверку. Я не уверен, когда это началось, где-то вокруг Windows 7 SP1. Вероятно, это полезно, но вызывает "работу на моем компьютере, а не на сценарии отказа клиента".

Ответ 2

Вот мое решение, которое работает как в .net 2, так и 4. hInstance - это ProcessModule.BaseAddress.

public static class ModuleHelper
    {
        public static ProcessModule GetCurrentModule()
        {
            // need instance handle to module to create a system-wide hook
            Module[] list = System.Reflection.Assembly.GetExecutingAssembly().GetModules();
            System.Diagnostics.Debug.Assert(list != null && list.Length > 0);

            var currentProcess = Process.GetCurrentProcess();
            var modules = currentProcess.Modules;
            ProcessModule mod = null;
            foreach (ProcessModule m in modules)
                            //for .net 2 we will find module here
                if (m.ModuleName == list[0].Name)
                {
                    mod = m;
                    break;
                }

                    //for .net 4 take current module
            if (mod == null)
                mod = Process.GetCurrentProcess().MainModule;

            return mod;
        }
    }

Ответ 3

В .Net 4.0 для работы этого кода мне пришлось заменить вызов:

SetWindowsHookEx (WH_KEYBOARD_LL, _Hook, moduleHandle, 0);

с:

SetWindowsHookEx (WH_KEYBOARD_LL, _Hook, IntPtr.Zero, 0);

это фиксировало проблему , Это работает, когда вызов выполняется из того же модуля.

Я получил это от здесь