概要

一个常见的编程任务是枚举所有运行的"应用程序"。Windows 任务管理器就是一个很好的例子。它用两种方式列出"应用程序"。任务管理器的第一个选项卡列出桌面上的所有"应用程序窗口"。第二个选项卡列出系统中的所有"进程"。本文提供了如何执行这些任务的详细信息。

更多信息

枚举顶层窗口

如果将枚举进程与枚举桌面上的顶层窗口进行比较,那么枚举顶层窗口可能更容易一些。要枚举顶层窗口,请使用 EnumWindows() 函数。不要使用 GetWindow() 创建自己的窗口列表,因为它可能受 z 轴次序更改和丢失窗口的干扰。

EnumWindows() 将指向某个回调函数的指针以及用户定义的 LPARAM 值作为其参数。它对桌面上的每个窗口(或顶层窗口)分别调用一次回调函数。然后,回调函数可以对此窗口句柄进行某种处理,如将其添加到一个列表中。此方法可以保证不受窗口 z 轴次序更改等项的干扰。在获取窗口句柄后,可以通过调用 GetWindowText() 来获取其标题。

枚举进程

在系统中创建进程列表比枚举窗口要稍微复杂一些。这主要是因为执行此操作的 API 函数因所使用的 Win32 操作系统而异。在 Windows 95、Windows 98、Windows Millennium Edition、Windows 2000 和 Windows XP 中,可以使用 ToolHelp32 API 库中的函数。但是,在 Windows NT 中,必须使用 PSAPI API 库中的函数(Platform SDK 中包含该库)。本文将讨论这两种技术,另外还提供一个称为 EnumProcs() 的示例包装函数,该函数可以在 Win32 操作系统中运行。

使用 ToolHelp32 库枚举进程

首先讨论 ToolHelp32 方法。KERNEL32.dll 中的 ToolHelp32 函数是标准的 API 函数。注意,在 Windows NT 4.0 中不提供这些 API。

可以使用 ToolHelp32 提供的各种函数,枚举系统中的进程和线程以及获取内存和模块信息。但是,在枚举进程时,仅需要使用以下三个函数:CreateToolhelp32Snapshot()Process32First()Process32Next()

使用 ToolHelp32 函数的第一步是创建系统信息的"快照"。可以使用 CreateToolhelp32Snapshot() 函数完成这一步。此函数允许选择在快照中存储的信息类型。如果您需要进程信息,则一定要包括 TH32CS_SNAPPROCESS 标志。CreateToolhelp32Snapshot() 函数返回一个句柄,在使用完该句柄后,必须将其传递到 CloseHandle()

下一步,为了检索快照中的进程列表,可以调用一次 Process32First,然后重复调用多次 Process32Next。执行此操作,直到其中的一个函数返回 FALSE 为止。这将逐个获得快照进程列表中的进程。这两个函数都将"快照"句柄和指向 PROCESSENTRY32 结构的指针作为其参数。

在调用 Process32FirstProcess32Next 后,PROCESSENTRY32 结构将包含系统中某个进程的有用信息。进程 ID 在该结构的 th32ProcessID 成员中。可以将它传递到 OpenProcess() 以获取该进程的句柄。该进程的可执行文件和路径存储在结构的 szExeFile 成员中。还可以在此结构中找到其他有用的信息。

备注:切记,在调用 Process32First() 之前,将 PROCESSENTRY32 结构的 dwSize 成员设置为 sizeof(PROCESSENTRY32)

使用 PSAPI 库枚举进程

在 Windows NT 中创建进程列表的方法是使用 PSAPI.dll 中的 PSAPI 函数。此文件是随 Platform SDK 分发的,可以从以下 Microsoft Web 站点下载该文件:

Microsoft Platform SDK
http://www.microsoft.com/msdownload/platformsdk/sdkupdate/ (http://www.microsoft.com/msdownload/platformsdk/sdkupdate/)

所需的 PSAPI.h 和 PSAPI.lib 文件也包含在 Platform SDK 中。

要使用 PSAPI 库中的函数,请将 PSAPI.lib 文件添加到您的项目中,并将 PSAPI.h 文件包含到任何调用 PSAPI API 的模块中。切记,要将 PSAPI.dll 文件与使用它的任何可执行文件一起分发,因为没有随 Windows NT 4.0 分发该文件。可以从以下位置下载可重新分发的 PSAPI.dll 版本(如果没有完整的 Platform SDK 的话):

Platform SDK 可重新分发的版本:用于 Windows NT 的 PSAPI
http://www.microsoft.com/downloads/release.asp?releaseid=30337 (http://www.microsoft.com/downloads/release.asp?releaseid=3 0337)

与 ToolHelp32 函数一样,PSAPI 也包含很多其他的有用函数。但是,本文只讨论与枚举进程有关的函数:EnumProcesses()EnumProcessModules()GetModuleFileNameEx()GetModuleBaseName()

创建进程列表的第一步是调用 EnumProcesses()。以下是其声明:

BOOL EnumProcesses( DWORD *lpidProcess, DWORD cb, DWORD *cbNeeded );

需要给 EnumProcesses() 传入三个参数:指向 DWORD 数组的指针 (lpidProcess)、该数组的大小 (cb) 以及另一个指向 DWORD 的指针 (cbNeeded)(用于获取返回数据的长度)。使用当前所运行进程的进程 ID 数组来填充 DWORD 数组。cbNeeded 参数返回所使用数组的大小。以下计算告诉您返回了多少个进程 ID:nReturned = cbNeeded / sizeof(DWORD)。

备注:虽然本文将返回的 DWORD 命名为"cbNeeded",但实际上无法确定在数组中传递的进程到底有多少。EnumProcesses()cbNeeded 中返回的值绝不会大于在 cb 参数中传递的数组值。因此,要确保成功使用 EnumProcesses() 函数,唯一的方法是分配一个 DWORD 数组,如果返回时 cbNeeded 等于 cb,则分配较大的数组并重试,直到 cbNeeded 小于 cb 时为止。

现在,数组包含系统中的每个进程 ID。如果您的目标是获取进程名,则必须先获取句柄。要从进程 ID 中获取句柄,请使用 OpenProcess()

在获取句柄后,需要获取进程的第一个模块。要获取进程的第一个模块,请使用以下参数调用 EnumProcessModules() API:

EnumProcessModules( hProcess, &hModule, sizeof(hModule), &cbReturned );

这将把进程第一个模块的句柄放在 hModule 变量中。切记,进程实际上并没有名称,但进程中的第一个模块是该进程的可执行文件。现在,可以将返回的模块句柄 (hModule) 用于 GetModuleFileNameEx()GetModuleBaseName() API,以获取进程可执行文件的完整路径名或简单模块名。这两个函数需要传入进程句柄、模块句柄以及返回名称的缓冲区指针,后面是缓冲区的大小。

通过对 EnumProcesses() API 返回的每个进程 ID 重复上述步骤,即可创建 Windows NT 中进程的列表。

16 位进程

在 Windows 95、Windows 98 和 Windows Millennium Edition 上,对 ToolHelp32 而言,所有 16 位应用程序是平等的。与 Win32 应用程序一样,16 位应用程序也具有进程 ID 等。但是,在 Windows NT、Windows 2000 或 Windows XP 中,则不是这样。在这些操作系统上,16 位应用程序运行在 DOS 虚拟机 (VDM) 中。

要在 Windows NT、Windows 2000 和 Windows XP 中枚举 16 位应用程序,必须使用名为 VDMEnumTaskWOWEx() 的函数。必须在源模块中包含 VDMDBG.h,并且 VDMDBG.lib 文件必须与项目链接在一起。这两个文件包含在 Platform SDK 中。

此函数的声明如下:

INT WINAPI VDMEnumTaskWOWEx( DWORD dwProcessId, TASKENUMPROCEX fp,
LPARAM lparam );

其中,dwProcessId 是要枚举其 16 位任务的 NTVDM 进程的标识符。fp 参数是指向回调枚举函数的指针。lparam 参数是要传递给枚举函数的用户定义值。

枚举函数应当定义如下:

BOOL WINAPI Enum16( DWORD dwThreadId, WORD hMod16, WORD hTask16, PSZ
pszModName, PSZ pszFileName, LPARAM lpUserDefined );

对于传递到 VDMEnumTaskWOWEx() 的 NTVDM 进程中运行的每个 16 位任务,分别调用一次该函数。如果要继续枚举,则应该返回 FALSE,如果要结束枚举,则应该返回 TRUE。注意,这与 EnumWindows() 正好相反。

示例代码

下示例代码将 PSAPI 和 ToolHelp32 函数封装在名为 EnumProcs() 的函数中。此函数与 EnumWindows() 类似,因为它也接受指向某个函数的指针并重复调用它(对系统中的每个进程分别调用一次)。以下是该函数的声明:

BOOL WINAPI EnumProcs( PROCENUMPROC lpProc, LPARAM lParam );

如果使用该函数,则将回调函数声明如下:

BOOL CALLBACK Proc( DWORD dw, WORD w16, LPCSTR lpstr, LPARAM lParam );

其中,dw 参数将包含 ID;"w16"是 16 位任务编号或 0(如果是 32 位进程,则在 Windows 95 下始终为零);lpstr 参数将指向文件名;lParam 是传递给 EnumProcs() 的用户定义的 lParam

EnumProcs() 函数通过显式链接使用 ToolHelp32 和 PSAPI 函数,而不是通过更常用的隐式链接。通过使用这种技术,可以使代码在所有 Win32 操作系统中二进制兼容。

//
// EnumProc.c
//
#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
#include <vdmdbg.h>
typedef BOOL (CALLBACK *PROCENUMPROC)(DWORD, WORD, LPSTR, LPARAM);
typedef struct {
DWORD          dwPID;
PROCENUMPROC   lpProc;
DWORD          lParam;
BOOL           bEnd;
} EnumInfoStruct;
BOOL WINAPI EnumProcs(PROCENUMPROC lpProc, LPARAM lParam);
BOOL WINAPI Enum16(DWORD dwThreadId, WORD hMod16, WORD hTask16,
PSZ pszModName, PSZ pszFileName, LPARAM lpUserDefined);
//
// The EnumProcs function takes a pointer to a callback function
// that will be called once per process with the process filename
// and process ID.
//
// lpProc -- Address of callback routine.
//
// lParam -- A user-defined LPARAM value to be passed to
//           the callback routine.
//
// Callback function definition:
// BOOL CALLBACK Proc(DWORD dw, WORD w, LPCSTR lpstr, LPARAM lParam);
//
BOOL WINAPI EnumProcs(PROCENUMPROC lpProc, LPARAM lParam) {
OSVERSIONINFO  osver;
HINSTANCE      hInstLib  = NULL;
HINSTANCE      hInstLib2 = NULL;
HANDLE         hSnapShot = NULL;
LPDWORD        lpdwPIDs  = NULL;
PROCESSENTRY32 procentry;
BOOL           bFlag;
DWORD          dwSize;
DWORD          dwSize2;
DWORD          dwIndex;
HMODULE        hMod;
HANDLE         hProcess;
char           szFileName[MAX_PATH];
EnumInfoStruct sInfo;
// ToolHelp Function Pointers.
HANDLE (WINAPI *lpfCreateToolhelp32Snapshot)(DWORD, DWORD);
BOOL (WINAPI *lpfProcess32First)(HANDLE, LPPROCESSENTRY32);
BOOL (WINAPI *lpfProcess32Next)(HANDLE, LPPROCESSENTRY32);
// PSAPI Function Pointers.
BOOL (WINAPI *lpfEnumProcesses)(DWORD *, DWORD, DWORD *);
BOOL (WINAPI *lpfEnumProcessModules)(HANDLE, HMODULE *, DWORD,
LPDWORD);
DWORD (WINAPI *lpfGetModuleBaseName)(HANDLE, HMODULE, LPTSTR, DWORD);
// VDMDBG Function Pointers.
INT (WINAPI *lpfVDMEnumTaskWOWEx)(DWORD, TASKENUMPROCEX, LPARAM);
// Retrieve the OS version
osver.dwOSVersionInfoSize = sizeof(osver);
if (!GetVersionEx(&osver))
return FALSE;
// If Windows NT 4.0
if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT
&& osver.dwMajorVersion == 4) {
__try {
// Get the procedure addresses explicitly.We do
// this so we don't have to worry about modules
// failing to load under OSes other than Windows NT 4.0
// because references to PSAPI.DLL can't be resolved.
hInstLib = LoadLibraryA("PSAPI.DLL");
if (hInstLib == NULL)
__leave;
hInstLib2 = LoadLibraryA("VDMDBG.DLL");
if (hInstLib2 == NULL)
__leave;
// Get procedure addresses.
lpfEnumProcesses = (BOOL (WINAPI *)(DWORD *, DWORD, DWORD*))
GetProcAddress(hInstLib, "EnumProcesses");
lpfEnumProcessModules = (BOOL (WINAPI *)(HANDLE, HMODULE *,
DWORD, LPDWORD)) GetProcAddress(hInstLib,
"EnumProcessModules");
lpfGetModuleBaseName = (DWORD (WINAPI *)(HANDLE, HMODULE,
LPTSTR, DWORD)) GetProcAddress(hInstLib,
"GetModuleBaseNameA");
lpfVDMEnumTaskWOWEx = (INT (WINAPI *)(DWORD, TASKENUMPROCEX,
LPARAM)) GetProcAddress(hInstLib2, "VDMEnumTaskWOWEx");
if (lpfEnumProcesses == NULL
|| lpfEnumProcessModules == NULL
|| lpfGetModuleBaseName == NULL
|| lpfVDMEnumTaskWOWEx == NULL)
__leave;
//
// Call the PSAPI function EnumProcesses to get all of the
// ProcID's currently in the system.
//
// NOTE:In the documentation, the third parameter of
// EnumProcesses is named cbNeeded, which implies that you
// can call the function once to find out how much space to
// allocate for a buffer and again to fill the buffer.
// This is not the case.The cbNeeded parameter returns
// the number of PIDs returned, so if your buffer size is
// zero cbNeeded returns zero.
//
// NOTE:The "HeapAlloc" loop here ensures that we
// actually allocate a buffer large enough for all the
// PIDs in the system.
//
dwSize2 = 256 * sizeof(DWORD);
do {
if (lpdwPIDs) {
HeapFree(GetProcessHeap(), 0, lpdwPIDs);
dwSize2 *= 2;
}
lpdwPIDs = (LPDWORD) HeapAlloc(GetProcessHeap(), 0,
dwSize2);
if (lpdwPIDs == NULL)
__leave;
if (!lpfEnumProcesses(lpdwPIDs, dwSize2, &dwSize))
__leave;
} while (dwSize == dwSize2);
// How many ProcID's did we get?
dwSize /= sizeof(DWORD);
// Loop through each ProcID.
for (dwIndex = 0; dwIndex < dwSize; dwIndex++) {
szFileName[0] = 0;
// Open the process (if we can... security does not
// permit every process in the system to be opened).
hProcess = OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE, lpdwPIDs[dwIndex]);
if (hProcess != NULL) {
// Here we call EnumProcessModules to get only the
// first module in the process.This will be the
// EXE module for which we will retrieve the name.
if (lpfEnumProcessModules(hProcess, &hMod,
sizeof(hMod), &dwSize2)) {
// Get the module name
if (!lpfGetModuleBaseName(hProcess, hMod,
szFileName, sizeof(szFileName)))
szFileName[0] = 0;
}
CloseHandle(hProcess);
}
// Regardless of OpenProcess success or failure, we
// still call the enum func with the ProcID.
if (!lpProc(lpdwPIDs[dwIndex], 0, szFileName, lParam))
break;
// Did we just bump into an NTVDM?
if (_stricmp(szFileName, "NTVDM.EXE") == 0) {
// Fill in some info for the 16-bit enum proc.
sInfo.dwPID = lpdwPIDs[dwIndex];
sInfo.lpProc = lpProc;
sInfo.lParam = (DWORD) lParam;
sInfo.bEnd = FALSE;
// Enum the 16-bit stuff.
lpfVDMEnumTaskWOWEx(lpdwPIDs[dwIndex],
(TASKENUMPROCEX) Enum16, (LPARAM) &sInfo);
// Did our main enum func say quit?
if (sInfo.bEnd)
break;
}
}
} __finally {
if (hInstLib)
FreeLibrary(hInstLib);
if (hInstLib2)
FreeLibrary(hInstLib2);
if (lpdwPIDs)
HeapFree(GetProcessHeap(), 0, lpdwPIDs);
}
// If any OS other than Windows NT 4.0.
} else if (osver.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS
|| (osver.dwPlatformId == VER_PLATFORM_WIN32_NT
&& osver.dwMajorVersion > 4)) {
__try {
hInstLib = LoadLibraryA("Kernel32.DLL");
if (hInstLib == NULL)
__leave;
// If NT-based OS, load VDMDBG.DLL.
if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) {
hInstLib2 = LoadLibraryA("VDMDBG.DLL");
if (hInstLib2 == NULL)
__leave;
}
// Get procedure addresses.We are linking to
// these functions explicitly, because a module using
// this code would fail to load under Windows NT,
// which does not have the Toolhelp32
// functions in KERNEL32.DLL.
lpfCreateToolhelp32Snapshot =
(HANDLE (WINAPI *)(DWORD,DWORD))
GetProcAddress(hInstLib, "CreateToolhelp32Snapshot");
lpfProcess32First =
(BOOL (WINAPI *)(HANDLE,LPPROCESSENTRY32))
GetProcAddress(hInstLib, "Process32First");
lpfProcess32Next =
(BOOL (WINAPI *)(HANDLE,LPPROCESSENTRY32))
GetProcAddress(hInstLib, "Process32Next");
if (lpfProcess32Next == NULL
|| lpfProcess32First == NULL
|| lpfCreateToolhelp32Snapshot == NULL)
__leave;
if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) {
lpfVDMEnumTaskWOWEx = (INT (WINAPI *)(DWORD, TASKENUMPROCEX,
LPARAM)) GetProcAddress(hInstLib2, "VDMEnumTaskWOWEx");
if (lpfVDMEnumTaskWOWEx == NULL)
__leave;
}
// Get a handle to a Toolhelp snapshot of all processes.
hSnapShot = lpfCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapShot == INVALID_HANDLE_VALUE) {
FreeLibrary(hInstLib);
return FALSE;
}
// Get the first process' information.
procentry.dwSize = sizeof(PROCESSENTRY32);
bFlag = lpfProcess32First(hSnapShot, &procentry);
// While there are processes, keep looping.
while (bFlag) {
// Call the enum func with the filename and ProcID.
if (lpProc(procentry.th32ProcessID, 0,
procentry.szExeFile, lParam)) {
// Did we just bump into an NTVDM?
if (_stricmp(procentry.szExeFile, "NTVDM.EXE") == 0) {
// Fill in some info for the 16-bit enum proc.
sInfo.dwPID = procentry.th32ProcessID;
sInfo.lpProc = lpProc;
sInfo.lParam = (DWORD) lParam;
sInfo.bEnd = FALSE;
// Enum the 16-bit stuff.
lpfVDMEnumTaskWOWEx(procentry.th32ProcessID,
(TASKENUMPROCEX) Enum16, (LPARAM) &sInfo);
// Did our main enum func say quit?
if (sInfo.bEnd)
break;
}
procentry.dwSize = sizeof(PROCESSENTRY32);
bFlag = lpfProcess32Next(hSnapShot, &procentry);
} else
bFlag = FALSE;
}
} __finally {
if (hInstLib)
FreeLibrary(hInstLib);
if (hInstLib2)
FreeLibrary(hInstLib2);
}
} else
return FALSE;
// Free the library.
FreeLibrary(hInstLib);
return TRUE;
}
BOOL WINAPI Enum16(DWORD dwThreadId, WORD hMod16, WORD hTask16,
PSZ pszModName, PSZ pszFileName, LPARAM lpUserDefined) {
BOOL bRet;
EnumInfoStruct *psInfo = (EnumInfoStruct *)lpUserDefined;
bRet = psInfo->lpProc(psInfo->dwPID, hTask16, pszFileName,
psInfo->lParam);
if (!bRet)
psInfo->bEnd = TRUE;
return !bRet;
}
BOOL CALLBACK MyProcessEnumerator(DWORD dwPID, WORD wTask,
LPCSTR szProcess, LPARAM lParam) {
if (wTask == 0)
printf("%5u   %s/n", dwPID, szProcess);
else
printf("  %5u %s/n", wTask, szProcess);
return TRUE;
}
void main() {
EnumProcs((PROCENUMPROC) MyProcessEnumerator, 0);

使用 Win32 API 枚举应用程序相关推荐

  1. python枚举窗口句柄_如何使用 WIN32 API 枚举窗口?

    枚举顶层(top-level)窗口 枚举桌面顶层窗口相对于枚举进程来说可能要容易一些.枚举桌面顶层窗口的方法是用 EnumWindows() 函数.不要用 GetWindow()来创建窗口列表,因为窗 ...

  2. Win32 API 获取其他程序剪贴板内容

    代码如下:使用CFree: #include <windows.h>int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInst ...

  3. Win32 API 枚举打印机

    主要是使用EnumPrinters函数: #include <windows.h> #include <Winspool.h>int APIENTRY WinMain(HINS ...

  4. C#调用Win32 api学习总结

    转载:https://blog.csdn.net/bcbobo21cn/article/details/50930221 从.NET平台调用Win32 API Win32 API可以直接控制Micro ...

  5. MSIL 教程(二):数组、分支、循环、使用不安全代码和如何调用Win32 API(转)...

    转自:http://www.cnblogs.com/Yahong111/archive/2007/08/16/857574.html 续上文[翻译]MSIL 教程(一) ,本文继续讲解数组.分支.循环 ...

  6. Win32 API 概论

    1.1为什么使用 Win32 API 在Windows程序设计领域处于发展初期时,Windows程序员可使用的编程工具唯有API函数.这些函数在程序员手中犹如"积木块"一样,可搭建 ...

  7. Win32 API概论

    1.1为什么使用 Win32 API 在Windows程序设计领域处于发展初期时,Windows程序员可使用的编程工具唯有API函数.这些函数在程序员手中犹如"积木块"一样,可搭建 ...

  8. 【C#】什么是Win32 API,为什么使用,参考手册

    前言 作为Microsoft 32位平台的应用程序编程接口,Win32 API是从事Windows应用程序开发所必备的. Win32 API作为 Microsoft 32位平台(包括:Windows ...

  9. 图解C# 调用Win32 API 示例程序

    一 弹出消息框和发声 先上代码:相关函数不解释:网上比较容易查到: using System; using System.Collections.Generic; using System.Compo ...

最新文章

  1. 【GCN】万字长文带你入门 GCN
  2. pip安装模块警告InsecurePlatformWarning: A true SSLContext object is not available.
  3. oracle在哪些系统运行,ORACLE 查看系统运行情况
  4. Java自学!Java项目面试介绍
  5. 感知机算法的两种表示
  6. Security+jwt+验证码实现验证和授权
  7. pm2集群模式mysql配置_配置 PM2 一键部署
  8. 一段基于Jsoup和Dom4j的海报爬取小程序
  9. 计算机专业带给我们的启示,一次电脑网络调查带给我的启示
  10. java 内部编码_Java 中文编码分析
  11. Java经典实例:处理单个字符串
  12. iOS pickerView(所有类型一网打尽)
  13. matlab仿真限幅发散,GSM通信系统性能分析与MATLAB仿真.doc
  14. 攻防世界-Misc-gif
  15. pyGurobi使用手册
  16. 【UML建模案例】小型网上书店系统
  17. Jsonp解决跨域问题原理
  18. 常用域名控制面板地址
  19. DVB-S/S2天线及信号相关知识
  20. testerhome学习笔记3_Bash应用一

热门文章

  1. 微信小程序-常用api
  2. 第三讲 Matlab/Simulink入门——离散系统仿真实例
  3. Jetson Orin平台4-16路 GMSL2/GSML1相机采集套件推荐
  4. 360度全景摄像是什么?360度全景摄像有什么用?
  5. 产业安全专家谈 | 数字化转型过程中,企业如何建立顶级反欺诈能力?
  6. java程序员必读书籍
  7. 'https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/3.1.2/gradle-3
  8. 荣耀30可以升级鸿蒙系统,惊喜!荣耀手机也能升鸿蒙:这5款机型用户有福了
  9. InnoDB存储引擎 随记
  10. (function ($, undefined){ })(jQuery); 的使用及说明