Java运用JNI调用dll实现屏蔽系统热键中遇到某些问题记录

首先,来源是http://blog.csdn.net/uikoo9/article/details/7454209
        以下是照抄原文:

【前言】

这几天用Java做个锁屏软件需要屏蔽系统热键,就是Win+D,Ctrl+Alt+Del等,

网上找了好多,发现这篇文章:

java运用jni调用dll(含源码)实现屏蔽系统热键和任务栏

http://hi.baidu.com/nowgame/blog/item/4530e11f20f289fee1fe0ba1.html

但是对于只懂Java的人无疑是一种折磨,

好不容易跟着文章做下来又报错,总之很是痛苦,

今天又看了篇文章:

http://wenku.baidu.com/view/51bf0d96daef5ef7ba0d3c54.html

结合这两篇文章终于实现了Java屏蔽系统热键了。

 

下面写出来,图文结合,宣泄一下这几天的郁闷。

 

【说明】

1.本文都是在eclipse下开发的,dos下可以自己尝试。

2.以下源码都是上面第一篇文章中的

 

【1】编写Java文件,编译出class文件,javah出.h文件

——Java源码

[html] view plaincopy
  1. package com.uikoo9.JLocker;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileOutputStream;  
  5. import java.io.InputStream;  
  6.   
  7. /**   
  8. *   
  9. * @author Administrator   
  10. */   
  11. public class ShieldHotKey {   
  12.     static{   
  13.         //下面这部分是为了增加灵活性,dll可以放到jar包中  
  14.         try  
  15.         {  
  16.             File file = File.createTempFile("shieldHK", ".dll");  
  17.             FileOutputStream fout = new FileOutputStream(file);  
  18.             InputStream in = ShieldHotKey.class.getResourceAsStream("shieldHK.dll");  
  19.               
  20.             byte[] b = new byte[1024];  
  21.             int len = 0;   
  22.             while((len = in.read(b)) != -1){  
  23.                 fout.write(b, 0, len);  
  24.             }  
  25.               
  26.             fout.flush();  
  27.             in.close();  
  28.             fout.close();  
  29.               
  30.             System.load(file.getAbsolutePath());  
  31.         }   
  32.         catch (Exception e) {}  
  33.           
  34. //        System.load("D:/shieldHK.dll");   
  35.     }   
  36.     public static native void Attach();//启动屏蔽   
  37.     public static native void Detach();//关闭屏蔽   
  38. }   

说明:1.类ShieldHotKey是建在com.uikoo9.JLocker包下的,

            2.将原来的System.load("D:/shieldHK.dll");改写是为了增加灵活性。

 ——Eclipse自动会生成.class文件,找到它,如图:

 

——cmd下,到这个bin的这一层,输入已下命令生成.h文件:

生成的.h文件:

 

 

【2】VC下生成dll文件

——在VC下新建一个dll工程,具体见下图:

说明:1.选择Win32 Dynamic-Link Library工程,

            2.工程名就是将来生成的dll名称,但是无关紧要可以更改。

            3.点确定之后选一个空的dll工程,完成。

 

——找到这个工程在电脑上的地方:

 

——将以下三个.h文件都复制到上面工程文件夹中

第一个.h文件:com_uikoo9_JLocker_ShieldHotKey.h,就是刚才生成的.h文件;

第二个.h文件:jni.h,在jdk下include文件夹下;

第三个.h文件:jni_md.h,在jdk下include文件夹下的win32文件夹中;

 

——VC中导入.h文件

在FileView窗口中,右键Header Files文件夹选添加文件到目录,将上面的三个文件都导入。

 

——VC中编写cpp文件

VC中新建——文件——C++ Source File ——文件名随便起

代码如下:

  1. /* Replace "dll.h" with the name of your header */   
  2. #include "shieldHK.h"   
  3. #define _WIN32_WINNT 0x0500 //Use WH_KEYBOARD_LL   
  4. #include <windows.h>   
  5. #include <stdio.h>   
  6.   
  7. //SAS window句柄   
  8. HWND hSASWnd = NULL;   
  9. //原有SAS window回调函数地址   
  10. FARPROC FOldProc = NULL;   
  11. //起屏蔽作用的新SAS window回调函数   
  12. LRESULT CALLBACK SASWindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam);   
  13. //枚举所有窗体句柄的回调函数   
  14. BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lParam);   
  15. //Dll所创建线程的句柄   
  16. HANDLE hThread = NULL;   
  17. //Dll所创建线程的ID   
  18. DWORD dwThreadId = 0;   
  19. //Dll所创建线程的线程函数   
  20. DWORD WINAPI ThreadFunc();   
  21. //_H钩子句柄   
  22. HHOOK hHook = NULL;   
  23. //_H低级键盘钩子回调函数   
  24. LRESULT CALLBACK KeyboardProc(int,WPARAM,LPARAM);   
  25. //对外输出字符串   
  26. char szOutput[36];   
  27.   
  28. BOOL APIENTRY Attach()   
  29. {   
  30.      switch(DLL_PROCESS_ATTACH)   
  31.      {   
  32.       case DLL_PROCESS_ATTACH:   
  33.       sprintf(szOutput,"Dll成功加载于 %d 号进程。",GetCurrentProcessId());   
  34.       OutputDebugString(szOutput);   
  35.       //创建更替SAS window回调函数的线程   
  36.       if(FOldProc == NULL)   
  37.                   hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadFunc,NULL,0,&dwThreadId);   
  38.       break;   
  39.       case DLL_PROCESS_DETACH:   
  40.       sprintf(szOutput,"Dll成功卸载。",GetCurrentProcessId());   
  41.       //MessageBox(NULL, szOutput, "ZZ", MB_ICONINFORMATION | MB_OK);   
  42.       OutputDebugString(szOutput);   
  43.       //恢复原有SAS window的回调函数   
  44.       if(FOldProc != NULL)   
  45.                 SetWindowLong(hSASWnd,GWL_WNDPROC,long(FOldProc));              
  46.       //_H卸载低级键盘钩子   
  47.       if(hHook != NULL)   
  48.       {   
  49.        if(!UnhookWindowsHookEx(hHook))   
  50.        {   
  51.         OutputDebugString("Unhook failed..");   
  52.         //__leave;   
  53.         break;   
  54.        }   
  55.        OutputDebugString("键盘钩子成功取消");   
  56.       }   
  57.       TerminateThread(hThread,1);   
  58.       CloseHandle(hThread);   
  59.       break;   
  60.       case DLL_THREAD_ATTACH:   
  61.       break;   
  62.       case DLL_THREAD_DETACH:   
  63.       break;   
  64.      }   
  65.      return TRUE;   
  66. }   
  67.   
  68. BOOL APIENTRY Detach()   
  69. {   
  70.      switch(DLL_PROCESS_DETACH)   
  71.      {   
  72.       case DLL_PROCESS_ATTACH:   
  73.       sprintf(szOutput,"Dll成功加载于 %d 号进程。",GetCurrentProcessId());   
  74.       OutputDebugString(szOutput);   
  75.       //创建更替SAS window回调函数的线程   
  76.       if(FOldProc == NULL)   
  77.                   hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadFunc,NULL,0,&dwThreadId);   
  78.       break;   
  79.       case DLL_PROCESS_DETACH:   
  80.       sprintf(szOutput,"Dll成功卸载。",GetCurrentProcessId());   
  81.       //MessageBox(NULL, szOutput, "ZZ", MB_ICONINFORMATION | MB_OK);   
  82.       OutputDebugString(szOutput);   
  83.       //恢复原有SAS window的回调函数   
  84.       if(FOldProc != NULL)   
  85.                 SetWindowLong(hSASWnd,GWL_WNDPROC,long(FOldProc));              
  86.       //_H卸载低级键盘钩子   
  87.       if(hHook != NULL)   
  88.       {   
  89.        if(!UnhookWindowsHookEx(hHook))   
  90.        {   
  91.         OutputDebugString("Unhook failed..");   
  92.         //__leave;   
  93.         break;   
  94.        }   
  95.        OutputDebugString("键盘钩子成功取消");   
  96.       }   
  97.       TerminateThread(hThread,1);   
  98.       CloseHandle(hThread);   
  99.       break;   
  100.       case DLL_THREAD_ATTACH:   
  101.       break;   
  102.       case DLL_THREAD_DETACH:   
  103.       break;   
  104.      }   
  105.      return TRUE;   
  106. }   
  107.   
  108. //Dll所创建线程的线程函数   
  109. DWORD WINAPI ThreadFunc()   
  110. {   
  111.       //打开Winlogon桌面   
  112.       HDESK hDesk = OpenDesktop("Winlogon",0,FALSE,MAXIMUM_ALLOWED);   
  113.       //枚举桌面所有窗体   
  114.       EnumDesktopWindows(hDesk,(WNDENUMPROC)EnumWindowsProc,0);   
  115.       //修改SAS window的回调函数   
  116.       if(hSASWnd != NULL)   
  117.       {   
  118.        FOldProc = (FARPROC)SetWindowLong(hSASWnd,GWL_WNDPROC,long(SASWindowProc));   
  119.       }   
  120.       CloseHandle(hDesk);   
  121.       //_H同一桌面上进程之间只能发送窗口消息。无法跨进程与其他桌面发送它们。   
  122.       //_H同样,Windows消息是限制应用程序定义挂钩。   
  123.       //_H特定桌面中运行的进程挂钩过程将〈〈只获得针对同一桌面上创建窗口消息。〉〉   
  124.       //_H详见http://support.microsoft.com/kb/171890/zh-cn ;  
  125.       //_H所以,这里必须设置钩子所在线程的桌面为Default桌面   
  126.       //_H才能使得钩子所在线程能接收到Default桌面的消息   
  127.       hDesk = OpenDesktop("Default",0,FALSE,MAXIMUM_ALLOWED);   
  128.       SetThreadDesktop(hDesk);   
  129.       CloseHandle(hDesk);   
  130.       //_H设置低级键盘钩子,屏蔽非SAS window的热键   
  131.       //_H需要#define _WIN32_WINNT 0x0500   
  132.       hHook = SetWindowsHookEx(WH_KEYBOARD_LL,KeyboardProc,GetModuleHandle(NULL),0);   
  133.       if (hHook == NULL)   
  134.       {   
  135.        OutputDebugString("Set hook failed..");   
  136.        //__leave;   
  137.        return 1;   
  138.       }   
  139.       OutputDebugString("键盘钩子成功设置");   
  140.       //_H在非GUI线程中使用消息钩子必须主动接收并分发收到的消息   
  141.       MSG msg;   
  142.       while(GetMessage(&msg, NULL, 0, 0))   
  143.       {   
  144.        TranslateMessage(&msg);   
  145.        DispatchMessage(&msg);   
  146.       }   
  147.       return 1;   
  148. }   
  149.   
  150.   
  151. //枚举所有窗体句柄的回调函数   
  152. BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lParam)   
  153. {   
  154.      char ClassBuf[128];   
  155.      //获得当前窗体的显示文本   
  156.      GetWindowText(hwnd,ClassBuf,sizeof(ClassBuf));   
  157.      //在"Winlogon"桌面中查询窗口"SAS window"。   
  158.      if(strstr(ClassBuf,"SAS window")!=NULL)   
  159.      {   
  160.       //返回SAS window句柄   
  161.       hSASWnd = hwnd;   
  162.       return FALSE;   
  163.      }   
  164.      return TRUE;   
  165. }   
  166.   
  167.   
  168. //起屏蔽作用的新SAS window回调函数   
  169. LRESULT CALLBACK SASWindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)   
  170. {   
  171.         if(uMsg == WM_HOTKEY)   
  172.         {   
  173.          //屏蔽所有WM_HOTKEY消息   
  174.          OutputDebugString("All SAS window's hotkeys are disabled");   
  175.          return 1;   
  176.          WORD wKey = HIWORD(lParam);   
  177.          WORD wModifier = LOWORD(lParam);   
  178.          bool IsCtrlDown = ((wModifier & VK_CONTROL) != 0);   
  179.          bool IsAltDown = ((wModifier & VK_MENU) != 0);   
  180.          bool IsShiftDown = ((wModifier & VK_SHIFT) != 0);   
  181.          //Ctrl + Alt + Del组合键   
  182.          if(IsCtrlDown && IsAltDown && wKey == VK_DELETE)   
  183.          {   
  184.           return 1; //屏蔽   
  185.          }   
  186.          //Ctrl + Shift + Esc组合键,这个组合键将显示任务管理器,可根据需要是否屏蔽。   
  187.          else if(IsCtrlDown && IsShiftDown && wKey == VK_ESCAPE)   
  188.          {   
  189.           // Do nothing   
  190.          }   
  191.         }   
  192.         return CallWindowProc((WNDPROC)FOldProc,hwnd,uMsg,wParam,lParam);   
  193. }   
  194.   
  195.   
  196. //_H低级键盘钩子回调函数   
  197. LRESULT CALLBACK KeyboardProc(int nCode,WPARAM wParam,LPARAM lParam)   
  198. {   
  199.         if (nCode == HC_ACTION)   
  200.         {   
  201.          switch (wParam)   
  202.          {   
  203.           case WM_KEYDOWN: case WM_SYSKEYDOWN:   
  204.           //case WM_KEYUP:    case WM_SYSKEYUP:   
  205.           PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT) lParam;   
  206.           if (p->vkCode == VK_F12)   
  207.           {   
  208.            //实现模拟按键代码   
  209.            MessageBox(GetForegroundWindow(),"I'm in position..","ZZ",MB_OK);   
  210.           }   
  211.           //屏蔽ALT+TAB   
  212.           else if ((p->vkCode == VK_TAB) && ((p->flags & LLKHF_ALTDOWN) != 0))   
  213.           {   
  214.            OutputDebugString("ALT+TAB is disabled");   
  215.            return 1;   
  216.           }   
  217.           //屏蔽ALT+ESC   
  218.           else if ((p->vkCode == VK_ESCAPE) && ((p->flags & LLKHF_ALTDOWN) != 0))   
  219.           {   
  220.            OutputDebugString("ALT+ESC is disabled");   
  221.            return 1;   
  222.           }   
  223.           //屏蔽CTRL+ESC   
  224.           else if ((p->vkCode == VK_ESCAPE) && ((GetKeyState(VK_CONTROL) & 0x8000) != 0))   
  225.           {   
  226.            OutputDebugString("CTRL+ESC is disabled");   
  227.            return 1;   
  228.           }   
  229.           //屏蔽CTRL+SHIFT+ESC,(SAS window中也已屏蔽)   
  230.           else if ((p->vkCode == VK_ESCAPE) &&   
  231.           ((GetKeyState(VK_CONTROL) & 0x8000) != 0) &&   
  232.           ((GetKeyState(VK_SHIFT) & 0x8000) != 0))   
  233.           {   
  234.            OutputDebugString("CTRL+SHIFT+ESC is disabled");   
  235.            return 1;   
  236.           }   
  237.           //屏蔽ALT+F4   
  238.           else if ((p->vkCode == VK_F4) && ((p->flags & LLKHF_ALTDOWN) != 0))   
  239.           {   
  240.            OutputDebugString("ALT+F4 is disabled");   
  241.            return 1;   
  242.           }   
  243.           //屏蔽左右windows键   
  244.           else if (p->vkCode == VK_LWIN || p->vkCode == VK_RWIN)   
  245.           {   
  246.            OutputDebugString("windows key is disabled");   
  247.            return 1;   
  248.           }   
  249.           //此处无法屏蔽CTRL+ALT+DEL,已在SAS window中屏蔽   
  250.           else if ((p->vkCode == VK_DELETE) &&   
  251.           ((GetKeyState(VK_CONTROL) & 0x8000) != 0) &&   
  252.           ((GetKeyState(VK_MENU) & 0x8000) != 0 ))   
  253.                                  return 1;   
  254.           break;   
  255.          }   
  256.         }   
  257.         return CallNextHookEx(hHook,nCode,wParam,lParam);   
  258. }   
  259.   
  260. JNIEXPORT void JNICALL Java_shieldHK_ShieldHotKey_Attach   
  261. (JNIEnv *env, jclass obj){   
  262. Attach();   
  263. }   
  264.   
  265. JNIEXPORT void JNICALL Java_shieldHK_ShieldHotKey_Detach   
  266. (JNIEnv *env, jclass obj){   
  267. Detach();   
  268. }   


 

——对几个文件的修改

将com_uikoo9_JLocker_ShieldHotKey.h中的#include <jni.h>改为#include "jni.h"

将test.cpp中的#include "shieldHK.h"改为#include "com_uikoo9_JLocker_ShieldHotKey.h",也就是上面的.h文件

test.cpp的末尾两个方法的代码如下:

  1. JNIEXPORT void JNICALL Java_shieldHK_ShieldHotKey_Attach   
  2. (JNIEnv *env, jclass obj){   
  3. Attach();   
  4. }   
  5.   
  6. JNIEXPORT void JNICALL Java_shieldHK_ShieldHotKey_Detach   
  7. (JNIEnv *env, jclass obj){   
  8. Detach();   
  9. }   

 

com_uikoo9_JLocker_ShieldHotKey.h中的两个方法代码如下:

  1. JNIEXPORT void JNICALL Java_com_uikoo9_JLocker_ShieldHotKey_Attach  
  2.   (JNIEnv *, jclass);  
  3.   
  4. /* 
  5.  * Class:     com_uikoo9_JLocker_ShieldHotKey 
  6.  * Method:    Detach 
  7.  * Signature: ()V 
  8.  */  
  9. JNIEXPORT void JNICALL Java_com_uikoo9_JLocker_ShieldHotKey_Detach  
  10.   (JNIEnv *, jclass);  


将test.cpp中的两个方法名改为com_uikoo9_JLocker_ShieldHotKey.h中两个对应的方法名,

否则在eclipse中会报错。

 

——VC中编译cpp文件生成dll文件

对test.cpp先Compile再Build,生成dll文件

生成的dll文件在vc项目的debug文件夹中

 

【3】Eclipse中使用

——将testdll.dll文件改名为shieldHK.dll,复制到eclipse中java代码处,如图:

 

——编写test.java测验一下能行不,代码:

[java] view plaincopy
  1. package com.uikoo9.JLocker;  
  2.   
  3. public class Test  
  4. {  
  5.     public static void main(String[] args)  
  6.     {  
  7.         try  
  8.         {  
  9.             ShieldHotKey.Attach();  
  10.             Thread.sleep(5000);//5秒内键盘热键都被屏蔽了。  
  11.         }   
  12.         catch (Exception e) {}  
  13.     }  
  14. }  


 

【后记】

成功了,

从想到到实现,过程是痛苦的,结果是欣喜的,

有不懂得可以留言。

 

感谢上面两篇文章!
        照抄结束,以下是在这过程中遇到的一些问题
        首先关于win8.1系统使用VC++6.0程序崩溃问题,其实解决办法很简单找到vc++6.0安装文件夹,然后按以下路径进入
Microsoft Visual Studio→Common→MSDev98→Bin然后在Bin中找到 MSDEV.EXE并将其重命名为MSDEV3.EXE,然后再右键属性,以兼容模式运行这个程序,如下图
 图片
 

        (顺便吐槽下,反正我就是不明白了这是什么玄学机制,你在背后只能加个数字3,如果你加其他数字的话,呵呵,依旧崩溃,所以我把这个归类于见鬼类别 )
        在能运行VC++6.0后,你是不是觉得就没问题了?
        图样图森破啊!
        你以为这就结束了么?真的无法理解vc6.0的兼容问题
        你在打开程序后,当你右键想要添加文件到目录的时候,那么你会发现一个很蛋疼的问题,那就是

图片

 所以,我又苦逼的去寻找解决办法了,幸好这个问题不只有我遇到,所以解决办法也找到了,就是利用FileTool去设置文件导入热键就OK(具体:http://blog.csdn.net/yuebinghaoyuan/article/details/8006479
        解决完文件导入问题后,你就可以按开头文章的步骤去编写dll文件了(但是还有一个我不确定的东西,可能会导致你在java中应用dll文件时发生
java.lang.UnsatisfiedLinkError错误!这个在后面我会提到 
        当你编写完dll文件导出后,如果你是准备将其运用在32位的java上,那差不多就没问题了,但是!!!!!我是准备运用在64位的java之上,所以这时候我苦逼的发现,vc6.0的编译环境只有32位,换句话说只能编译出32位的dll动态库(其实还有另外的解决办法,那就是用vc6.0+sdk2003来模拟64位编译环境,但是微软早已经停止提供sdk2003的下载服务了,我唯一在这里:
http://demon.tw/software/microsoft-platform-sdk-febrary-2003.html 找到了sdk2003的下载,但是不知道什么原因,在我这台机子上安装不了sdk2003,所以这个方法对我无效TAT,顺带吐槽下,基本我现在对VC6.0的看法是,这是一个已经被别人遗弃的孩子,但是大学校园依旧把他当作家里的顶梁柱看(顶梁柱?应该可以这么比喻吧)) 所以说,这时候,我毅然决然的决定。。。。。。换个vc工具。。。。
        然后百度了下 ,发现貌似vc2005以上的版本都支持64位的编译环境,而我正好有下vs2010express版,所以就安装了,满怀我能解决的信心打开vc2010express折腾了半天之后,我蛋疼的发现,或许是因为这是学习版,所以,它并没有64位的编译环境设置(眼泪流了下来。。。),所以我有苦逼的在网络上找到了
Visual Studio 2010 Ultimate,也就是最终版,才配置了64位的编译环境。。。。。。
        (ps:顺带一提,由于vc2010默认使用的Unicode字符集,所以字符会报错,这时候点击项目→属性页→配置属性→常规,找到字符集选项,下拉菜单,选择使用多字节字符集,点击应用就可以解决了。)
        编译环境设置完,我们这时候就可以开始编写dll动态库了(其实就是ctrl+c和ctrl+v),编写完之后,预编译的时候,你就会发现另一个蛋疼的问题了(warn不用管,没影响),这时候系统会提示

error C2065: “GWL_WNDPROC”: 未声明的标识符  的错误。(明明在32位编译环境没问题,为毛到x64上就出事了,而且我并没有学过C++关于这个的部分,你叫我怎么改错! 

        于是,我又去请求度娘,然而度娘并没有回应我的呼唤,我觉得可能是因为没几个人碰到过这些问题吧,于是我转投必应娘的怀抱,在MSDN论坛的某个提问上找到了解决办法(那哥们遇到的问题跟我差不多,都是调用win32的声明,顺便挂地址:https://social.msdn.microsoft.com/Forums/vstudio/en-US/c62b1bf5-6a03-4c66-b24b-ea9fec3f2695/error-c2065-gwluserdata-undeclared-identifier?forum=vcgeneral),还有个很蛋疼的问题是,回答的人的代码打错了,正确的应该是GWLP_而不是GWPL_所以这也导致了我又蛋疼的遭遇报错。。。(
        在历尽无数困难后,我终于编译得到了一个64位的dll动态库,于是我高高兴兴的扔进myeclipse里去运行了,然后,
myeclipse就开始报错了,错误是 java.lang.UnsatisfiedLinkError ,可以简单理解为是找不到库文件,明明我已经把库放进去了,为什么又找不到了?

查了点东西后,得到解决办法,编译的时候使用release编译,而不是使用debug编译,具体是为什么,我没查到,所以这里就只提供解决办法(因为vc6.0我使用的是debug编译得到的dll,但是因为用不了,所以就没用了,所以就不清楚vc6.0的能不能用)
          release重新编译后dll,放入myeclipse中,嗯,能用了,能运行了,然后我满怀欣喜的试用了下,嗯,alt+tab禁用了,win键禁用了,嗯ctrl+alt+del。。。。。。诶,为什么任务管理器跳了出来!卧槽!,然后回顾了作者的文章,发现,作者也说了xp下可以屏蔽所有按键“所以在win7及以上的版本,因为系统调用优先度的问题,并不可以屏蔽调用任务管理器的热键
图片图片
        所以说,我之前花费了那么多时间,究竟意义何在啊!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!








什么都不多说了,让我一个人静静。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值