[英]method.Invoke(o, null); can be called only once from the body and not from within the keyboard handler
我正在嘗試創建一個低級鍵盤處理程序,以便在檢測到特定鍵盤組合時使用反射啟動外部應用程序。
我可以直接啟動應用程序一次,沒有錯誤,但是從按鍵處理程序內部,應用程序在正確顯示MessageBox
對話框后崩潰。
我正在將外部 dll 讀入一個字節數組,對於這個測試,您可以使用任何 .Net 應用程序,一個簡單的 Hello World win 表單應用程序將可以工作。
string filePath = @".\x.exe";
FileStream fs = new FileStream(filePath, FileMode.Open);
BinaryReader br = new BinaryReader(fs);
byte[] bin = br.ReadBytes(Convert.ToInt32(fs.Length));
fs.Close();
br.Close();
然后加載外部Assembly
並通過反射執行它:
public Assembly a;
public MethodInfo method;
public object o;
a = Assembly.Load(bin);
method = a.EntryPoint;
o = a.CreateInstance(method.Name);
method.Invoke(o, null);
這工作一次,但如果我重復上面的代碼再次執行它會失敗。
下面的代碼演示了第二次調用的失敗,如果你注釋掉,你可以看到Left + Down + A的鍵盤檢測失敗
即使您注釋掉程序根目錄中的兩個調用,應用程序也會在第一次檢測到鍵時意外關閉。
using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Text;
using System.Diagnostics;
using System.Reflection;
using System.IO;
using System.Windows.Input;
public class Program
{
public static bool lctrlKeyPressed;
public static bool f1KeyPressed;
public static bool AKeyPressed;
public static bool LeftKeyPressed;
public static bool DownKeyPressed;
public static Assembly a;
// search for the Entry Point
public static MethodInfo method;
// create an istance of the Startup form Main method
public static object o;
[STAThread]
public static void Main()
{
//Application.EnableVisualStyles();
//Application.SetCompatibleTextRenderingDefault(false);
string filePath = @".\x.exe";
FileStream fs = new FileStream(filePath, FileMode.Open);
BinaryReader br = new BinaryReader(fs);
byte[] bin = br.ReadBytes(Convert.ToInt32(fs.Length));
fs.Close();
br.Close();
//public Assembly a;
//public MethodInfo method;
//public object o;
a = Assembly.Load(bin);
method = a.EntryPoint;
o = a.CreateInstance(method.Name);
method.Invoke(o, null);
a = Assembly.Load(bin);
method = a.EntryPoint;
o = a.CreateInstance(method.Name);
method.Invoke(o, null);
LowLevelKeyboardHook kbh = new LowLevelKeyboardHook();
kbh.OnKeyPressed += kbh_OnKeyPressed;
kbh.OnKeyUnpressed += kbh_OnKeyUnPressed;
kbh.HookKeyboard();
Application.Run();
kbh.UnHookKeyboard();
}
public static void kbh_OnKeyPressed(object sender, Keys e)
{
if (e == Keys.A)
{
AKeyPressed = true;
}
else if (e == Keys.Left)
{
LeftKeyPressed = true;
}
else if (e == Keys.Down)
{
DownKeyPressed = true;
}
CheckKeyCombo();
}
public static void kbh_OnKeyUnPressed(object sender, Keys e)
{
if (e == Keys.A)
{
AKeyPressed = false;
}
else if (e == Keys.Left)
{
LeftKeyPressed = false;
}
else if (e == Keys.Down)
{
DownKeyPressed = false;
}
}
public static void CheckKeyCombo()
{
if (AKeyPressed && LeftKeyPressed && DownKeyPressed)
{
MessageBox.Show("Left Key + Down Key + A Key + Pressed");
//a = Assembly.Load(bin);
method = a.EntryPoint;
o = a.CreateInstance(method.Name);
method.Invoke(o, null);
}
}
}
public class LowLevelKeyboardHook
{
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private const int WM_SYSKEYDOWN = 0x0104;
private const int WM_KEYUP = 0x101;
private const int WM_SYSKEYUP = 0x105;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
public delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
public event EventHandler<Keys> OnKeyPressed;
public event EventHandler<Keys> OnKeyUnpressed;
private LowLevelKeyboardProc _proc;
private IntPtr _hookID = IntPtr.Zero;
public LowLevelKeyboardHook()
{
_proc = HookCallback;
}
public void HookKeyboard()
{
_hookID = SetHook(_proc);
}
public void UnHookKeyboard()
{
UnhookWindowsHookEx(_hookID);
}
private IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
}
}
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN)
{
int vkCode = Marshal.ReadInt32(lParam);
OnKeyPressed.Invoke(this, ((Keys)vkCode));
}
else if (nCode >= 0 && wParam == (IntPtr)WM_KEYUP || wParam == (IntPtr)WM_SYSKEYUP)
{
int vkCode = Marshal.ReadInt32(lParam);
OnKeyUnpressed.Invoke(this, ((Keys)vkCode));
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
}
這段代碼有兩個相互競爭的問題:
Main
方法連續兩次啟動應用程序。 您還沒有發布特定的異常,但是如果我為x.exe
創建一個獨立的控制台應用程序,那么我可以復制第一個問題,它會引發此異常:
System.InvalidOperationException: '在應用程序中創建第一個 IWin32Window 對象之前,必須調用 SetCompatibleTextRenderingDefault。
在x.exe
應用程序中,我可以從 main 方法中刪除以下行以解決此問題。 但它暗示的是,應用程序第二次啟動時,前一次調用仍會加載到當前AppDomain 中。
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
//Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
如果我們注釋掉測試加載器,則會檢測到第二個異常,如下所示:
System.InvalidOperationException: '在單個線程上啟動第二個消息循環不是有效的操作。 改用 Form.ShowDialog。
因此,即使我們沒有將應用程序兩次加載到同一上下文中的問題,問題是我們試圖在[STAThread]
單線程隔間的范圍內啟動一個新的消息循環。
如果您不需要對正在啟動的應用程序的引用,那么使用 ProcessStart 啟動外部進程可能會更簡單,這類似於從命令提示符 (CMD) 執行外部應用程序:
ProcessStartInfo ps = new ProcessStartInfo(filePath);
System.Diagnostics.Process.Start(ps);
這非常有效,而且比將程序集加載到內存中並反射到其中要簡單得多,除非您確實需要,否則我不會尋找更復雜的東西,這將適用於您現有的應用程序,並為其他非.網絡應用程序也是如此。
玩一下以下顯示幾個選項的代碼:
private static string filePath;
[STAThread]
public static void Main()
{
//Application.EnableVisualStyles();
//Application.SetCompatibleTextRenderingDefault(false);
filePath = @".\x.exe";
// This will start an instance and no wait for it to close
StartProcess();
// These will each wait for the launched process to end before moving on
WaitForProcess();
WaitForProcess();
LowLevelKeyboardHook kbh = new LowLevelKeyboardHook();
kbh.OnKeyPressed += kbh_OnKeyPressed;
kbh.OnKeyUnpressed += kbh_OnKeyUnPressed;
kbh.HookKeyboard();
Application.Run();
kbh.UnHookKeyboard();
}
private static void StartProcess()
{
ProcessStartInfo ps = new ProcessStartInfo(filePath);
System.Diagnostics.Process.Start(ps);
}
private static bool _InProgress = false;
/// <summary>Launches the external process if it was not already launched, state is managed using <seealso cref="_InProgress"/></summary>
/// <returns> true if the app was launched, false indicates the process was still waiting.</returns>
private static bool WaitForProcess()
{
if (_InProgress) return false; // do nothing
_InProgress = true;
try
{
ProcessStartInfo ps = new ProcessStartInfo(filePath);
System.Diagnostics.Process.Start(ps).WaitForExit();
return true;
}
finally
{
_InProgress = false;
}
}
public static void kbh_OnKeyPressed(object sender, Keys e)
{
if (e == Keys.A)
{
AKeyPressed = true;
}
else if (e == Keys.Left)
{
LeftKeyPressed = true;
}
else if (e == Keys.Down)
{
DownKeyPressed = true;
}
CheckKeyCombo();
}
public static void kbh_OnKeyUnPressed(object sender, Keys e)
{
if (e == Keys.A)
{
AKeyPressed = false;
}
else if (e == Keys.Left)
{
LeftKeyPressed = false;
}
else if (e == Keys.Down)
{
DownKeyPressed = false;
}
}
public static void CheckKeyCombo()
{
if (AKeyPressed && LeftKeyPressed && DownKeyPressed)
{
MessageBox.Show("Left Key + Down Key + A Key + Pressed");
//It's up to you if is makes sense to wait for a response or not.
//StartProcess();
// Wait will still raise the above message box if the other app is still running
// But it wont't try to launch the app again
// You can play around with the timing logic for what makes sense for you.
WaitForProcess();
}
}
}
這超出了本文的范圍,但如果您確實需要使用反射來加載外部應用程序,則需要對這兩個應用程序進行更改。
以下是我在通讀后能夠卸載外部程序集的嘗試: https://www.west-wind.com/presentations/dynamicCode/DynamicCode.htm但第一個調用之后的每個調用仍然存在,所以它不是真的卸載根本。
這需要您將 dll 加載代碼放回啟動中,但將
byte[] bin
存儲為靜態字段,而不是本地方法。
private static void LoadApp()
{
AppDomainSetup domainStarter = new AppDomainSetup();
domainStarter.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
AppDomain domain = AppDomain.CreateDomain("XternalApp", null, domainStarter);
var assembly = domain.Load(bin);
var cls = assembly.EntryPoint;
//object instance = assembly.CreateInstance(domain.Load((assembly.FullName, cls.DeclaringType.FullName + "." + cls.Name);
object result = cls.Invoke(null, null);
result = null;
//instance = null;
cls = null;
assembly = null;
GC.Collect();
AppDomain.Unload(domain);
//GC.Collect();
}
對原始應用程序進行這些更改會使上述LoadApp()
方法起作用
即使來自鍵盤事件! :
static Form1 instance = null;
/// <summary>
/// The main entry point for the application.
/// </summary>
//[STAThread]
[MTAThread]
static void Main()
{
if (instance == null)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
instance = new Form1();
Application.Run(instance);
}
else
{
// TODO: take this out, just here for effect
MessageBox.Show("Already running!");
instance = new Form1();
instance.Show();
}
}
非常感謝大家的參與
基本上我打算讓 C# 代碼做的是這個
這是一種可怕的做事方式
但這是我現在要做的最好的事情
因此,在 C# 代碼中,我需要能夠盡快運行 X.exe
這就是我將它加載到內存中的原因
X.exe 也應該可以多次調用
這是我正在玩的游戲
C# 程序在我玩游戲時在后台運行
如果我按一定順序的鍵盤按鈕,C# 將運行 X.exe
這本質上只是 JitBit Macro Recorder 的一個宏
為什么我使用 Jitbit 宏記錄器?
因為它是一個非常快的宏引擎,而且運行良好
用我剛才說的
非常感謝對 C# 程序的任何進一步貢獻
真心感謝大家!!!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.