1. Not So Lightweight
Office 2000
4 out of 4 rated this helpful - Rate this topic
This article may contain URLs that were valid when originally published, but now link to sites or
pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text,
but disabled the links.
VBA Hacker
The Powerful String and Path Functions in Shlwapi.dll
By Romke Soldaat
When the functions of the Windows API are discussed, the core libraries such as User32 and
Kernel32 are usually mentioned. They've been around since the very first version of Windows,
although the "32" suffix was only added when the operating system became 32-bit. Other
libraries with interesting functions (Shell32, Comdlg32, etc.) didn't become available until
Windows 95 was released. All of these libraries are part of the operating system, so you don't
have to question their presence on any PC.
Things are less clear-cut with the library named Shlwapi. The first version (4.71) was shipped
with Internet Explorer (IE) 4.0 and Windows 98/NT5. The next version (4.72) came only with
the IE 4.01 upgrade. Version 5.00 was introduced with IE5, and also became part of Windows
2000.
This means you can be assured that everybody has the Shlwapi.dll file, except those who are still
running Windows 95/NT4, and never installed IE4 or IE5. Since this is probably a small
minority, it's worthwhile to look at this library, and see what its functions offer.
Shlwapi stands for Shell Lightweight API. As you'll find out, the word "lightweight" has nothing
to do with the library's features, but more with its file size (although the file grew from a mere 90
KB to nearly 280 KB over the past few generations). The functions can be organized into three
categories: string manipulation, path manipulation, and registry access. In this article, I'll focus
on the first two. I may dedicate a future article to the registry access functions.
ANSI or Unicode? Pointers or Strings?
2. Unlike system libraries such as User32 and Kernel32, Shlwapi comes with both ANSI and
Unicode support, even on Windows 95/98. ANSI functions have the drawback that they can only
deal with 8-bit character sets (character values 0-255), while Unicode functions support virtually
every character used on our planet (with room to spare for languages of any aliens that may
invade us).
From that point of view, it's worthwhile using the Unicode versions of the Shlwapi functions.
That way you can be sure your code will run on every PC and every recent version of Windows.
In this article and the accompanying download file (see end of article for details), you'll see these
functions declared as aliases of the "W" (wide = Unicode) versions.
Shlwapi functions that deal with strings accept two ways to process them. You can pass the
strings as they are, or you can pass them as string pointers. The following declarations are both
valid:
Declare Function PathAddBackslashByString Lib "Shlwapi" _
Alias "PathAddBackslashW" _
(ByVal lpszPath As String) As Long
Declare Function PathAddBackslashByPointer Lib "Shlwapi" _
Alias "PathAddBackslashW" _
(ByVal lpszPath As Long) As Long
You would expect that the first declaration works fine with strings of any origin because VBA
uses Unicode internally, but this is not so. When VBA passes strings to external functions (even
if they are Unicode functions), it converts them first to ANSI format, which means that any
characters that don't belong to the active code page get lost. There are two ways to prevent this
from happening: You can convert the strings to byte arrays, or you can send the string's pointer
(its memory address) to the API function. In either case, you can rest assured that VBA leaves
your strings alone. Since pointers are easier to use than byte arrays, I'll stick to them in this
article.
Getting a string pointer. VBA comes with a number of undocumented (and unsupported)
functions that return pointers: StrPtr, VarPtr, and ObjPtr. Of these functions, StrPtr returns a
pointer to the internal Unicode string - exactly what we need when we want to call an API
function that expects a Unicode string pointer. The following code (from Listing One) shows
how:
Declare Function StrTrim Lib "Shlwapi" Alias "StrTrimW"
As Boolean
Public Function TrimAny As String
Dim pAddr As Long: pAddr = StrPtr
Call StrTrim(pAddr, StrPtr(CharList))
TrimAny = StringFromAddr(pAddr)
End Function
Getting a string back from a pointer. In the above code, the Shlwapi StrTrim function takes
two string pointers. The pAddr variable points to the string to be trimmed. When the function
3. returns, this variable holds the pointer to the trimmed string. To get at the string itself, we have to
perform some memory juggling, which is done in the following Ptr2StrU function:
Public Function Ptr2StrU(ByVal pAddr As Long) As String
Dim lAddr As Long: lAddr = lstrlenW(pAddr)
StringFromAddr = Space$(lAddr)
CopyMemoryByVal StrPtr(Ptr2StrU), ByVal pAddr, lAddr * 2
End Function
This function receives the Unicode string pointer as its argument. The Windows lstrlenW
function tells us how long that string is, and the value is used to create a text buffer (using the
Space$ function), which receives the actual string. Then the Windows CopyMemory function
(an alias for the cryptic internal name RtlMoveMemory) copies the source string pointer
(pAddr) to the destination string pointer (obtained with the StrPtr function). The third
parameter specifies the number of bytes to be copied. lAddr is multiplied by 2, because of the
Unicode strings, which use two bytes for every character. After this operation, Ptr2StrU
contains the string we're looking for.
So much for the introduction - time for the real stuff!
String Functions
The Shlwapi string functions are primarily designed to give C programmers enhanced versions
of what they already have in their C++ run-time libraries. FIGURE 1 lists them. Most of these
functions are not very useful for Office programming, because VBA offers similar ones, or lets
you imitate them with built-in functions. For example, the Shlwapi StrStr/StrStrI functions
work exactly like the VBA Instr function, and the Shlwapi StrCmp/StrCmpI pair doesn't offer
anything you can't do with VBA's StrComp. The third column in FIGURE 1 shows which VBA
functions you can use to mimic their Shlwapi counterparts.
Shlwapi Function
StrTrim
StrToIntEx
StrToInt
StrStr/StrStrI
StrSpn
StrRStrI
StrRChr/StrRChrI
Description
Removes (trims) specified leading and
trailing characters from a string.
Converts a decimal or hexadecimal string to
an integer.
Converts a decimal string to an integer.
Finds the first occurrence of a substring
within a string.
Obtains the length of a substring within a
string that consists entirely of characters
contained in a specified buffer.
Searches for the last occurrence of a
specified substring within a string. The
comparison is not case sensitive.
Searches a string for the last occurrence of a
VBA Equivalent
See TrimAny function in
Listing One
Combine Int and Val
Combine Int and Val
Instr
Not used in this project
InstrRev
InstrRev
4. specified character.
Searches a string for the first occurrence of a
StrPBrk
character contained in a specified buffer.
Appends a specified number of characters
from the beginning of one string to the end
StrNCat
of another.
Compares a specified number of characters
from the beginning of two strings to
StrIsIntlEqual
determine if they are equal.
Converts a time interval, specified in
StrFromTimeInterval
milliseconds, to a string.
Converts a numeric value into a string that
represents the number expressed as a size
StrFormatByteSize value in bytes, kilobytes (KB), megabytes
(MB), or gigabytes (GB), depending on the
size.
Duplicates a string.
StrDup
Searches a string for the first occurrence of
any of a group of characters. The NULL
StrCSpn/StrCSpnI
terminator is included within the search
pattern match.
Copies a specified number of characters
StrCpyN
from the beginning of one string to another.
Copies one string to another.
StrCpy
Compares a specified number of characters
StrCmpN/StrCmpNI from the beginning of two strings to
determine if they are the same.
Compares two strings to determine if they
StrCmp/StrCmpI
are the same.
Searches a string for the first occurrence of a
character that matches the specified
StrChr
character. The comparison is case sensitive.
Appends one string to another.
StrCat
Performs a comparison between two
characters. The comparison is not case
ChrCmpI
sensitive.
Tip: Use StrCSpn and
StrCSpnI instead
Combine Left$ and &
operator
Combine Left$ and
StrComp
See FormatTimeInterval
function in Listing One
See FormatByteSize
function in Listing One
= operator
See InstrAny function in
Listing One
Combine Left$ function
and Mid$ statement
= operator
Combine Left$ and
StrComp
StrComp
Instr
& operator
StrComp
FIGURE 1: Common string functions in all versions of Shlwapi. Where function names
appear in pairs, the version that ends with an uppercase "I" performs a non-case-sensitive
operation; the other one processes case-sensitive strings.
Following is a brief discussion of the handful of functions let you do things in VBA that would
otherwise require quite a bit of code.
5. Smart trimming. The VBA Trim$ function returns a string that contains a copy of a specified
string from which leading and trailing spaces have been removed. The Shlwapi StrTrim
function goes a step further, by letting you specify a list of characters you want to trim. Each
character in the list is removed from the start and end of the string you specify. Listing One
implements StrTrim in a TrimAny VBA function. The following example prints "Test" in the
Debug window:
s1 = "...Test///"
s2 = "/."
Debug.Print TrimAny(s1, s2)
Smart substring location. VBA's Instr function locates a substring inside another string. By
using the appropriate compare argument, you can make the search binary (case sensitive) or
textual (case insensitive). The Shlwapi StrCSpn and StrCSpnI functions differ in that they
accept a list of characters you want to locate. The functions return the position of the first listed
character they find. The InstrAny function in Listing One converts this into VBA-friendly code.
You'll also find a ContainsAnyChar function that simply returns True if a string contains at
least one of a series of characters.
The following example demonstrates how you can determine if a suggested file name holds
invalid characters. In this case the ContainsAnyChar function returns True, because the ">"
character in FileName is one of the characters in TabooChars:
FileName = "myfile>.txt"
TabooChars = "/:*?<>|" & Chr(34)
Debug.Print ContainsAnyChar(FileName, TabooChars) ' True
Smart number formatting. VBA 2000 introduced a number of handy functions that convert
numeric values into currency, date, and percentage strings. Thanks to the Shlwapi
StrFormatByteSize and StrFromTimeInterval functions, you can now also create strings that
convert bytes into KB, GB, and terabytes (TB), and convert milliseconds into a string that
expresses a value as hours, minutes, and seconds. Listing One incorporates these Shlwapi
functions in two VBA functions: FormatTimeInterval and FormatByteSize.
FormatTimeInterval takes two arguments: the number of milliseconds (a Long, so the
maximum value is 2,147,483,647, equivalent to 596 hr 31 min 24 sec), and the output precision,
expressed as a number from one to seven. If no output precision is specified, the function uses
the maximum value of seven. The following list demonstrates how these values are interpreted
(note that fractions of seconds are ignored):
Output
Output
Output
Output
Output
Output
Output
precision
precision
precision
precision
precision
precision
precision
7:
6:
5:
4:
3:
2:
1:
596
596
596
596
596
590
500
hr
hr
hr
hr
hr
hr
hr
31
31
31
30
min 24 sec
min 20 sec
min
min
6. FormatByteSize converts a given number of bytes into a string that represents the value in
bytes, MB, GB, or TB. The Unicode version of the Shlwapi StrFormatByteSize function is
designed to accept a 64-bit (also called LongLong) value. The only VBA data type that comes
close to a 64-bit integer is Currency, except that this type is scaled by 10,000 to give a fixed
point number with 15 digits to the left of the decimal point and four digits to the right, which
limits the range to a maximum integer value of 922,337,203,685,477. Since this amount of bytes
is the equivalent of 838 TB (nearly 900 million MB), it'll be a while before you get an overflow
error when you convert a file size with the FormatByteSize function.
Private Declare Function GetDiskFreeSpaceEx _
Lib "kernel32" Alias "GetDiskFreeSpaceExA" _
(ByVal lpRootPathName As String, _
lpFreeBytesAvailableToCaller As Currency, _
lpTotalNumberOfBytes As Currency, _
lpTotalNumberOfFreeBytes As Currency) As Long
Private Sub DiskInfo()
Dim cuAvailable As Currency
Dim cuTotal As Currency
Dim cuFree As Currency
Call GetDiskFreeSpaceEx("C:", cuAvailable, _
cuTotal, cuFree)
Debug.Print "Disk space info" & vbCr & _
"Available : "; _
FormatByteSize(cuAvailable * 10000) & vbCr & _
"Total
: "; _
FormatByteSize(cuTotal * 10000) & vbCr & _
"Free
: "; _
FormatByteSize(cuFree * 10000) & vbCr & _
"Used
: "; _
FormatByteSize((cuTotal - cuFree) * 10000)
End Sub
FIGURE 2: Using the Currency data type to manage huge integers (up to
922,337,203,685,477 bytes - 838 TB).
FIGURE 2 demonstrates its use. Note that the GetDiskFreeSpaceEx API function is called with
the Currency data type for its numeric parameters, and that the DiskInfo routine multiplies the
return values by 10,000 to compensate for the fact that this data type divides 64-bit values by the
same amount. The result on my system is:
Disk space info
Available : 2.78
Total
: 7.36
Free
: 2.78
Used
: 4.58
Path Functions
GB
GB
GB
GB
7. Most likely, your function library contains a number of homegrown routines that determine
whether a file or folder exists, and that extract file and directory names from path strings. If you
have Shlwapi, you can now dump those routines and use lightning fast API calls instead.
FIGURE 3 lists most path functions in the Shlwapi library. (I've left out the ones I consider less
useful in the context of VBA applications.) The third column contains the names of the
associated VBA functions in Listing Two. All routines call the Unicode versions of the Shlwapi
functions, so they also work with path names containing characters that would be invalid under
Windows 95/98.
Shlwapi Function
PathFileExists
Description
Adds a backslash to the end of a string to
create the correct syntax for a path. If the
source path already has a trailing
backslash, no backslash is added.
Adds a file extension to a path string. If
there is already a file extension present,
no extension is added.
Appends one path to the end of another.
Creates a root path from a given drive
number.
Concatenates two strings that represent
properly formed paths into one path, as
well as any relative path pieces.
Compares two paths to determine if they
share a common prefix. A prefix is one
of these types: "C:", ".", "..", "..".
Truncates a file path to fit within a given
pixel width by replacing path
components with ellipses.
Truncates a path to fit within a certain
number of characters by replacing path
components with ellipses.
Determines if a file exists.
PathFindExtension
Searches a path for an extension.
PathAddBackslash
PathAddExtension
PathAppend
PathBuildRoot
PathCombine
PathCommonPrefix
PathCompactPath
PathCompactPathEx
PathFindFileName
PathGetDriveNumber
Function in Listing Two
AddBackslash
AddRemoveBackslash
AddExtension
AddRemoveExtension
BuildPath
RootFromDriveNumber
BuildPath2
GetCommonPath
CompactPathByPixels
CompactPathByChars
FileExists
GetExtension
HasExtension
GetFile
Searches a path for a file name.
Searches a path for a drive letter within
the range of "A" to "Z" and returns the
corresponding drive number. Returns 0
GetDriveNumber
through 25 (corresponding to "A"
through "Z") if the path has a drive letter,
or -1 otherwise.
8. Determines if a file's registered content
type matches the specified content type.
This function obtains the content type for
PathIsContentType
the specified file type, and compares that
string with the specified content type.
The comparison is not case sensitive.
Verifies that a path is a valid directory.
PathIsDirectory
Searches a path for any path-delimiting
characters (for example, ":" or "" ). If
there are no path-delimiting characters
PathIsFileSpec
present, the path is considered to be a
File Spec path.
Searches a path to determine if it
contains a valid prefix of a specified
PathIsPrefix
type. A prefix is one of these types:
"C:", ".", "..", "..".
Searches a path and determines if it is
PathIsRelative
relative.
Parses a path to determine if it is a
PathIsRoot
directory root.
Compares two paths to determine if they
PathIsSameRoot
have a common root component.
Determines if the string is a valid
Universal Naming Convention (UNC)
PathIsUNC
for a server and share path.
Determines if a string is a valid UNC for
PathIsUNCServer
a server path only.
Determines if a string is a valid UNC
PathIsUNCServerShare
share path, servershare.
Tests a given string to determine if it
PathIsURL
conforms to a valid URL format.
Searches a string using a DOS wildcard
PathMatchSpec
match type.
Searches a path for spaces. If spaces are
found, the entire path is enclosed in
PathQuoteSpaces
quotation marks.
PathRemoveBackslash
PathRemoveExtension
PathRemoveFileSpec
Removes the trailing backslash from a
given path.
Removes the file extension from a path,
if there is one.
Removes the trailing file name and
IsContentType (not
reliable; always returns
False)
FolderExists
HasPath
HasPrefix
IsRelative
IsRoot
HaveSameRoot
IsValidUNC
IsValidUNCServer
IsValidUNCServerShare
IsValidURL
MatchSpec
QuoteSpaces
AddRemoveQuotes
RemoveBackslash
AddRemoveBackslash
RemoveExtension
AddRemoveExtension
GetFolder
9. backslash from a path, if it has them.
Replaces the extension of a file name
with a new extension. If the file name
PathRenameExtension does not contain an extension, the
extension is attached to the end of the
string.
Determines if a given path is correctly
PathSearchAndQualify
formatted and fully qualified.
Sets the text of a child control in a
window or dialog box, using
PathSetDlgItemPath
PathCompactPath to make sure the
path fits in the control.
Parses a path, ignoring the drive letter or
UNC server/share path parts. Returns the
address of the beginning of the subpath
PathSkipRoot
that follows the root (drive letter or UNC
server/share).
Removes the path portion of a fully
PathStripPath
qualified path and file.
Removes all parts of the path except for
PathStripToRoot
the root information.
PathUnquoteSpaces
Removes quotes from the beginning and
end of a path.
RenameExtension
AddRemoveExtension
QualifyPath
CompactPathDlgCrl
SkipRoot
Not used; use
PathFindFileName instead
GetRoot
UnquoteSpaces
AddRemoveQuotes
FIGURE 3: Common path functions in all versions of Shlwapi. This list is not
comprehensive.
Most functions in Listing Two are self-explanatory. In the following section, I'll limit myself to
comments and examples.
Extracting File Path Components
Use the GetFile, GetFolder, GetExtension, GetRoot, SkipRoot, and GetDriveNumber
functions to extract individual components from a fully qualified path:
MyFile = "C:My DocumentsTest.doc"
Debug.Print GetFile(MyFile)
' Prints: "Test.doc"
Debug.Print GetFolder(MyFile)
' "C:My Documents"
Debug.Print GetExtension(MyFile) ' ".doc"
Debug.Print GetRoot(MyFile)
' "C:"
Debug.Print SkipRoot(MyFile)
' "My DocumentsTest.doc"
Debug.Print GetDriveNumber(MyFile) ' 2
The GetCommonPath function compares two path strings and returns the path they have in
common:
10. MyFile1 = "C:My DocumentsDocsStuffTest.doc"
MyFile2 = "C:My DocumentsDocsThingsDummy.doc"
' The following prints "C:My DocumentsDocs"
Debug.Print GetCommonPath(MyFile1, MyFile2)
Testing the Validity of File and Folder Names
You can use FileExists to test the existence of both files and folders. FolderExists tests only the
validity of a folder name. Folder names can be specified with or without a terminating backslash.
You can use FolderExists in conjunction with the RootFromDriveNumber function to get a list
of available drives on a system:
For i = 0 To 25
d = RootFromDriveNumber(i)
If FolderExists(d) Then Debug.Print d
Next
The HasPath and HasExtension functions return True if a file name contains path information
or an extension, respectively. Note that HasPath also returns True if the path information is
relative, as in:
Debug.Print HasPath("..debug.txt") ' True
HasPrefix lets you test whether a file name starts with a specified path component:
MyFile = "C:My DocumentsStuffTest.doc"
' True
Debug.Print HasPrefix("C:My Documents", MyFile)
' True
Debug.Print HasPrefix("C:My DocumentsStuff", MyFile)
' False
Debug.Print HasPrefix("C:My DocumentsDemo", MyFile)
IsRelative returns True if the file name is preceded with a fully qualified path indicator:
Debug.Print IsRelative("C:debug.txt") ' False
Debug.Print IsRelative("..debug.txt") ' True
Debug.Print IsRelative("debug.txt")
' True
The IsRoot function returns True if the specified path is a drive root (e.g. "C:") or a server root
(e.g. "OfficeWord"). HaveSameRoot compares two paths to determine if they have a
common root component.
Theoretically, Shlwapi offers you two ways to determine if a file name matches a specified file
type. You can use MatchSpec to test a file name against a DOS wildcard match type. The
IsContentType function (which calls the Shlwapi PathIsContentType function) doesn't seem
to work, but this may have been fixed in later versions. Here's how you can use them:
MyFile = "C:My DocumentsTest.doc"
Debug.Print MatchSpec(MyFile, "*.doc")
' True
11. ' The following should print True, but always
' seems to print False:
Debug.Print IsContentType( _
MyFile, "Microsoft Word Document")
The QualifyPath function returns True if a given path is correctly formatted and fully qualified.
If the path name doesn't contain folder info, the name of the active directory is used to create a
qualified path.
IsValidUNC, IsValidUNCServer, IsValidUNCServerShare, and IsValidURL functions
return True if the specified path has a valid format.
Formatting and Modifying File and Folder Strings
The AddBackslash and RemoveBackslash functions do exactly what their names suggest.
AddRemoveBackslash combines the functionality of both. Of the following instructions, the
first two add a backslash to MyDir if there isn't one, and the last one removes the terminating
backslash if there is one:
Debug.Print AddRemoveBackslash(MyDir)
Debug.Print AddRemoveBackslash(MyDir, True)
Debug.Print AddRemoveBackslash(MyDir, False)
Four functions let you manipulate file name extensions. AddExtension adds an extension if the
file name doesn't have one. RenameExtension replaces an existing extension, or adds an
extension if there isn't one. RemoveExtension removes any existing extension. The
AddRemoveExtension function does all of the above, depending on how you use its parameters.
Here are some examples:
MyFile = "Test.doc"
' "Test.doc"
Debug.Print AddRemoveExtension(MyFile,
' "Test.txt"
Debug.Print AddRemoveExtension(MyFile,
' "Test.txt"
Debug.Print AddRemoveExtension(MyFile,
' "Test"
Debug.Print AddRemoveExtension(MyFile)
' "Test"
Debug.Print AddRemoveExtension(MyFile,
"txt", False)
"txt", True)
"txt")
vbNullString)
Listing Two contains two functions with similar names: BuildPath and BuildPath2. They use
different Shlwapi functions that appear to work in exactly the same way; each lets you join two
given path components, and automatically insert a backslash between the two parts if necessary.
The functions also cope well with relative paths, as shown here:
MyFolder = "C:My DocumentsDocsStuff"
' This prints "C:My DocumentsDocsStuffTest.doc"
Debug.Print BuildPath(MyFolder,"Test.doc")
' This prints "C:My DocumentsTest.doc"
12. Debug.Print BuildPath(MyFolder,"....Test.doc")
Some Windows functions and applications can't cope with file names if they contain spaces. The
solution is to enclose the name in quotation marks. QuoteSpaces does it for you, and
UnquoteSpaces reverses the action. The AddRemoveQuotes function combines the two
functions in a single routine.
Finally, Listing Two contains three functions that truncate a file path to fit within a given width
by replacing path components with ellipses. CompactPathByPixels function fits the string
within a given pixel width. This function requires a handle to a device context (hDC) used for
font metrics, which limits its usability in regular VBA applications. CompactPathDlgCrl fits
the string in a dialog box control. This function requires a handle to the dialog's window, and an
ID number for the dialog box control, which doesn't make it useful in VBA forms. The most
useful function therefore is CompactPathByChars, which fits the string within a certain number
of characters. Here's how to use it:
MyFile = _
"C:My DocumentsVBAHACKSLightweight APIPart One.doc"
Debug.Print CompactPathByChars(MyFile, 20)
Debug.Print CompactPathByChars(MyFile, 30)
Debug.Print CompactPathByChars(MyFile, 40)
These instructions print the following truncated file names:
C:...Part One.doc
C:My Documen...Part One.doc
C:My DocumentsVBAHACK...Part One.doc
Conclusion
The purpose of this series is to demonstrate that there is a lot more to VBA programming than
you may think. With a bit of hacking, you can greatly enhance your programming environment,
and produce many useful functions that VBA doesn't offer. In this installment, you learned how
to use the Shell Lightweight API to create powerful routines that deal with strings and path
names. Watch this space for more VBA hacks!
Dutchman Romke Soldaat was hired by Microsoft in 1988 to co-found the Microsoft
International Product Group in Dublin, Ireland. That same year he started working with the
prototypes of WinWord, writing his first macros long before the rest of the world. In 1992 he left
Microsoft, and created a number of successful add-ons for Office. Living in Italy, he divides his
time between writing articles for this magazine, enjoying the Mediterranean climate, and steering
his Land Rover through the world's most deserted areas. Romke can be contacted at
romke@soldaat.com.
Begin Listing One - ShlWAPIString.bas
Option Explicit
13. Private nRet As Long
Private Declare Function lstrlenW Lib "kernel32" ( _
ByVal lpString As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias _
"RtlMoveMemory" (dest As Any, source As Any, _
ByVal Bytes As Long)
Private Declare Function StrCSpn Lib "Shlwapi" _
Alias "StrCSpnW" (ByVal lpStr As Long, _
ByVal lpSet As Long) As Long
Private Declare Function StrCSpnI Lib "Shlwapi" _
Alias "StrCSpnW" (ByVal lpStr As Long, _
ByVal lpSet As Long) As Long
Private Declare Function StrTrim Lib "Shlwapi" _
Alias "StrTrimW" (ByVal pszSource As Long, _
ByVal pszTrimChars As Long) As Boolean
Private Declare Function StrFormatByteSize Lib "Shlwapi" _
Alias "StrFormatByteSizeW" (ByVal dw As Currency, _
ByVal pszBuf As Long, ByVal cchBuf As Long) As Long
Private Declare Function StrFromTimeInterval _
Lib "Shlwapi" Alias "StrFromTimeIntervalW" ( _
ByVal pszOut As Long, ByVal cchMax As Long, _
ByVal dwTimeMS As Long, ByVal digits As Long) As Long
Public Function InstrAny(SearchString As String, _
CharList As String, _
Optional CaseSensitive As Boolean = True) As Long
' Returns the position of the first occurrence of
' a character in CharList within SearchString.
If CaseSensitive Then
nRet = StrCSpn(StrPtr(SearchString), StrPtr(CharList))
Else
nRet = StrCSpnI(StrPtr(SearchString), StrPtr(CharList))
End If
Select Case nRet
Case 0, Len(SearchString)
InstrAny = 0
Case Else
InstrAny = nRet + 1
End Select
End Function
Public Function ContainsAnyChar(
ByVal SearchString As String, CharList As String, _
Optional CaseSensitive As Boolean = True) As Boolean
' Returns True if SearchString contains any
' character in CharList.
ContainsAnyChar = CBool(InstrAny(SearchString, _
CharList, CaseSensitive))
14. End Function
Public Function TrimAny(ByVal SearchString As String, _
CharList As String) As String
' Removes all characters in CharList from the start and
' end of SearchString.
Dim pAddr As Long: pAddr = StrPtr(SearchString)
Call StrTrim(pAddr, StrPtr(CharList))
TrimAny = Ptr2StrU(pAddr)
End Function
Public Function FormatByteSize(Bytes As Currency) As String
' Converts a bytes value into a bytes, MB, GB,
' or TB string max Bytes = 922,337,203,685,477 (838 TB).
Dim strBuff As String * 256
nRet = StrFormatByteSize(Bytes / 10000, _
StrPtr(strBuff), 256)
If nRet Then FormatByteSize = Ptr2StrU(nRet)
End Function
Public Function FormatTimeInterval(Milliseconds As Long, _
Optional OutputPrecision As Long = 7) As String
' Converts a milliseconds value into an hr min sec string
' max Milliseconds = 2,147,483,647 (596 hr 31 min 24 sec)
' Pass zero values to obtain required buffer length.
nRet = StrFromTimeInterval(0, 0, Milliseconds, _
OutputPrecision)
' Create buffer and get pointer.
Dim strBuff As String: strBuff = Space$(nRet)
Dim pAddr As Long: pAddr = StrPtr(strBuff)
' Call function again.
Call StrFromTimeInterval(pAddr, nRet, Milliseconds, _
OutputPrecision)
FormatTimeInterval = Ptr2StrU(pAddr)
End Function
Private Function Ptr2StrU(ByVal pAddr As Long) As String
' Retrieves the Unicode string from a given address.
Dim lAddr As Long: lAddr = lstrlenW(pAddr)
Ptr2StrU = Space$(lAddr)
CopyMemory ByVal StrPtr(Ptr2StrU), ByVal pAddr, LAddr * 2
End Function
End Listing One
Begin Listing Two - ShlWAPIPath.bas
Option Explicit
Const MAX_PATH As Long = 260
Private pAddr As Long
Private Declare Sub CopyMemory Lib "kernel32" _
Alias "RtlMoveMemory" (dest As Any, source As Any, _
15. ByVal Bytes As Long)
Private Declare Function lstrlenW Lib "kernel32" _
(ByVal lpString As Long) As Long
' ===== DIRECTORY FUNCTIONS =====
Private Declare Function PathIsDirectory Lib "Shlwapi" _
Alias "PathIsDirectoryW" _
(ByVal lpszPath As Long) As Boolean
Private Declare Function PathRemoveFileSpec Lib "Shlwapi" _
Alias "PathRemoveFileSpecW" _
(ByVal pszPath As Long) As Boolean
Private Declare Function PathAddBackslash Lib "Shlwapi" _
Alias "PathAddBackslashW" _
(ByVal lpszPath As Long) As Long
Private Declare Function PathRemoveBackslash _
Lib "Shlwapi" Alias "PathRemoveBackslashW" _
(ByVal lpszPath As Long) As Long
Private Declare Function PathIsPrefix Lib "Shlwapi" _
Alias "PathIsPrefixW" (ByVal pszPrefix As Long, _
ByVal lpszPath As Long) As Boolean
Private Declare Function PathCommonPrefix Lib "Shlwapi" _
Alias "PathCommonPrefixW" (ByVal pszFile1 As Long, _
ByVal pszFile2 As Long, ByVal pszPath As Long) As Long
Private Declare Function PathIsRelative Lib "Shlwapi" _
Alias "PathIsRelativeW" _
(ByVal lpszPath As Long) As Boolean
' ======= FILE FUNCTIONS ========
Private Declare Function PathFileExists Lib "Shlwapi" _
Alias "PathFileExistsW" _
(ByVal lpszPath As Long) As Boolean
Private Declare Function PathIsFileSpec Lib "Shlwapi" _
Alias "PathIsFileSpecW" _
(ByVal lpszPath As Long) As Boolean
Private Declare Sub PathStripPath Lib "Shlwapi" _
Alias "PathStripPathW" (ByVal pszPath As Long)
Private Declare Function PathFindFileName Lib "Shlwapi" _
Alias "PathFindFileNameW" (ByVal pPath As Long) As Long
' ===== EXTENSION FUNCTIONS =====
Private Declare Function PathAddExtension Lib "Shlwapi" _
Alias "PathAddExtensionW" (ByVal lpszPath As Long, _
ByVal pszExtension As Long) As Boolean
Private Declare Sub PathRemoveExtension Lib "Shlwapi" _
Alias "PathRemoveExtensionW" (ByVal lpszPath As Long)
16. Private Declare Function PathFindExtension Lib "Shlwapi" _
Alias "PathFindExtensionW" (ByVal pPath As Long) As Long
Private Declare Function PathRenameExtension _
Lib "Shlwapi" Alias "PathRenameExtensionW" _
(ByVal lpszPath As Long, _
ByVal pszExtension As Long) As Boolean
Private Declare Function PathMatchSpec Lib "Shlwapi" _
Alias "PathMatchSpecW" (ByVal pszFileParam As Long, _
ByVal pszSpec As Long) As Boolean
Private Declare Function PathIsContentType Lib "Shlwapi" _
Alias "PathIsContentTypeW" (ByVal pszPath As Long, _
ByVal pszContentType As Long) As Boolean
' ===== UNC AND URL FUNCTIONS =====
Private Declare Function PathIsUNC Lib "Shlwapi" _
Alias "PathIsUNCW" (ByVal lpszPath As Long) As Boolean
Private Declare Function PathIsUNCServer Lib "Shlwapi" _
Alias "PathIsUNCServerW" _
(ByVal lpszPath As Long) As Boolean
Private Declare Function PathIsUNCServerShare _
Lib "Shlwapi" Alias "PathIsUNCServerShareW" _
(ByVal lpszPath As Long) As Boolean
Private Declare Function PathIsURL Lib "Shlwapi" _
Alias "PathIsURLW" (ByVal lpszPath As Long) As Boolean
' ===== ROOT AND DRIVE FUNCTIONS =====
Private Declare Function PathIsRoot Lib "Shlwapi" _
Alias "PathIsRootW" (ByVal lpszPath As Long) As Boolean
Private Declare Function PathIsSameRoot Lib "Shlwapi" _
Alias "PathIsSameRootW" (ByVal lpszPath1 As Long, _
ByVal lpszPath2 As Long) As Boolean
Private Declare Function PathStripToRoot Lib "Shlwapi" _
Alias "PathStripToRootW" _
(ByVal szRoot As Long) As Boolean
Private Declare Function PathSkipRoot Lib "Shlwapi" _
Alias "PathSkipRootW" (ByVal pszPath As Long) As Long
Private Declare Function PathBuildRoot Lib "Shlwapi" _
Alias "PathBuildRootW" (ByVal szRoot As Long, _
ByVal iDrive As Integer) As Long
Private Declare Function PathGetDriveNumber Lib "Shlwapi" _
Alias "PathGetDriveNumberW" _
(ByVal pszPath As Long) As Long
' ==== BUILD PATH FUNCTIONS =====
Private Declare Function PathAppend Lib "Shlwapi" _
Alias "PathAppendW" (ByVal pszPath As Long, _
17. ByVal pszMore As Long) As Boolean
Private Declare Function PathCombine Lib "Shlwapi" _
Alias "PathCombineW" (ByVal lpszDest As Long, _
ByVal lpszDir As Long, ByVal lpszFile As Long) As Long
Private Declare Function PathSearchAndQualify _
Lib "Shlwapi" Alias "PathSearchAndQualifyW" _
(ByVal pcszPath As Long, _
ByVal pszFullyQualifiedPath As Long, _
ByVal cchFullyQualifiedPath As Integer) As Boolean
' ==== FORMATTING FUNCTIONS =====
Private Declare Sub PathQuoteSpaces Lib "Shlwapi" _
Alias "PathQuoteSpacesW" (ByVal lpsz As Long)
Private Declare Sub PathUnquoteSpaces Lib "Shlwapi" _
Alias "PathUnquoteSpacesW" (ByVal lpsz As Long)
' ==== COMPACTING FUNCTIONS ========
Private Declare Function PathCompactPath Lib "Shlwapi" _
Alias "PathCompactPathW" (ByVal hDc As Long, _
ByVal lpszPath As Long, ByVal dx As Integer) As Boolean
Private Declare Function PathCompactPathEx Lib "Shlwapi" _
Alias "PathCompactPathExW" (ByVal pszOut As Long, _
ByVal pszSrc As Long, ByVal cchMax As Integer, _
dwFlags As Long) As Boolean
Private Declare Function PathSetDlgItemPath Lib "Shlwapi" _
Alias "PathSetDlgItemPathW" (ByVal hDlg As Long, _
ByVal id As Long, ByVal pszPath As Long) As Boolean
' ================================
' Extracting file path components.
' ================================
Public Function GetFile(ByVal Path As String) As String
' Returns the file naGetFileme from a path name.
GetFile = Ptr2StrU(PathFindFileName(StrPtr(Path)))
End Function
Public Function GetFolder(ByVal Path As String) As String
' Returns the folder name from a path name.
pAddr = StrPtr(Path)
Call PathRemoveFileSpec(pAddr)
GetFolder = Ptr2StrU(pAddr)
End Function
Public Function GetExtension(ByVal Path As String) _
As String
' Returns the extension name from a file name.
GetExtension = Ptr2StrU(PathFindExtension(StrPtr(Path)))
End Function
Public Function GetRoot(ByVal Path As String) As String
' Returns the root name from a file name.
18. pAddr = StrPtr(Path)
Call PathStripToRoot(pAddr)
GetRoot = Ptr2StrU(pAddr)
End Function
Public Function GetCommonPath(Path1 As String, _
Path2 As String) As String
' Returns the common path component of two path names.
Dim strBuff As String * MAX_PATH
pAddr = StrPtr(strBuff)
Call PathCommonPrefix(StrPtr(Path1), _
StrPtr(Path2), pAddr)
GetCommonPath = Ptr2StrU(pAddr)
End Function
Public Function SkipRoot(ByVal Path As String) As String
' Returns the subpath that follows the root.
SkipRoot = Ptr2StrU(PathSkipRoot(StrPtr(Path)))
End Function
Public Function GetDriveNumber(Path As String) As Long
' Returns the drive number of a path, in the range
' from 0 (drive A) to 25 (drive Z).
GetDriveNumber = PathGetDriveNumber(StrPtr(Path))
End Function
' ==============================================
' Testing the validity of file and folder names.
' ==============================================
Public Function FileExists(Path As String) As Boolean
' Returns True if the path name is valid.
FileExists = PathFileExists(StrPtr(Path))
End Function
Public Function FolderExists(Path As String) As Boolean
' Returns True if the folder name is valid.
FolderExists = PathIsDirectory(StrPtr(Path))
End Function
Public Function HasPath(Path As String) As Boolean
' Returns True if the path name contains folder info.
HasPath = (PathIsFileSpec(StrPtr(Path)) = False)
End Function
Public Function HasExtension(Path As String) As Boolean
' Returns True if the path name contains an extension.
HasExtension = Len(GetExtension(Path))
End Function
Public Function HasPrefix(Prefix As String, _
Path As String) As Boolean
' Retuns True if the path name starts with a specified
' path component.
HasPrefix = PathIsPrefix(StrPtr(Prefix), StrPtr(Path))
End Function
Public Function IsRelative(Path As String) As Boolean
19. ' Returns True if the file name is preceded with a
' path indicator.
IsRelative = PathIsRelative(StrPtr(Path))
End Function
Public Function IsRoot(Path As String) As Boolean
' Returns True if the path name is a root name.
IsRoot = PathIsRoot(StrPtr(Path))
End Function
Public Function HaveSameRoot(Path1 As String, _
Path2 As String) As Boolean
' Returns True if the two path names have the same root.
HaveSameRoot = _
PathIsSameRoot(StrPtr(Path1), StrPtr(Path2))
End Function
Public Function MatchSpec(File As String, _
Spec As String) As Boolean
' Returns True if the file name matches a wildcard match
' type (e.g. "*.doc").
MatchSpec = PathMatchSpec(StrPtr(File), StrPtr(Spec))
End Function
Public Function IsContentType(File As String,_
FileType As String) As Boolean
' bug? - always returns False.
IsContentType = PathIsContentType(StrPtr(File), _
StrPtr(FileType))
End Function
Public Function QualifyPath(Path As String) As String
' Returns True if the path is correctly formatted and
' fully qualified. If the path name doesn't contain
' folder info, the name of the active directory is used
' to create a qualified path.
Dim strBuff As String * MAX_PATH
pAddr = StrPtr(strBuff)
If PathSearchAndQualify( _
StrPtr(Path), pAddr, MAX_PATH) Then
QualifyPath = Ptr2StrU(pAddr)
End If
End Function
Public Function IsValidUNC(Path As String) As Boolean
' Returns True if the string is a valid UNC path.
IsValidUNC = PathIsUNC(StrPtr(Path))
End Function
Public Function IsValidUNCServer(Path As String) As Boolean
' Returns True if the string is a valid UNC path for a
' server only (no share name)IsValidUNCServer =
' PathIsUNCServer(StrPtr(Path)).
End Function
Public Function IsValidUNCServerShare(Path As String) _
As Boolean
20. ' Returns True if the string is in the form
' servershare.
IsValidUNCServerShare = _
PathIsUNCServerShare(StrPtr(Path))
End Function
Public Function IsValidURL(Path As String) As Boolean
' Returns True if the path has a valid URL format.
IsValidURL = PathIsURL(StrPtr(Path))
End Function
' =================================================
' Formatting and modifying file and folder strings.
' =================================================
Public Function AddBackslash(ByVal Path As String) _
As String
' Adds a final backslash to the path is there
' is no backslash.
pAddr = StrPtr(PadBuffer(Path))
Call PathAddBackslash(pAddr)
AddBackslash = Ptr2StrU(pAddr)
End Function
Public Function RemoveBackslash(ByVal Path As String) _
As String
' Removes a final backslash from the path
' if there is one.
pAddr = StrPtr(Path)
Call PathRemoveBackslash(pAddr)
RemoveBackslash = Ptr2StrU(pAddr)
End Function
Public Function AddRemoveBackslash(ByVal Path As String, _
Optional Add As Boolean = True) As String
' Adds or removes a final backslash.
pAddr = StrPtr(PadBuffer(Path))
If Add Then
Call PathAddBackslash(pAddr)
Else
Call PathRemoveBackslash(pAddr)
End If
AddRemoveBackslash = Ptr2StrU(pAddr)
End Function
Public Function AddExtension(ByVal Path As String, _
Extension As String) As String
' Adds the specified extension to the path
' if there is no extension.
QualifyExtension Extension
pAddr = StrPtr(PadBuffer(Path))
Call PathAddExtension(pAddr, StrPtr(Extension))
AddExtension = Ptr2StrU(pAddr)
End Function
Public Function RemoveExtension(ByVal Path As String) _
As String
' Removes the extension from the path if there is one.
21. pAddr = StrPtr(Path)
Call PathRemoveExtension(pAddr)
RemoveExtension = Ptr2StrU(pAddr)
End Function
Public Function RenameExtension(ByVal Path As String, _
Extension As String) As String
' Renames the extension of the path if there is one, or
' adds the extension if there is none.
QualifyExtension Extension
pAddr = StrPtr(PadBuffer(Path))
Call PathRenameExtension(pAddr, StrPtr(Extension))
RenameExtension = Ptr2StrU(pAddr)
End Function
Public Function AddRemoveExtension(ByVal Path As String, _
Optional Extension As String, _
Optional RenameIfExists As Boolean = True)
' Combines the three functions above. If Extension is
' omitted, any existing extension is removed. If
' RenameIfExists is True, the specified extension
' replaces any existing extension.
pAddr = StrPtr(PadBuffer(Path))
Select Case Extension
Case vbNullString
Call PathRemoveExtension(pAddr)
Case Else
QualifyExtension Extension
If RenameIfExists = True Then
Call PathRenameExtension(pAddr, StrPtr(Extension))
Else
Call PathAddExtension(pAddr, StrPtr(Extension))
End If
End Select
AddRemoveExtension = Ptr2StrU(pAddr)
End Function
Public Function BuildPath(ByVal Path As String, _
File As String) As String
' Combines two path components, inserting a
' backslash if needed.
pAddr = StrPtr(PadBuffer(Path))
Call PathAppend(pAddr, StrPtr(File))
BuildPath = Ptr2StrU(pAddr)
End Function
Public FunctionBuildPath2(Path As String,_
File As String) As String
' Combines two path components, inserting a
' backslash if needed.
DimstrBuff As String* MAX_PATH
BuildPath2 = Ptr2StrU(PathCombine(StrPtr(strBuff), _
StrPtr(Path), StrPtr(File)))
End Function
Public Function QuoteSpaces(ByVal Path As tring) As String
' Encloses the path in quotes if the path
22. ' contains spaces.
pAddr = StrPtr(PadBuffer(Path))
Call PathQuoteSpaces(pAddr)
QuoteSpaces = Ptr2StrU(pAddr)
End Function
Public Function UnquoteSpaces(ByVal Path As String) _
As String
' Removes any leading and trailing quotes from the path.
pAddr = StrPtr(Path)
Call PathUnquoteSpaces(pAddr)
UnquoteSpaces = Ptr2StrU(pAddr)
End Function
Public Function AddRemoveQuotes(ByVal Path As String, _
Optional Add As Boolean = True)
' Adds or removes quotes.
pAddr = StrPtr(PadBuffer(Path))
If Add Then
Call PathQuoteSpaces(pAddr)
Else
Call PathUnquoteSpaces(pAddr)
End If
AddRemoveQuotes = Ptr2StrU(pAddr)
End Function
Public Function CompactPathByChars( _
Path As String, MaxChars) As String
' Truncates the path to fit within the specified
' number of characters.
Dim strBuff As String * MAX_PATH
pAddr = StrPtr(strBuff)
Call PathCompactPathEx( _
pAddr, StrPtr(Path), MaxChars + 1, 0)
CompactPathByChars = Ptr2StrU(pAddr)
End Function
Public Function CompactPathByPixels(ByVal Path As String, _
hDc As Long, MaxPixels As Long) As String
' Truncates the path to fit within the specified
' number of pixels.
pAddr = StrPtr(Path)
Call PathCompactPath(hDc, pAddr, MaxPixels)
CompactPathByPixels = Ptr2StrU(pAddr)
End Function
Public Sub CompactPathDlgCrl(ByVal Path As String, _
hDlg As Long, id As Long)
' Truncates the path to fit within the available space of
' a dialog box control, and sets the text of the control.
pAddr = StrPtr(Path)
Call PathSetDlgItemPath(hDlg, id, pAddr)
End Sub
' ====
' MISC
' ====
23. Public Function RootFromDriveNumber(DriveNumber As Long) _
As String
' Converts a drive number (from 0 to 25) into a root path
' (from "A:" to "Z:").
Select Case DriveNumber
Case 0 To 25
Dim strBuff As String * 4
RootFromDriveNumber = Ptr2StrU(PathBuildRoot( _
StrPtr(strBuff), DriveNumber))
Case Else:
End Select
End Function
Private Function Ptr2StrU(ByVal pAddr As Long) As String
Dim lAddr As Long: lAddr = lstrlenW(pAddr)
Ptr2StrU = Space$(lAddr)
CopyMemory ByVal StrPtr(Ptr2StrU), ByVal pAddr, lAddr * 2
End Function
Private Function PadBuffer(ByVal strPath As String) _
As String
PadBuffer = strPath & String$(MAX_PATH - Len(strPath), 0)
End Function
Private Sub QualifyExtension(Extension As String)
If Left$(Extension, 1) <> "." Then _
Extension = "." & Extension
End Sub
End Listing Two