C# 程序钩子的问题?C#调用C++动态库的时候,如果捕获C++线程里的错误

2023-11-28 00:30:04 :73

c# 程序钩子的问题?C#调用C++动态库的时候,如果捕获C++线程里的错误

“线程钩子”相关信息最新大全有哪些,这是大家都非常关心的,接下来就一起看看c# 程序钩子的问题?C#调用C++动态库的时候,如果捕获C++线程里的错误!

本文目录

c# 程序钩子的问题

C#是面向对象的编程语言,所以钩子是写在类中的。

/// 《summary》/// 设置的钩子类型/// 《/summary》public enum HookType : int{         /// 《summary》    /// WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以把守菜单,迁移转变     ///条,消息框,对话框消息并且发明用户应用ALT+TAB or ALT+ESC 组合键切换窗口。     ///WH_MSGFILTER Hook只能把守传递到菜单,迁移转变条,消息框的消息,以及传递到通     ///过安装了Hook子过程的应用法度建树的对话框的消息。WH_SYSMSGFILTER Hook     ///把守所有应用法度消息。     ///WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式轮回时代     ///过滤消息,这等价于在主消息轮回中过滤消息。                ///经由过程调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。经由过程应用这     ///个函数,应用法度可以或许在模式轮回时代应用雷同的代码去过滤消息,如同在主消息循     ///环里一样    /// 《/summary》    WH_MSGFILTER = -1,    /// 《summary》    /// WH_JOURNALRECORD Hook用来把守和记录输入事务。典范的,可以应用这     ///个Hook记录连气儿的鼠标和键盘事务,然后经由过程应用WH_JOURNALPLAYBACK Hook     ///往返放。WH_JOURNALRECORD Hook是全局Hook,它不克不及象线程特定Hook一样     ///应用。WH_JOURNALRECORD是system-wide local hooks,它们不会被打针到任何行     ///程地址空间    /// 《/summary》    WH_JOURNALRECORD = 0,    /// 《summary》    /// WH_JOURNALPLAYBACK Hook使应用法度可以插入消息到体系消息队列。可     ///以应用这个Hook回放经由过程应用WH_JOURNALRECORD Hook记录下来的连气儿的鼠     ///标和键盘事务。只要WH_JOURNALPLAYBACK Hook已经安装,正常的鼠标和键盘     ///事务就是无效的。WH_JOURNALPLAYBACK Hook是全局Hook,它不克不及象线程特定     ///Hook一样应用。WH_JOURNALPLAYBACK Hook返回超时价,这个值告诉体系在处     ///理来自回放Hook当前消息之前须要守候多长时候(毫秒)。这就使Hook可以把握实     ///时事务的回放。WH_JOURNALPLAYBACK是system-wide local hooks,它们不会被     ///打针到任何行程地址空间    /// 《/summary》    WH_JOURNALPLAYBACK = 1,    /// 《summary》           /// 在应用法度中,WH_KEYBOARD Hook用来把守WM_KEYDOWN and      ///WM_KEYUP消息,这些消息经由过程GetMessage or PeekMessage function返回。可以使     ///用这个Hook来把守输入到消息队列中的键盘消息    /// 《/summary》    WH_KEYBOARD = 2,    /// 《summary》    /// 应用法度应用WH_GETMESSAGE Hook来把守从GetMessage or PeekMessage函     ///数返回的消息。你可以应用WH_GETMESSAGE Hook去把守鼠标和键盘输入,以及     ///其它发送到消息队列中的消息    /// 《/summary》    WH_GETMESSAGE = 3,    /// 《summary》    /// 把守发送到窗口过程的消息,体系在消息发送到接管窗口过程之前调用    /// 《/summary》    WH_CALLWNDPROC = 4,    /// 《summary》    /// 在以下事务之前,体系都邑调用WH_CBT Hook子过程,这些事务包含:     ///1. 激活,建树,烧毁,最小化,最大化,移动,改变尺寸等窗口事务;     ///2. 完成体系指令;     ///3. 来自体系消息队列中的移动鼠标,键盘事务;     ///4. 设置输入核苦衷务;     ///5. 同步体系消息队列事务。    ///Hook子过程的返回值断定体系是否容许或者防止这些操纵中的一个    /// 《/summary》    WH_CBT = 5,    /// 《summary》    /// WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以把守菜单,迁移转变     ///条,消息框,对话框消息并且发明用户应用ALT+TAB or ALT+ESC 组合键切换窗口。     ///WH_MSGFILTER Hook只能把守传递到菜单,迁移转变条,消息框的消息,以及传递到通     ///过安装了Hook子过程的应用法度建树的对话框的消息。WH_SYSMSGFILTER Hook     ///把守所有应用法度消息。     ///WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式轮回时代     ///过滤消息,这等价于在主消息轮回中过滤消息。     ///经由过程调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。经由过程应用这     ///个函数,应用法度可以或许在模式轮回时代应用雷同的代码去过滤消息,如同在主消息循     ///环里一样    /// 《/summary》    WH_SYSMSGFILTER = 6,    /// 《summary》    /// WH_MOUSE Hook把守从GetMessage 或者 PeekMessage 函数返回的鼠标消息。     ///应用这个Hook把守输入到消息队列中的鼠标消息    /// 《/summary》    WH_MOUSE = 7,    /// 《summary》    /// 当调用GetMessage 或 PeekMessage 来从消息队列种查询非鼠标、键盘消息时    /// 《/summary》    WH_HARDWARE = 8,    /// 《summary》    /// 在体系调用体系中与其它Hook接洽关系的Hook子过程之前,体系会调用     ///WH_DEBUG Hook子过程。你可以应用这个Hook来决意是否容许体系调用与其它     ///Hook接洽关系的Hook子过程    /// 《/summary》    WH_DEBUG = 9,    /// 《summary》    /// 外壳应用法度可以应用WH_SHELL Hook去接管首要的通知。当外壳应用法度是     ///激活的并且当顶层窗口建树或者烧毁时,体系调用WH_SHELL Hook子过程。     ///WH_SHELL 共有5钟景象:     ///1. 只要有个top-level、unowned 窗口被产生、起感化、或是被摧毁;     ///2. 当Taskbar须要重画某个按钮;     ///3. 当体系须要显示关于Taskbar的一个法度的最小化情势;     ///4. 当今朝的键盘布局状况改变;     ///5. 当应用者按Ctrl+Esc去履行Task Manager(或雷同级此外法度)。     ///遵守常规,外壳应用法度都不接管WH_SHELL消息。所以,在应用法度可以或许接     ///收WH_SHELL消息之前,应用法度必须调用SystemParametersInfo function注册它自     ///己    /// 《/summary》    WH_SHELL = 10,    /// 《summary》    /// 当应用法度的前台线程处于余暇状况时,可以应用WH_FOREGROUNDIDLE      ///Hook履行低优先级的任务。当应用法度的前台线程可能要变成余暇状况时,体系就     ///会调用WH_FOREGROUNDIDLE Hook子过程    /// 《/summary》    WH_FOREGROUNDIDLE = 11,    /// 《summary》    /// 把守发送到窗口过程的消息,体系在消息发送到接管窗口过程之后调用    /// 《/summary》    WH_CALLWNDPROCRET = 12,    /// 《summary》    /// 把守输入到线程消息队列中的键盘消息    /// 《/summary》    WH_KEYBOARD_LL = 13,    /// 《summary》    /// 把守输入到线程消息队列中的鼠标消息    /// 《/summary》    WH_MOUSE_LL = 14}public partial class Form1 : Form    {        public Form1()        {            InitializeComponent();        }        private static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);//导入钩子函数消息传递的方法private static extern IntPtr SetWindowsHookEx(HookType idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);//导入设置钩子的方法private static extern bool UnhookWindowsHookEx(int idHook);    //导入卸载钩子的方法private delegate int HookProc(int nCode, int wParam, IntPtr lParam);  //声明回调函数///《summary》///自定义回调函数///《/summary》private int HookCallback(int nCode, IntPtr wParam, IntPtr lParam){    MessageBox.Show(nCode.ToString());    //键盘按下时    if (nCode 》= 0 && wParam == (IntPtr)0)    {         int vkCode = Marshal.ReadInt32(lParam);         Keys key = (Keys)vkCode;         MessageBox.Show(key.ToString());    }    return CallNextHookEx(handle, nCode, wParam, lParam);}        private void Form1_Load(object sender, EventArgs e)        {            //创建钩子(这个函数的参数你可以百度找找,C#钩子研究过但没成功)            SetWindowsHookEx(params);        }    }

C#调用C++动态库的时候,如果捕获C++线程里的错误

全局系统钩子的使用。为此,我开发了一个可重用的类库并创建一个相应的示例程序(见下图)。 你可能注意到另外的关于使用系统钩子的文章。本文与之类似但是有重要的差别。这篇文章将讨论在.NET中使用全局系统钩子,而其它文章仅讨论本地系统钩子。这些思想是类似的,但是实现要求是不同的。 二、 背景 如果你对Windows系统钩子的概念不熟悉,让我作一下简短的描述: ・一个系统钩子允许你插入一个回调函数-它拦截某些Windows消息(例如,鼠标相联系的消息)。 ・一个本地系统钩子是一个系统钩子-它仅在指定的消息由一个单一线程处理时被调用。 ・一个全局系统钩子是一个系统钩子-它当指定的消息被任何应用程序在整个系统上所处理时被调用。已有若干好文章来介绍系统钩子概念。在此,不是为了重新收集这些介绍性的信息,我只是简单地请读者参考下面有关系统钩子的一些背景资料文章。如果你对系统钩子概念很熟悉,那么你能够从本文中得到你能够得到的任何东西。 ・关于MSDN库中的钩子知识。 ・Dino Esposito的《Cutting Edge-Windows Hooks in the .NET Framework》。 ・Don Kackman的《在C#中应用钩子》。 本文中我们要讨论的是扩展这个信息来创建一个全局系统钩子-它能被.NET类所使用。我们将用C#和一个DLL和非托管C++来开发一个类库-它们一起将完成这个目标。 三、 使用代码 在我们深入开发这个库之前,让我们快速看一下我们的目标。在本文中,我们将开发一个类库-它安装全局系统钩子并且暴露这些由钩子处理的事件,作为我们的钩子类的一个.NET事件。为了说明这个系统钩子类的用法,我们将在一个用C#编写的Windows表单应用程序中创建一个鼠标事件钩子和一个键盘事件钩子。 这些类库能用于创建任何类型的系统钩子,其中有两个预编译的钩子-MouseHook和KeyboardHook。我们也已经包含了这些类的特定版本,分别称为MouseHookExt和KeyboardHookExt。根据这些类所设置的模型,你能容易构建系统钩子-针对Win32 API中任何15种钩子事件类型中的任何一种。另外,这个完整的类库中还有一个编译的HTML帮助文件-它把这些类归档化。请确信你看了这个帮助文件-如果你决定在你的应用程序中使用这个库的话。 MouseHook类的用法和生命周期相当简单。首先,我们创建MouseHook类的一个实例。mouseHook = new MouseHook();//mouseHook是一个成员变量 接下来,我们把MouseEvent事件绑定到一个类层次的方法上。mouseHook.MouseEvent+=new MouseHook.MouseEventHandler(mouseHook_MouseEvent);// ...private void mouseHook_MouseEvent(MouseEvents mEvent, int x, int y){ string msg =string.Format("鼠标事件::(,).",mEvent.ToString(),x,y); AddText(msg);//增加消息到文本框} 为开始收到鼠标事件,简单地安装下面的钩子即可。mouseHook.InstallHook(); 为停止接收事件,只需简单地卸载这个钩子。mouseHook.UninstallHook(); 你也可以调用Dispose来卸载这个钩子。 在你的应用程序退出时,卸载这个钩子是很重要的。让系统钩子一直安装着将减慢系统中的所有的应用程序的消息处理。它甚至能够使一个或多个进程变得很不稳定。因此,请确保在你使用完钩子时一定要移去你的系统钩子。我们确定在我们的示例应用程序会移去该系统钩子-通过在Form的Dispose方法中添加一个Dispose调用。protected override void Dispose(bool disposing) { if (disposing) { if (mouseHook != null) { mouseHook.Dispose(); mouseHook = null; } // ... }} 使用该类库的情况就是如此。该类库中有两个系统钩子类并且相当容易扩充。 四、 构建库 这个库共有两个主要组件。第一部分是一个C#类库-你可以直接使用于你的应用程序中。该类库,反过来,在内部使用一个非托管的C++ DLL来直接管理系统钩子。我们将首先讨论开发该C++部分。接下来,我们将讨论怎么在C#中使用这个库来构建一个通用的钩子类。就象我们讨论C++/C#交互一样,我们将特别注意C++方法和数据类型是怎样映射到.NET方法和数据类型的。 你可能想知道为什么我们需要两个库,特别是一个非托管的C++ DLL。你还可能注意到在本文的背景一节中提到的两篇参考文章,其中并没有使用任何非托管的代码。为此,我的回答是,"对!这正是我写这篇文章的原因"。当你思考系统钩子是怎样实际地实现它们的功能时,我们需要非托管的代码是十分重要的。为了使一个全局的系统钩子能够工作,Windows把你的DLL插入到每个正在运行的进程的进程空间中。既然大多数进程不是.NET进程,所以,它们不能直接执行.NET装配集。我们需要一种非托管的代码代理- Windows可以把它插入到所有将要被钩住的进程中。 首先是提供一种机制来把一个.NET代理传递到我们的C++库。这样,我们用C++语言定义下列函数(SetUserHookCallback)和函数指针(HookProc)。int SetUserHookCallback(HookProc userProc, UINT hookID)typedef void (CALLBACK *HookProc)(int code, WPARAM w, LPARAM l) SetUserHookCallback的第二个参数是钩子类型-这个函数指针将使用它。现在,我们必须用C#来定义相应的方法和代理以使用这段代码。下面是我们怎样把它映射到C#。private static extern SetCallBackResultsSetUserHookCallback(HookProcessedHandler hookCallback, HookTypes hookType)protected delegate void HookProcessedHandler(int code, UIntPtr wparam, IntPtr lparam)public enum HookTypes { JournalRecord = 0, JournalPlayback = 1, // ... KeyboardLL = 13, MouseLL = 14}; 首先,我们使用DllImport属性导入SetUserHookCallback函数,作为我们的抽象基钩子类SystemHook的一个静态的外部的方法。为此,我们必须映射一些外部数据类型。首先,我们必须创建一个代理作为我们的函数指针。这是通过定义上面的HookProcessHandler 来实现的。我们需要一个函数,它的C++签名为(int,WPARAM,LPARAM)。在Visual Studio .NET C++编译器中,int与C#中是一样的。也就是说,在C++与C#中int就是Int32。事情并不总是这样。一些编译器把C++ int作为Int16对待。我们坚持使用Visual Studio .NET C++编译器来实现这个工程,因此,我们不必担心编译器差别所带来的另外的定义。 接下来,我们需要用C#传递WPARAM和LPARAM值。这些确实是指针,它们分别指向C++的UINT和LONG值。用C#来说,它们是指向uint和int的指针。如果你还不确定什么是WPARAM,你可以通过在C++代码中单击右键来查询它,并且选择"Go to definition"。这将会引导你到在windef.h中的定义。//从windef.h:typedef UINT_PTR WPARAM;typedef LONG_PTR LPARAM; 因此,我们选择System.UIntPtr和System.IntPtr作为我们的变量类型-它们分别相应于WPARAM和LPARAM类型,当它们使用在C#中时。现在,让我们看一下钩子基类是怎样使用这些导入的方法来传递一个回叫函数(代理)到C++中-它允许C++库直接调用你的系统钩子类的实例。首先,在构造器中,SystemHook类创建一个到私有方法InternalHookCallback的代理-它匹配HookProcessedHandler代理签名。然后,它把这个代理和它的HookType传递到C++库以使用SetUserHookCallback方法来注册该回叫函数,如上面所讨论的。下面是其代码实现:public SystemHook(HookTypes type){ _type = type; _processHandler = new HookProcessedHandler(InternalHookCallback); SetUserHookCallback(_processHandler, _type);} InternalHookCallback的实现相当简单。InternalHookCallback在用一个catch-all try/catch块包装它的同时仅传递到抽象方法HookCallback的调用。这将简化在派生类中的实现并且保护C++代码。记住,一旦一切都准备妥当,这个C++钩子就会直接调用这个方法。private void InternalHookCallback(int code, UIntPtr wparam, IntPtr lparam){try catch {}} 我们已增加了一个方法实现属性-它告诉编译器不要内联这个方法。这不是可选的。至少,在我添加try/catch之前是需要的。看起来,由于某些原因,编译器在试图内联这个方法-这将给包装它的代理带来各种麻烦。然后,C++层将回叫,而该应用程序将会崩溃。 现在,让我们看一下一个派生类是怎样用一个特定的HookType来接收和处理钩子事件。下面是虚拟的MouseHook类的HookCallback方法实现:protected override void HookCallback(int code, UIntPtr wparam, IntPtr lparam){ if (MouseEvent == null) int x = 0, y = 0; MouseEvents mEvent = (MouseEvents)wparam.ToUInt32(); switch(mEvent) { case MouseEvents.LeftButtonDown: GetMousePosition(wparam, lparam, ref x, ref y); break; // ... } MouseEvent(mEvent, new Point(x, y));} 首先,注意这个类定义一个事件MouseEvent-该类在收到一个钩子事件时激发这个事件。这个类在激发它的事件之前,把数据从WPARAM和 LPARAM类型转换成.NET中有意义的鼠标事件数据。这样可以使得类的消费者免于担心解释这些数据结构。这个类使用导入的 GetMousePosition函数-我们在C++ DLL中定义的用来转换这些值。为此,请看下面几段的讨论。 在这个方法中,我们检查是否有人在听这一个事件。如果没有,不必继续处理这一事件。然后,我们把WPARAM转换成一个MouseEvents枚举类型。我们已小心地构造了MouseEvents枚举来准确匹配它们在C ++中相应的常数。这允许我们简单地把指针的值转换成枚举类型。但是要注意,这种转换即使在WPARAM的值不匹配一个枚举值的情况下也会成功。 mEvent的值将仅是未定义的(不是null,只是不在枚举值范围之内)。为此,请详细分析System.Enum.IsDefined方法。 接下来,在确定我们收到的事件类型后,该类激活这个事件,并且通知消费者鼠标事件的类型及在该事件过程中鼠标的位置。 最后注意,有关转换WPARAM和LPARAM值:对于每个类型的事件,这些变量的值和意思是不同的。因此,在每一种钩子类型中,我们必须区别地解释这些值。我选择用C++实现这种转换,而不是尽量用C#来模仿复杂的C++结构和指针。例如,前面的类就使用了一个叫作GetMousePosition的 C++函数。下面是C++ DLL中的这个方法:bool GetMousePosition(WPARAM wparam, LPARAM lparam, int amp; x, int amp; y) { MOUSEHOOKSTRUCT * pMouseStruct = (MOUSEHOOKSTRUCT *)lparam; x = pMouseStruct-》pt.x; y = pMouseStruct-》pt.y; return true;} 不是尽量映射MOUSEHOOKSTRUCT结构指针到C#,我们简单地暂时把它回传到C++层以提取我们需要的值。注意,因为我们需要从这个调用中返回一些值,我们把我们的整数作为参考变量传递。这直接映射到C#中的int*。但是,我们可以重载这个行为,通过选择正确的签名来导入这个方法。private static extern bool InternalGetMousePosition(UIntPtr wparam,IntPtr lparam, ref int x, ref int y) 通过把integer参数定义为ref int,我们得到通过C++参照传递给我们的值。如果我们想要的话,我们还可以使用out int。 五、 限制 一些钩子类型并不适合实现全局钩子。我当前正在考虑解决办法-它将允许使用受限制的钩子类型。到目前为止,不要把这些类型添加回该库中,因为它们将导致应用程序的失败(经常是系统范围的灾难性失败)。下一节将集中讨论这些限制背后的原因和解决办法。HookTypes.CallWindowProcedureHookTypes.CallWindowProretHookTypes.ComputerBasedTrainingHookTypes.DebugHookTypes.ForegroundIdleHookTypes.JournalRecordHookTypes.JournalPlaybackHookTypes.GetMessageHookTypes.SystemMessageFilter 六、 两种类型的钩子 在本节中,我将尽量解释为什么一些钩子类型被限制在一定的范畴内而另外一些则不受限制。如果我使用有点偏差术语的话,请原谅我。我还没有找到任何有关这部分题目的文档,因此,我编造了我自己的词汇。另外,如果你认为我根本就不对,请告诉我好了。 当Windows调用传递到SetWindowsHookEx()的回调函数时它们会因不同类型的钩子而被区别调用。基本上有两种情况:切换执行上下文的钩子和不切换执行上下文的钩子。用另一种方式说,也就是,在放钩子的应用程序进程空间执行钩子回调函数的情况和在被钩住的应用程序进程空间执行钩子回调函数的情况。 钩子类型例如鼠标和键盘钩子都是在被Windows调用之前切换上下文的。整个过程大致如下: 1. 应用程序X拥有焦点并执行。 2. 用户按下一个键。 3. Windows从应用程序X接管上下文并把执行上下文切换到放钩子的应用程序。 4. Windows用放钩子的应用程序进程空间中的键消息参数调用钩子回调函数。 5. Windows从放钩子的应用程序接管上下文并把执行上下文切换回应用程序X。 6. Windows把消息放进应用程序X的消息排队。 7. 稍微一会儿之后,当应用程序X执行时,它从自己的消息排队中取出消息并且调用它的内部按键(或松开或按下)处理器。 8. 应用程序X继续执行... 例如CBT钩子(window创建,等等。)的钩子类型并不切换上下文。对于这些类型的钩子,过程大致如下: 1. 应用程序X拥有焦点并执行。 2. 应用程序X创建一个窗口。 3. Windows用在应用程序X进程空间中的CBT事件消息参数调用钩子回调函数。 4. 应用程序X继续执行... 这应该说明了为什么某种类型的钩子能够用这个库结构工作而一些却不能。记住,这正是该库要做的。在上面第4步和第3步之后,分别插入下列步骤: 1. Windows调用钩子回调函数。 2. 目标回调函数在非托管的DLL中执行。 3. 目标回调函数查找它的相应托管的调用代理。 4. 托管代理被以适当的参数执行。 5. 目标回调函数返回并执行相应于指定消息的钩子处理。 第三步和第四步因非切换钩子类型而注定失败。第三步将失败,因为相应的托管回调函数不会为该应用程序而设置。记住,这个DLL使用全局变量来跟踪这些托管代理并且该钩子DLL被加载到每一个进程空间。但是这个值仅在放钩子的应用程序进程空间中设置。对于另外其它情况,它们全部为null。 Tim Sylvester在他的《Other hook types》一文中指出,使用一个共享内存区段将会解决这个问题。这是真实的,但是也如Tim所指出的,那些托管代理地址对于除了放钩子的应用程序之外的任何进程是无意义的。这意味着,它们是无意义的并且不能在回调函数的执行过程中调用。那样会有麻烦的。 因此,为了把这些回调函数使用于不执行上下文切换的钩子类型,你需要某种进程间的通讯。 我已经试验过这种思想-使用非托管的DLL钩子回调函数中的进程外COM对象进行IPC。如果你能使这种方法工作,我将很高兴了解到这点。至于我的尝试,结果并不理想。基本原因是很难针对各种进程和它们的线程(CoInitialize(NULL))而正确地初始化COM单元。这是一个在你可以使用 COM对象之前的基本要求。 我不怀疑,一定有办法来解决这个问题。但是我还没有试用过它们,因为我认为它们仅有有限的用处。例如,CBT钩子可以让你取消一个窗口创建,如果你希望的话。可以想像,为使这能够工作将会发生什么。 1. 钩子回调函数开始执行。 2. 调用非托管的钩子DLL中的相应的钩子回调函数。 3. 执行必须被路由回到主钩子应用程序。 4. 该应用程序必须决定是否允许这一创建。 5. 调用必须被路由回仍旧在运行中的钩子回调函数。 6. 在非托管的钩子DLL中的钩子回调函数从主钩子应用程序接收到要采取的行动。 7. 在非托管的钩子DLL中的钩子回调函数针对CBT钩子调用采取适当的行动。 8. 完成钩子回调函数的执行。 这不是不可能的,但是不算好的。我希望这会消除在该库中的围绕被允许的和受限制的钩子类型所带来的神秘。 七、 其它 ・库文档:我们已经包含了有关ManagedHooks类库的比较完整的代码文档。当以"Documentation"构建配置进行编译时,这被经由Visual Studio.NET转换成标准帮助XML。最后,我们已使用NDoc来把它转换成编译的HTML帮助(CHM)。你可以看这个帮助文件,只需简单地在该方案的解决方案资源管理器中点击Hooks.chm文件或通过查找与该文相关的可下载的ZIP文件。 ・增强的智能感知:如果你不熟悉Visual Studio.NET怎样使用编译的XML文件(pre-NDoc output)来为参考库的工程增强智能感知,那么让我简单地介绍一下。如果你决定在你的应用程序中使用这个类库,你可以考虑复制该库的一个稳定构建版本到你想参考它的位置。同时,还要把XML文档文件 (SystemHooks\ManagedHooks\bin\Debug\Kennedy.ManagedHooks.xml)复制到相同的位置。当你添加一个参考到该库时,Visual Studio.NET将自动地读该文件并使用它来添加智能感知文档。这是很有用的,特别是对于象这样的第三方库。 ・单元测试:我相信,所有的库都应有与之相应的单元测试。既然我是一家公司(主要负责针对.NET环境软件的单元测试)的合伙人和软件工程师,任何人不会对此感到惊讶。因而,你将会在名为ManagedHooksTests的解决方案中找到一个单元测试工程。为了运行该单元测试,你需要下载和安装 HarnessIt-这个下载是我们的商业单元测试软件的一个自由的试用版本。在该单元测试中,我对这给予了特殊的注意-在此处,方法的无效参数可能导致 C++内存异常的发生。尽管这个库是相当简单的,但该单元测试确实能够帮助我在一些更为微妙的情况下发现一些错误。 ・非托管的/托管的调试:有关混合解决方案(例如,本文的托管的和非托管的代码)最为技巧的地方之一是调试问题。如果你想单步调试该C++代码或在C++代码中设置断点,你必须启动非托管的调试。这是一个Visual Studio.NET中的工程设置。注意,你可以非常顺利地单步调试托管的和非托管的层,但是,在调试过程中,非托管的调试确实严重地减慢应用程序的装载时间和执行速度。 八、 最后警告 很明显,系统钩子相当有力量;然而,使用这种力量应该是有责任性的。在系统钩子出了问题时,它们不仅仅垮掉你的应用程序。它们可以垮掉在你的当前系统中运行的每个应用程序。但是到这种程度的可能性一般是很小的。尽管如此,在使用系统钩子时,你还是需要再三检查你的代码。 我发现了一项可以用来开发应用程序的有用的技术-它使用系统钩子来在微软的虚拟PC上安装你的喜爱的开发操作系统的一个拷贝和Visual Studio.NET。然后,你就可以在此虚拟的环境中开发你的应用程序。用这种方式,当你的钩子应用程序出现错误时,它们将仅退出你的操作系统的虚拟实例而不是你的真正的操作系统。我已经不得不重启动我的真正的OS-在这个虚拟OS由于一个钩子错误崩溃时,但是这并不经常。

虚拟机关不了机怎么办

关闭时,首先调用关闭钩子,所有钩子执行完毕后,如果需要进行垃圾回收就调用 finalize 方法,否则直接关闭虚拟机。JVM 关闭过程中,不会中断或停止任何线程,在最终关闭虚拟机时强制关闭所有线程。关闭钩子关闭钩子是一个可以在 JVM 关闭时执行的回调,可以通过 Runtime.addShutdownHook 进行注册。JVM 关闭时首先会调用这些关闭钩子,但不会保证关闭钩子执行的顺序。关闭钩子的执行时间要尽可能短,不应该再做耗时的操作,因为这会影响 JVM 的关闭时间。```javaRuntime.getRuntime().addShutdownHook(new Thread(){//TODO});```如果调用关闭钩子时还有线程在运行,那么关闭钩子将和这些线程同时运行。因此,关闭钩子的操作必须保证是线程安全的,访问数据需要使用同步机制,要避免死锁。同时,关闭钩子需要考虑 JVM 关闭的所有可能性,不能假设关闭的原因,也不应该尝试去分析 JVM 关闭的原因。关闭钩子通常用于服务的清理工作,如 dubbo 使用关闭钩子来关闭连接并通知注册中心注销服务。但关闭钩子是并发执行的,需要考虑多个钩子之间的相互影响,如提前关闭日志服务可能导致其他钩子或线程无法再使用日志。建议只使用一个关闭钩子来处理所有的事情,这样可以确保任务串行执行,从而避免多个钩子之间的竞争和死锁。守护线程Java 中的线程分为普通线程和守护线程。一个线程被创建时会继承创建它的那个线程的守护状态。JVM 启动时创建的线程除了主线程是普通线程,其他线程(如 GC 等)都是守护线程。当线程退出时,JVM 会检查剩余线程的状态,如果剩余的线程都是守护线程已经没有普通线程,那么 JVM 会进行关闭操作。JVM 最终关闭时,守护线程会被直接抛弃,既不会执行 finally 也不会执行回卷栈。因此应该尽量不要使用守护线程,使用时也应该进行简单的操作。垃圾回收JVM 关闭的最后一步是进行垃圾回收,主要是文件或套接字资源。这一步主要是调用 finalize 方法进行最后的资源释放。finalize 方法访问的数据可能会被其他线程并发访问,必须对访问进行同步控制。JVM 不保证何时调用 finalize 方法,甚至无法保证是否调用 finalize 方法

C++ Hook编程

这个有点难,而且做出来也需要时间,我没有时间做,给你一点资料吧:捕捉所有进程关闭的消息,用SetWindowsHookEx挂钩到全局钩子,SetWindowsHookEx函数的资料SetWindowsHookEx( idHook: Integer; {钩子类型} lpfn: TFNHookProc; {函数指针} hmod: HINST; {包含钩子函数的模块(EXE、DLL)句柄; 一般是 HInstance; 如果是当前线程这里可以是 0} dwThreadId: DWORD {关联的线程; 可用 GetCurrentThreadId 获取当前线程; 0 表示是系统级钩子}): HHOOK; {返回钩子的句柄; 0 表示失败}//钩子类型 idHook 选项:WH_MSGFILTER = -1; {线程级; 截获用户与控件交互的消息}WH_JOURNALRECORD = 0; {系统级; 记录所有消息队列从消息队列送出的输入消息, 在消息从队列中清除时发生; 可用于宏记录}WH_JOURNALPLAYBACK = 1; {系统级; 回放由 WH_JOURNALRECORD 记录的消息, 也就是将这些消息重新送入消息队列}WH_KEYBOARD = 2; {系统级或线程级; 截获键盘消息}WH_GETMESSAGE = 3; {系统级或线程级; 截获从消息队列送出的消息}WH_CALLWNDPROC = 4; {系统级或线程级; 截获发送到目标窗口的消息, 在 SendMessage 调用时发生}WH_CBT = 5; {系统级或线程级; 截获系统基本消息, 譬如: 窗口的创建、激活、关闭、最大最小化、移动等等}WH_SYSMSGFILTER = 6; {系统级; 截获系统范围内用户与控件交互的消息}WH_MOUSE = 7; {系统级或线程级; 截获鼠标消息}WH_HARDWARE = 8; {系统级或线程级; 截获非标准硬件(非鼠标、键盘)的消息}WH_DEBUG = 9; {系统级或线程级; 在其他钩子调用前调用, 用于调试钩子}WH_SHELL = 10; {系统级或线程级; 截获发向外壳应用程序的消息}WH_FOREGROUNDIDLE = 11; {系统级或线程级; 在程序前台线程空闲时调用}WH_CALLWNDPROCRET = 12; {系统级或线程级; 截获目标窗口处理完毕的消息, 在 SendMessage 调用后发生}这里有一个示例程序:***隐藏网址***

键盘钩子失灵

VB编程中钩子的实现及应用 Windows系统中钩子具有相当强大的功能,通过这种技术可以对几乎所有的Windows 系统中的消息进行拦截、监视、处理。这种技术可以广泛应用于各种软件,尤其是需要有监控、自动记录等对系统进行监测功能的软件。本文针对这个专题进行了探讨,希望可以为读者朋友们起到抛砖引玉的作用。 一、钩子的机制及类型 Windows的应用程序都是基于消息驱动的,应用程序的操作都依赖于它所得到的消息的类型及内容。钩子与Dos中断截获处理机制有类似之处。钩子(Hook)是Windows消息处理机制的一个平台,通过安装各种钩子,应用程序可以在上面设置子程序以监视指定窗口的某种消息,并且当消息到达目标窗口之前处理它。 在Windows中,钩子有两种,一种是系统钩子(RemoteHook),它对消息的监视是整个系统范围,另一种是线程钩子(LocalHook),它的拦截范围只有进程内部的消息。对于系统钩子,其钩子函数(HookFunction)应在Windows系统的动态链接库(DLL)中实现,而对于线程钩子来说,钩子函数可以在DLL之中实现,也可以在相应的应用程序之中实现。这是因为当开发人员创建一个钩子时,Windows先在系统内存中创建一个数据结构,该数据结构包含了钩子的相关信息,然后把该结构体加到已经存在的钩子链表中去,并且新的钩子将排在老的钩子的前面。当一个事件发生时,如果安装的是一个局部钩子,当前进程中的钩子函数将被调用。如果是一个远程钩子,系统就必须把钩子函数插入到其它进程的地址空间,要做到这一点就要求钩子函数必须在一个动态链接库中,所以如果想要使用远程钩子,就必须把该钩子函数放到动态链接库中去。对于钩子所监视的消息类型来说,Windws一共提供了如下几种类型:如表1所示:表一、Windows消息类型消息类型常量标识值消息类型适用范围WH_CALLWNDPROC4 发给窗口的消息线程或系统WH_CALLWNDPROCRET12窗口返回的消息线程或系统WH_CBT5 窗口变化、焦点设定等消息线程或系统WH_DEBUG 9 是否执行其它Hook的Hook线程或系统WH_FOREGROUNDIDLE11前台程序空闲 线程或系统WH_GETMESSAGE 3 投放至消息队列中的消息线程或系统WH_JOURNALPLAYBACK1 将所记载的消息进行回放系统WH_JOURNALRECORD0 监视并记录输入消息系统WH_KEYBOARD 2 键盘消息 线程或系统WH_MOUSE7 鼠标消息线程或系统WH_MSGFILTER-1菜单滚动条、对话框消息 线程或系统WH_SHELL10 外壳程序的消息 线程或系统WH_SYSMSGFILTER6 所有线程的菜单滚动条、对话框消息系统 二、VB编程中钩子的实现 (一)钩子函数(HOOK Function)的格式。Hook Function实际上是一个函数,如果是系统钩子,该函数必须放在动态链接库中。该函数有一定的参数格式,在VB中如下:Private Function HookFunc(ByVal nCode As Long,ByVal wParam As Long,ByVal lParam As Long)As Long 其中,nCode代表是什么情况之下所产生的钩子,随钩子的不同而有不同组的可能值;参数wParam,lParam传回值包括了所监视到的消息内容,它随Hook所监视消息的种类和nCode的值不同而不同。对于用VB所设置的钩子函数,一般的框架形式如下:Private Function HookFunc(ByVal nCode As Long,ByVal wParam As Long,ByVal lParam As Long)As Long Select case of nCode case ncode《0:hookfunc=callnexthookex(hHookFunc,nCode,wParam,lParam) case值1:处理过程1:HookFunc=X1 case2:处理过程2:HookFunc=X1 …… end selectend Function 函数的传回值,如果消息要被处理,则传0,否则传1,吃掉消息。 (二)钩子的安装及执行。钩子的安装要用到几个API函数:可以使用API函数SetWindowsHookEx()把一个应用程序定义的钩子子程安装到钩子链表中。SetWindowsHookEx()函数的声明如下:Declare function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA"(ByVal idHook As Long,ByVal lpfn As Long,ByVal hmod As Long,ByVal dwThreadId As Long)As Long idHook值为它处理的消息类型;lpfn值为钩子子程序的地址指针。如果dwThreadId参数为0或是一个由别的进程创建的线程的标识,lpfn必须指向DLL中的钩子子程。除此以外,lpfn可以指向当前进程的一段钩子子程代码。hMod值为应用程序的句柄,标识包含lpfn所指的子程的DLL。如果dwThreadId标识当前进程创建的一个线程,而且子程代码位于当前进程,hMod必须为0。dwThreadId值为与安装的钩子子程相关联的线程的标识符,如果为0,钩子子程与所有的线程关联。钩子安装成功则返回钩子子程的句柄,失败返回0。 另外,一般应在钩子子程中调用CallNextHookEx()函数以执行钩子链表所指的下一个钩子子程,否则安装了别的钩子的应用程序就会收不到钩子通知,从而产生错误的结果。CallNextHookEx()函数的声明如下:Declare Function CallNextHookEx Lib"user32" Alias "CallNextHookEx"(ByVal hHook As Long,ByVal ncode As Lonog, ByVal wParam As Long,lParam As Any)As Long hHook值是SetWindowsHookEx()的传回值,nCode、wParam、lParam则是Hook函数中的三个参数。在程序终止之前,必须调用UnhookWindowsHookEx()函数释放与钩子关联的系统资源。UnhookWindowsEx()函数声明如下:Declare Function Unhook WindowsHookEx Lib "user32" Alias "Unhook WindowsHookEx(ByVal hHook As Long)As Long hHook为安装钩子时的返回值,即钩子子程的句柄。 (三)VB中钩子安装应注意的问题。lpfn参数是一个HookFunc的地址,VB规定必须将HookFunc代码放到标准的.BAS模块中,并以"Address Of HookFunc"传入,而不可以将其放到类模块中,也不能将其附加到窗体上。而对于RemoteHook来说,HookFunc应包含在动态链接库中,因此如果在VB中使用RemoteHook,则还要用到GetModuleHandle()、GetProcAddress()两个API函数,它们的声明如下:Declare Function GetModuleHandle Lib"kernel32" Alias "GetModuleHandleA"(ByVal lpModuleName As String)As LongDeclare Function GetProcAddress Lib "kernel32" Alias "GetProcAddress"(ByVal hModule As Long,ByVal lpProcName As String)As Long hmod值是含钩子过程的模块名柄,如果是LocalHook,该值可以是Null(VB中传0),而如果是RemoteHook,则可以使用GetModuleHandle("名称.dll")来传入。三、实例--键盘消息的拦截 在程序开发时常用的有对输入消息进行监视的键盘钩子,对于所监视到的消息应进行处理,下面对键盘钩子参数的具体内容组成进行说明: 如果有键盘消息(WM_KEYUP或WM_KEYDOWN)将被处理时,则系统调用键盘钩子。 nCode为HC_ACTION或HC_NOREMOVE,若小于0,则要求处理函数向下传递该消息。 wParam表示按键键码常数,A键到Z键与其ASCII码的相应值’A’到’Z’是一致的,例如按C键,则wParam值为67。 lParam与WM_KEYDOWN同,占四个字节,其包括的内容较多,其二进制结构如下: 0 1 …… 15 16 ………23 24 25 …… 28 29 30 31 0-15位(Key repeat count),键码重复次数。16-23位(Scan code),按键的扫描码。24位(Extended_Key flag),扩展键(功能键、数字小键盘上的键)标志,为1则是扩展键,否则为0。25-28位被保留。29位(Context Code),状态描述码,ALT键被按下则为1,否则为0。30位(Previouskey_stateflag)指定先前的键状态,如果消息被发出之前键处于按下状态,则为1;键处于释放状态则为0。31位(Transiton_stateflag)状态转换标志,如果键是被按下值为1,如果键被放开值为0。 本例中的钩子用来监视并记录应用程序中的按键信息。在程序中,ALT+F4组合键被屏蔽。下面是部分代码:Public hHook as LongPrivate Sub Form_Load()′程序启动时安装钩子hHook=SetWindowsHookEx(2,Address of MyKBHook,0,App.ThreadID)End Sub′具体的钩子程序,本例中该过程被包含在Module1中Public Function MyKBHook(ByVal nCode As Long,ByVal wParam As Long,ByVal lParam As Long)As LongIf nCode》=0 thenOpen "C:\Keyfile.txt" For Append As #1 ’将键盘的操作记录在Keyfile.txt文件之中’记录所操作的键、操作时间、日期操作时的按键状态,用16进制记录Write #1,wParam,Hex(lParam),Date,timeClose #1MyKBHook=0 ’表示要处理这个消息’屏蔽ALT+F4组合键if wParam=115 And(lParam And&H20000000)《》0 Thenif(lParam And &HC000000)=0 Then ’是否进行ALT+F4操作MyHBHook=1 ’钩子吃掉这个消息End if End ifEnd if Call CallNextHookEx(hHook,nCode,wParam,lParam)’将消息传给下一个钩子End Function’程序退出时卸载钩子Private Sub Form_Unload(Cancel As Interger)Call Unhook WindowsHookEx(hHook)End Sub 四、总结 钩子处理程序是Windows高级编程技术,一般程序员都使用VC++等程序设计工具实现,本文表明,对于VB来说,虽然很多人认为是非专业的设计工具,但实现钩子这样的高级技术也是非常方便的。另外在使用钩子时应注意到,钩子虽然功能比较强,但如果使用不当将会严重影响系统的效率,所以要尽量避免使用系统钩子,并且在不用钩子时,应将钩子及时卸载。这些当中出现了矛盾祝你早日解决

程序开发中模型又是什么意思呢

模型就是在程序开发中定义来开发的标准内容。这个库提供了 Lu a 程序调试接口()的功能。 其中一些函数违反了 Lu a 代码的基本假定 (例如,不会从函数之外访问函数的局部变量; 用户数据的元表不会被 Lu a 代码修改; L ua 程序不会崩溃), 因此它们有可能危害到其它代码的安全性。 此外,库里的一些函数可能运行的很慢。这个库里的所有函数都提供在表 debug 内。 所有操作线程的函数,可选的第一个参数都是针对的线程。 默认值永远是当前线程。进入一个用户交互模式,运行用户输入的每个字符串。 使用简单的命令以及其它调试设置,用户可以检阅全局变量和局部变量, 改变变量的值,计算一些表达式,等等。 输入一行仅包含 cont 的字符串将结束这个函数, 这样调用者就可以继续向下运行。注意,debug.debug 输入的命令在文法上并没有内嵌到任何函数中, 因此不能直接去访问局部变量。返回三个表示线程钩子设置的值: 当前钩子函数,当前钩子掩码,当前钩子计数 (debug.sethook 设置的那些)。返回关于一个函数信息的表。 你可以直接提供该函数, 也可以用一个数字 f 表示该函数。 数字 f 表示运行在指定线程的调用栈对应层次上的函数: 0 层表示当前函数(getinfo 自身); 1 层表示调用 getinfo 的函数 (除非是尾调用,这种情况不计入栈);等等。 如果 f 是一个比活动函数数量还大的数字, getinfo 返回 nil。只有字符串 what 中有描述要填充哪些项, 返回的表可以包含 l ua_getinfo 能返回的所有项。 what 默认是返回提供的除合法行号表外的所有信息。 对于选项 ’f’ ,会在可能的情况下,增加 func 域保存函数自身。 对于选项 ’L’ ,会在可能的情况下,增加 activelines 域保存合法行号表。例如,表达式 debug.getinfo(1,"n") 返回带有当前函数名字信息的表(如果找的到名字的话), 表达式 debug.getinfo(print) 返回关于 print 函数的 包含有所有能提供信息此函数返回在栈的 f 层处函数的索引为 local 的局部变量 的名字和值。 这个函数不仅用于访问显式定义的局部变量,也包括形参、临时变量等。第一个形参或是定义的第一个局部变量的索引为 1 , 然后遵循在代码中定义次序,以次类推。 其中只计算函数当前作用域的活动变量。 负索引指可变参数; -1 指第一个可变参数。 如果该索引处没有变量,函数返回 nil。 若指定的层次越界,抛出错误。 (你可以调用 debug.getinfo 来检查层次是否合法。)以 ’(’ (开括号)打头的变量名表示没有名字的变量 (比如是循环控制用到的控制变量, 或是去除了调试信息的代码块)。参数 f 也可以是一个函数。 这种情况下,getlocal 仅返回函数形参的名字。

rt thread钩子函数怎么用

RTT在空闲的时候可以使用钩子函数执行些简单的任务,例如LED闪烁之类的程序,利用这个功能我们可以做个工作状态指示灯要使用钩子必须在配置里打开钩子的配置,在rt-config.h里添加HOOK宏定义(如果没有的话)#defineRT_USING_HOOK然后在应用程序里设置钩子函数#ifdefRT_USING_HOOKrt_thread_idle_sethook(rt_hw_led_flash);#endif下面就是该怎样实现这个函数了voidrt_hw_led_flash(void){rt_uint32_ti;rt_hw_led_init();while(1){for(i=0;i《2700000;i++);//500msGPIO_WriteBit(state_led_gpio,state_led_pin,(BitAction)(1-GPIO_ReadOutputDataBit(state_led_gpio,state_led_pin)));}}这样写过之后,在系统空闲的时候就会执行这个函数,当然,如果系统繁忙的时候是不会进入这个idel任务的,不过如果系统一直处于繁忙的状态就是有问题了另外一点记住,在这个函数里不能调用系统提供的使线程挂起的函数例如:rtthreaddelay,rtsemtake等while(1){for(i=0;i《2700000;i++);//500msGPIO_WriteBit(state_led_gpio,state_led_pin,(BitAction)(1-GPIO_ReadOutputDataBit(state_led_gpio,state_led_pin)));}关键是这个while(1)没跳出的,所以全部在这里执行了。好像还有同学对其中一些并不是完全清楚,所以继续解析下,当做结贴:1.idle线程是系统中最后一道防线,它将是系统中,如果无其他事可干时的最后能够运行的线程。--所以idle线程不应该被阻塞。如果你有自己的线程能够成为这最后一道防线,那么这个限制将不存在。而对于一些原来系统中放在idle线程中做的工作(例如原来的最终的线程删除动作),在0.4.x中,可以手工调用rt_thread_idle_excute函数来执行。2.当系统空闲的时候,idle线程将执行这个钩子函数。假设钩子函数一次运行会执行1ms,如果idle线程有机会运行200ms,那么钩子函数将被调用200次。3.钩子函数运行时,不应该把idle线程总是纠结在这个函数中运行,必须要让idle线程有机会去运行rt_thread_idle_excute函数(因为还有一些事情等待idle线程去处理)。--所以在钩子函数中,不应该使用while(1);的方式。while(1){if(indicator==RT_TRUE){....dosomething}elsedelay(20ms)}这样那20ms会执行idle线程吧,问题是idle20ms够吗,如果我改20ms为5ms呢?如果没有其他线程处理事务,将转换到idle线程去,通常idle线程中的系统任务会在0.xxms以内执行完毕

vb钩子 钩其他线程 的键盘事件 总是内存出错

研究到大半夜得出结论是VB做不到这一点,具体原因入下:VB可以写出标准DLL,用一些特殊的插件或者自己编写一些小工具可以做到这一点,百度上我也回答过,验证是可行的,你写的注入代码除了我前面提到过的那一点之外,也都是正确的,但是VB确实每次都会让目标进程崩溃,用IDA查了一下,发现总是死在这个函数里vbaSetSystemError ,在CSDN上查了一下,这个函数是VB编译时候自动加上的,它会检查每次调用API的返回值,但是因为我们是在DLL内部掉API,VB本身设计时没考虑这一点,所以到这个函数里头,它就崩溃了,而我们也没办法阻止我们自己的代码调用这个函数,所以这个只能说是VB自身的问题,确实没办法用VB做线程钩子,只能用VC去做。查了一下,如何去掉这个函数网上也提了一些说法,不过整体上感觉过于复杂,不值得使用,其中主要的手段就是修改汇编指令,把这个函数从内存里改掉,这个办法通用性差,不值得使用,别的方法就不太有效了。所以最后总结下来VB就是不支持这么做,没有办法了============================Private Declare Function CallNextHookEx Lib "user32" (ByVal hhook As Long, ByVal ncode As Long, ByVal wParam As Long, lParam As Any) As Long这个API是从API浏览器上拷贝下来的吧?这么干是不行的。。必须这样:Private Declare Function CallNextHookEx Lib "user32" (ByVal hhook As Long, ByVal ncode As Long, ByVal wParam As Long, ByVal lParam As Long) As Long 你代码里没有callnexthook,这样恐怕不行吧

关于本次线程钩子和c# 程序钩子的问题的问题分享到这里就结束了,如果解决了您的问题,我们非常高兴。

c# 程序钩子的问题?C#调用C++动态库的时候,如果捕获C++线程里的错误

本文编辑:admin
Copyright © 2022 All Rights Reserved 威海上格软件有限公司 版权所有

鲁ICP备20007704号

Thanks for visiting my site.