首页
/
每日頭條
/
生活
/
vba代碼與vb代碼的區别
vba代碼與vb代碼的區别
更新时间:2024-10-12 01:21:52

VB一直以來被認為有以下優缺點:優點是上手快、開發效率高;缺點是能力有限,運行效率低。這正是有些軟件把VB做為首選語言,而有些軟件肯定不會用VB做的原因。而很多VC,DELPHI的程序員都認為VB裡搞開發不自由,它讓我們做事變容易的同時,也讓我們發揮的餘地越來越小。的确,簡單和功能強大這兩者本身就是一對矛盾。那怕一行代碼不寫,僅僅起動運行一個空窗體這樣簡單動作,VB在底下就為我們做了大量複雜的工作(決不僅僅是注冊窗口類、顯示窗口、起動消息循環這麼簡單),這些工作對程序員是透明的。我們在感謝VB開發小組對我們程序員體貼入微的同時,不禁也要責怪為什麼在文檔中對這些底層的動作隻字未提,雖然這些動作對最終的程序也許并無影響,但我們擁有知情權,更何況這些動作有時的确會影響我們的工作。

vba代碼與vb代碼的區别(那麼VB和VBA又有什麼不同呢)1

然而,所有希望從本文得到"未公開技術秘密"的朋友你将會很失望,因為我能夠知道的和你一樣多,我們所能做的一切就是站在外面來猜VB在裡面做了什麼?所以我決不是要帶大家一起去将VB反向工程,而是想通過猜想VB的内部工作來将一些原來比較模糊的概念搞清楚。作為一個系列的第一篇文章,它的目的是為了後面的深入打下基礎,所以我會在需要的時候指出我們必須掌握的知識點。

最後,要聲明我在本文中所做的各種實驗和推斷僅是我個人的觀點,不能保證其正确性,并且不承擔任何相關的法律責任。

好,開始吧!首先準備好我們的武器,我下面要使用的工具主要有:

VB6中文企業版 SP5(廢話),還有SPY 、Dependency Walk和OLE Viewer(以下簡稱SPY和DEPEND和OLEVIEW,SPY在VB光盤的common\tools\vb\下的SPY目錄中,OLEVIEW是其下OLETOOLS目錄中的OLEVIEW.EXE,注意其下還有一個OLE2VW32.EXE功能類似,不過本文所指的是OLEVIEW.EXE,還Denpend在其下的Unsupprt\DEPEND裡)。還要用用VC(上面提的工具在VC裡有),因為我們還要看看VB生成的代碼,搞VB高級開發的朋友一定要會用VC調試器,懂點彙編更好。當然,本文的重點不在這兒,所以沒有VC也不要緊。

打開VB6新建一标準EXE工程,在"工程"->"引用"對話框裡應該已有四個引用,簡單點就是: 1、Visual Basic For Application(VBA)

VB運行時對象庫

VB對象庫

OLE自動化。

前面三個是任何VB工程都必須的,你想不要都不行,不信你試着去掉對它們的引用。那麼這三個核心類型庫各有什麼用,在最終生成的可執行程序中扮演怎樣的角色,這是本文要分析的第一個問題。

1)VB、VBA、VBS的區别你搞清楚了嗎?

首先VBS不應該和VB、VBA放在一起比較,它是微軟按照自己定義的ActiveX Scripting規範完全從頭開始寫成的腳本語言,雖然它的語法結構和VB非常相似,但VBS僅僅依靠自動化對象來擴充其功能(隻有後期綁定),它不能用implements來實現接口,不可能在VBS裡直接使用API,沒有VarPtr這樣能得到指針的函數,而VBS缺少的這些功能正是VB和VBA所特有的。當然,這不是說VBS不如VB或VBA,Windows已經為VBS提供了足夠強大的功能,我們可以用VBS來做腳本COM組件,而且借自動化對象的能力VBS可以說能力無限,所以有病毒用VBS來寫,對程序員來說VBS最重要的功能莫過于可以給自己的軟件提供宏功能,就象VC中提供的VBS宏功能那樣。注意,VBS是Free的,這和在Office中使用VBA來提供宏功能不同,要集成VBA需要價格不低的許可證費用,關于腳本語言可參見MSDN中Platform SDK\Tools and Languages\Scripting。(在本系列後面的文章《腳本功能》中我會實做一個用VBS來提供宏功能的小軟件)

那麼VB和VBA又有什麼不同呢?好吧,眼見為實,開始我們的實驗吧!

vba代碼與vb代碼的區别(那麼VB和VBA又有什麼不同呢)2

如果裝了Office 2000以上版本,那麼打開OLEVIEW,點擊File下的View TypeLib查看位于E:\Program Files\COMmon Files\Microsoft Shared\VBA\VBA6下的VBE6.dll的類型庫,再用同樣的方法看看MSVBVM60.dll的類型庫,你會發現它們的類型庫基本上一模一樣,除了VBE6多了一個VBEGlobal接口和實現這個接口的Global對象,這個Global對象我們也可以在VBA編程環境(比如用WORD的VB編輯器)中用對象浏覽器看到。它有二個方法Load和UnLoad,還有一個UserForms屬性,這是因為VBA6使用MS Form 2.0 Form設計器(FM20.dll)來設計和使用UserForm窗體(而在VB6中,我們可以使用多個設計器。比如通過使用MS Form 2.0 Form設計器,我們就能在VB中使用VBA所使用的UserForm用戶窗體)。和VBA的Global對象類似,在VB中也有GLobal對象,從VB的對象浏覽器中可以知道它在vb6.olb這個類型庫中,這個類型庫就是每個工程都必須引用的VB對象庫,所有的VB内置對象都在這裡。而VBA的UserForm中使用的對象都在FM20.dll中。

除了上述不同外,VB和VBA還有一個最大的不同,就是VBA不能生成EXE可執行文件,但可以猜想在IDE環境中VBA和VB都要把代碼編譯成p-code來執行,後面我将用實驗來證明的确是這樣,雖然在具體的實現上VB和VBA有很大的不同。

從上面的分析上可以看到VB和VBA還是有很大不同的,這種不同主要體現在編程環境和對象結構上,但在本質上它們之間卻有着不可割舍的血源關系。如果剛才你仔細地觀察了MSVBVM60.dll的類型庫,你就會發現如下的片斷:

// Generated .IDL file (by the OLE/COM Object Viewer)

[

dllname("VBA6.DLL"),

uuid(35BFBDA0-2BCC-1069-82D5-00DD010EDFAA),

helpcontext(0x000f6ec4)

]

module Strings {

[entry(0x60000000), helpcontext(0x000f665f)]

short _stdcall Asc([in] BSTR String);

[entry(0x60000001), helpcontext(0x000f6e9f)]

BSTR _stdcall _B_str_Chr([in] long CharCode);

……………

}

什麼?在MSVBVM60.dll中的對象其方法卻定義在VBA6.DLL中?!VB安裝目錄下不就有個VBA6.DLL嗎?再用OLEVIEW看看它,哇噻,真是想不到它居然和MSVBVM60.DLL的一模一樣。怎麼回事?趕快再拿出DEPEND來看看VBA6.dll、MSVBVM60.dll和VBE6.dll這三個DLL的輸出函數。哈,又有新發現,我們可以發現在三個DLL的輸出函數中從編号512到717絕大部分都是一模一樣的一些以rtc開頭的函數,比如595的rtcMsgBox(rtc是什麼?應該是Run Time Component Control Code有誰知道嗎?),這說明三個DLL都有着相同的運行時VBA函數。

我們再用DEPEND來觀察一下VB6.EXE, 我們可以發現VB6.EXE引入了VBA6.DLL中一些它特有的以Eb和Tip開頭的函數,從這些函數的名稱上可以發現它們的功能都是IDE相關的,比如79的EbShowCode和82的TipDeleteModule。VB6.EXE恰恰沒有引入任何rtc開頭的函數(注意一)。我們再來看看MSVBVM60.DLL,随便找一個用了MsgBox函數的編譯後的文件,用DEPEND來觀察它,就會發現它引入MSVBVM60.DLL輸出的595号rtcMsgBox函數(注意二)。并且引入MSVBVM60.DLL中很多以下劃線開頭的函數,比如__vbaVarAbs(注意三)。其實從這個三個"注意"中我們已經可以進行一些猜想,無論對錯,你可以先想想。

如果你沒有跟着我做實驗,而僅僅是看這篇文章的話,我猜想你應該有點昏了。如果你自己動手做了這些實驗,現在你應該充滿了疑問而急侍看到結論。所以請一定要親手試一試,學習研究問題的方法比看結論更重要。

到這裡至少我們可以得出結論:VB和VBA本就是同宗的姐妹,隻不過姐姐VB的功夫要比妹妹VBA曆害些。不過姐姐隻會單打獨鬥是女強人;妹妹卻隻會傍大款。姐姐有生育能力,是真正的女人;妹妹卻不會生崽,但深譜相夫之道,一番教導指揮之下可使她老公增色不少,而VBS呢,也是大戶人家的女兒,不過沒有VB和VBA姐妹優秀的血統,嬌小玲珑幹不得粗活隻能指揮些自動聽話的對象來幹活,她樂于助人品德好不象VBA那樣隻認大款,VB、VBA、vbs三個女人我都喜歡。

2)Native Code(本地代碼)到底做了什麼?

打起精神,我們再深入一步。用OLEVIEW得到的類型庫還不能正确的反映各對象方法對應的DLL中的函數入口,你應該已經發現用OLEVIEW得到的IDL文件中各個方法的entry屬性值都是0x600000XX這樣的假東西。要得到類型庫中各方法在DLL中的真正入口,我們需要自己來寫段程序。

即使在VB中我們也可以非常容易地獲取類型庫信息,再加上點COM初始化和調用代碼,我們就能用自己的代碼實現VB6才引入的CallByName函數(在本系列後面的《Hack COM》中我會更深入談談COM,作為一名VB程序員對COM的理解非常重要)。由于本文的關鍵不是指導如何在VB裡使用類型庫,所以下面提供的方法盡量從簡。

新建一個标準EXE工程,添加對TypeLib Infomation的引用,在Form中放一個名為lblInfo的标簽,然後添加如下代碼:

Private Sub Form_Load()

Dim oTLInfo As TypeLibInfo

Dim oMemInfo As MemberInfo

Dim sDllName As String

Dim sOrdinal As Integer

Set oTLInfo = TLI.TypeLibInfoFromFile("MSVBVM60.DLL")

lblInfo = "MATH模塊包含以下方法:" & vbCrLf

For Each oMemInfo In oTLInfo.TypeInfos.NamedItem("Math").Members

With oMemInfo

.GetDllEntry sDllName, vbNullString, sOrdinal

lblInfo = lblInfo & .Name _

& "定義在" & sDllName & "中," _

& "其編号為" & sOrdinal _

& vbCrLf

End With

Next

End Sub

運行以後我們就可以知道MATH模塊中的Abs方法定義在VBA6.DLL中,其編号為656。在DEPEND中查看VBA6.DLL中編号為656的函數,果然就是rtcAbsVar,用VBE6.DLL試試結果相同。

還記得前面的注意一吧,VB6.EXE沒有引入rtc開頭的函數這說明在IDE環境中執行的VBA方法實際上是通過COM調用VBA對象庫中的方法(跟蹤p-code是噩夢,所以我無法驗證它用的是什麼綁定方式)。而注意二中提到的最終可執行程序中引入了rtcMsgBox,如我們所料最終的程序會直接調用它,這要比COM調用快一點,但在跟蹤最終程序時,我發現rtcMsgBox内部卻是經過了二萬五千裡長征後才會去調用MessageBoxA這個API,其間有多次對其它對象的COM調用,慢!可能是因為顯示的是模态對話框,在多進程多線程環境有很多需要考慮的因素吧,如果你是瘋狂在意效率的程序員,你應該試試用API來重寫MsgBox,絕對快不少。再來看看注意三,讓我們把以下的程序編譯成使用本地代碼的"程序2.EXE"(為了後面的實驗,可以在工程屬性的編譯選項卡中将它設成"無優化"和"生成符号化調試信息"程序2.EXE""):

'程序2

Private Declare Sub DebugBreak Lib "kernel32" ()

Private Sub Main()

Dim i As Long, j As Long

Dim k

i = &H1234

DebugBreak

k = 1234

j = Abs(k)

j = Abs(i)

MsgBox "ss"

j = VarPtr(i)

End Sub

用DEPEND觀察"程序2.EXE",我們可以發現"程序2.EXE"并沒有如我們預期的一樣在引入595的rtcMsgBox的同時引入656的rtcAbsVar,相反它引入了__vbaVarAbs和__vbaI4Abs,看看函數名就知道一個針對的是Variant,一個針對的是long。這說明VB在最終生成的代碼中對象Abs這樣的可以進一步針對不同類型優化的VBA函數進行了相應的處理,觀察一下所有以__vba開頭的函數絕大部分都是那些最基本最常用的VBA函數,可以說__vba開頭的VBA函數是rtc開頭的VBA函數的優化版本,它們基本上是VB開發小組重新寫的,絕大多數在函數内部實現自身功能,而rtc開頭的函數大多數是調用COM服務對象來完成工作。從這麼多__vba開頭的函數上可以看出VB小組在Native Code(本地代碼)的優化上下了不少功夫,這決對不是吹牛。它的确高度優化了不少科學計算相關的函數,以ABS為例Native Code要比p-code快4倍以上。但是并不是所有的計算函數都經過了這樣的優化,比如Rnd函數,它就沒有對應的__vba開頭的優化函數,而是直接對應到rtcRandomNext函數上,雖然rtcRandomNext也已經優化過,但内部依然用了COM調用,還是不如自己重寫的快,我不明白為什麼VB開發小組沒有考慮為它寫一個對應的__vbaRnd。

不要以為上面的分析沒有意義,因為我們可以從現象看本質,也可以從本質來解釋現象。比如我們再做一個實驗,給你的代碼加入一個類模塊,你可以試試聲明一個和内部方法同名的公有的方法(這是一個很有用的技術,在本系列後面的《錯誤處理》中我們會用到這種方法),比如我們可以聲明一個Public Function Rnd(x) as single,同樣我們可以自己寫一個同名的MsgBox。但是你試試能不能聲明一個Public Function abs(x) ,這時VB肯定會彈出一個莫名其妙的編譯錯誤提示框告訴你"缺少标識符",這種錯誤發生在你的函數名和VB關鍵字沖突的時候。但是為什麼同樣是MATH模塊中的函數,abs是關鍵字,rnd卻不是,VB文檔裡是不會告訴你為什麼的,但如果你認真的看了我上面的實驗分析,我們就能猜想這是因為VB對需要進一步優化的函數已經做了高度優化處理,VB開發小組為了保護他們的勞動成果,并顯示他們對自己優化技術的自信,而禁止我們重寫這些函數,同時VB開發小組也承認還有些函數有待進一步優化,所以準許我們重寫之。在這裡我要提出一個偉大的猜想:凡是能夠被重寫的函數就能夠被優化,就象凡是大于2的偶數就能夠被分解成兩個質因數的和一樣。

說到優化,還應該談談直接API調用和使用API類型庫的差别,還必須談談VB所使用的後端優化器(和VC用的是一樣的優化器),還想談談如何盡最大可能來使用vTable綁定……(準備在本系列中另寫一篇《優化》來談這些問題)。

看了本地代碼,我們再來看看p-code,要是你看了MSDN中關于p-code的原理,你肯定會頭大。平心而論p-code真是一個了不起的技術,代碼大小平均可以縮小50%。我們把程序2編譯成p-code看看,還是用DEPEND來觀察,發現它并沒有引入__vba開頭函數(沒有使用優化的VBA函數?),卻引入了CallEngine這樣的東西(肯定是為了調用p-code僞碼解釋引擎),而且和Native Code一樣都引入了rtcMsgBox(編譯生成的p-code在調用MsgBox時應該比在IDE環境中運行的p-code快)。

如果你迫不及待地運行了程序2,你就會發現它将彈出一個應用程序錯誤對話框,說程序發生異常。别怕,這是因為調用了DebugBreak這個API的緣故,這個API其實就是産生一個Int 3中斷,使得我們能夠中斷程序執行。如果你裝了VC這樣的支持即時調試的調試器,你可以在錯誤對話框中點擊"取消",這樣可以起動調試器來調試程序。我就是這樣跟蹤程序運行的。如果你想看看VB生成的程序反彙編代碼可以自己試試,我們可以用同樣的技術在VB或VBA的IDE中來中斷程序執行,比如我們完全可以在Word的VB編輯器中運行上面程序2的代碼,從而中斷于Word的進程中,并可觀察到VBA生成的p-code代碼。比如VB和VBA在IDE中生成的p-code代碼就會發現它們這間有很大的不同。

所以,IDE中運行的程序和最終生成的程序是完全不同的。用SPY 看看你在IDE中運行的窗體,你會發現它在VB的主線程下,也就是說在IDE中你用程序做出的窗體和VB IDE工作窗口一樣屬于VB IDE,你的程序在IDE中運行時申請的資源也屬于VB IDE。有些程序在IDE中運行會讓IDE死掉(在VB5中寫純API多線程就千萬别在IDE中運行,定死無疑,相比之下VB6的IDE健壯得多)。還有些程序可能在IDE中能正常工作,但生成EXE後就工作不了。總之,在寫系統程序時要考慮到這種不同可能引起的問題。

3)VB的編譯技術,要我怎麼誇你,又要我怎麼罵你。

看了上面對Native Code的高度評價,你可能會對VB做出的東西更有信心了,腰闆更直了。是的,作為VB程序員沒有什麼需要害羞的,一個功力深厚的VB程序員理應拿比普通VC程序員更多的工資,因為他的生産力是VC程序員的好幾倍,而做出的程序在質量上和VC做的相差無幾。

甚至有大師開玩笑說VB的内置對象就是用VB寫出的,比如我們可以自己寫Form.cls、Label.ctl,呵呵,我們還真不能排除這種可能性(雖然用VB不可能直接生成vb6.olb)。如果真是這樣,看來VB小組自己都對自己的編譯優化技術非常有信心。

實際上我們看看VB安裝目錄下的C2.exe的屬性,再看看VC的C2.DLL的屬性,就會發現它們是同一個東西,同樣Link.exe也是VC的,所以我們完全可以對VB程序的後端優化編譯器以及聯結放心了。它們根本就是VC開發小組東西,或者VB、VC都是同一個編譯器開發小組在做編譯模塊。總之,我們可以壯着膽說我們VB做的程序其二次優化和聯結用的是和VC一樣的技術,嘿嘿,你有的我也有,我有的你沒有的(純屬詭辯)。

還有,沒有任何編譯器比VB編譯器更快,因為在IDE中VB就是一種解釋型語言,這才是VB開發效率高的關鍵,快得幾乎感覺不得編譯過程。其請求時編譯,後台編譯技術更是一隻獨秀,厲害啊!想想看,别的語言的程序員有多少時間花在了等待代碼編譯和重新聯結上啊!

不要高興得太早,因為最終的目的還是要生成可執行文件。在VB中沒有分塊編譯和增量聯結的功能,VB在生成可執行程序時總是編譯所有模塊并完全重新聯結,而在别的編譯語言中我們可以僅編譯最近修改過的文件(分塊編譯),聯結時将新生成的代碼附在可執行程序的後面,并将原來的代嗎标記為作廢(增量聯結,最終的可執行程序會越來越大,但聯結時間大大縮短)。做實驗看看,會發現在VB中每次生成可執行文件所花時間都是相同的。我不知VB開發小組為什麼不提供分塊編譯和增量聯結的功能,可能VB開發小組認為生成可執行文件在VB中不是經常要做的工作。但是實際上這種理由是說不過去的,因為如前面所說IDE中運行程序和最終程序有很大不同,如我們要經常編譯出可執行文件才能真正對它進行Profile,又如我們要調試多線程程序不能在VB IDE中做,在這些情況下每次修改後都要重新生成可執行文件,我們浪費了不少時間去編譯已編譯過的代碼,聯結已聯結過的程序。我猜想這是因為VB生成可執行程序時進行了全局優化,所以必須得全部重新編譯聯結。但提供一個新的功能讓我們能夠生成不進行全局優化的可以分塊編譯的調試版本,對Vb開發小組應該不是難事吧!(我有一個變通的解決方案,還在試驗中)

在來看看VB6安裝目錄下的VBAEXE6.lib,怎麼隻有1k大一點,可以猜想裡面應該不會有代碼,多半是些象vTable這樣的函數地址跳轉表,或者是些全局常量,我也不知道。但至少說明VB可以用靜态聯結庫了,為什麼不把這個功能提供給我們,讓我們有更多的選擇。

再做個實驗看看,做一個标準EXE工程,裡面隻有一個标準模塊,模塊裡面隻一個Sub Main,Sub Main裡面什麼也沒有,将它生成為EXE文件。看看,嚯,有16k多。你要是有時間跟蹤這個什麼也不做的程序看看,就會知道它要做很多事,初始化Err和App對象,準備COM調用,準備VB、VBA對象庫,甚至為使用ActiveX控制也做了準備,嘿嘿,看服務多周到。你必須得用VB對象庫中的控制,不用也不行。你再多找幾個EXE工程看看,有很多東西相同,都是一個模子做出的,而且你沒有選擇模子自由。ActiveX工程也是一樣,都是Dual雙接口,你做的ActiveX控制都必須要躲在一個Extender Object後面。是的,在VB裡有很多東西你沒有選擇的自由。如果需要這種自由要麼不用VB,要麼就得采取一些未公開的非官方的古怪的技巧(本系列文章最重要的目的之一,就是介紹這樣的非官方技巧)。

這又到文章開頭說的,VB讓我們做事情變得容易的同時也讓我們失去了不少自由。在最終代碼的生成上則也采取了公式化的做法。當然,我們應該全面地來看待這個問題,如同生産線上生産的東西不一定比手工的精緻,群養的家禽不如野味好吃的道理一樣,如果需要精緻的野味,意味着更多的勞動和更大的成本,這和VB所追求的更容易更便宜的目标是相違背的。

4)VB程序員也得有HACK精神。

本文的最後這個标題是嚴重離題了,但我想在此為本系列文章定下一個充滿HACK精神的基調。HACK精神是什麼?沒有準确的定義,我的理解是:HACK精神 = 總想探尋未知領域的好奇心 凡事總想知道為什麼的研究欲 總想拿出自己的東西的創新精神 解決問題的耐心和恒心。 VB的程序員也一樣需要這種精神。

最後,我們都知道VB開發小組已經達上.NET的快車飛起來了,不能不說VB6以後再沒有VB的新版本了。微軟已經用.NET為我們劃出了新的圈子,VB.NET是這個新圈子裡的新産物。在圈子裡面我們能夠飛得更高,但是圈子外面的天空更大,所以我依然樂意站在圈子外,虔誠地祈禱真正的VB7的誕生,阿門。

,
Comments
Welcome to tft每日頭條 comments! Please keep conversations courteous and on-topic. To fosterproductive and respectful conversations, you may see comments from our Community Managers.
Sign up to post
Sort by
Show More Comments
推荐阅读
他才是周星馳電影最醜的配角
他才是周星馳電影最醜的配角
提到周星馳,即使不怎麼關注香港電影的人,也能随口說過幾部他主演的經典電影,像《大話西遊》、《少林足球》、《唐伯虎點秋香》等等。但是有人知道周星馳的第一部作品是什麼嗎?周星馳或許有人會提到1988年的《霹靂先鋒》。不錯,周星馳正是靠這部電影拿...
2024-10-12
qt代碼布局技巧
qt代碼布局技巧
繼續我們的左側導航欄/主按鈕欄,随便叫啥吧,這裡主要用的是QDockWidget。->第一次慣例:介紹介紹下QDockWidget是可以停靠在主窗口(QMainWindows->CentralWidget)四周或者懸浮的特殊窗體,可停靠區域...
2024-10-12
托管連鎖加盟技巧
托管連鎖加盟技巧
現在小學生托管市場發展前景一直備受關注,人們對托管機構的需求逐漸加大,開托管機構前景不錯,創業者首選要做好全面的籌備工作,從店鋪選址到店鋪裝修經營等,任何一個細小環節都将直接影響店鋪成敗。對于廣大創業者來說最為關注的則是開店費用,那麼加盟托...
2024-10-12
燈泡肌什麼意思
燈泡肌什麼意思
燈泡肌什麼意思?燈泡肌就是指肌膚像燈泡一樣特别有光澤,閃耀着柔和的光芒,主要是指皮膚光滑細膩沒有任何污垢,不過不是所有人都能夠擁有燈泡肌的,一般情況下肌膚都會有過多過少的小毛病,要想擁有燈泡肌,就要改善我們的肌膚問題,從而達到像燈泡一樣閃耀...
2024-10-12
銀飾發黑氧化怎麼清洗掉顔色
銀飾發黑氧化怎麼清洗掉顔色
夏天是最适合佩戴銀飾的季節,風格各異的銀飾總能為愛美的女子裝扮出不一樣的美麗,然後在這個氣候炎熱的夏季,當汗水與銀飾過多接觸時,銀飾總是會出現不同程度的氧化。面對曾經精挑細選的他們,氧化後,你依然在佩戴嗎?你是否已幫他們除氧了呢?銀飾氧化變...
2024-10-12
Copyright 2023-2024 - www.tftnews.com All Rights Reserved