Which Is Better Coding?

File I/O in a multiuser environment has presented problems for me for a while. i have resorted the the not so ideal method of making very short references to files, keeping them open for the least amount of time possible. this works ok for my needs, but if there is a Class level method for file I/O which can help in multiuser context i would be very keen to see more (hint :))

Hi mate

I'm actually talking about creating your own class to handle the file I/O but then utilise the class's terminate event to ensure that it gets closed properly. Here's an example which should hopefully illustrate the idea. I'm assuming that the file I/O you're referring to is the normal Open, Close, Print, Write, etc but let me know if not.

Insert a class module and name it CFile and paste this code:

Code:
Option Explicit

Private Const mlCLASS_ERROR As Long = vbObjectError + 1
Private miFreeFile As Integer
Private mbFileOpen As Boolean

Public Function OpenFile(sFilename As String)

    On Error GoTo ErrHandler

    If Not mbFileOpen Then
        miFreeFile = FreeFile()
        Open sFilename For Output As #miFreeFile
        mbFileOpen = True
    Else
        Err.Raise mlCLASS_ERROR, , "File already open."
    End If


ExitFunction:
    Exit Function

ErrHandler:
    Err.Raise mlCLASS_ERROR, , Err.Description

End Function

Public Sub WriteLine(sValue As String)

    If Not mbFileOpen Then
        Err.Raise mlCLASS_ERROR, , "File not open."
    Else
        Print #miFreeFile, sValue
    End If
End Sub

Public Sub CloseFile()
    Close #miFreeFile
    mbFileOpen = False
End Sub


Private Sub Class_Terminate()
    On Error Resume Next
    CloseFile
End Sub

Then to test it use something like this:

Code:
Sub TestIt()
    
    'Test of CFile class

    On Error GoTo ErrHandler

    Dim oFile As CFile

    Set oFile = New CFile

    oFile.OpenFile ("C:\temp\hello.txt")

    oFile.WriteLine "test file written by CFile object..."
    oFile.WriteLine "another line..."
    oFile.WriteLine "closing now..."
    oFile.CloseFile

    MsgBox "File succesfully created.", vbInformation, "CFile Test"

ExitSub:
    Exit Sub

ErrHandler:
    MsgBox Err.Description, vbExclamation, "An error occurred."

End Sub

With this technique the file gets closed regardless of whether your code errors out (e.g. trying to create a file on a non-existent folder) or if you don't explicitly call the CloseFile method. You can test this by putting a Msgbox in the class's CloseFile method and then commenting out the oFile.CloseFile.

HTH
DK
 

Excel Facts

How to find 2nd largest value in a column?
MAX finds the largest value. =LARGE(A:A,2) will find the second largest. =SMALL(A:A,3) will find the third smallest
@dk

ah yes i see how that works. must admit i would not have thought about tackling the problem like that, but i shall give it a try.
thx


a question: does the "File already open error" refer to the current code already having the file open, or another user having the file open?
 
Last edited:
@dk
a question: does the "File already open error" refer to the current code already having the file open, or another user having the file open?

It means that the CFile object can only have 1 file open at a time. If you wanted multiple files then you'd just use multiple CFile objects i.e. 1 CFile object = 1 file.
 
As an addendum, the following function does appear to work, but I only tested it lightly and only under Win7. Various versions seem to be floating around the internet. I pulled this from ozgrid, but not sure who is the original author
Code:
Function GetFileOwner(fileName As String) As String
 
    Dim secUtil As Object
    Dim secDesc As Object
 
    Set secUtil = CreateObject("ADsSecurityUtility")
    Set secDesc = secUtil.GetSecurityDescriptor(strfilename, 1, 1)
    GetFileOwner = secDesc.Owner
 
End Function

EDIT - I always like to try an early bound version when playing around. Here's what that looks like:
Code:
Function GetFileOwner(fileName As String) As String
    
    '// to use early bound, set reference to
    '// Active DS Type Library
    
    Dim secUtil As ActiveDs.ADsSecurityUtility, _
        secDesc As ActiveDs.SecurityDescriptor
    
    Set secUtil = New ActiveDs.ADsSecurityUtility
    Set secDesc = secUtil.GetSecurityDescriptor(fileName, 1, 1)
    GetFileOwner = secDesc.Owner
     
End Function

Thanks for sharing Greg. :) I wasn't aware of the quirks in the previous function; having only ever used it on excel workbooks within the same instance. Nice to have someone else do all the hard work for me. :biggrin:
 
Hmmm, now I'm gonna have to experiment with this when I get the chance. I'd seen it before in other apps, but had not adopted it as a general practice. Though I probably will going forward.

In particular - I have an Excel add-in that has about 80 users in the client's company and I've had an issue where if a user crashes Excel at a particular spot they have a common file open and locked and then subsequent users cannot open the file and it raises errors. I have no idea if the Terminate code for the class module will run during an Excel crash. I rather suspect it won't. But certainly worth playing with. The hard part is reliably crashing Excel. (I'm not soliciting help on this - just saying why it caught my eye so. But if anyone has a brilliant solution, feel free to post it for the public's edification.)

G'day Greg

If you're talking about Excel at crashing as opposed to a run-time error then you're right that the class technique is not going to help. I found <a href="http://www.mrexcel.com/forum/showpost.php?p=69672&postcount=5">some code</a> for crashing Excel (use with caution!) and tested if the terminate event got called. Unsurprisingly it was not called so not sure how you could reliably handle the situation you've got.

Cheers
Dan
 
Hi Dan,

I my specific context, I was able to code around the problem. It wasn't particularly elegant or pretty; but it was also a rare event, so I didn't want to invest an excessive amount of time on it. Nonetheless, just in general terms it would be nice to have a solution path for a "crash left file locked" situation. I'll certainly play with everything we've mentioned here when I get the chance.

Just curious - do you go ahead and base all your custom error off vbObjectError? I know it's the "right thing to do" but I just have a completely irrational dislike for it because it's a large negative number. I just start mine at 1100 (and yes, I know there's a danger that someday MS will use those numbers).

@ diddi, I've also seen (and I would expect dk has too) versions of the "generic file" class that have an optional access/mode argument.
 
Just curious - do you go ahead and base all your custom error off vbObjectError? I know it's the "right thing to do" but I just have a completely irrational dislike for it because it's a large negative number. I just start mine at 1100 (and yes, I know there's a danger that someday MS will use those numbers).

To be honest, no. That class code was something i quickly typed up to demonstrate the idea. Nearly all of my code uses the error handling techniques from the Professional Excel Development Handbook - I have just looked at one of the modules and they have defined the error code as:

Public Const glHANDLED_ERROR As Long = 9999

So not even a mention of vbObjectError. This has me confused now - according to the help it says:

<i>Required. Long integer that identifies the nature of the error. Visual Basic errors (both Visual Basic-defined and user-defined errors) are in the range 0–65535. The range 0–512 is reserved for system errors; the range 513–65535 is available for user-defined errors. When setting the Number property to your own error code in a class module, you add your error code number to the vbObjectError constant. For example, to generate the error number 513, assign vbObjectError + 513 to the Number property.</i>

However, if I do this I do not get the 513 as a result:

Code:
Sub blah()

    Const z As Long = vbObjectError + 513

    On Error GoTo errHandler

    Err.Raise z, , "custom error"


    Exit Sub

errHandler:
    MsgBox "error code: " & Err.Number & ", Desc: " & Err.Description

End Sub

I get "error code: -2147220991, Desc: custom error" so no mention of error code 513 so not sure what's going on there. Any ideas?

Dan
 
I get "error code: -2147220991, Desc: custom error" so no mention of error code 513 so not sure what's going on there. Any ideas?

Well, you get that value because vbObjectError is equal to -2147221504.

The way I interpret what's in help is "we [MS] will never use an error code [reasonably] north of vbObjectError, so use that as your base and go from there", i.e.

Code:
Public Enum ge_ProjectErrors
    '// general
    errUserAborted = vbObjectError + 1
 
    '// used by basXL_CommandBars
    errControlCreationFailure
    errBarResetBackLoopUnknown
 
    '// used by basXL_OpenClose
    errXLVersionTooLow
    errMyDocsPathIsNetworkPath
 
    '// used by clsExcelApp
    errNoWorkbookObj_Allocation
    errNoWorkbookObj_Merchant
    errNoWorkbookObj_PRC
    errNoWorksheetObj_Allocation
    errNoWorksheetObj_Merchant
    errNoWorksheetObj_PRC
    errVrsnTooLow
 
    '// used by basXL_EntryPoints
    errBadMenuParam
    errBadTemplateVersion
    errInvalidDataDetail
    errInvalidDataHeader
    errNoDetailData
    errNotInProject
    errUnknownTemplate
'...
End Enum

Like I said, my objection is purely emotional: -2147221504 just comes across as so dad gummed arbitrary although it is a little prettier in hex (FF80040000).
 
Last edited:
You (the consumer of the object) have to remove the vbobjecterror again to get a true error number. IMO this is more relevant to VB programmers building dlls that ought to follow conventions (I think the error number is actually a couple of specific error bits that would have more meaning to C++ programmers, IIRC). For VBA I think it's irrelevant as long as you stay out of the MS error number range.
 
You (the consumer of the object) have to remove the vbobjecterror again to get a true error number. IMO this is more relevant to VB programmers building dlls that ought to follow conventions (I think the error number is actually a couple of specific error bits that would have more meaning to C++ programmers, IIRC). For VBA I think it's irrelevant as long as you stay out of the MS error number range.

I see. That sort of ties in with what I read here yesterday: http://vb.mvps.org/hardcore/html/howtoraiseerrors.htm - although I have been using C++ for the last year I have yet to do any COM stuff (just XLLs and stand alone executables) so this will be something for me to look forward to.

Cheers
 

Forum statistics

Threads
1,224,808
Messages
6,181,072
Members
453,020
Latest member
mattg2448

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top