为何TextStream类型对象log被设为Nothing?VBA报错排查
VBA日志工具问题分析与修复
问题现象
- 首次执行
Initialize函数可正常初始化日志对象,但调用WriteString时全局变量log会变为Nothing - 重试调用
Initialize时,执行Set log = FSO.OpenTextFile(...)返回错误70(权限拒绝) VbaLog文件夹的只读属性取消后会自动恢复,即使禁用权限继承也无法永久清除
原始代码
Option Explicit Public log As TextStream Function Initialize() Dim FSO As FileSystemObject Dim sLogName As String Dim sLogPath As String Set FSO = New FileSystemObject 'Call Terminate sLogPath = "C:\Users\" & UserName & "\Documents\VbaLog" sLogName = "\progressLog.txt" If Not FSO.FolderExists(sLogPath) Then MkDir sLogPath End If Retry: On Error GoTo eError If Dir(sLogPath & sLogName) <> "" Then Set log = FSO.OpenTextFile(sLogPath & sLogName, 2, 0) Else Set log = FSO.CreateTextFile(sLogPath & sLogName, False) End If Exit Function Set FSO = Nothing Exit Function eError: If Err.Number = 70 Then CreateObject("WScript.Shell").Run "attrib -r " & sLogPath End If GoTo Retry End Function Function WriteString(argument As String) Retry: If Not log Is Nothing Then log.WriteLine Now & ": " & argument Else Call Initialize GoTo Retry End If End Function Function Terminate() If Not log Is Nothing Then log.Close Else Set log = Nothing End If End Function
问题根源
- 全局对象引用丢失:
Initialize函数中Set FSO = Nothing语句在Exit Function之后,永远不会执行,可能导致对象资源泄漏;且Terminate函数被注释,日志文件句柄未正确释放,后续操作中log对象被意外置空。 - 权限错误处理不全:错误70触发时仅移除文件夹的只读属性,但实际是日志文件本身为只读导致无法写入;且
attrib命令未对路径加引号,若用户名含空格会执行失败。 - 文件夹只读属性误解:Windows用户文档下的自定义文件夹显示的只读属性是系统默认标记(仅用于标识文件夹有自定义图标/属性),并不影响文件写入,真正阻碍操作的是文件的只读属性。
修复方案
修改后的代码
Option Explicit Public log As TextStream ' 初始化日志,返回是否成功 Function Initialize() As Boolean Dim FSO As FileSystemObject Dim sLogName As String Dim sLogPath As String Dim sFullLogPath As String On Error GoTo eError Set FSO = New FileSystemObject sLogPath = "C:\Users\" & UserName & "\Documents\VbaLog" sLogName = "progressLog.txt" ' 使用BuildPath避免路径拼接的斜杠错误 sFullLogPath = FSO.BuildPath(sLogPath, sLogName) ' 确保日志文件夹存在,创建后移除只读标记 If Not FSO.FolderExists(sLogPath) Then MkDir sLogPath CreateObject("WScript.Shell").Run "attrib -r """ & sLogPath & """", , True End If ' 若日志文件存在,先移除其只读属性 If FSO.FileExists(sFullLogPath) Then CreateObject("WScript.Shell").Run "attrib -r """ & sFullLogPath & """", , True ' 2=覆盖模式,True=文件不存在则自动创建,避免分支判断 Set log = FSO.OpenTextFile(sFullLogPath, 2, True) Else Set log = FSO.CreateTextFile(sFullLogPath, False) End If Initialize = True Cleanup: ' 确保FSO对象被正确释放 Set FSO = Nothing Exit Function eError: If Err.Number = 70 Then ' 同时移除文件夹和文件的只读属性 CreateObject("WScript.Shell").Run "attrib -r """ & sLogPath & """", , True If FSO.FileExists(sFullLogPath) Then CreateObject("WScript.Shell").Run "attrib -r """ & sFullLogPath & """", , True End If Resume ' 重试当前出错的行 Else Initialize = False Resume Cleanup End If End Function ' 写入日志内容 Function WriteString(argument As String) On Error GoTo eError ' 日志对象为空时重新初始化 If log Is Nothing Then If Not Initialize() Then MsgBox "日志初始化失败,无法写入内容", vbCritical Exit Function End If End If log.WriteLine Now & ": " & argument Exit Function eError: ' 出错后重置日志对象并重试一次 If Not log Is Nothing Then log.Close Set log = Nothing End If Resume End Function ' 释放日志资源 Function Terminate() If Not log Is Nothing Then log.Close Set log = Nothing End If End Function
关键修改点
- 用
FSO.BuildPath拼接路径,避免手动拼接的斜杠错误 Initialize函数返回布尔值,明确告知调用方初始化结果- 打开文件前优先移除日志文件的只读属性(之前仅处理文件夹)
attrib命令路径添加双引号,适配含空格的用户名路径- 错误处理改用
Resume重试当前行,逻辑更清晰 WriteString增加错误捕获,失败后重置日志对象并重试- 确保
FSO对象在Cleanup块中释放,避免内存泄漏 - 取消
Terminate函数的注释,在程序退出时调用以释放文件句柄
关于文件夹只读属性的说明
Windows用户文档下的文件夹显示的只读属性是系统默认的标记,仅用于标识文件夹包含自定义设置(如图标),不会影响文件的读写操作。无需强制取消该属性,只需确保日志文件本身不是只读状态即可。
内容的提问来源于stack exchange,提问作者BMStacks
相关产品推荐
相关产品推荐

