为什么这还是入门呢?因为前面的我们什么都没学会,只是学会了建立一个dll项目,并生成了一个Firefox可以识别dll,在测试页面中不会弹出需要下载插件的错误。而任何功能都没有实现。所以这里才是真正的入门。
本文随着作者本人的学习进度逐渐深入,难免有不周全之处,请看到本文的朋友帮忙指出有误的地方。

插件窗口在哪里

入门篇暂时只讲述一个问题,根据我的理解,plugin创建时,浏览器会创建一个子窗口,作为plugin的窗口(对于有窗口的plugin)。但是一直不知道这个子窗口怎么获取。仔细看上面的例子才发现,plugin类实现的不光是Plugin.cpp中的那几个函数,最重要的一个函数实现在Plugin.h中:NPBool init(NPWindow* pNPWindow) { m_bInitialized = TRUE; return TRUE;}
这个函数没有实现什么功能,但是注意这个函数的参数NPWindow*。啊哈!这个不就是我苦苦寻求的plugin窗口么?!!!
为了确认这一点,我就在这里下一个断点来看看,我觉得这个函数放在头文件中简直就是个错误,于是我将头文件中实现的三个函数都放到了.cpp文件中去实现了。
修改之后的头文件为:

[cpp] view plain copy      
  1. #pragma once
  2. #include "pluginbase.h"
  3. class Plugin :
  4. public nsPluginInstanceBase
  5. {
  6. private:
  7. NPP m_pNPInstance;
  8. NPBool m_bInitialized;
  9. public:
  10. Plugin(NPP pNPInstance);
  11. ~Plugin();
  12. NPBool init(NPWindow* pNPWindow);
  13. void shut();
  14. NPBool isInitialized();
  15. };

移动到Plugin.cpp文件中实现的三个函数为:

[cpp] view plain copy      
  1. NPBool Plugin::init(NPWindow* pNPWindow)
  2. {
  3. m_bInitialized = TRUE;
  4. return TRUE;
  5. }
  6. void Plugin::shut()
  7. {
  8. m_bInitialized = FALSE;
  9. }
  10. NPBool Plugin::isInitialized()
  11. {
  12. return m_bInitialized;
  13. }

这样再次生成这个插件,下面以此为例展示一下如何对插件进行调试。

插件调试

首先在程序中下好断点:

然后打开网页,接着附加到进程:

注意plugin-container可能不止一个,选择的时候要根据其路径看准用来调试的那个。
附加到进程之后,在浏览器中刷新含有要调试的插件的页面。程序就在下断点的地方断下了:

可以看到pNPWindow传递过来的参数。这就是调试插件的具体操作了。

使用窗口句柄

从上面的调试过程可以发现,pNPWindow的window成员应该就是插件窗口的句柄了,根据相关资料进一步得知,对于windowed的plugin确实是窗口的句柄,而对于winless的plugin,window成员是一个HDC。因此,如果是winless的plugin,可以直接将pNPWindow->window强制转换为HDC然后在窗口中进行绘制、输出内容。
插件分为winless和windowed的,其主要区别是winless的插件窗口不是真正意义上的窗口只是网页的一部分,而windowed的插件窗口是浏览器的一个子窗口。由于winless的插件没有窗口所以其事件处理有一个专门的函数NPP_HandleEvent来对窗口的事件进行处理。对于winless的插件,事件响应函数就需要继承nsPluginInstanceBase类并实现函数HandleEvent;而对于windowed 的插件,插件窗口完全由插件控制,因此需要根据这个窗口句柄自行在插件中实现各种功能,包括事件消息响应等等。下面分别探讨在winless的插件和windowed的插件中如何使用窗口句柄。

winless插件

要创建winless的插件需要在新实例创建的时候指定为winless的,否则插件会默认创建为windowed的。修改NS_NewPluginInstance函数(该函数是我们自己实现的,在Plugin.cpp中,尽量不要修改np_entry.cpp、npn_gate.cpp、npp_gate.cpp这几个文件更不用说npapi.h了)。

[cpp] view plain copy      
  1. nsPluginInstanceBase * NS_NewPluginInstance(nsPluginCreateData * aCreateDataStruct)
  2. {
  3. if(!aCreateDataStruct)
  4. return NULL;
  5. Plugin * plugin = new Plugin(aCreateDataStruct->instance);
  6. BOOL bWindowed = FALSE;
  7. NPN_SetValue(aCreateDataStruct->instance, NPPVpluginWindowBool, (void *)bWindowed);//winless插件需要在这里指出,默认为windowed
  8. return plugin;
  9. }

用NPN_SetValue指定我们要创建的插件是winless的。上面的写法是中规中矩的写法,如果图省事可以写成NPN_SetValue(aCreateDataStruct->instance, NPPVpluginWindowBool, NULL);
本例参考sdk中sample目录下的winless例子来写的,有必要的话你可以参考那个例子。下面要实现一个功能:将插件窗口的背景设置为蓝色,另外按下鼠标左键会发出声音,鼠标移动时在插件窗口画线。
首先,为plugin类添加一个私有成员mWindow保存窗口指针,覆写基类的SetWindow函数,

[cpp] view plain copy      
  1. NPError Plugin::SetWindow(NPWindow* Window)
  2. {
  3. mWindow=Window;
  4. return NPERR_NO_ERROR;
  5. }

其他功能都要在事件处理函数中实现,因此基类的虚函数HandleEvent需要加以实现。

[cpp] view plain copy      
  1. uint16_t Plugin::HandleEvent(void* aEvent)
  2. {
  3. NPEvent *event = (NPEvent *)aEvent;
  4. switch(event->event)
  5. {
  6. case WM_PAINT:
  7. {
  8. if(!mWindow)
  9. break;
  10. // get the dirty rectangle to update or repaint the whole window
  11. RECT * drc = (RECT *)event->lParam;
  12. if(drc)
  13. FillRect((HDC)event->wParam, drc, (HBRUSH)(COLOR_WINDOW));
  14. else {
  15. RECT rc;
  16. rc.bottom = mWindow->y + mWindow->height;
  17. rc.left = mWindow->x;
  18. rc.right = mWindow->x + mWindow->width;
  19. rc.top = mWindow->y;
  20. FillRect((HDC)event->wParam, &rc, (HBRUSH)(COLOR_WINDOW));
  21. }
  22. break;
  23. }

下一个功能:按下鼠标左键时发声:

[cpp] view plain copy      
  1. case WM_LBUTTONDOWN:
  2. Beep(1000,200);
  3. break;

最后实现画线的功能,这也是真正展示处理窗口的window成员的地方:
首先添加私有成员变量: int m_oldX,m_OldY,m_newX,m_newY;
在左键按下的事件中记录一个坐标,将左键按下的代码修改为:

[cpp] view plain copy      
  1. case WM_LBUTTONDOWN:
  2. Beep(1000,200);
  3. m_oldX=LOWORD(event->lParam);
  4. m_OldY=HIWORD(event->lParam);
  5. break;

接着实现鼠标移动的事件响应代码:

[cpp] view plain copy      
  1. case WM_MOUSEMOVE:
  2. {
  3. m_newX=LOWORD(event->lParam);
  4. m_newY=HIWORD(event->lParam);
  5. HDC hDC = (HDC)mWindow->window;
  6. MoveToEx(hDC,m_oldX,m_OldY,NULL);
  7. LineTo(hDC,m_newX,m_newY);
  8. m_oldX=m_newX;m_OldY=m_newY;
  9. break;
  10. }

这里直接将mWindow->window强制转换为HDC,是因为对于winless的插件window成员在windows平台下就是该插件的HDC。注意由于这里有变量hDC的定义,因此这个case的代码必须用大括号括起来,否则会报错。下面是事件处理函数最后一点代码:

[cpp] view plain copy      
  1. default:
  2. return 0;
  3. }
  4. return 1;
  5. }

实现完毕之后,生成项目,然后用前面的测试页面对这个新的dll进行测试。如下图:

上面实现的有一个问题,就是只有窗口重绘的时候才会显示出画的线,但我想已经说明了window成员在winless类型的插件中的使用,因此就不深入研究了,如果谁看到这个例子能够给出好的改进意见,我将不胜感激。

可能有的朋友一步步跟下来之后发现还是有问题,下面贴出前面这个项目的plugin.h文件及plugin.cpp文件的完整内容

[cpp] view plain copy      
  1. //Plugin.h文件
  2. #pragma once
  3. #include "pluginbase.h"
  4. class Plugin :
  5. public nsPluginInstanceBase
  6. {
  7. private:
  8. NPP m_pNPInstance;
  9. NPBool m_bInitialized;
  10. NPWindow* mWindow;
  11. int m_oldX,m_OldY,m_newX,m_newY;
  12. bool bMdown;
  13. public:
  14. Plugin(NPP pNPInstance);
  15. ~Plugin();
  16. NPBool init(NPWindow* pNPWindow);
  17. void shut();
  18. NPBool isInitialized();
  19. NPError SetWindow(NPWindow* Window);
  20. uint16_t HandleEvent(void* event);//windowed 插件窗口自己接收消息,不需handleevent。
  21. };
[cpp] view plain copy      
  1. //Plugin.cpp文件
  2. #include "Plugin.h"
  3. // functions /
  4. NPError NS_PluginInitialize()
  5. {
  6. return NPERR_NO_ERROR;
  7. }
  8. void NS_PluginShutdown()
  9. {
  10. }
  11. nsPluginInstanceBase * NS_NewPluginInstance(nsPluginCreateData * aCreateDataStruct)
  12. {
  13. if(!aCreateDataStruct)
  14. return NULL;
  15. Plugin * plugin = new Plugin(aCreateDataStruct->instance);
  16. BOOL bWindowed = FALSE;
  17. NPN_SetValue(aCreateDataStruct->instance, NPPVpluginWindowBool, (void *)bWindowed);//winless插件需要在这里指出,默认为windowed
  18. return plugin;
  19. }
  20. void NS_DestroyPluginInstance(nsPluginInstanceBase * aPlugin)
  21. {
  22. if(aPlugin)
  23. delete (Plugin *)aPlugin;
  24. }
  25. // Plugin /
  26. Plugin::Plugin(NPP pNPInstance):nsPluginInstanceBase(),
  27. m_pNPInstance(pNPInstance),
  28. m_bInitialized(FALSE)
  29. {
  30. bMdown=false;
  31. }
  32. NPBool Plugin::init(NPWindow* pNPWindow)
  33. {
  34. m_bInitialized = TRUE;
  35. return TRUE;
  36. }
  37. void Plugin::shut()
  38. {
  39. m_bInitialized = FALSE;
  40. }
  41. NPBool Plugin::isInitialized()
  42. {
  43. return m_bInitialized;
  44. }
  45. Plugin::~Plugin(void)
  46. {
  47. }
  48. NPError Plugin::SetWindow(NPWindow* Window)
  49. {
  50. mWindow=Window;
  51. return NPERR_NO_ERROR;
  52. }
  53. uint16_t Plugin::HandleEvent(void* aEvent)
  54. {
  55. NPEvent *event = (NPEvent *)aEvent;
  56. switch(event->event)
  57. {
  58. case WM_PAINT:
  59. {
  60. if(!mWindow)
  61. break;
  62. // get the dirty rectangle to update or repaint the whole window
  63. RECT * drc = (RECT *)event->lParam;
  64. if(drc)
  65. FillRect((HDC)event->wParam, drc, (HBRUSH)(COLOR_WINDOW));
  66. else {
  67. RECT rc;
  68. rc.bottom = mWindow->y + mWindow->height;
  69. rc.left   = mWindow->x;
  70. rc.right  = mWindow->x + mWindow->width;
  71. rc.top    = mWindow->y;
  72. FillRect((HDC)event->wParam, &rc, (HBRUSH)(COLOR_WINDOW));
  73. }
  74. break;
  75. }
  76. case WM_LBUTTONDOWN:
  77. Beep(1000,200);
  78. m_oldX=LOWORD(event->lParam);
  79. m_OldY=HIWORD(event->lParam);
  80. bMdown=true;
  81. break;
  82. case WM_MOUSEMOVE:
  83. {
  84. m_newX=LOWORD(event->lParam);
  85. m_newY=HIWORD(event->lParam);
  86. if (bMdown)
  87. {
  88. HDC hDC = (HDC)mWindow->window;
  89. MoveToEx(hDC,m_oldX,m_OldY,NULL);
  90. LineTo(hDC,m_newX,m_newY);
  91. }
  92. NPN_ForceRedraw(m_pNPInstance);
  93. m_oldX=m_newX;m_OldY=m_newY;
  94. break;
  95. }
  96. case WM_LBUTTONUP:
  97. bMdown=false;NPN_ForceRedraw(m_pNPInstance);break;
  98. default:
  99. return 0;
  100. }
  101. //
  102. return 1;
  103. }

windowed插件

创建windowed的插件,可以这样:

[cpp] view plain copy      
  1. BOOL bWindowed = TRUE;
  2. NPN_SetValue(aCreateDataStruct->instance, NPPVpluginWindowBool, (void *)bWindowed);//winless插件需要在这里指出,默认为windowed

也可以什么都不写,因为默认就是创建windowed的插件。这个例子实现两个功能:设置背景并在创建窗口中输出一段文字。
首先,为Plugin类添加一个私有成员:HWND m_hWnd;
要对窗口进行处理需要使用到窗口子类化的函数,要包含头文件#include <windowsx.h>(Windows.h在其他头文件中已经包含了,这里可以不需要再写)
在plugin.cpp中声明一个回调函数和一个静态变量:

[cpp] view plain copy      
  1. static LRESULT CALLBACK PluginWinProc(HWND, UINT, WPARAM, LPARAM);
  2. static WNDPROC lpOldProc = NULL;

在类的构造函数中将m_hWnd初始化为NULL
修改Plugin::init为:

[cpp] view plain copy      
  1. NPBool Plugin::init(NPWindow* pNPWindow)
  2. {
  3. mWindow=pNPWindow;
  4. m_hWnd =(HWND)pNPWindow->window;
  5. if (m_hWnd==NULL) return FALSE;
  6. // 对窗口进行子类化,这样就可以对消息进行处理并在窗口中进行绘制
  7. lpOldProc = SubclassWindow(m_hWnd, (WNDPROC)PluginWinProc);
  8. // 将窗口与 Plugin 对象相关联,这样就可以在窗口处理中访问Plugin 对象
  9. SetWindowLongPtr(m_hWnd, GWLP_USERDATA, (LONG_PTR)this);
  10. m_bInitialized = TRUE;
  11. return TRUE;
  12. }

对于windowed的插件pNPWindow->window存储就是插件窗口的句柄,上面将其转换为HWND,并将窗口进行子类化,关键代码的作用请参看注释。将Plugin::shut修改为:

[cpp] view plain copy      
  1. void Plugin::shut()
  2. {
  3. // 反子类化subclass it back
  4. SubclassWindow(m_hWnd, lpOldProc);
  5. m_hWnd = NULL;
  6. m_bInitialized = FALSE;
  7. }

到这一步已经完成了对窗口句柄的获取与释放,最后还需要的就是消息处理函数了,如下:

[cpp] view plain copy      
  1. static LRESULT CALLBACK PluginWinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
  2. {
  3. switch (msg) {
  4. case WM_PAINT:
  5. {
  6. // draw a frame and display some string
  7. PAINTSTRUCT ps;
  8. HDC hdc = BeginPaint(hWnd, &ps);
  9. RECT rc;
  10. GetClientRect(hWnd, &rc);
  11. FillRect( hdc, &rc, (HBRUSH) (COLOR_WINDOW));
  12. FrameRect(hdc, &rc, GetStockBrush(BLACK_BRUSH));
  13. Plugin * p = (Plugin*) GetWindowLongPtr(hWnd, GWLP_USERDATA);
  14. if(p) {
  15. char *s = "Hello , MY FIRST PLUGIN!";//p->GetGuiText();
  16. DrawText(hdc, s, strlen(s), &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
  17. }
  18. EndPaint(hWnd, &ps);
  19. }
  20. break;
  21. default:
  22. break;
  23. }
  24. return DefWindowProc(hWnd, msg, wParam, lParam);
  25. }

这里只是对WM_PAINT消息进行了处理,其他消息用DefWindowProc进行默认的处理。在WM_PAINT的消息处理代码中我们为窗口设置了背景,并输出一段文字"Hello , MY FIRST PLUGIN!",没错,这就是我们的插件的hello world!!
效果如下图:

最后这个项目的plugin.h文件和plugin.cpp文件完整内容

[cpp] view plain copy      
  1. //Plugin.h
  2. #pragma once
  3. #include "pluginbase.h"
  4. class Plugin :
  5. public nsPluginInstanceBase
  6. {
  7. private:
  8. NPP m_pNPInstance;
  9. NPBool m_bInitialized;
  10. NPWindow* mWindow;
  11. HWND m_hWnd;
  12. public:
  13. Plugin(NPP pNPInstance);
  14. ~Plugin();
  15. NPBool init(NPWindow* pNPWindow);//  {  m_bInitialized = TRUE;  return TRUE;}
  16. void shut();//  {  m_bInitialized = FALSE;  }
  17. NPBool isInitialized();// {  return m_bInitialized;  }
  18. };
[cpp] view plain copy      
  1. //Plugin.cpp
  2. #include "Plugin.h"
  3. #include <windowsx.h>
  4. // functions /
  5. NPError NS_PluginInitialize()
  6. {
  7. return NPERR_NO_ERROR;
  8. }
  9. void NS_PluginShutdown()
  10. {
  11. }
  12. nsPluginInstanceBase * NS_NewPluginInstance(nsPluginCreateData * aCreateDataStruct)
  13. {
  14. if(!aCreateDataStruct)
  15. return NULL;
  16. Plugin * plugin = new Plugin(aCreateDataStruct->instance);
  17. return plugin;
  18. }
  19. void NS_DestroyPluginInstance(nsPluginInstanceBase * aPlugin)
  20. {
  21. if(aPlugin)
  22. delete (Plugin *)aPlugin;
  23. }
  24. // Plugin /
  25. Plugin::Plugin(NPP pNPInstance):nsPluginInstanceBase(),
  26. m_pNPInstance(pNPInstance),
  27. m_bInitialized(FALSE)
  28. {
  29. m_hWnd = NULL;
  30. }
  31. Plugin::~Plugin(void)
  32. {
  33. }
  34. static LRESULT CALLBACK PluginWinProc(HWND, UINT, WPARAM, LPARAM);
  35. static WNDPROC lpOldProc = NULL;
  36. NPBool Plugin::init(NPWindow* pNPWindow)
  37. {
  38. mWindow=pNPWindow;
  39. m_hWnd =(HWND)pNPWindow->window;
  40. if (m_hWnd==NULL) return FALSE;
  41. // 对窗口进行子类化,这样就可以对消息进行处理并在窗口中进行绘制
  42. lpOldProc = SubclassWindow(m_hWnd, (WNDPROC)PluginWinProc);
  43. // 将窗口与 Plugin 对象相关联,这样就可以在窗口处理中访问Plugin 对象
  44. SetWindowLongPtr(m_hWnd, GWLP_USERDATA, (LONG_PTR)this);
  45. m_bInitialized = TRUE;
  46. return TRUE;
  47. }
  48. void Plugin::shut()
  49. {
  50. // 将窗口子类化回去subclass it back
  51. SubclassWindow(m_hWnd, lpOldProc);
  52. m_hWnd = NULL;
  53. m_bInitialized = FALSE;
  54. }
  55. NPBool Plugin::isInitialized()
  56. {
  57. return m_bInitialized;
  58. }
  59. static LRESULT CALLBACK PluginWinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
  60. {
  61. switch (msg) {
  62. case WM_PAINT:
  63. {
  64. // draw a frame and display some string
  65. PAINTSTRUCT ps;
  66. HDC hdc = BeginPaint(hWnd, &ps);
  67. RECT rc;
  68. GetClientRect(hWnd, &rc);
  69. FillRect( hdc, &rc, (HBRUSH) (COLOR_WINDOW));
  70. FrameRect(hdc, &rc, GetStockBrush(BLACK_BRUSH));
  71. Plugin * p = (Plugin*) GetWindowLongPtr(hWnd, GWLP_USERDATA);
  72. if(p) {
  73. char *s = "Hello , MY FIRST PLUGIN!";//p->GetGuiText();
  74. DrawText(hdc, s, strlen(s), &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
  75. }
  76. EndPaint(hWnd, &ps);
  77. }
  78. break;
  79. default:
  80. break;
  81. }
  82. return DefWindowProc(hWnd, msg, wParam, lParam);
  83. }

转载于:https://www.cnblogs.com/lifan3a/articles/8000761.html

NPAPI插件开发详细记录:插件开发入门相关推荐

  1. android浏览器插件开发,Android浏览器插件开发.docx

    Android浏览器插件开发 Android浏览器插件开发 需要了解的有以下几个方面的知识:1.插件是什么2.android浏览器怎样加载插件和创建实例3浏览器插件和脚本语言的交互4插件内部的数据流一 ...

  2. 动态规划27k字超详细保姆级入门讲解——附DP经典线性、区间、二维图、四维8个模型题解

    动态规划27k字超详细保姆级入门讲解 写在前面: 这篇文章是目前为止我写过最长也是最久的文章,前面关于DP的讲解我查阅了大量的博客资料,学习其他博主对DP的理解,也翻阅了很多经典的纸质书籍,同时做了近 ...

  3. 超详细的MySQL入门教程(四)

    MySQL:简单的增删改查 查询数据 基本语法介绍 打印任意值 查询表中全部数据 查询表中部分字段 限定条件查询 例1:查询编号值小于指定值的记录 例2:查询地址不等于某值的记录 例3:查询一级地址等 ...

  4. 详细记录python的range()函数用法

    详细记录python的range()函数用法 使用python的人都知道range()函数很方便,今天再用到他的时候发现了很多以前看到过但是忘记的细节.这里记录一下range(),复习下list的sl ...

  5. 实操教程|详细记录solov2的ncnn实现和优化

    作者丨欧阳慧宇@知乎(已授权) 来源丨https://zhuanlan.zhihu.com/p/361900997 编辑丨极市平台 极市导读 由于目前solo系列在GitHub中没有转ncnn的项目, ...

  6. 最简单最详细的SpringBoot入门项目——HelloWorld

    最详细的SpringBoot入门项目--HelloWorld 关于SpringBoot的介绍以及优点这里就不说了,想了解的可以直接点击这里 Spring Boot百度百科 接下来我们直奔主题,开始用S ...

  7. OpenFOAM安装+ParaView安装+环境配置(deb直接安装详细记录-Ubuntu14.04+OpenFOAM4.1)

    OpenFOAM安装+ParaView安装+环境配置 Ubuntu14.04+OpenFOAM4.1(deb直接安装详细记录) Ubuntu14.04安装配置OpenFOAM4.1:https://w ...

  8. linux入门_Linux超详细0基础入门篇(一)

    首先要感谢大康老师对我在Linux操作系统上的教导. 今天来讲一下用途广泛的Linux的基础入门教程 仅仅是做入门使用,如果想更加深入的学习那就需要自己做探索了. 本次例子使用的是kali linux ...

  9. oracle19c的版本号_Windows10安装Oracle19c数据库详细记录(图文详解)

    1. 下载资源 官网下载地址: 点此进入 直接点击下载,会自动开始下载. 2. 开始安装 将下载的安装包解压到本地,右键-以管理员身份运行setup.exe,开始安装(一定要以管理员身份运行,不然后面 ...

  10. oracle19c二次安装报错,Windows10安装Oracle19c数据库详细记录(图文详解)

    这篇文章主要介绍了Windows10安装Oracle19c数据库详细记录,文中给大家介绍了修改密码的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以 ...

最新文章

  1. FLASH与ASP.NET通讯
  2. 自定义工作流任务控件
  3. mongodb全套配置
  4. boost::hana::just用法的测试程序
  5. 有关手游设计ICON的几点建议
  6. 按钮配置之自定义按钮使用(一)——JEPLUS软件快速开发平台
  7. 论文浅尝 | XQA:一个跨语言开放域问答数据集
  8. 【BZOJ3684】大朋友和多叉树(拉格朗日反演)
  9. 深入理解Java Proxy机制
  10. VC2013同一个工程生成的exe文件显示不同的图标
  11. RS232和RS485的协议原理及应用
  12. xp系统支持的python版本_xp系统安装不上python
  13. 重启计算机可以使用什么组合键,win10系统重启电脑的快捷键是什么呢?
  14. Linux 基本指令 Pt.2
  15. 关于CISC和RISC的一些总结
  16. 源发行版 8 需要目标发行版 1.8
  17. 元宇宙的东风吹向何处?企业如何乘势布局?
  18. 【博客625】keepalived开启garp refresh的重要性
  19. oracle 百分位数,Oracle分析函数PERCENTILE_CONT
  20. -XX:+PrintGCTimeStamps 打印CG发生的时间戳

热门文章

  1. 沪江、外研社、新东方联合组建海外出版公司,AI 背景下的出版业将去何处?...
  2. 在数据中心里SDN技术发展现状
  3. 颠覆大数据分析之结论
  4. 李洪强iOS开发之XMPP
  5. jQuery系列:Ajax
  6. 南京铁道学院计算机应用,南京铁道职业技术学院铁道交通运营管理专业
  7. Xshell用于windows主机和linux虚拟机之间互传文件
  8. python基础算法面试_【归纳】面试中常见的python基础知识
  9. springboot mybatis 事务_真香——Github上的优秀SpringBoot框架
  10. python的本质是什么意思_python生成器指的是什么意思