通过windows API 实现一个微软自带的音量合成器,用户可以通过当前软件很方便调节其他应用程序的音量, 若用户通过windows自带的音量合成器进行调节 IAudioSessionEvents类可以收到该进程调节音量的回调。

上代码:

CAudioSessionEvent.h

#ifndef CAUDIOSESSIONEVENT_H
#define CAUDIOSESSIONEVENT_H#include <audiopolicy.h>
#include <psapi.h>
#include <atlbase.h>
#include <mmdeviceapi.h>
#include <functional>//-----------------------------------------------------------
// Client implementation of IAudioSessionEvents interface.
// WASAPI calls these methods to notify the application when
// a parameter or property of the audio session changes.
//-----------------------------------------------------------
class CAudioSessionEvents : public IAudioSessionEvents
{LONG _cRef;std::function<void(float NewVolume, BOOL NewMute, LPCGUID EventContext)> m_volumeChange_cb = nullptr;public:CAudioSessionEvents() :_cRef(1){}~CAudioSessionEvents(){}void setVolumeChangeCallBack(const std::function<void(float NewVolume, BOOL NewMute, LPCGUID EventContext)> &volumeChange) {m_volumeChange_cb = volumeChange;}private:// IUnknown methods -- AddRef, Release, and QueryInterfaceULONG STDMETHODCALLTYPE AddRef(){return InterlockedIncrement(&_cRef);}ULONG STDMETHODCALLTYPE Release(){ULONG ulRef = InterlockedDecrement(&_cRef);if (0 == ulRef){delete this;}return ulRef;}HRESULT STDMETHODCALLTYPE QueryInterface(REFIID  riid,VOID  **ppvInterface){if (IID_IUnknown == riid){AddRef();*ppvInterface = (IUnknown*)this;}else if (__uuidof(IAudioSessionEvents) == riid){AddRef();*ppvInterface = (IAudioSessionEvents*)this;}else{*ppvInterface = NULL;return E_NOINTERFACE;}return S_OK;}// Notification methods for audio session eventsHRESULT STDMETHODCALLTYPE OnDisplayNameChanged(LPCWSTR NewDisplayName,LPCGUID EventContext){return S_OK;}HRESULT STDMETHODCALLTYPE OnIconPathChanged(LPCWSTR NewIconPath,LPCGUID EventContext){return S_OK;}HRESULT STDMETHODCALLTYPE OnSimpleVolumeChanged(float NewVolume,BOOL NewMute,LPCGUID EventContext){if (NewMute){//printf("MUTE\n");}else{//printf("Volume = %d percent\n", (UINT32)(100*NewVolume + 0.5));}if (m_volumeChange_cb) {m_volumeChange_cb(NewVolume, NewMute, EventContext);}return S_OK;}HRESULT STDMETHODCALLTYPE OnChannelVolumeChanged(DWORD ChannelCount,float NewChannelVolumeArray[],DWORD ChangedChannel,LPCGUID EventContext){return S_OK;}HRESULT STDMETHODCALLTYPE OnGroupingParamChanged(LPCGUID NewGroupingParam,LPCGUID EventContext){return S_OK;}HRESULT STDMETHODCALLTYPE OnStateChanged(AudioSessionState NewState){/*char *pszState = "?????";switch (NewState){case AudioSessionStateActive:pszState = "active";break;case AudioSessionStateInactive:pszState = "inactive";break;}//printf("New session state = %s\n", pszState);*/return S_OK;}HRESULT STDMETHODCALLTYPE OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason){/*char *pszReason = "?????";switch (DisconnectReason){case DisconnectReasonDeviceRemoval:pszReason = "device removed";break;case DisconnectReasonServerShutdown:pszReason = "server shut down";break;case DisconnectReasonFormatChanged:pszReason = "format changed";break;case DisconnectReasonSessionLogoff:pszReason = "user logged off";break;case DisconnectReasonSessionDisconnected:pszReason = "session disconnected";break;case DisconnectReasonExclusiveModeOverride:pszReason = "exclusive-mode override";break;}//printf("Audio session disconnected (reason: %s)\n", pszReason);*/return S_OK;}
};#endif // CAUDIOSESSIONEVENT_H

CAudioManager.h

#pragma once
#include <vector>
#include <list>
#include <iostream>
#include <memory>
#define interface struct
#include "CAudioSessionEvent.h"#pragma comment(lib, "ole32.lib")typedef std::function<void(float NewVolume, BOOL NewMute, LPCGUID EventContext)> AudioChangeCallBack;typedef struct _ProcessInfo {std::shared_ptr<TCHAR> szName;DWORD dwPid = 0;HICON hIcon;
}ProcessInfo;class CAudioManager {
public:CAudioManager();~CAudioManager();/** @brief 获取音频会话总数*/int getEnumSessionsCount();/** @brief 获取音频会话程序信息*/HRESULT getSessionNameList(std::vector<ProcessInfo>& vSessionName);/** @brief 获取指定音频会话音量* @param Pid 进程id* @param fLevel 音量等级 0.0 ~ 1.0*/HRESULT getVolume(DWORD Pid, float& fLevel);/** @brief 设置指定音频会话音量* @param Pid 进程id* @param fLevel 音量等级 0.0 ~ 1.0*/HRESULT setVolume(DWORD Pid, float fLevel);/** @brief 获取指定音频是否静音* @param Pid 进程id* @param mute 1 静音 0 不静音*/HRESULT getMute(DWORD Pid, int& mute);/** @brief 设置指定音频是否静音* @param Pid 进程id* @param mute 1 静音 0 不静音*/HRESULT setMute(DWORD Pid, int mute);/*** @brief setVolumeChange 设置音频会话音量变化回调函数* @param volumeChange*/void setVolumeChange(AudioChangeCallBack volumeChange) {m_volumeChange_cb = volumeChange;auto iter = m_audiosessionevents_list.begin();for (int i = 0; i < m_audiosessionevents_list.size(); i++) {iter->get()->setVolumeChangeCallBack(m_volumeChange_cb);iter++;}}private:HRESULT CreateSessionManager();HRESULT EnumSessions();private:CComQIPtr <IAudioSessionManager2> m_psession_manager = nullptr;CComQIPtr <IMMDeviceEnumerator>   m_pEnumerator = nullptr;CComQIPtr <IMMDevice>             m_pDevice = nullptr;std::list <std::shared_ptr<CAudioSessionEvents>> m_audiosessionevents_list;std::list <CComQIPtr <IAudioSessionControl>>     m_audiosessioncontrol_list;AudioChangeCallBack m_volumeChange_cb = nullptr;
};

CAudioManager.cpp

#include "CAudioManager.h"// goto done
#define CHECK_HR(hr) \if(hr != S_OK){       \goto done;         \}                     #define SAFE_RELEASE(p) \if ((p)) {            \p->Release();       \delete (p);         \(p) = NULL;         \}typedef struct EnumHWndsArg {std::vector<HWND> *vecHWnds;DWORD dwProcessId;
}EnumHWndsArg, *LPEnumHWndsArg;// 判断窗口是否属于目标进程
BOOL CALLBACK lpEnumFunc(HWND hwnd, LPARAM lParam) {EnumHWndsArg *pArg = (LPEnumHWndsArg)lParam;DWORD  processId;// 检索窗口线程标识符GetWindowThreadProcessId(hwnd,            // 窗口句柄&processId       // 接收 PID 的指针);// 如果这个 HWND 属于这个 PID ,则加入到 vecHWnds 数组末尾if (processId == pArg->dwProcessId) {pArg->vecHWnds->push_back(hwnd);}return TRUE;
}// 根据 PID 获取 HWND
void GetHWndsByProcessID(DWORD processID, std::vector<HWND> &vecHWnds) {EnumHWndsArg wi;wi.dwProcessId = processID;wi.vecHWnds = &vecHWnds;// 枚举所有顶级窗口EnumWindows(lpEnumFunc,       // 回调函数指针(LPARAM)&wi        // 传递给回调函数的值);
}CAudioManager::CAudioManager() {// CoUninitialize();//HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);//if(hr != S_OK){//    qDebug() << "CoInitializeEx Fail";//}CreateSessionManager();EnumSessions();
}CAudioManager::~CAudioManager() {m_audiosessioncontrol_list.clear();m_audiosessionevents_list.clear();//CoUninitialize();
}int CAudioManager::getEnumSessionsCount() {if (!m_psession_manager){return E_INVALIDARG;}HRESULT hr = S_OK;int cbSessionCount = 0;CComQIPtr <IAudioSessionEnumerator> pSessionList;CComQIPtr <IAudioSessionControl> pSessionControl;// Get the current list of sessions.CHECK_HR(hr = m_psession_manager->GetSessionEnumerator(&pSessionList));// Get the session count.CHECK_HR(hr = pSessionList->GetCount(&cbSessionCount));done:if (hr != S_OK) {return -1;}return cbSessionCount;
}HRESULT CAudioManager::getSessionNameList(std::vector<ProcessInfo>& vSessionName) {if (!m_psession_manager) {return E_INVALIDARG;}HRESULT hr = S_OK;int cbSessionCount = 0;CComQIPtr <IAudioSessionEnumerator> pSessionList;// Get the current list of sessions.CHECK_HR(hr = m_psession_manager->GetSessionEnumerator(&pSessionList));// Get the session count.CHECK_HR(hr = pSessionList->GetCount(&cbSessionCount));for (int index = 0; index < cbSessionCount; index++){CComQIPtr <IAudioSessionControl> pSessionControl;DWORD dwPid = 0;hr = pSessionList->GetSession(index, &pSessionControl);CComQIPtr<IAudioSessionControl2> pAudioSessionControl2(pSessionControl);if (pAudioSessionControl2 == NULL) {continue;}hr = pAudioSessionControl2->GetProcessId(&dwPid);if (hr != S_OK) {continue;}HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);std::vector<HWND> vecHWnds;   // 进程下的窗体句柄数组GetHWndsByProcessID(dwPid, vecHWnds);// 获取 HWND 窗口标题、窗口类名TCHAR szBuf_title[MAX_PATH];TCHAR szBuf_class[MAX_PATH];for (const HWND &h : vecHWnds) {//TCHAR szDisName[MAX_PATH] = { 0 };LPWSTR* szDisName;//::GetWindowModuleFileName(h, szDisName, MAX_PATH);//GetWindowText(// h,                  // 窗口句柄//   szBuf_title,        // 接收窗口标题的缓冲区指针//   MAX_PATH            // 缓冲区字节大小//);//GetClassName(// h,                  // 窗口句柄//   szBuf_class,        // 接收窗口类名的缓冲区指针//   MAX_PATH            // 缓冲区字节大小//);//if (wcscmp(szBuf_title, L"") == 0 || wcscmp(szBuf_title, L"") == 0)//   continue;// 获取进程图标//HICON hIcon = ::ExtractIcon(GetModuleHandle(NULL), process.szExeFile, 0);//if (hIcon != nullptr) {//  QPixmap pixmap = QtWin::fromHICON(hIcon);//}std::shared_ptr<TCHAR> szName = std::shared_ptr<TCHAR>(new TCHAR[MAX_PATH], std::default_delete<TCHAR>());DWORD nSize = 260;//GetProcessImageFileNameW(hProcess, szName.get(), nSize);::GetModuleFileNameEx(hProcess, NULL, szName.get(), MAX_PATH);// 获取应用程序信息SHFILEINFO sfi;ZeroMemory(&sfi, sizeof(SHFILEINFO));SHGetFileInfo(szName.get(), 0, &sfi, sizeof(SHFILEINFO), SHGFI_ICON);// 输出结果    //qDebug() << "szBuf_title = " << ConvertLPWSTRToLPSTR(szBuf_title);//qDebug() << "szBuf_class = " << ConvertLPWSTRToLPSTR(szBuf_class);//qDebug() << "--------------------------------------------" << endl;// 获取标题if (wcscmp(szBuf_class, L"") != 0) {ProcessInfo info;info.dwPid = dwPid;info.szName = szName;info.hIcon = sfi.hIcon;vSessionName.push_back(info);break;}}}done:return hr;
}HRESULT CAudioManager::getVolume(DWORD Pid, float& fLevel) {if (!m_psession_manager) {return E_INVALIDARG;}HRESULT hr = S_OK;int cbSessionCount = 0;CComQIPtr <IAudioSessionEnumerator> pSessionList;// Get the current list of sessions.CHECK_HR(hr = m_psession_manager->GetSessionEnumerator(&pSessionList));// Get the session count.CHECK_HR(hr = pSessionList->GetCount(&cbSessionCount));for (int index = 0; index < cbSessionCount; index++){CComQIPtr < IAudioSessionControl> pSessionControl;DWORD dwPid = 0;hr = pSessionList->GetSession(index, &pSessionControl);CComQIPtr<IAudioSessionControl2> pAudioSessionControl2(pSessionControl);if (pAudioSessionControl2 == NULL) {continue;}hr = pAudioSessionControl2->GetProcessId(&dwPid);if (hr != S_OK) {continue;}HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);TCHAR szName[MAX_PATH] = { 0 };LPSTR name;::GetModuleBaseName(hProcess, NULL, szName, MAX_PATH);CComQIPtr<ISimpleAudioVolume> pSimpleAudioVolume(pAudioSessionControl2);if (pSimpleAudioVolume == NULL) {continue;}//if (wcscmp(szName, sessionName) == 0)if(Pid == dwPid){CComQIPtr<ISimpleAudioVolume> pSimpleAudioVolume(pAudioSessionControl2);if (pSimpleAudioVolume == NULL) {continue;}hr = pSimpleAudioVolume->GetMasterVolume(&fLevel);break;}}done:return hr;
}HRESULT CAudioManager::setVolume(DWORD Pid, float fLevel) {if (!m_psession_manager){return E_INVALIDARG;}HRESULT hr = S_OK;int cbSessionCount = 0;CComQIPtr <IAudioSessionEnumerator> pSessionList;// Get the current list of sessions.CHECK_HR(hr = m_psession_manager->GetSessionEnumerator(&pSessionList));// Get the session count.CHECK_HR(hr = pSessionList->GetCount(&cbSessionCount));for (int index = 0; index < cbSessionCount; index++){CComQIPtr < IAudioSessionControl> pSessionControl;DWORD dwPid = 0;hr = pSessionList->GetSession(index, &pSessionControl);CComQIPtr<IAudioSessionControl2> pAudioSessionControl2(pSessionControl);if (pAudioSessionControl2 == NULL) {continue;}hr = pAudioSessionControl2->GetProcessId(&dwPid);if (hr != S_OK) {continue;}HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);TCHAR szName[MAX_PATH] = { 0 };LPSTR name;::GetModuleBaseName(hProcess, NULL, szName, MAX_PATH);CComQIPtr<ISimpleAudioVolume> pSimpleAudioVolume(pAudioSessionControl2);if (pSimpleAudioVolume == NULL) {continue;}//if (wcscmp(szName, sessionName) == 0)if(Pid == dwPid){CComQIPtr<ISimpleAudioVolume> pSimpleAudioVolume(pAudioSessionControl2);if (pSimpleAudioVolume == NULL){continue;}GUID guid;guid.Data1 = Pid;hr = pSimpleAudioVolume->SetMasterVolume(fLevel, &guid);break;}}done:return hr;
}HRESULT CAudioManager::getMute(DWORD Pid, int& mute) {if (!m_psession_manager) {return E_INVALIDARG;}HRESULT hr = S_OK;int cbSessionCount = 0;CComQIPtr <IAudioSessionEnumerator> pSessionList;// Get the current list of sessions.CHECK_HR(hr = m_psession_manager->GetSessionEnumerator(&pSessionList));// Get the session count.CHECK_HR(hr = pSessionList->GetCount(&cbSessionCount));for (int index = 0; index < cbSessionCount; index++){CComQIPtr < IAudioSessionControl> pSessionControl;DWORD dwPid = 0;hr = pSessionList->GetSession(index, &pSessionControl);CComQIPtr<IAudioSessionControl2> pAudioSessionControl2(pSessionControl);if (pAudioSessionControl2 == NULL) {continue;}hr = pAudioSessionControl2->GetProcessId(&dwPid);if (hr != S_OK) {continue;}HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);TCHAR szName[MAX_PATH] = { 0 };LPSTR name;::GetModuleBaseName(hProcess, NULL, szName, MAX_PATH);CComQIPtr<ISimpleAudioVolume> pSimpleAudioVolume(pAudioSessionControl2);if (pSimpleAudioVolume == NULL) {continue;}//if (wcscmp(szName, sessionName) == 0)if(Pid == dwPid){CComQIPtr<ISimpleAudioVolume> pSimpleAudioVolume(pAudioSessionControl2);if (pSimpleAudioVolume == NULL) {continue;}hr = pSimpleAudioVolume->GetMute(&mute);break;}}done:return hr;
}HRESULT CAudioManager::setMute(DWORD Pid, int mute) {if (!m_psession_manager) {return E_INVALIDARG;}HRESULT hr = S_OK;int cbSessionCount = 0;CComQIPtr <IAudioSessionEnumerator> pSessionList;// Get the current list of sessions.CHECK_HR(hr = m_psession_manager->GetSessionEnumerator(&pSessionList));// Get the session count.CHECK_HR(hr = pSessionList->GetCount(&cbSessionCount));for (int index = 0; index < cbSessionCount; index++){CComQIPtr < IAudioSessionControl> pSessionControl;DWORD dwPid = 0;hr = pSessionList->GetSession(index, &pSessionControl);CComQIPtr<IAudioSessionControl2> pAudioSessionControl2(pSessionControl);if (pAudioSessionControl2 == NULL) {continue;}hr = pAudioSessionControl2->GetProcessId(&dwPid);if (hr != S_OK) {continue;}HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);TCHAR szName[MAX_PATH] = { 0 };LPSTR name;::GetModuleBaseName(hProcess, NULL, szName, MAX_PATH);CComQIPtr<ISimpleAudioVolume> pSimpleAudioVolume(pAudioSessionControl2);if (pSimpleAudioVolume == NULL) {continue;}//if (wcscmp(szName, sessionName) == 0)if (Pid == dwPid){CComQIPtr<ISimpleAudioVolume> pSimpleAudioVolume(pAudioSessionControl2);if (pSimpleAudioVolume == NULL) {continue;}hr = pSimpleAudioVolume->SetMute(mute, NULL);break;}}done:return hr;
}HRESULT CAudioManager::CreateSessionManager()
{HRESULT hr = S_OK;// Create the device enumerator.CHECK_HR(hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),NULL, CLSCTX_ALL,__uuidof(IMMDeviceEnumerator),(void**)&m_pEnumerator));// Get the default audio device.CHECK_HR(hr = m_pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &m_pDevice));// Get the session manager.CHECK_HR(hr = m_pDevice->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL,NULL, (void**)&m_psession_manager));
done:return hr;
}HRESULT CAudioManager::EnumSessions()
{if (!m_psession_manager){return E_INVALIDARG;}HRESULT hr = S_OK;int cbSessionCount = 0;CComQIPtr <IAudioSessionEnumerator> pSessionList;// Get the current list of sessions.CHECK_HR(hr = m_psession_manager->GetSessionEnumerator(&pSessionList));// Get the session count.CHECK_HR(hr = pSessionList->GetCount(&cbSessionCount));for (int index = 0; index < cbSessionCount; index++){CComQIPtr <IAudioSessionControl> pSessionControl;CHECK_HR(hr = pSessionList->GetSession(index, &pSessionControl));std::shared_ptr<CAudioSessionEvents> AudioSessionEvent = std::make_shared<CAudioSessionEvents>();pSessionControl->RegisterAudioSessionNotification(AudioSessionEvent.get());m_audiosessionevents_list.push_back(AudioSessionEvent);m_audiosessioncontrol_list.push_back(pSessionControl);}done:return hr;
}

Windows API 获取所有进程音量并调节相关推荐

  1. java 线程不足_jvm - 如何在没有运行缺点的Windows上获取Java进程的线程和堆转储...

    jvm - 如何在没有运行缺点的Windows上获取Java进程的线程和堆转储 我有一个Java应用程序,我从控制台运行,然后控制台执行另一个Java进程. 我想获得该子进程的线程/堆转储. 在Uni ...

  2. C# 使用Windows API获取系统当前鼠标信息(图案)

    通过使用Windows API来获取当前鼠标的图案,不论是系统图片还是自定义图标都能够获取到,在这个示例中,为了方便测试,给Form1添加了一个KeyPress事件,在程序激活状态下,将鼠标移动到任意 ...

  3. Windows API 获取卷的基本信息

    通过对 Windows API 基础的一些学习,自己编写了一个可以读取卷的名称.文件系统类型.大小和剩余大小这些基本信息的小程序.程序很简单,却也受益匪浅. 定义的宏: #define BUFFSIZ ...

  4. windows api获取主板序列号

    微软官方给出api:GetSystemFirmwareTable GetSystemFirmwareTable function (sysinfoapi.h) - Win32 apps | Micro ...

  5. Windows API获取系统配置文件的配置参数

    在Windows平台下获取系统配置文件(如:System.ini)的配置参数. 系统配置文件System.ini的内容如下: [SYSTEM] ServiceIP = 10.128.11.99:600 ...

  6. Windows API获取屏幕真实大小

    在Petzold的书中写用GetDeviceCaps(hdc,LOGPIXELSX)得到屏幕DPI(dot per inch)计算真实大小,但我实际操作了发现不对.在Windows系统中,我得到LOG ...

  7. java 获取光标_如何在java中使用Windows API获取当前鼠标光标类型?

    这在Win7上对我很有用.该脚本使用Python,但应该很容易翻译成任何其他语言.当然,它仅在相应的应用程序不使用自定义游标时才有效: from win32con import IDC_APPSTAR ...

  8. C# 获得窗体句柄并发送消息(利用windows API可在不同进程中获取)

    C#使用Windows API获取窗口句柄控制其他程序窗口 编写程序模拟鼠标和键盘操作可以方便的实现你需要的功能,而不需要对方程序为你开放接口.比如,操作飞信定时发送短信等.我之前开发过飞信耗子,用的 ...

  9. OpenProcess | Process32First | GetExitCodeProcess - WINDOWS API 第二弹 非通信方式的子进程获取父进程信息

    OpenProcess | Process32First | GetExitCodeProcess - WINDOWS API 第二弹 进程相关 目录 一.得到父进程ID(Process32First ...

  10. Windows API用以实现屏幕截图的方法

    分成两个部分,一个是用getScrnBmp函数使用Windows API获取BMP图像,getJpg函数使用atlimage类实现将bmp压缩成jpg格式,注意不像jpeglib一样,压缩的出的jpg ...

最新文章

  1. (加强版)大数加减乘除,一文彻底搞定
  2. 软件工程 / 为什么基于接口而非实现编程?
  3. 2021年Java后端技术知识体系
  4. VTK:Rendering之AmbientSpheres
  5. mod_wsgi的工作模式和配置
  6. 活动页面html设计,活动查看页面.html
  7. android 方法参数默认值,Kotlin函数默认值的完全讲解
  8. R语言中,保留含有特定字符的元素
  9. IOS设计模式学习(7)单例
  10. 【MOS】Redundant Interconnect ora.cluster_interconnect.haip (文档 ID 1210883.1)
  11. matlab矩阵除法用python改写
  12. python选择时间窗口_对pandas中时间窗函数rolling的使用详解
  13. sublime text 批量删除空白行的方法
  14. 基于Python实现的微信好友数据分析
  15. 开关造成的毛刺_令人困扰的DAC输出毛刺消灭记
  16. 护理和计算机哪个专业好,护理专业考研的就业前景和方向
  17. 表格下拉选项怎么添加,开启快速添加模式
  18. 第十次 Java作业
  19. Le wagon编程训练营2021数据科学就业市场最全分析
  20. 题目1025:最大报销额(动态规划之01背包问题)

热门文章

  1. 使用文本编辑器来写Python代码
  2. Java一个简单的Employee类
  3. 全国多年太阳辐射空间分布数据1981-2022年、气温分布数据、蒸散量数据、蒸发量数据、降雨量分布数据、日照数据、风速数据
  4. android防丢器设计,基于单片机的防丢器设计
  5. DiskGenius屏蔽硬盘坏道方法
  6. php 简转繁体,PHP简体转繁体——MediaWiki-zhconvert
  7. innography专利搜索与分析数据库
  8. kafka no record information is available
  9. 用户与计算机的交互界面是什么,终于知道交互界面设计是什么
  10. Python翻译库 pygtrans 谷歌翻译