下载源代码

Microsoft© Active Accessibility 2.0 is a COM-based technology that improves the

way accessibility aids work with applications running on Microsoft Windows?. It

provides dynamic-link libraries that are incorporated into the operating system

as well as a COM interface and application programming elements that provide

reliable methods for exposing information about user interface elements.

基础
    Microsoft© Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。
    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。
    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。

Active Accessibility 原理
    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。
    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。
    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。
    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。
    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。

如何得到 IAccessible 接口指针
    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。
    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。
    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。
    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。
    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。
    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。
下面结合代码介绍一下它的用法。
我们来得到下面运行窗口的 IAccessible 接口指针。

图一

HWND hWndMainWindow;

IAccessible *paccMainWindow = NULL;

HRESULT hr;

//得到标题为"运行"的窗口的句柄

if(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))

{

MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);

}

else

{

//通过窗口句柄得到窗口的 IAccessible 接口指针。

if(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow,

OBJID_WINDOW,

IID_IAccessible,

(void**)&paccMainWindow)))

{

//……我们可以通过这个指针paccMainWindow进行操作。

paccMainWindow->Release();

}

}

现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!
    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……
    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:

图二

我们现在主要关注的信息是:Name、Role、Window className。

Name = "打开(O):"

Role = "可编辑文字"

Window className = "Edit"

当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。
    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。
下面我们开始取文本输入框的 IAccessible 接口指针。

IAccessible*           paccControl = NULL;//输入框的 IAccessible 接口

VARIANT                 varControl;                          //子ID。

FindChild( paccMainWindow,

"打开(O):",

"可编辑文字",

"Edit",

&paccControl,

&varControl )

第一个参数是先前得到的窗口 IAccessible 接口指针。
第二、三、四个参数分别是名字、角色、类。
后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。

BOOL FindChild (IAccessible* paccParent,

LPSTR szName, LPSTR szRole,

LPSTR szClass,

IAccessible** paccChild,

VARIANT* pvarChild)

{

HRESULT hr;

long numChildren;

unsigned long numFetched;

VARIANT varChild;

int index;

IAccessible* pCAcc = NULL;

IEnumVARIANT* pEnum = NULL;

IDispatch* pDisp = NULL;

BOOL found = false;

char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];

//得到父亲支持的IEnumVARIANT接口

hr = paccParent -> QueryInterface(IID_IEnumVARIANT, (PVOID*) & pEnum);

if(pEnum)

pEnum -> Reset();

//取得父亲拥有的可访问的子的数目

paccParent -> get_accChildCount(&numChildren);

//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。

for(index = 1; index <= numChildren && !found; index++)

{

pCAcc = NULL;

// 如果支持IEnumVARIANT接口,得到下一个子ID

//以及其对应的 IDispatch 接口

if (pEnum)

hr = pEnum -> Next(1, &varChild, &numFetched);

else

{

//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号

varChild.vt = VT_I4;

varChild.lVal = index;

}

// 找到此子ID对应的 IDispatch 接口

if (varChild.vt == VT_I4)

{

//通过子ID序号得到对应的 IDispatch 接口

pDisp = NULL;

hr = paccParent -> get_accChild(varChild, &pDisp);

}

else

//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口

pDisp = varChild.pdispVal;

// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc

if (pDisp)

{

hr = pDisp->QueryInterface(IID_IAccessible, (void**)&pCAcc);

hr = pDisp->Release();

}

// Get information about the child

if(pCAcc)

{

//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF

VariantInit(&varChild);

varChild.vt = VT_I4;

varChild.lVal = CHILDID_SELF;

*paccChild = pCAcc;

}

else

//如果子不支持IAccessible 接口

*paccChild = paccParent;

//跳过了有不可访问状态的元素

GetObjectState(*paccChild,

&varChild,

szObjState,

sizeof(szObjState));

if(NULL != strstr(szObjState, "unavailable"))

{

if(pCAcc)

pCAcc->Release();

continue;

}

//通过get_accName得到Name

GetObjectName(*paccChild, &varChild, szObjName, sizeof(szObjName));

//通过get_accRole得到Role

GetObjectRole(*paccChild, &varChild, szObjRole, sizeof(szObjRole));

//通过WindowFromAccessibleObject和GetClassName得到Class

GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));

//以上实现代码比较简单,大家自己看代码吧。

//如果这些参数与输入相符或输入为NULL

if ((!szName ||

!strcmp(szName, szObjName)) &&

(!szRole ||

!strcmp(szRole, szObjRole)) &&

(!szClass ||

!strcmp(szClass, szObjClass)))

{

found = true;

*pvarChild = varChild;

break;

}

if(!found && pCAcc)

{

// 以这次得到的子接口为父递归调用

found = FindChild(pCAcc,

szName,

szRole,

szClass,

paccChild,

pvarChild);

if(*paccChild != pCAcc)

pCAcc->Release();

}

}//End for

// Clean up

if(pEnum)

pEnum -> Release();

return found;

}

// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,

//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。

//将这些或数转换成相应的用逗号分割的状态字符串。

UINT GetObjectState(IAccessible* pacc,

VARIANT* pvarChild,

LPTSTR lpszState,

UINT cchState)

{

HRESULT hr;

VARIANT varRetVal;

*lpszState = 0;

VariantInit(&varRetVal);

hr = pacc->get_accState(*pvarChild, &varRetVal);

if (!SUCCEEDED(hr))

return(0);

DWORD dwStateBit;

int cChars = 0;

if (varRetVal.vt == VT_I4)

{

// 根据返回的状态值生成以逗号连接的字符串。

for (dwStateBit = STATE_SYSTEM_UNAVAILABLE;

dwStateBit < STATE_SYSTEM_ALERT_HIGH;

dwStateBit <<= 1)

{

if (varRetVal.lVal & dwStateBit)

{

cChars += GetStateText(dwStateBit,

lpszState + cChars,

cchState - cChars);

*(lpszState + cChars++) = '','';

}

}

if(cChars > 1)

*(lpszState + cChars - 1) = ''/0'';

}

else if (varRetVal.vt == VT_BSTR)

{

WideCharToMultiByte(CP_ACP,

0,

varRetVal.bstrVal,

-1,

lpszState,

cchState,

NULL,

NULL);

}

VariantClear(&varRetVal);

return(lstrlen(lpszState));

}

好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)

在 IAccessible 接口上执行动作
    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。
    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。
    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。

//在文本输入框输入"regedit"

if(1 == FindChild (paccMainWindow, "打开(O):",

"可编辑文字",

"Edit",

&paccControl,

&varControl))

{

//在这里修改文本编辑框的值

hr = paccControl->put_accValue(varControl,

CComBSTR("regedit"));

paccControl->Release();

VariantClear(&varControl);

}

// 找到确定按钮,并执行默认动作。

if(1 == FindChild (paccMainWindow,

"确定",

"按下按钮",

"Button",

&paccControl,

&varControl))

{

//这里执行按钮的默认动作,即"按下这个按钮"

hr = paccControl->accDoDefaultAction(varControl);

paccControl->Release();

VariantClear(&varControl);

}

现在,你会发现已经成功启动了注册表编辑器!!

模拟键盘和鼠标输入
    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。
    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。
    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
下面的代码将模拟 ALT+F4 按键来关闭窗口。

INPUT input[4];

memset(input, 0, sizeof(input));

//设置模拟键盘输入

input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;

input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;

input[1].ki.wVk  = input[3].ki.wVk = VK_F4;

// 释放按键,这非常重要

input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;

SendInput(4, input, sizeof(INPUT));

具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)

监视WinEvents
    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。
    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:

if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))

{

hEventHook = SetWinEventHook(

EVENT_MIN,            // eventMin ID

EVENT_MAX,           // eventMax ID

NULL,                      // always NULL for outprocess hook

WinCreateNotifyProc,                            // call back function

0,                                                          // idProcess

0,                                                          // idThread

// always the same for outproc hook

WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);

}

第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。
下面是回调函数:

void CALLBACK WinCreateNotifyProc(

HWINEVENTHOOK  hEvent,

DWORD   event,

HWND    hwndMsg,

LONG    idObject,

LONG    idChild,

DWORD   idThread,

DWORD   dwmsEventTime

)

{

if( event != EVENT_OBJECT_CREATE)

return;

char bufferName[256];

IAccessible *pacc=NULL;

VARIANT varChild;

VariantInit(&varChild);

//得到触发事件的 UI 元素的 IAccessible 接口/子ID对

HRESULT hr= AccessibleObjectFromEvent(hwndMsg,

idObject,

idChild,

&pacc,

&varChild);

if(!SUCCEEDED(hr))

{

VariantClear(&varChild);

return;

}

//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。

GetObjectName(pacc, &varChild, bufferName, sizeof(bufferName));

if(strstr(bufferName, szMainTitle))

PostThreadMessage(GetCurrentThreadId(),

WM_TARGET_WINDOW_FOUND,

0,

0);

return;

}

恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。

附录:

关于IAccessible 接口/子ID对:
    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!
    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。
    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。
    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!

注:
    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)
    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。

参考资料:

  • 1、 Dmitri Klementiev写的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》及其源程序。http://msdn.microsoft.com/msdnmag/issues/0400/aaccess/default.aspx
  • 2、 MSDN中的相关章节。

Active Accessibility相关推荐

  1. Qt利用avilib实现录屏功能_利用 dogtail 快速进行 GUI 自动化测试

    最近在协助测试小组做一些 GUI 方面的自动化测试,主要使用了 Python 中的 dogtail 框架,以及 Qt 中的 Accessibility 技术. 这个东西很有意思,可以让 GUI 的测试 ...

  2. 【转】激活 ActiveX 控件

    用户无法直接与 APPLET.EMBED 或 OBJECT 元素加载的 Microsoft ActiveX 控件交互. 用户激活这些控件的用户界面后才可以与这些控件交互. 本主题介绍了 Microso ...

  3. VS 2010 测试功能学习(八) - RnP与Coded UI测试(继续篇)

    题外话:CSDN博客不能上传图 片已经有一周多了,以前的博客内容中的图片仍然是"图片审核中..."状态,没关系,咱是中国网民,咱最大的优点就是能忍!图片不能传,咱继续发文字内容 的 ...

  4. Delphi通过IE窗口句柄获取网页接口(IWebBrowser2)

    主要用到的是MSAA(Microsoft Active Accessibility) 函数:ObjectFromLResult,该函数在动态链接库 oleacc.dll 中定义. uses SHDoc ...

  5. 无障碍开发(六)之ARIA在HTML中的使用规则

    ARIA使用规则一 如果你使用的元素( HTML5 )具有语义化,应该使用这些元素,而不应该重新定义一个添加ARIA的角色.状态或属性的元素. 浏览器的语义化标签已经默认隐含ARIA语义,像nav,a ...

  6. wxWidgets:wxAccessible类用法

    wxWidgets:wxAccessible类用法 wxWidgets:wxAccessible类用法 用法详细说明 wxWidgets:wxAccessible类用法 用法详细说明 #include ...

  7. UIAutomation 自动化

    Introduction UI Automation是Microsoft .NET 3.0框架下提供的一种用于自动化测试的技术,是在MSAA基础上建立的,MSAA就是Microsoft Active ...

  8. UIAutomation识别UI元素

    MS UI Automation(Microsoft User Interface Automation:UIA)是随.net framework3.0一起发布的,虽然在如今这个几乎每天都有各种新名词 ...

  9. jar 工程我怎么在网页上url访问某一个方法_搜狗用这个骚技术,把百度逼上了绝路。。。...

    点击上方[全栈开发者社区]→右上角[...]→[设为星标⭐] 前几天在百度搜索的时候,一不小心误点搜索候选词,给我跑到搜狗搜索里面去了,索性花了点时间分析一下这其中的猫腻,不看不知道,一看吓一跳. 在 ...

最新文章

  1. 【题解】 CF718C Sasha and Array
  2. 四则运算个人项目反思总结
  3. netty系列之:内置的Frame detection
  4. 入门第十一课 Python语句的嵌套
  5. [Python]网络爬虫(12):爬虫框架Scrapy的第一个爬虫示例入门教程
  6. 韩顺平php视频笔记75-76 抽象类 接口
  7. JQueryEasyUI学习笔记(一)
  8. 玩转JavaScript OOP[0]——基础类型
  9. Spring基于注解及SpringMVC
  10. 初级程序员面试不靠谱指南(四)
  11. 2021-05-31 GSM模块 SIM800A 使用说明
  12. 用计算机解决线性代数,线性代数问题计算机应用.doc
  13. 领导含泪叮嘱我:MySQL 建表字段记得用 not null,不然就收拾包袱滚蛋
  14. 设计一个O(n2)时间的算法, 找出由n个数组成的序列的最长单调递增子序列。
  15. 猿创征文|我命由我,不由天
  16. C#合并多个pdf到一个pdf文件;不使用Aspose.pdf.dll,避免水印
  17. OSX + iTerm2 + vim + ssh + CentOS/本机配色原理小分析
  18. 集合有哪些 java_java集合有哪些
  19. Web1.0时期进入Web3.0时代,即将跨入Web4.0时代
  20. 小程序流量主能赚多少_微信:6月1日起调整小程序和公众号流量主广告收入分成比例...

热门文章

  1. 什么是终身学习(Life Long Learning / Continuous Learning / Never Ending Learning / Incremental Learning)
  2. t20天正建筑软件服务器为空,天正建筑T20常见问题(四)
  3. 使用Cisco Packet Tracer之图解PDUs的使用
  4. Linux环境下制作启动U盘
  5. 使用golang解压带有文件夹的tgz文件
  6. murmurhash2 Python实现(复现开源项目主页上的 Murmurhash2)
  7. Windows下启动Redis失败,报错creating server tcp listening socket 127.0.0.1:6379: bind No error
  8. 【微积分的本质|笔记】绪论——微积分中的核心思想
  9. 云计算基础服务(四) http协议--虚拟主机
  10. 手动制做字体子集(字体文件裁剪、抽取、提取,特别适用处理少量繁体异体字)