I am using the following code to insert text into an arbitrary section of a file:
Dim prebuffer() As Byte
Dim postbuffer() As Byte
Dim number As Integer
number = FreeFile
Open file For Binary Access Read Write Lock Read Write As number
ReDim prebuffer开发者_如何学C(after - 2)
ReDim postbuffer(LOF(number) - before)
Get number, 1, prebuffer
Get number, before, postbuffer
Seek number, 1
Put number, , prebuffer
Put number, , value
Put number, , postbuffer
Close number
after
and before
are longs retrieved from previous calls to Seek(number)
. When after
equals before
, I simply want to insert data without erasing any. But when after
is less than before
, I'm overwriting existing data with some of my own.
This works great when (before - after) <= Len(value)
, as Windows knows to increase my file size to accommodate the new bytes. However, when I'm inserting less bytes than I'm removing, the file does not shrink, and leaves the existing bytes there.
For example, if my binary file is abcdefghijklmnopqrstuvwxyz
and I want to insert HELLO
with after=5
and before=15
I get abcdeHELLOopqrstuvwxyzvwxyz
, with vwxyz
being repeated. How do I shrink the file so that I only get abcdeHELLOopqrstuvwxyz
?
Clarification: I know that I could delete the original file and write the entire buffer back, but I'm aiming for high performance, and wrote the insert this way because reading and writing the entire file line-by-line was too slow. Also, I would like to keep the properties of the file in tact, and don't want to destroy them every time I insert a record by making an entirely new file.
There are no native VB functions for setting the length of the file, sadly. One might hope to use FileAttr to get some sort of file handle the operating system can use, but this is valid only in 16-bit code.
VBnet suggests doing the whole thing using the Windows API functions:
- CreateFile / CloseHandle to open/close the file
- GetFileSize to get the size from the file handle
- SetFilePointer to seek within the file
- SetEndOfFile to truncate the file
I've written some simple code based on the VBnet example:
Option Explicit
'' constants for CreateFile
Private Const OPEN_ALWAYS As Long = 4, GENERIC_WRITE As Long = &H40000000, GENERIC_READ As Long = &H80000000, FILE_ATTRIBUTE_NORMAL As Long = &H80, INVALID_HANDLE_VALUE As Long = -1
'' constants for SetFilePointer
Private Const FILE_BEGIN As Long = 0, INVALID_SET_FILE_POINTER As Long = -1
'' kernel32 functions needed
Private Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByVal lpSecurityAttributes As Long, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
Private Declare Function GetFileSize Lib "kernel32" (ByVal hfile As Long, lpFileSizeHigh As Long) As Long
Private Declare Function SetFilePointer Lib "kernel32" (ByVal hfile As Long, ByVal lDistanceToMove As Long, lpDistanceToMoveHigh As Long, ByVal dwMoveMethod As Long) As Long
Private Declare Function SetEndOfFile Lib "kernel32" (ByVal hfile As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hfile As Long) As Long
Sub truncatefile(filename As String, ByVal size As Currency)
Dim hfile As Long
Dim dwFileSizeLow As Long
Dim dwFileSizeHigh As Long
Dim ret As Long
'' open the file
hfile = CreateFile(filename, _
GENERIC_WRITE Or GENERIC_READ, _
0&, ByVal 0&, _
OPEN_ALWAYS, _
FILE_ATTRIBUTE_NORMAL, _
0&)
Debug.Assert (hfile <> INVALID_HANDLE_VALUE) '' make sure file opened OK
'' optional: get the current file length
dwFileSizeLow = GetFileSize(hfile, dwFileSizeHigh)
Debug.Assert (dwFileSizeLow >= 0 And dwFileSizeHigh = 0) '' TODO: handle 2GB and higher
Debug.Print "Old file size: " & dwFileSizeLow
'' split length into DWORDs (TODO: handle 2GB and higher)
dwFileSizeLow = size
dwFileSizeHigh = 0
'' seek to the desired file length
ret = SetFilePointer(hfile, dwFileSizeLow, dwFileSizeHigh, FILE_BEGIN)
Debug.Assert ret <> INVALID_SET_FILE_POINTER
'' set this as the length of the file
ret = SetEndOfFile(hfile)
Debug.Assert (ret <> 0)
'' close the file handle
Debug.Assert CloseHandle(hfile) <> 0
End Sub
精彩评论