cef js返回c++的代碼_CEF3開發者系列之外篇——IE中JS與C++交互

使用IE內核開發客戶端產品,系統和前端頁面之間的交互,通常給開發和維護帶來很大的便利性。但操作系統和前端之間的交互卻是比較復雜的。具體來說就是腳本語言和編譯語言的交互。在IE內核中html和css雖然不兼容,但是IE編程接口是完全一樣的,這得益于微軟的COM組件的結構化設計和實現。所以與IE交互,必須得先說一下COM,COM全稱組件對象模型(Component Object Model)。

COM的基本思想很簡單,所有的組件模塊都提供一個最根本的接口, IUnkown,它有三個方法,AddRef和Release實現了引用計數,QueryInterface實現了根據接口id查詢另外的接口,所有的接口都從IUnkown派生。基于IE內核做開發,有一個接口是最關鍵的,IDispatch(欲知詳情移步IDispatch接口 - GetIDsOfNames和Invoke)。 IDispatch接口是COM自動化的核心。其實,IDispatch這個接口本身也很簡單,只有4個方法:最關鍵的兩個方法Invoke和 GetIDsOfNames。腳本語言和編譯型語言之間進行通信是通過IDispatch接口來行的。下面看一下這個關鍵的方法的原型:

IDispatch:public IUnkown

{

//...

HRESULT Invoke( DISPID dispIdMember,REFIID riid, LCID lcid,WORD wFlags,DISPPARAMS FAR* pDispParams,VARIANT FAR* pVarResult, EXCEPINFO FAR* pExcepInfo, unsigned int FAR* puArgErr );

//...

}

這個方法每個參數的意義msdn上有詳細的闡述,就我的理解而看,它作為一個組件, 向外提供了一個萬能接口,據此可以實現兩個很有用的功能:

1. 獲取和設置組件的屬性變量. 對應wFlags的DISPATCH_PROPERTYGET和DISPATCH_PROPERTYPUT

2.以任意參數調用任意一個被支持的方法. 對應wFlags的DISPATCH_METHOD

用面向對象的觀點來看,有了這兩個功能,任意一個實現該接口的組件就抽象成同一個接口實現的萬能對象,可以通過指定的字符串名字獲取設置屬性和調用方 法。如果有過腳本編程經驗開發人員一定會發現,腳本恰恰就是如此。c++書寫代碼的時候也是通過名字訪問對象,但編譯好后變成二進制代碼后就沒有名字的概念了,只有偏移和地址。而腳本里頭書寫代碼的時候是通過名字,解釋執行的時候也是通過名字。腳本和native語言的最大區別是腳本對象的所有屬性和方法 是動態的,在執行的時候還可以修改。看到這里,很容易聯想到實現了IDispatch的組件對象具有了腳本的特性,c++對象被腳本化了!這就意味著你可 以把原來用 c++寫的類的所有屬性和方法都通過Invoke來執行,在腳本里頭可以直接訪問!相當于給腳本增加了native的擴展。IDispatch接口很重要 的一個功能就是如此,微軟通常所說的雙接口就是這個意思。了解了這些,接下來要和JS腳本交互就比較容易了。

既然是IE內核里的JS與C++互相調用,我們先來簡單的了解下IE內核編程需要的幾個常用接口。說多了不好理解,先來看圖。

IWebBrowser2,

IHTMLWindow2,IHTMLDocument2 這三個常用接口都是從IDispatch

派生的。IWebBrowser2接口里主要提供瀏覽器常規功能如打開URL、前進、后退等功能。IHTMLWindow2主要是提供接口操作瀏覽器中打

開的window對象,IHTMLDocument2獲取文檔相關信息,以及審查和修改HTML元素和文檔中文本,包括獲取JS對象。

IHTMLWindow2

對應于一個window的視圖,IHTMLDocument2是IHTMLWindow2渲染文檔,對應著dom樹結構。在js中有兩個全局對象

window和document,分別對應著IHTMlWindow2和 IHTMLDocument2。

想要詳細了解這些,參看如下資料:

要完成c++和js交互,可以分解成兩個任務,一是c++調用js代碼;二是js調用c++代碼,這其實也所有腳本和natvie交互的兩個基本任務。本文主要根據自己的理解從設計開發的角度去闡述為什么要這么做。

C++調用JS

每段js執行代碼都有它自己的執行環境,在IE里面可以看做是IHTMLWindow2。

據上邊所講,我們用先獲取全局對象document,從document變成的IDispatch接口中得到

IHTMLDocument2,IHTMLDocument2接口的get_Script方法獲取到了HTML文檔中JS代碼的IDispatch接口,

我們用IDispatch接口,把HTML文檔中的JS代碼當作一個COM對象,對他進行操作。

CComPtr GetScript()

{

CComPtr spWebBrowser;

HRESULT hResult = QueryControl(IID_IWebBrowser2, (void**)&spWebBrowser);

if (SUCCEEDED(hResult))

{

CComPtr spDocDisp;

hResult = spWebBrowser->get_Document(&spDocDisp);

if (SUCCEEDED(hResult))

{

CComPtr spDocDisp2;

hResult = spDocDisp->QueryInterface(IID_IHTMLDocument2, (void**)&spDocDisp2);

if (SUCCEEDED(hResult))

{

CComPtr spScript;

hResult = spDocDisp2->get_Script(&spScript);

if (SUCCEEDED(hResult))

{

return spScript;

}

}

}

}

}

有兩種方案可以執行JS,一種是直接調用IHTMLWindow2的execScript方法.

HRESULT?execScript(?BSTR?code,? ? BSTR?language,?VARIANT?*pvarRet);

代碼示例:

wstring strJavaScript;

CComVariant pvarRet;

CComBSTR bstrJavaScript(strJavaScript.c_str());

CComBSTR bstrScriptType(_T("javascript"));

CComQIPtr spWindow2(spScriptDisp);

spWindow2->execScript(bstrJavaScript, bstrScriptType, &pvarRet);

要看懂這段代碼不難,我們先來了解下CComQIPtr,用IDispatch接口調用COM對象的各種方法、設置與獲取COM對象的屬性、讓COM對象 回調我們,都是用IDispatch的Invoke方法來實現。一個Invoke就要實現那么多功能,用起來當然很麻煩。不過好在ATL智能指針類中的 CComDispatchDriver(即CComQIPtr)封裝了IDispatch接口,使用起來非常方便!先拿到IHTMLWindow2接口的智能指針,直接把js代碼環境IDispatch指針的賦值給它。不過注意這里是BSTR的字符串,可以用 SysAllocString來分配。

第二種方案同樣是使用IHTMLDocument的get_Script()方法。它能得到一個IDispatch指針,這個IDispatch就是IHTMLDocument里的JS。按照前面介紹的IDispatch的使用,你通過它就可以調用任意js函數了。例如要執行一個 js中的函數 function。

CComPtr spDocDisp2;

spDocDisp2->get_Script(&spScript);

OLECHAR * Names= L"function" ;

DISPID dispID=0;

//先獲取接受調度標示符DISPID,需要調用GetIDsOfNames來獲取

spScript->GetIDsOfNames(IID_NULL,&Names,1,LOCALE_SYSTEM_DEFAULT, &dispID);

//通過Invoke(援引)方法調用JS方法

spScript->Invoke(dispID,,IID_NULL ,LOCALE_SYSTEM_DEFAULT,DISPATCH_METHOD,NULL,NULL,NULL,NULL);

這里function是js里面的一個全局函數。這里可以看到 Invoke并沒有直接把字符串名字拿過來用,而是通過另一個方法GetDispofNames做了一個映射,獲取接受調度標示符DISPID。通過 IHTMLDocument得到的script接口對應著該頁面的JS全局環境,從中可以通過多次invoke得到任意一個全局變量,函數,從而能夠得到對象的成員變量或成員方法。

第二種方案就是通過Invoke調用來實現在c++中存取js變量和調用函數。這和第一種方案的區別很明顯,一個是在用c++寫js代碼,有點類似自己在解析執行js了,而前者更簡單,再復雜的js調用序列,一個字符串全部搞定。

要做到c++和腳本交互有一個基本的問題要做好,就是腳本中的數據類型和c++中的數據類型如何對應起來。眾所周知,js中有很多類型,Boolean, Number, String, Object, Array , Function等。寫到這里,插一句,基本所有的語言里頭都有字符串和數字這兩種基本的數據類型(c/c++中僅為以\0結尾的字符數組),面向對象的 語言中還會有Object這樣的復合數據類型。在Invoke調用參數中, VARAINT就代表了c中的基本數據類型,js中的數字會轉換成VT_I4或者VT_R4或VT_R8。字符串會轉換成VT_BSTR類型的 bstr(這是微軟com標準里使用的字符串類型),其他所有的復合類型包括對象數組函數在c中都對應著VT_DISPATCh的一個IDispatch 指針。有了IDispatch指針,你就可以按照前面的方法任意存取對象的屬性,也可以發起函數調用并獲得返回值。了解了這些,就可以進行c與js的交互 了,它們都通過IDispatch的invoke調用來完成。CComDispatchDriver對GetIDsOfNames和Invoke進一步進 行了封裝,只需更少的參數即方便可調用。

Invoke0??? //調用0個參數的方法

Invoke1??? //調用1個參數的方法

Invoke2??? //調用2個參數的方法

InvokeN??? //調用多個參數的方法

說了這么多,估計有些人看得云里霧里的。下邊直接給出例子:

我們動手寫一個HTML,其中包含這樣一段JS代碼:

function Add(value1, value2) {

return value1 + value2;

}

然后我們用WebBrowser加載這個HTML后,在VC中這樣來調用這個函數名為Add的JS函數:

//別忘了#include

//m_WebBrowser是一個WebBrowser的Activex控件對象。

CComQIPtr spDoc = m_WebBrowser.get_Document();

CComDispatchDriver spScript;

spDoc->get_Script(&spScript);

CComVariant var1 = 10, var2 = 20, varRet;

spScript.Invoke2(L"Add", &var1, &var2, &varRet);

spScript.Invoke2的作用是調用JS函數中名為Add的函數,傳入兩個參數,用varRet接收返回值。Invoke2調用成功后,varRet得到了返回值30。

但這樣的話一次只能接受一個返回值。如果要一次接受多個返回值的話,怎么辦呢?

我們可以讓JS返回一個JS中的Array數組或Object對象。

當 JS函數return一個Array或一個Object對象時,VC這邊的 varRet將接受到一個代表該對象的IDispatch接口。我們仍然用CComDispatchDriver來管理這個IDispatch。 CComDispatchDriver有四個方法:

GetProperty

GetPropertyByName

PutProperty

PutPropertyByName

來從這個Array或Object對象中取出我們要的數據。

實踐是檢驗真理的唯一標準,讓我們再來寫一個JS函數:

function Add(value1, value2) {

var array = new Array();

array[0] = value1;

array[1] = value2;

array[2] = value1 + value2;

return array;

}

然后在VC中這樣寫:

CComQIPtr spDoc = m_WebBrowser.get_Document();

CComDispatchDriver spScript;

spDoc->get_Script(&spScript);

CComVariant var1 = 10, var2 = 20, varRet;

spScript.Invoke2(L"Add", &var1, &var2, &varRet);

CComDispatchDriver spArray = varRet.pdispVal;

//獲取數組中元素個數,這個length在JS中是Array對象的屬性

CComVariant varArrayLen;

spArray.GetPropertyByName(L"length", &varArrayLen);

//獲取數組中第0,1,2個元素的值:

CComVariant varValue[3];

spArray.GetPropertyByName(L"0", &varValue[0]);

spArray.GetPropertyByName(L"1", &varValue[1]);

spArray.GetPropertyByName(L"2", &varValue[2]);

可以看到,10,20,30,這三個JS函數返回的值已經躺在我們的varValue[3]里了。

當然,如果不知道JS返回的Array對象里面有幾個元素,我們可以在VC這邊獲取它的length屬性,然后在一個循環中取出數組中的每個值。

如果我們的JS函數返回一個包含有多個屬性值的Object對象,VC這邊該如何接收呢?

讓我們再來寫一個JS函數:

function Add(value1, value2) {

var data = new Object();

data.result = value1 + value2;

data.str = "Hello,World!";

return data;

}

然后在VC中我們這樣接收:

CComQIPtr spDoc = m_WebBrowser.get_Document();

CComDispatchDriver spScript;

spDoc->get_Script(&spScript);

CComVariant var1 = 10, var2 = 20, varRet;

spScript.Invoke2(L"Add", &var1, &var2, &varRet);

CComDispatchDriver spData = varRet.pdispVal;

CComVariant varValue1, varValue2;

spData.GetPropertyByName(L"result", &varValue1);

spData.GetPropertyByName(L"str", &varValue2);

我 們從JS返回的Object對象里取出了它的兩個屬性,result和str,分別是一個整形數據和一個字符串。這里JS代碼是我們自己寫的,在VC這邊 當然事先知道這個JS函數返回的對象有result和str這兩個屬性。如果JS代碼不是我們寫的,或者它的屬性是事先不能確定的,該怎么辦呢?答案是使用IDispatchEx接口來枚舉這個對象的相關信息(方法名、屬性名)。

C++調用JS的實例演示到此為止。

js調用c++代碼

按照前面所說的IDispatch的用途,就可以推斷出如何做到這一點了,自定義一個c++類,實現一個IDispatch的接口,把它的指針通過某次 js調用作為返回值返回給js,那么js代碼中就持有該對象了,就可以像使用普通js對象一樣的使用它。問題是,一開始js啥都沒有,怎么直接調到c++ 里頭從而返回c++對象呢?IE已經考慮好了這個問題,它對于每個IWebbrowser2實例(頂層)有一個內置的IDispatch對象,該對象可以 在創建瀏覽器控件實例之后在c++中自己制定,而在js中則使用window.external來訪問。也就是說每個js環境都已經內置了一個全局對象 external,并且它對應的c++中的IDispatch可以由程序員自己指定。下面談一下如何來設置這個對象實例。

在windows中要自己host一個active控件,如果用sdk自己寫。其中有一個接口叫IDocHostUIHandler ,它有一個方法GetExternalDisp用以向宿主查詢一個IDispatch對象,就直接對應著js中的external腳本對象。 IDocHostUIHandler 還有一個有用的方法ShowContextMenu,當要show菜單的時候這個方法會被回調,應用程序就可以自定義菜單了。MFC也可以很方便的 host一個IE控件,但它的類庫太龐大了,幸虧微軟又出了ATL,提供了一個輕量級的方法讓你可以達到同樣的效果。下面直接貼代碼片段.

class CWebBrowser : public CAxHostWindow

{

private:

CComPtr m_pWebBrowser; //保存創建出來的瀏覽器控件實例

BEGIN_MSG_MAP(CWebBrowser)

MESSAGE_HANDLER(WM_CREATE,OnCreate)

CHAIN_MSG_MAP(CAxHostWindow)

END_MSG_MAP()

LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)

{

// Create WebBrowser object

LPOLESTR pName=NULL;

StringFromCLSID(CLSID_WebBrowser,&pName);

CComPtrdisp;

CComPtr p;

_InternalQueryInterface(IID_IDispatch,(void**)&disp);

// 創建 WebBrowser

CreateControlEx(pName,m_hWnd,NULL,&p,DIID_DWebBrowserEvents2,disp);

CoTaskMemFree(pName);

// 查詢IWebBrowser2 接口,用于控制

HRESULT hRet = QueryControl(IID_IWebBrowser2, (void**)&this->m_pWebBrowser);

return m_pWebBrowser?S_OK:-1;

}

}

CWebBrowser 是用戶自己的宿主窗口,在它的OnCreate里頭創建com對象,一個瀏覽器窗口就出來了,這個代碼是不是很簡潔?CAxHostWindow為我們做 了很多事情,包括IDocHostUIHandler也被實現,所以我們從它派生就天然的擁有了很多控制IE控件的能力,當然都是通過com接口來完成 的。以后如果有定制需求,大可重寫父類的虛函數來達到目的。CAxHostWindow還封裝了一個方法SetExternalDispatch,到這里 一切都可以暫時告一段落了,你可以在CWebBrowser中實現IDispatch也可以單獨用一個類來實現,然后把IDispatch接口設進去就可 以了。有興趣研究這個寄宿控件過程的童鞋們可以看CAxHostWindow的代碼實現,全在一個頭文件中。

假設你的external提供了一個函數創建對象 ? function newMyObject,在js中

var newObject=window.external.newMyObject(); //通過external構建一個c++對象交給js持有

alert(newObject.name); ? ? ? //訪問該對象的屬性

alert(newObject.GetValue()) ?//調用該對象的方法

那么你需要做的事情其實還是關注Invoke就可以了.在external的IDispatch的Invoke實現中

STDMETHODIMP CWebBrowserDisp::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, unsigned int* puArgErr)

{

HRESULT nRet = S_OK;

if(wFlags&DISPATCH_METHOD) //屬于方法調用

{

//給newMyObject分配的id,字符串名字映射

if(dispIdmember== DISPID_newMyObject)

{

IDispatch* pMyObject=NULL;

//創建c++對象并獲取其IDispatch接口

CreateMyObject(&pMyObject);

pVarResult->vt=VT_DISPATCH;

pVarResult->pdispVal=pMyObject; //作為返回值傳遞給js

}

}

return 0;

}

這個代碼也很簡潔。據此可以看出,要把c++對象導出到js中,那么該對象必須要實現IDispatch接口,只需要把這個接口作為Invoke的返回值 傳給js即可。它有引用計數,不必擔心內存的釋放問題,在js的垃圾回收被觸發的某個時刻自然會被銷毀。接下來,MyObject有哪些屬性和方法可以被 js調用,那就又歸它自己的IDispatch的Invoke實現來關心了。

另外一種就是在webbrowser控件中,JS調用C++方法。如果你對webbrowser控件熟悉的話,這里使用起來就更簡單了。Invoke接口實現基本上和上邊的類似,唯一不同的是如何讓JS調用到本地的C++ 代碼。在JS代碼中創建了函數window.external.newMyObject()。頁面渲染時,會觸發瀏覽器的GETEXTERNAL事件,在瀏覽器中,通過消息過濾,當消息為WN_GETEXTERNAL時,通過IDispatch接口,獲取JS代碼需要調用的類。

IDispatch **ppDispatch = (IDispatch**)wParam;

*ppDispatch = &m_superCall;

綜上所述,在IE中和c++與js交互,IDispatch扮演了很重要的角色,理解好了它你就可以隨心所欲的c++和js的混合編程了。COM接口很不容易理 解,知道怎么用,卻難以了解其內部機制。其實,在前面所講的過程中,IDispatch是自己的代碼創建的,和系統完全無關。從c++的語法看,它就是繼 承了一個虛基類,實現其全部方法而已,還有就是引用計數。所以,我們完全可以用很簡單的c++代碼來寫自己的IDispatch,不必去理會那么多的COM特性。js執行環境總是在主線程,所以你要知道一點你的對象的方法也總是在主線程被調用。下邊給出簡單的實現代碼:

#include "StdAfx.h"

#include "SQSuperCall.h"

CJSCallC::CJSCallC(void)

{

m_mapFunction[TEXT("FuncTest")] = DISPID_FuncTest;

}

CJSCallC::~CJSCallC(void)

{

}

HRESULT STDMETHODCALLTYPE CJSCallC::GetIDsOfNames(

/* [in] */ __RPC__in REFIID riid,

/* [size_is][in] */ __RPC__in_ecount_full(cNames) LPOLESTR *rgszNames,

/* [range][in] */ UINT cNames,

/* [in] */ LCID lcid,

/* [size_is][out] */ __RPC__out_ecount_full(cNames) DISPID *rgDispId)

{

HRESULT hr = NOERROR;

for (UINT nIndex = 0; nIndex < cNames; ++nIndex)

{

wstring strFuntion = rgszNames[nIndex];

map::iterator iter = m_mapFunction.find(strFuntion);

if (m_mapFunction.end() != iter)

{

rgDispId[nIndex] = iter->second;

}

else

{

hr = ResultFromScode(DISP_E_UNKNOWNNAME);

rgDispId[nIndex] = DISPID_UNKNOWN;

}

}

return hr;

}

/* [local] */ HRESULT STDMETHODCALLTYPE CJSCallC::Invoke(

/* [in] */ DISPID dispIdMember,

/* [in] */ REFIID riid,

/* [in] */ LCID lcid,

/* [in] */ WORD wFlags,

/* [out][in] */ DISPPARAMS *pDispParams,

/* [out] */ VARIANT *pVarResult,

/* [out] */ EXCEPINFO *pExcepInfo,

/* [out] */ UINT *puArgErr)

{

if (dispIdMember == DISPID_FuncTest)

{

int paramsCount = pDispParams->cArgs;

if (paramsCount < 2)

return S_FALSE;

VARIANTARG* cmdVar = (VARIANTARG*)(&pDispParams->rgvarg[paramsCount - 1]);

if (!(cmdVar->vt == VT_I4 || cmdVar->vt == VT_BSTR))

return S_FALSE;

int nCmdId = cmdVar->intVal;

cmdVar = (VARIANTARG*)(&pDispParams->rgvarg[paramsCount - 2]);

if( cmdVar->vt != VT_BSTR )

return S_FALSE;

CString csInfos = cmdVar->bstrVal;

wstring strInfos(csInfos);

}

return S_OK;

}

HRESULT STDMETHODCALLTYPE CJSCallC::QueryInterface(

/* [in] */ REFIID riid,

/* [iid_is][out] */

__RPC__deref_out void **ppvObject)

{

//*ppvObject = NULL;

if (riid == IID_IUnknown)

{

*ppvObject = static_cast(this);

}

else if (riid == IID_IDispatch)

{

*ppvObject = static_cast(this);

}

else

{

return E_NOINTERFACE;

}

return S_OK;

}

參考文檔:

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/453838.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/453838.shtml
英文地址,請注明出處:http://en.pswp.cn/news/453838.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

多線程編程指南 part 2

多線程編程指南Sun Microsystems, Inc.4150 Network CircleSanta Clara, CA95054U.S.A.文件號碼819–7051–102006 年10 月版權所有2005 Sun Microsystems, Inc. 4150 Network Circle, Santa Clara, CA95054 U.S.A. 保留所有權利。本文檔及其相關產品的使用、復制、分發和反編譯…

00030_ArrayList集合

1、數組可以保存多個元素&#xff0c;但在某些情況下無法確定到底要保存多少個元素&#xff0c;此時數組將不再適用&#xff0c;因為數組的長度不可變 2、JDK中提供了一系列特殊的類&#xff0c;這些類可以存儲任意類型的元素&#xff0c;并且長度可變&#xff0c;統稱為集合 3…

1.3tf的varible\labelencoder

1.tf的varible變量 import tensorflow as tf #定義變量--這里是計數的變量 statetf.Variable(0,namecounter) print (state.name) #輸出變量值 onetf.constant(1) #常量new_valuetf.add(state,one) updatetf.assign(state,new_value)#初始化所有變量 inittf.initialize_all_var…

kafka 日志相關配置

日志目錄 ${kafka.logs.dir}/server.log &#xff1a;服務器日志${kafka.logs.dir}/state-change.log&#xff1a;狀態變化日志${kafka.logs.dir}/kafka-request.log&#xff1a;請求處理日志${kafka.logs.dir}/log-cleaner.log&#xff1a;日志清理${kafka.logs.dir}/control…

linux mc服務器 mod_如何在linux搭建MC服務器

摘要&#xff1a;前言Q&#xff1a;為什么要用linux搭建MC服務器&#xff1f;A&#xff1a;因為linux系統占用率低&#xff0c;跑服務器效果非常好Q&#xff1a;看不懂linux ssh&#xff0c;后期自己無力運維怎么辦A&#xff1a;沒關系&#xff0c;這個教程是通過安裝BT...前言…

flask-SQLAlchemy 使用 session.commit() 處理異常回滾

最為原始的try/except辦法&#xff0c;多次插入數據就要寫多次&#xff0c;很麻煩&#xff0c;使用python原生的contextlib.contextmanager簡化代碼&#xff01; try:user_db User(emailself.email, nicknameself.nickname, passwordself.password)db.session.add(user_db)#所…

小米機器人清理主刷和軸承_掃地機主刷怎樣更換

原標題&#xff1a;掃地機主刷怎樣更換隨著電動掃地機逐漸取代手動清潔&#xff0c;電動掃地機的工作量不斷增加&#xff0c;從而導致主刷磨損更加嚴重&#xff0c;并增加了主刷更換的次數。因此&#xff0c;偉頓在這里告訴大家掃地機主刷怎樣更換&#xff1a;1 .首先&#xff…

多線程編程指南

1. 多線程編程指南1--線程基礎 線程編程指南1--線程基礎 Wednesday, 29. March 2006, 11:48:45 多線程 本文出自:BBS水木清華站 作者:Mccartney (coolcat) (2002-01-29 20:25:25) multithreading可以被翻譯成多線程控制。與傳統的UNIX不同&#xff0c;一個傳統 的UNIX進…

路由器和貓的區別

路由器和貓的區別 網絡在我們現在生活中必不可少,我們鏈接互聯網經常需要用到貓和路由器,但是依然有很多菜鳥根本不知道什么是貓什么是路由器,至于貓和路由器怎么使用就更不知道了,下面給大家詳細的講解下路由器和貓的區別。 路由器和貓的用途和鏈接位置不一樣,如下圖: 路由器:…

Docker 精通之 docker-compose

1、Docker-compose簡介 1.1、ocker-compose簡介 Docker-Compose項目是Docker官方的開源項目&#xff0c;負責實現對Docker容器集群的快速編排。   Docker-Compose將所管理的容器分為三層&#xff0c;分別是工程&#xff08;project&#xff09;&#xff0c;服務&#xff08…

json數據格式 穗康碼_Json數據格式

JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式。JSON采用完全獨立于語言的文本格式&#xff0c;這些特性使JSON成為理想的數據交換語言。易于人閱讀和編寫&#xff0c;同時也易于機器解析和生成。JSON 比 XML 更小、更快&#xff0c;更易解析。JSON建構于兩種結…

Linux 系統版本查詢

顯示Linux版本信息輸入"cat /proc/version",說明正在運行的內核版本。輸入"cat /etc/issue", 顯示的是發行版本信息。輸入"lsb_release -a ",可對多個linux版本適用。輸入"uname -a ",可顯示電腦以及操作系統的相關信息。轉載于:https…

kafka 命令行命令大全

kafka 腳本 connect-distributed.sh connect-mirror-maker.sh connect-standalone.sh kafka-acls.sh kafka-broker-api-versions.sh kafka-configs.sh kafka-console-consumer.sh kafka-console-producer.sh kafka-consumer-groups.sh kafka-consumer-perf-test.sh kafka-dele…

kotlin將對象轉換為map_Kotlin程序將哈希映射(HashMap)轉換為列表(List)

Kotlin程序將哈希映射(HashMap)轉換為列表(List)在此程序中&#xff0c;您將學習在Kotlin中將map轉換為列表的不同方法。示例&#xff1a;將map轉換為列表示例import java.util.ArrayListimport java.util.HashMapfun main(args: Array) {val map HashMap()map.put(1, "a…

零元學Expression Blend 4 - Chapter 4元件重復運用的觀念

零元學Expression Blend 4 - Chapter 4元件重復運用的觀念 原文:零元學Expression Blend 4 - Chapter 4元件重復運用的觀念本章將教大家Blend元件重復運用的觀念&#xff0c;這在Silverlight設計中是非常重要的&#xff0c;另外加碼贈送漸層工具(Gradient Tool)。 ? 本章將教…

Python 內置模塊之 ConfigParser - 解析 ini 文件

ini配置文件是被configParser直接解析然后再加載的&#xff0c;如果只是修改配置文件&#xff0c;并不會改變已經加載的配置 INI文件結構簡單描述 INI文件就是擴展名為“ini”的文件。在Windows系統中&#xff0c;INI文件是很多&#xff0c;最重要的就是“System.ini”、“Sy…

電腦老是彈出vrvedp_m_出現三個可疑進程vrvedp_m.exe?vrvrf_c.exe?vrvsafec.exe

滿意答案 你機器里裝了北信源的DeviceRegist軟件,這個軟件不是殺毒軟件或者防毒軟件,而是一個遠程桌面管理軟件。這類軟件其實和木馬程序原理上一樣,只不過是正規軟件公司開發的,但是流氓程度不容小覷,即使在安全模式下也會加載vrvrf_c.exe,vrvedp_m.exe,vrvsafec.exe,wat…

音視頻編解碼 文件格式 協議內容詳解

編解碼學習筆記&#xff08;一&#xff09;&#xff1a;基本概念 媒體業務是網絡的主要業務之間。尤其移動互聯網業務的興起&#xff0c;在運營商和應用開發商中&#xff0c;媒體業務份量極重&#xff0c;其中媒體的編解碼服務涉及需求分析、應用開發、釋放license收費等等。最…

git 拉取遠程其他分支代碼_【記錄】git 拉取遠程分支代碼,同步到另一個git上...

最近有需求從某git 上拉取所有分支代碼同步到另一git上&#xff0c;現記錄操作步驟&#xff0c;以便日后使用&#xff1a;1&#xff1a;先克隆其中一個分支代碼到本地環境git clone -b test http://賬號:密碼XXX.git2&#xff1a;查看本地分支git brach3&#xff1a;查看遠程分…

WIN下的CMD下載命令

certutil -urlcache -split -f 遠程地址 本地保存的文件跑徑與文 件名 # 如里不寫本地文 件名與路徑名&#xff0c; 會自動跟遠程文 件名相同&#xff0c; 并保存到當前目 錄下另一個是&#xff1a; bitsadmin /rawreturn /transfer getfile http://download.sysinternals.com…