patternMinor
Win32 File API in VBA
Viewed 0 times
apifilevbawin32
Problem
Win32 File API Wrapper
Based on a few fundamental frustrations with VBA (namely the lack of ability to work with files larger than 2GB, the lack of encapsulation of the file functions and the lack of intellisense to guide my use of the file statements) I put together a wrapper for the Win32 File API. This includes 64-bit functions which allow reading and writing past the 4GB limit of 32 bit addressing.
Concerns
One issue with this wrapper is that for the 32 bit functions, offsets larger than 2GB are negative numbers in VBA since it doesn't have unsigned longs, which I suppose is fine as long as you're aware of it, but it does make use of the API less intuitive and you have to be careful of offset math.
Another issue is the use of Currency for the 64 bit functions - it's kind of a hack and it again makes the math awkward. I would love to incorporate the
I'd appreciate any style advice, corrections to mistakes I've made or suggestions for how to make the wrapper more intuitive.
clsFile
```
Option Compare Database
Option Explicit
'Based on the example on msdn:
'http://support.microsoft.com/kb/189981
'Some of the constants come from Winnt.h
Public Enum SeekOrigin
so_Begin = 0
so_Current = 1
so_End = 2
End Enum
Public Enum FileAccess
' FILE_READ_DATA = &H1 ' winnt.h:1801
' 'FILE_LIST_DIRECTORY = &H1 ' winnt.h:1802
' FILE_WRITE_DATA = &H2 ' winnt.h:1804
' 'FILE_ADD_FILE = &H2 ' winnt.h:1805
' FILE_APPEND_DATA = &H4 ' winnt.h:1807
' 'FILE_ADD_SUBDIRECTORY = &H4 ' winnt.h:1808
' 'FILE_CREATE_PIPE_INSTANCE = &H4 ' winnt.h:1809
' FILE_READ_EA = &H8 ' winnt.h:1811
' FILE_READ_PROPERTIES = &H8 ' winnt.h:1812
' FILE_WRITE_EA = &H10 ' winnt.h:
Based on a few fundamental frustrations with VBA (namely the lack of ability to work with files larger than 2GB, the lack of encapsulation of the file functions and the lack of intellisense to guide my use of the file statements) I put together a wrapper for the Win32 File API. This includes 64-bit functions which allow reading and writing past the 4GB limit of 32 bit addressing.
Concerns
One issue with this wrapper is that for the 32 bit functions, offsets larger than 2GB are negative numbers in VBA since it doesn't have unsigned longs, which I suppose is fine as long as you're aware of it, but it does make use of the API less intuitive and you have to be careful of offset math.
Another issue is the use of Currency for the 64 bit functions - it's kind of a hack and it again makes the math awkward. I would love to incorporate the
GB, MB, KB consts into the class somehow, but Enum only supports longs and Const variables can't be public.I'd appreciate any style advice, corrections to mistakes I've made or suggestions for how to make the wrapper more intuitive.
clsFile
```
Option Compare Database
Option Explicit
'Based on the example on msdn:
'http://support.microsoft.com/kb/189981
'Some of the constants come from Winnt.h
Public Enum SeekOrigin
so_Begin = 0
so_Current = 1
so_End = 2
End Enum
Public Enum FileAccess
' FILE_READ_DATA = &H1 ' winnt.h:1801
' 'FILE_LIST_DIRECTORY = &H1 ' winnt.h:1802
' FILE_WRITE_DATA = &H2 ' winnt.h:1804
' 'FILE_ADD_FILE = &H2 ' winnt.h:1805
' FILE_APPEND_DATA = &H4 ' winnt.h:1807
' 'FILE_ADD_SUBDIRECTORY = &H4 ' winnt.h:1808
' 'FILE_CREATE_PIPE_INSTANCE = &H4 ' winnt.h:1809
' FILE_READ_EA = &H8 ' winnt.h:1811
' FILE_READ_PROPERTIES = &H8 ' winnt.h:1812
' FILE_WRITE_EA = &H10 ' winnt.h:
Solution
I'm not overly familiar with using WinApi calls from VBA, but I'll do my best here, because this is a cool piece of code. Let's get started.
This line ties your class to access. It won't compile in any other host app. I try to keep utility classes like this host agnostic. Removing this option will allow you to use this class in any app that supports VBA. I honestly don't like this option anyway. It ties how the code behaves to the environment it's running in by letting Access determine how string comparisons are made. If you're going to use an
I love comments like this. Awesome. Well done!, but MS is notorious for killing urls on a whim with no redirect. It would help to leave the title of the article so it can be searched for if the link goes dead.
Normally, I'd say that this is dead code and you should kill it, but I get why you've done this. It's good documentation and all the work is already done should you decide that you need any of these additional values. I'd leave a comment threatening a psychotic episode should anyone ever "be helpful" and remove it, because, let's face it, someone like me could easily come along and wipe out all this "dead code" without batting an eye about it.
It's petty, and doesn't really matter, but I might do this the other way round for consistency, or better yet, leave a single explanatory comment.
I like the way you're handling errors, but you could extract a method to reduce the duplication.
There's a magic number in
I'm not terribly familiar with directly working with pointers this way, so I have no idea why this uses
And that's all very minor really. This is good code. Unfortunately, I've no better ideas about how to simplify the API. It's a side effect of needing to use the
What you may be able to do is some precompiler directive magic. You're using currency to get a 64bit integer, right? Well, on 64bit installs, there's the
Option Compare DatabaseThis line ties your class to access. It won't compile in any other host app. I try to keep utility classes like this host agnostic. Removing this option will allow you to use this class in any app that supports VBA. I honestly don't like this option anyway. It ties how the code behaves to the environment it's running in by letting Access determine how string comparisons are made. If you're going to use an
Option Compare, choose either Text or Binary depending on your needs. Both of those are available in any of the host apps by the way. (I know, Access probably inserted this line for you, moving on...)'Based on the example on msdn:
'http://support.microsoft.com/kb/189981I love comments like this. Awesome. Well done!, but MS is notorious for killing urls on a whim with no redirect. It would help to leave the title of the article so it can be searched for if the link goes dead.
Public Enum FileAccess
' FILE_READ_DATA = &H1 ' winnt.h:1801
' 'FILE_LIST_DIRECTORY = &H1 ' winnt.h:1802
' FILE_WRITE_DATA = &H2 ' winnt.h:1804
' 'FILE_ADD_FILE = &H2 ' winnt.h:1805
' FILE_APPEND_DATA = &H4 ' winnt.h:1807Normally, I'd say that this is dead code and you should kill it, but I get why you've done this. It's good documentation and all the work is already done should you decide that you need any of these additional values. I'd leave a comment threatening a psychotic episode should anyone ever "be helpful" and remove it, because, let's face it, someone like me could easily come along and wipe out all this "dead code" without batting an eye about it.
Private Const INVALID_FILE_HANDLE = -1 '&HFFFFFFFF
Private Const FORMAT_MESSAGE_FROM_SYSTEM = &H1000
Private Const INVALID_FILE_SIZE As Long = -1 '&HFFFFFFFF
Private Const INVALID_SET_FILE_POINTER As Long = -1 '&HFFFFFFFFIt's petty, and doesn't really matter, but I might do this the other way round for consistency, or better yet, leave a single explanatory comment.
' &HFFFFFFFF == -1
' &H1000 == 4096
Private Const INVALID_FILE_HANDLE = &HFFFFFFFF
Private Const FORMAT_MESSAGE_FROM_SYSTEM = &H1000
Private Const INVALID_FILE_SIZE As Long = &HFFFFFFFF
Private Const INVALID_SET_FILE_POINTER As Long = &HFFFFFFFFI like the way you're handling errors, but you could extract a method to reduce the duplication.
Private Sub RaiseError(ByVal caller As String)
Err.Raise vbObjectError + Err.LastDllError, TypeName(Me) & "." & caller, DecodeAPIErrors(Err.LastDllError)
End SubThere's a magic number in
SeekFile64bit.CopyMemory VarPtr(HiBytesOffset), VarPtr(offset) + 4, 4
CopyMemory VarPtr(LoBytesOffset), VarPtr(offset), 4
Ret = SetFilePointer(m_Handle, LoBytesOffset, HiBytesOffset, origin)
CopyMemory VarPtr(curFilePosition) + 4, VarPtr(HiBytesOffset), 4
CopyMemory VarPtr(curFilePosition), VarPtr(Ret), 4I'm not terribly familiar with directly working with pointers this way, so I have no idea why this uses
4. A well named constant would help a schmuck like me understand what's happening here.And that's all very minor really. This is good code. Unfortunately, I've no better ideas about how to simplify the API. It's a side effect of needing to use the
Currency type to get large enough values. I've got.... nothing. I think the best you can do is add some example code inside of the class and document the use of the class's API as best you can.What you may be able to do is some precompiler directive magic. You're using currency to get a 64bit integer, right? Well, on 64bit installs, there's the
LongLong type. So, you might be able to clean this up for use in that environment, but in my experience, very people are actually running 64bit installs of office, so it may not be worth the effort. Particularly when it would mean that you would effectively have two APIs for the same class.Code Snippets
Option Compare Database'Based on the example on msdn:
'http://support.microsoft.com/kb/189981Public Enum FileAccess
' FILE_READ_DATA = &H1 ' winnt.h:1801
' 'FILE_LIST_DIRECTORY = &H1 ' winnt.h:1802
' FILE_WRITE_DATA = &H2 ' winnt.h:1804
' 'FILE_ADD_FILE = &H2 ' winnt.h:1805
' FILE_APPEND_DATA = &H4 ' winnt.h:1807Private Const INVALID_FILE_HANDLE = -1 '&HFFFFFFFF
Private Const FORMAT_MESSAGE_FROM_SYSTEM = &H1000
Private Const INVALID_FILE_SIZE As Long = -1 '&HFFFFFFFF
Private Const INVALID_SET_FILE_POINTER As Long = -1 '&HFFFFFFFF' &HFFFFFFFF == -1
' &H1000 == 4096
Private Const INVALID_FILE_HANDLE = &HFFFFFFFF
Private Const FORMAT_MESSAGE_FROM_SYSTEM = &H1000
Private Const INVALID_FILE_SIZE As Long = &HFFFFFFFF
Private Const INVALID_SET_FILE_POINTER As Long = &HFFFFFFFFContext
StackExchange Code Review Q#102209, answer score: 8
Revisions (0)
No revisions yet.