vb中定制dllregisterserver、dllunregisterserver
 -阿鬼(heroyin)
vb作為一種簡(jiǎn)單容易上手的語(yǔ)言,可以讓開發(fā)者快速上手,開發(fā)速度快,效率高。但它過(guò)分的封裝也給開發(fā)者帶來(lái)諸多不便。
問(wèn)題的由來(lái)
最近本人在開發(fā)一個(gè)插件結(jié)構(gòu)的項(xiàng)目中就遇到了一個(gè)麻煩,我的項(xiàng)目是采用com架構(gòu),框架由delphi開發(fā),插件為com組件,插件可以由其他語(yǔ)言開發(fā),當(dāng)然也包括vb。每個(gè)插件必須注冊(cè)為一個(gè)固定的組件類別(categories)。在其他語(yǔ)言如vc、delphi中實(shí)現(xiàn)起來(lái)非常簡(jiǎn)單,只需要重載dllregisterserver、dllunregisterserver在兩個(gè)函數(shù)里加入注冊(cè)注銷類別的代碼,然后輸出就可以了。但到了vb就碰到麻煩了。
vb正常情況下是不能直接開發(fā)標(biāo)準(zhǔn)dll,所以也就不支持自定義導(dǎo)出函數(shù),開發(fā)active dll的時(shí)候,是由vb自動(dòng)導(dǎo)出必須的四個(gè)函數(shù)dllgetclassobject, dllcanunloadnow, dllregisterserver, dllunregisterserver。要加入注冊(cè)注銷類別代碼就必須重新導(dǎo)出dllregisterserver, dllunregisterserver。
好了,問(wèn)題就轉(zhuǎn)換成了如何在vb寫的dll中導(dǎo)出函數(shù)了
vb編譯內(nèi)幕
vb的編譯過(guò)程大致如下,當(dāng)我們?cè)诰庉嫮h(huán)境中編寫完代碼后,vb調(diào)用c2將所有的模塊(包括class)編譯成obj文件(能夠?yàn)闄C(jī)器語(yǔ)言識(shí)別的代碼)。一下是c2的一些編譯參數(shù)說(shuō)明(e文):
- the the name of the prefixed one used for the names of the rowscontaining ' precompilato', one 
risen of intermediate tails (from which name of the switch) temporary; these rows are 5 and finish withi suffissi gl, sy, former, in and db; they contained are not documented
- f the name of the rows to compile
- w3 warning level 3, level of ' attenzione' dedicating to i warnings
- gy it qualifies the connection to level of function (function-level linking)
- g5 optimization for the pentium
- gs4096 it allows not to insert the code for the control of stack (stack probe) if a function does not 
 use more than 4096 byte of stack
- dos not documented
- z1 it removes the name of the bookcase of default from the rows.obj
- fofileobj the name of rows obj to generate (rows output)
- qifdiv it puts in action the corrections for the bug of the division of the pentium (fdiv bug)
- mililiter it creates rows eseguibile single-threaded
- basic it indicates the compiler c2 the fact that the compilation it happens for a plan basic
 
c2完成編譯后,vb會(huì)調(diào)用link.exe將所有的obj文件連接成exe文件,完成編譯過(guò)程。下面是一段命令行演示如何調(diào)用link.exe:
link c:/test/form1.obj c:/test/modulo1.obj c:/test/progetto1.obj c:/programmi/microsoft visual studio/vb98/vbaexe6.lib /entry:__ vbas /out:c:/test/progetto1.exe /base:0x400000 /subsystem:windows, 4.0 /version:1.0 /debug /debugtype:cv /incremental:not /opt:ref/merge:.rdata =.text /ignore:4078
對(duì)于我們來(lái)說(shuō)這些參數(shù)沒有什么意義,用默認(rèn)的就行了。這段命令行中并沒有包括輸出函數(shù),如果我們希望輸出函數(shù),可以定義一個(gè).def文件,按照格式加入要輸出的函數(shù)列表,然后在命令行后面加上 “/def: 文件名”(當(dāng)然也可以直接加/ exports參數(shù)),再調(diào)用命令行編譯,用的denpendency工具查看你就會(huì)發(fā)現(xiàn)你要輸出的函數(shù)了。
def文件格式的定義:
 
library 程序名稱
description "mydll - (c) antonio giuliana, 2004"
exports
 函數(shù)名= ?函數(shù)名@函數(shù)所在模塊名@@aagxxz
 …
例:
library mydll
description "mydll - (c) antonio giuliana, 2004"
exports
 dllregisterserver= [email protected]@@aagxxz
 dllunregisterserver= [email protected]@@aagxxz
 
注意:函數(shù)名和模塊名是區(qū)分大小寫的
 
找到了解決方案了,但是,由于vb編譯完成后就會(huì)自動(dòng)刪除obj文件,如何再c2生成完obj文件后中斷編譯過(guò)程取得obj文件呢,網(wǎng)上有種方法就是替換調(diào)vb的link文件,然后中斷一下,將obj文件拷貝出來(lái),在用命令行編譯。這是個(gè)很好的辦法,但不夠智能化,本人在國(guó)外網(wǎng)站上發(fā)現(xiàn)了一個(gè)更為有效的方法。
制作vb的輔助編譯工具
 新建一個(gè)工程,命名為link,在main過(guò)程中加入代碼:
public sub main()
 dim cmd as string
 dim fout as long
 dim sout as string
 dim sdef as string
 
 cmd = command$
 if instr(cmd, "/dll") > 0 and instr(cmd, "vbaexe6.lib") > 0 then
 fout = instr(cmd, "/out:")
 sout = mid(cmd, fout + 6, instr(cmd, "/base:") - fout - 8)
 sdef = left(sout, len(sout) - 3) + "def"
 if len(dir(sdef)) then
 cmd = cmd & "/def:"" "& sdef &" "" "
 end if
end if
 shell "link32.exe "& cmd
 end sub
然后編譯成link.exe,先將vb目錄下的link.exe改名成link32.exe,再將link.exe拷貝到vb目錄下,這樣做的好處是不改變?cè)械木幾g過(guò)程,只是在編譯dll的時(shí)候才插入“/def:”參數(shù),不會(huì)對(duì)編譯其他程序造成影響。
 完成了替換操作后,如果要輸出其他函數(shù),只需要在程序輸出目錄下編輯一個(gè)和輸出文件同名的后綴為“.def”的文件就可以了。
開始替換dllregisterserver、dllunregisterserver
1、制作注冊(cè)和注銷的工具庫(kù)comregisterdll
 新建一個(gè)工程,引入兩個(gè)庫(kù):isa helper com component 1.0 type library和 typelib information。再創(chuàng)建一個(gè)類comregister,在comregister類中實(shí)現(xiàn)一個(gè)方法:
regtypelib(slib as string, byval bstate as boolean)用來(lái)實(shí)現(xiàn)注冊(cè)和注銷activex。代碼如下:
option explicit
public type guid
 data1 as long
 data2 as integer
 data3 as integer
 data4(0 to 7) as byte
end type
 
private enum esyskind
 sys_win16 = 0&
 sys_win32 = 1&
 sys_mac = 2&
end enum
 
private declare function loadtypelib lib "oleaut32.dll" ( _
 pfilename as byte, pptlib as object) as long
private declare function registertypelib lib "oleaut32.dll" ( _
 byval ptlib as object, szfullpath as byte, _
 szhelpfile as byte) as long
private declare function unregistertypelib lib "oleaut32.dll" ( _
 libid as guid, _
 byval wvermajor as integer, _
 byval wverminor as integer, _
 byval lcid as long, _
 byval tsyskind as esyskind _
 ) as long
private declare function clsidfromstring lib "ole32.dll" (lpsz as byte, pclsid as guid) as long
private declare function getmodulefilename lib "kernel32" alias "getmodulefilenamea" (byval hmodule as long, byval lpfilename as string, byval nsize as long) as long
private declare function getmodulehandle lib "kernel32" alias "getmodulehandlea" (byval lpmodulename as string) as long
‘取得模塊的路徑
private function getmodulepath(bmodulename as string) as string
 dim modleid as long
 dim path as string * 254
 
 modleid = getmodulehandle(bmodulename)
 call getmodulefilename(modleid, path, 254)
 getmodulepath = path
end function
 
‘slib為模塊名稱,無(wú)須路徑和后綴 bstate 為false時(shí)是注銷,反之為注冊(cè)
public function regtypelib(slib as string, byval bstate as boolean, byval btype as psdk.eplugincategory) as long
dim sulib() as byte
dim errok as long
dim tlb as object
 
dim ctli as typelibinfo
dim tguid as guid, sclsid as string
dim imajor as integer, iminor as integer
dim lcid as long
dim dllpath as string
 
 dllpath = getmodulepath(slib)
 if bstate then
 ' basic automatically translates strings to unicode byte arrays
 ' but doesn't null-terminate, so you must do it yourself
 
 sulib = dllpath & vbnullchar
 
 ' pass first byte of array
 
 errok = loadtypelib(sulib(0), tlb)
 
 if errok = 0 then
 errok = registertypelib(tlb, sulib(0), 0)
 
 end if
 regtypelib = errok
 else
 
 set ctli = tli.typelibinfofromfile(dllpath)
 sclsid = ctli.guid
 imajor = ctli.majorversion
 iminor = ctli.minorversion
 lcid = ctli.lcid
 set ctli = nothing
 
 sulib = sclsid & vbnullchar
 errok = clsidfromstring(sulib(0), tguid)
 if errok = 0 then
 
 
 errok = unregistertypelib(tguid, imajor, iminor, lcid, sys_win32)
 regtypelib = errok
 end if
 
 end if
 
end function
將工程編譯成comregiterdll.dll,注冊(cè)。這個(gè)庫(kù)為公用庫(kù),所有需要重新定制dllregisterserver、dllunregisterserver的工程都可以方便的引用它來(lái)方便的完成注冊(cè)和注銷過(guò)程。
 
2、定制dllregisterserver、dllunregisterserver
 新建一個(gè)工程mydll,引入comregiterdll.dll,添加一個(gè)module mainmodule,在mainmodule中新建兩個(gè)函數(shù)dllregisterserver、dllunregisterserver:
 
declare function messagebox lib "user32" alias "messageboxa" (byval hwnd as long, byval lptext as string, byval lpcaption as string, byval wtype as long) as long
public function dllregisterserver() as long
 dim comreg as comregister
 set comreg = new comregister
‘加入你的處理代碼
messagebox 0, ”注冊(cè)過(guò)程”, “mydll”, 0
 
 dllregisterserver = comreg.regtypelib("mydll", true)
end function
 
public function dllunregisterserver() as long
 dim comreg as comregister
 set comreg = new comregister
‘加入你的處理代碼
messagebox 0, ”注銷過(guò)程”, “mydll”, 0
 
 dllunregisterserver = comreg.regtypelib("mydll", false)
end function
在程序目錄中新建一個(gè)mydll.def,def文件內(nèi)容如下:
library mydll
description "mydll - (c) antonio giuliana, 2004"
exports
 dllregisterserver= [email protected] mainmodule @@aagxxz
 dllunregisterserver= [email protected] mainmodule @@aagxxz
保存,編譯,運(yùn)行regsvr32注冊(cè)mydll你就發(fā)現(xiàn)彈出對(duì)話框“注冊(cè)過(guò)程“
結(jié)束語(yǔ)
本人使用導(dǎo)出函數(shù)的方法引自:http://www.visual-basic.it/uploads/articoli/tecnici/agdllbyvb.htm
本人在實(shí)現(xiàn)過(guò)程中還碰到了一些問(wèn)題,就是在導(dǎo)出的函數(shù)過(guò)程中使用很多方法都會(huì)有限制,可能是由于vb的一下全局對(duì)象未初始化引起的,所以就把注冊(cè)方法封裝到comregisterdll.dll中,再去引用,如果不依靠comregisterdll.dll而直接將comregister放到要注冊(cè)的dll的單元中去引用就會(huì)出錯(cuò),歡迎大家與我一起探討這個(gè)問(wèn)題的解決方案。