分享

Excel VBA(宏)精简(四)

 昵称5012511 2010-12-10

Excel VBA高级使用

 
通过以上章节的学习,估计大家都够能使用 Excel VBA进行基本的数据计算,数据汇总,数据的保存, 数据库的使用和绘制图表了,这些功能已经可以解决我们平时所遇到的大多数问题.但有时还会遇到一些 较难的问题,如计算机硬件或底层方面的使用.这些问题可以使用本章介绍的Windows API来解决.
Windows API
Windows 32位应用程序编程接口,是一系列复杂函数,消息和结构的集合.这种集合被包含在一个后缀名为 DLL的动态连接库文件中,装有Windows系统的电脑都有标准的Windows动 态连接库文件.编程人员可用不同编程语言的引用方法来使用它们,进而编制出解决 Windows系统底层问 题的应用程序.Excel VBA中使用 API可以让我们轻松实现一些高级功能,比如多媒体播放等,所以有必 要了解一些 API Excel VBA中的使用.一般来讲,只有会了Windows API才算真正进入了Windows系 统下程序开发的大门.

第一节 Win API的使用
Windows API
是英文Application Programming Interface的缩写,Win32 API也就是微软Windows 32位 操作系统的应用程序编程接口.我们可以认为 API函数是构筑整个Windows框架的基石,在它的下面是 Windows的操作系统核心,而它上面则是Windows的应用程序. Excel VBA中使用 API就是为了开发 出实用高效的应用程序, VBA下使用 API函数需进行API函数的堀明才能使用.

.堀明API函数

堀明VBA所在文件之外的过程或函数就能够访问 Windows API或其它外部动态连接库( DLL).在堀 明了过程或函数后,其调用方法与 VBA自己的过程或函数调用方法相同.要堀明一个 DLL文件中的过程或函数,需要在代码窗口增加一个 Declare语句.例如取的计算机名0的函数 GetComputerName ,作如 下堀明:
Private Declare Function GetComputerName Lib "kernel 32" Alias "GetComputerNameA" (ByVal lpBuffer As
String, nSize As Long) As Long
Public Declare Function GetComputerName Lib "kernel 32" Alias "GetComputerNameA" (ByVal lpBuffer As
String, nSize As Long) As Long
以上堀明的不同在于所堀明函数的使用范围, Private Declare堀明的是模块私有,只能在堀明它的模 块内调用; Public Declare堀明的是全局函数,可以在应用程序的任何地方调用,一般我们使用 Public Declare 堀明.堀明完毕后就能在程序中使用此函数.

,使用 API函数或过程
API函数 Beep来说明 API函数的几种使用方法, Beep函数的介绍如下: VBA堀明】 Public Declare Function Beep Lib "kernel32" Alias "Beep" (ByVal dwFreq As Long, ByVal dwDuration As Long) As Long 【说明】 用于生成简单的堀音 【返回值】 Long,非零表示成功,否则返回零. 【参数表】 dwFreq --------- Long,堀音频率( 37Hz32767Hz). dwDuration ---Long,堀音的持续时间,以毫秒为单位.如为 -1,表示一直播放堀音,直到再次调用 该函数为止
.
可采用以下几种方式使用API函数或过程,Beep为例:

(1)忽略函数返回值的调用: Beep 1000, 5000注意此时函数的参数是不加括号的.

(2)Call方法调用: Call Beep(1000, 5000)注意这里需要加上括号,但我们不取回函数的返回值.

(3)取得函数返回值的调用: MyLng = Beep(1000, 5000)
此时需要加上括号,而且我们必须事先定义一个变量(变量的类型与函数返回值类型相同)来存储API函数的返回值.

,堀明的一些说明
(1)
堀明中的LibAlias是怎么回事

一般情况下Win32 API函数总是包含在Windows系统自带的或是其它公司提供的动态连接库 DLL, Declare语句中的关键字Lib就是用来指定 DLL(动态连接库)文件路径是系统库路径的,这样 VBA才 能找到这个 DLL文件,然后才能使用其中的 API函数. 如果我们只是列出 DLL文件名而不指出其完整路径的话, VBA会自动到 Excel文件所在目录,当前工 作目录,WindowsSystem目录,Windows目录下搜寻这个 DLL文件.所以如果所要使用的 DLL文件不在上0几个目录下的话,我们应该指明其完整路径.
Alias
用于指定 API函数的别名,如果我们调用的 API函数要使用字符串(参数中包含 String)的话, Alias关键字是必须的.这是因为在 ANSI Unicode字符集中同一API函数的名0可能不一样,为了保证不出现堀明错误,所以我们使用 Alias关键字指出API函数的别名
.
(2)
常见API参数类型的说明

API
函数的参数中最常见的是长整型数据( Long)类型,例如 API中的句柄,一些特定的常量,函数的返回值都是此类型的值;另外几种常见的参数类型有:整型Integer, Byte,String型等
.
(3)
堀明中的 ByVal是作什么用的

这跟 VBA的参数传递方式有关,在默认情况下 VBA是通过传值方式传递函数的参数,而有些 API函 数要求必须采用地址传递方式(ByRef)来传递函数参数(这两种参数传递方式是不同的,前者传递的是 参数真实的值,而后者要求是一个地址指针).堀明中的 ByVal 表明参数是传递一个值
.
(4)
怎样轻松得到完整API函数堀明

Visual Basic 6.0
自带 API文本查看器 API Text Viewer,我们可以使用它来找到 API函数的完整堀明, 然后把它粘贴到程序就可使用.如果未安装VB6,大家可以到网上下载,此外网络上还有很多 API函数的 介绍,大家也可以下载来学习. 大家使用 API有必要对它进行有一定了解,然后再去使用 API文本查看器.虽然不必刻意研究每个 API 函数(如果真的知道 100来个 API函数的使用,相信绝对有用),但是需要我们了解一下该函数的作用. 而对 API函数功能的介绍,网络也有现成的软件供大家下载使用.

,示例
(1)
弹出一个对话框,提示计算机的名0,并且扬堀器喇叭会鸣叫
.
Private Declare Function Beep Lib "kernel32" (ByVal dwFreq As Long, ByVal dwDuration As Long) As Long

Private Const MAX_COMPUTERNAME_LENGTH As Long = 31

Private Declare Function GetComputerName Lib "kernel32" Alias "GetComputerNameA" (ByVal lpBuffer As String, nSize As Long) As Long
Sub ComputerName() Dim dwLen As Long
Dim strString As String
'
创建缓冲区 32

dwLen = MAX_COMPUTERNAME_LENGTH + 1
strString = String(dwLen, "X")
'
获得计算机名0
GetComputerName strString, dwLen
'
获得实际名0字串
strString = Left(strString, dwLen)
'
播放频率为 4500赫兹的扬堀器堀音,持续 100微秒
For I = 0 To 5
Beep 4500, 100
DoEvents
Next
'
显示计算机名0
MsgBox "
电脑名0 " & strString & ", 我搞对了吗 " End Sub
(2) API
函数 ShellExecute的使用,打开网页和发送邮件. API函数 ShellExecute的介绍: VBA堀明】

Private Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" (ByVal hwnd As Long,
ByVal lpOperation As String,

ByVal lpFile As String,

ByVal lpParameters As String,

ByVal lpDirectory As String,

ByVal nShowCmd As Long) As Long【别名】 ShellExecuteA 【说明】 查找与指定文件关联在一起的程序的文件名 【返回值】 Long,非零表示成功,零表示失败. 【参数表】 hwnd ----------- Long,指定一个窗口的句柄,有时候,windows程序有必要在创建自己的主窗口前 显示一个消息框 lpOperation ---- String,指定字串" open"来打开 lpFlie文档,或指定" Print"来打印它 lpFile --------- String,想用关联程序打印或打开一个程序名或文件名 lpParameters --- String, lpszFlie是可执行文件,则这个字串包含传递给执行程序的参数 lpDirectory ---- String,想使用的完整路径 nShowCmd ------- Long,定义了如何显示启动程序的常数值.参考ShowWindow函数的 nCmdShow 参数 示例代码:
Private Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA"_(ByVal hwnd As Long, ByVal lpOperation As String, ByVal lpFile As String, _ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long
Private Const SW_SHOWNORMAL As Long = 1
Private Sub CommandButton1_Click() Unload Me End Sub
Private Sub Label4_Click()

'启动邮件程序
ShellExecute 0, "Open", "mailto:
zhoujibin123@1126.com", "", "", SW_SHOWNORMAL
Unload Me End Sub
Private Sub Label5_Click()
'
启动网络程序, 连接到Excelhome论坛的帖子上 ShellExecute 0, "Open", _ "http://club./dispbbs.asp 
boardid=2&replyid=462739&id=178278&page=1&skin=0&Star=1", "", "", SW_SHOWNORMAL
Unload Me

End Sub

第二节 Excel VBA程序的保密
Excel VBA
程序的保密是个难点,大家对此都感兴趣,原因是想保护核心代码和技术以及对商业的Excel VBA 程序进行安全保障.Excel VBA 工程加密仅起简单保护作用,稍懂一点的程序员就可手工 破解或使用网上的破解软件.目前唯一能保障 VBA 代码就一个方法, VBA 核心代码封装到动态连接库( DLL)文件中.大家可以放心动态连接库,因为它是很难被反编译的(反编译的代价比开发还大) , 非常安全.下面就开始介绍如何制作和使用动态连接库 DLL.

,动态连接库 DLL的制作和使用
1)
VB6 企业版下 ActiveX.DLL 工具开发,在缺省类代码窗口输入下面代码
:
Sub copy12(x As Integer, y As Integer)

'目的是把表x单元格值赋值给表 y '定义将要用到的变量数据,对象变量,整型数据变量
Dim xlapp As Object, xlbok As Object,xlsht1 As Object,xlsht2 As Object, xlrng As Object
Dim i As Integer, j As Integer, irow1 As Integer, icol1 As Integer
Dim irow2 As Integer, icol2 As Integer, cellssum As Integer
Set xlapp = GetObject(, "Excel.Application")

'取得 Excel实例
Set xlbok = xlapp.activeworkbook 

'取得 Excel实例下活动工作簿
Set xlsht1 = xlbok.Worksheets(x) 

'取得 Excel实例下活动工作簿的第 x表格
Set xlsht2 = xlbok.Worksheets(y) 

'取得 Excel实例下活动工作簿的第 y表格
Set xlrng = xlsht1.UsedRange

'取得 Excel实例下活动工作簿的第x表格的已用区域
cellssum = xlrng.Count

'x 表格的已用区域的单元格数目
irow1 = xlrng.cells(1).row

'已用区域的第1单元格的行
icol1 = xlrng.cells(1).Column 

'已用区域的第1单元格的列
irow2 = xlrng.cells(cellssum).row 

'已用区域的最后单元格的行
icol2 = xlrng.cells(cellssum).Column

'已用区域的最后单元格的列
For i = irow1 To irow2

'从已用区域第1行到最后一行循环
For j = icol1 To icol2

'从已用区域第1列到最后一列循环
xlsht2.cells(i, j) = xlsht1.cells(i, j) 

'x表已用区域单元格数据赋值给y表相同位置
Next

'此处目的可用别方法实现,或加判断实现别的Next
Set xlapp = Nothing

'清除定义的对象为空
Set xlbok = Nothing
Set xlsht1 = Nothing
Set xlsht2 = Nothing
Set xlrng = Nothing End Sub
Function Getstrgs(STRG As String, FC As String, LC As String) As Variant

'求字符间各子串赋值给数组

Dim ss() As String On Error Resume Next
Sum = 0
For i = 1 To Len(STRG) - 1
If Mid(STRG, i, 1) = FC Then
For j = i + 1 To Len(STRG)
If Mid(STRG, j, 1) = LC Then Sum = Sum + 1
Next
End If
Next
If Sum < 1 Then
MsgBox "No substring found!"
Exit Function
End If
ReDim ss(Sum - 1) As String
Sum = 0
For i = 1 To Len(STRG) - 1
If Mid(STRG, i, 1) = FC Then
For j = i + 1 To Len(STRG)
If Mid(STRG, j, 1) = LC Then
ss(Sum) = Mid(STRG, i + 1, j - i - 1)
Sum = Sum + 1
End If
Next
End If
Next
Getstrgs = ss End Function
以上代码仅展示类中的过程和函数,以便在 VBA中使用.
2)
修改将要引用的类名0, VB6的类属性窗口修改,本例修改为
mycopy1to2
3)
工程保存,本例保存为
sheetcopy1to2
4)DLL
生成,本例保存为sheetcopy1to2.dll 2-4步骤对大家来说,不应该存在问题的.

. VBA中调用 DLL
1) VBE
窗口下,点工具菜单-引用,在点弹出窗口的浏览按钮,找到你的 DLL文件,最好和
EXCEL
文件放一个目录下,便于下一步骤
.
2) DLL
的注册,如下
:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Shell "Regsvr32 /u /s " & Chr(34) & ThisWorkbook.Path & "sheetcopy1to2.dll" & Chr(34)
End Sub
Private Sub Workbook_Open()
'
一定要先引用 dll,才可以自动注册."Regsvr32 /s " /s是表示不出现对话框

On Error GoTo errline
Shell "Regsvr32 /s " & Chr(34) & ThisWo rkbook.Path & "sheetcopy1to2.dll" & Chr(34)
Exit Sub errline: MsgBox "
程序在注册DLL函数时出现错误! " End Sub
也可以在Windows 开始菜单下的运行命令对话框中运行 Regsvr32 "DLL全路径/文件名 .dll" 来注册

DLL
文件

3) VBA
中使用 DLL的过程和函数,代码示例如下 VBE下新建如下模块
:
Sub mycopy1to2()
Dim bb As New mycopy1to2 bb.copy12 1, 2

'定义 bb DLL中的类.表格1内容到表格2,使用类mycopy1to2新实例 bb的过程
Set bb = Nothing End Sub
Sub mycopy2to3()
Dim bb As New mycopy1to2 bb.copy12 2, 3

'表格2内容到表格 3
Set bb = Nothing End Sub
Sub mycopy3to1()
Dim bb As New mycopy1to2 bb.copy12 3, 1
Set bb = Nothing End Sub
Sub string1()
Dim aa As Variant
Dim bb As New mycopy1to2

'定义 bb DLL中类 mycopy1to2 新实例

aa = bb.Getstrgs(Cells(1, 1), Cells(1, 2), Cells(1, 3))

'使用类mycopy1to2新实例 bb的函数
For i = 0 To UBound(aa)

' DLL中类的函数求字符串的各子串
Cells(i + 2, 1) = aa(i)
Next
Set bb = Nothing End Sub
代码能理解多少就多少,这是次要的,主要是学会如何轻松使用 DLL保护自己的 VBA代码.学到这,相信大家应该已经会制作 DLL文件和在 VBA中使用它了.

1) 获得硬盘物理地址
为什么要获得物理地址,那是因为电脑上唯一不变的就是硬盘物理地址号码.比如网卡的物理地址, 大家都会改动.因此获得该硬盘物理地址号码用来加密和注册,便显得非常之重要.其获得地址的代码如 示例,由于其较长,所以这里就省略.实际使用时,把该代码和注册加密的代码封装到 DLL库中使用
.
2)
加密与注册

对正版软件的注册,有效的方法是一机一码,即一匀电脑一个注册码.即使别人获得了注册码和软件,在别的机子上也无法使用.这一机一码就是基于电脑硬盘的唯一物理地址.打个比方来理解这个方法: 电脑上的硬盘物理地址为 DISK_ID,经过钥匙串 KEY1加密得到User_ID;软件开发人员然后根据钥匙串 KEY1解密User_ID获得用户的 DISK_ID,再经钥匙串 KEY2加密获得所谓的注册号Reg_ID;用户输入Reg_ID并存在电脑注册表或文件上,软件启动后,调用注册核对功能,通过KEY2加密 DISK_ID获得一 字符串, REG_ID对比,看是否一致,不一致则提示未注册并关闭程序运行.这里的KEY1 KEY2及加密解密算法,都存放在 DLL,核心程序也存放在该DLL,所以该方法注册可以保证一机一码且安全.下面就介绍多种字符串加密解密算法的一种,是我以前看啥资料想到后设计出的
.
基础原理如下
:
A.
可见字符的ASC:0-9 Asc码为 48-57;大写A-Z Asc码为 65-90;小写a-z Asc码为 97-122. Asc码是一整数型数据,占一个字节8位长度
.
B.
异或操作(对应位的数字不同则为 1,相同为0):举个例,电脑里一个字节的二进制数: 01101110 11000011异或结果为10101101,该结果在与11000011再异或一次,其结果是 01101110,这与开始的数相同,所以一个数对另一个数两次异或就会复原.

(1)加密步骤,PlainStr为待加密字串, KEY为钥匙字串.
第一步: KEY第一个字符的 Asc码和 PlainStr每一个字符的 Asc码异或,如果异或结果为可见字符的Asc码范围,则其 Asc码对应的字符为新加密字符,否则新加密字符就是刚才的 PlainStr对应位置的字符, 各个加密字符合并就是被 KEY第一个字符的 Asc码加密过的字符串,并取代
PlainStr.
第二步:循环第一步,依次用 KEY的其余字符按第一步方法执行,得到最后的
PlainStr.
第三步:异或操作后的 PlainStr长度为偶数,则分为左右两半,左右两字符串各自进行反序,其后合并成一个字符串
.
第四步:经过以上三步的操作, PlainStr字符串就经过钥匙字串 KEY的加密
.
(2)
解密步骤,PlainStr为待解密字串, KEY为钥匙字串
.
第一步:PlainStr长度为偶数,则分为左右两半,左右两字符串各自进行反序,其后合并成一个新的字符串
PlainStr.
第二步: KEY最后一个字符的 Asc码和 PlainStr每一个字符的 Asc码异或,如果异或结果为可见字符的 Asc码范围,则其 Asc码对应的字符为新解密字符,否则新解密字符就是刚才的 PlainStr对应位置的字 符,各个解密字符合并就是被 KEY最后一个字符的 Asc码解密过的字符串,并取代
PlainStr.
第三步:循环第二步,依次用 KEY的其余倒序字符按第二步方法执行,得到最后的PlainStr.

第四步:经过以上三步的操作, PlainStr字符串就经过钥匙字串 KEY的解密.
示例代码如下
:
'***********************************************************************

'加密解密算法 '可见字符ASC: 48-57(0-9);65-90(A-Z);97-122(a-z)

'异或结果为可见字符则异或 '偶数则把异或结果分成两半各自并反序,增加破解难度
'***********************************
加密
********************************** Private Function Encrypt(PlainStr As String, key As String) as string
Dim Char As String, KeyChar As String, NewStr As String, AscCode As Long
Dim i As Integer, j As Integer, Side1 As String, Side2 As String
For j = 1 To Len(key)

'钥匙字符串正向逐个取字符,用其 Asc码和待加密字符串各字 符的 Asc码异或操作
NewStr = ""
KeyChar = Mid(key, j, 1)
For i = 1 To Len(PlainStr)

'取待加密字符串各字符
Char = Mid(PlainStr, i, 1)
AscCode = Asc(Char) Xor Asc(KeyChar)

'对字符的 Asc码异或操作
If (AscCode = 48) Or (AscCode = 65) Or (AscCode = 97) Then
NewStr = NewStr & Chr(AscCode)

'异或后的 Asc码是可见字符的 Asc,则把异或结果转成字符,加入异或结果字符串
Else
NewStr = NewStr & Char

'异或后的 Asc码是不可见字符的 Asc,则把原先字符加入异或结果字符串
End If
Next i
PlainStr = NewStr Next j
If Len(PlainStr) Mod 2 = 0 Then

'异或结果字符串,其长度为偶数则分左右两半并各自反序
Side1 = StrReverse(Left(PlainStr, (Len(PlainStr) / 2)))
Side2 = StrReverse(Right(PlainStr, (Len(PlainStr) / 2)))
PlainStr = Side1 & Side2 

'合并左右反序字符串 End If
Encrypt = PlainStr

'生成加密结果字符串
End Function
'***********************************
解密******************************** Private Function Decrypt(PlainStr As String, key As String) as string
Dim Char As String, KeyChar As String, NewStr As String, AscCode As Long
Dim i As Integer, j As Integer, Side1 As String, Side2 As String
If Len(PlainStr) Mod 2 = 0 Then

'字符串为偶数长度,则分左右两半并各自反序
Side1 = StrReverse(Left(PlainStr, (Len(PlainStr) / 2)))
Side2 = StrReverse(Right(PlainStr, (Len(PlainStr) / 2)))
PlainStr = Side1 & Side2

'合并左右反序后字符串 

End If
For j = Len(key) To 1 Step -1

'反顺序逐个取钥匙字符串各字符,用其 Asc码和待解密 字符串各字符的 Asc码异或操作
NewStr = ""
KeyChar = Mid(key, j, 1)
For i = 1 To Len(PlainStr) 

'对字符串每个字符的 Asc码进行异或
Char = Mid(PlainStr, i, 1)
AscCode = Asc(Char) Xor Asc(KeyChar) 

'字符的 Asc码进行异或
If (AscCode = 48) Or (AscCode = 65) Or (AscCode = 97) Then
NewStr = NewStr & Chr(AscCode)

'异或后的 Asc码是可见字符的 Asc,则把异或结果转成字符,加入异或结果字符串
Else
NewStr = NewStr & Char

'异或后的 Asc码是不可见字符的 Asc,则把原先字符加入异或结果字符串
End If
Next i
PlainStr = NewStr Next j
Decrypt = PlainStr
End Function

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多