|
|
||
|
Getting the Correct Filename for a ShortcutDemonstrates how to use the shell to get the suggested shortcut name, and provides a useful class for working with Strings in the API
If your application creates or otherwise manipulates shortcuts then you should use the Operating System to determine what the filename for the shortcut should be, as the extension is subject to i18n. This article demonstrates how to use the SHGetNewLinkInfo API call to do this, and also provides a handy class which can make working with ANSI and Unicode String API declares in the same code easier. The SHGetNewLinkInfo API callThe declaration of this function in the Platform SDK is as follows:
BOOL SHGetNewLinkInfo(
LPCTSTR pszLinkTo,
LPCTSTR pszDir,
LPTSTR pszName,
BOOL *pfMustCopy,
UINT uFlags
);
The function takes two input strings, pszLinkTo and pszDir, which represent the file to link to and the directory to create the new link in respectively and one ouput string for the new link file name, pszName, which must be pre-initialised to hold the result. pfMustCopy is set to 1 if the shortcut you're creating is a link to another shortcut and hence can be simply copied, and uFlags specifies flags which control the function. Note that the declare specifies TSTR string types. This implies that there are both ANSI and Unicode versions of the declares, and they take either an ANSI or Unicode string. Whilst VB's Declare keyword makes it (kind of) easy to work with ANSI strings, it does not help at all with Unicode ones. If you want to work with Unicode strings you need to either put the declare in a Type Library, or start manipulating the string memory directly. Either way is fairly complex to achieve, so in this sample I've included a class, cStringPointer which wraps some of this functionality up. Manipulating Memory to Represent StringsIn memory there isn't much difference between an ANSI and a Unicode string. Both consist of a pointer to a block of memory which is at least terminated by a null character (\0). The Unicode block is simply twice the size of the ANSI equivalent. Creating a pointer to a memory area which represents either string is fairly straightforward using the Win32 memory block allocation APIs. However, getting the data in and out of the memory block as a string, and making sure the memory buffer is cleaned up correctly is a bit more tedious. For this reason, a class wrapper to the code is a sensible idea. Here's the code for cStringPointer:
Option Explicit
Private Declare Function LocalAlloc Lib "kernel32" ( _
ByVal wFlags As Long, ByVal wBytes As Long) As Long
Private Declare Function LocalFree Lib "kernel32" ( _
ByVal hMem As Long) As Long
Private Declare Function LocalLock Lib "kernel32" ( _
ByVal hMem As Long) As Long
Private Declare Function LocalUnlock Lib "kernel32" ( _
ByVal hMem As Long) As Long
Private Const GMEM_ZEROINIT = &H40
Private Const GMEM_FIXED = &H0
Private Const GPTR = (GMEM_FIXED Or GMEM_ZEROINIT)
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long)
Private Declare Function lstrlenA Lib "kernel32" ( _
ByVal lpString As Long) As Long
Private Declare Function lstrlenW Lib "kernel32" ( _
ByVal lpString As Long) As Long
Private Declare Function GetVersion Lib "kernel32" () As Long
Private m_hMem As Long
Private m_lBufSize As Long
Private m_lPtr As Long
Public Sub SetString(ByVal sString As String)
Dispose
If (Len(sString) > 0) Then
Dim b() As Byte
If IsNt Then
b = sString
Else
b = StrConv(sString, vbFromUnicode)
End If
ReDim Preserve b(0 To UBound(b) + 1) As Byte
m_hMem = LocalAlloc(GPTR, UBound(b) + 1)
m_lPtr = LocalLock(m_hMem)
CopyMemory ByVal m_lPtr, b(0), UBound(b) + 1
Else
m_hMem = LocalAlloc(GPTR, 1)
m_lPtr = LocalLock(m_hMem)
End If
End Sub
Public Function GetString() As String
Dim sRet As String
If Not (m_lPtr = 0) Then
Dim b() As Byte
Dim lLen As Long
If IsNt Then
lLen = lstrlenW(m_lPtr) * 2
Else
lLen = lstrlenA(m_lPtr)
End If
If (lLen > 0) Then
ReDim b(0 To lLen) As Byte
CopyMemory b(0), ByVal m_lPtr, lLen
If IsNt Then
sRet = b
Else
sRet = StrConv(b, vbUnicode)
End If
Dim iPos As Long
iPos = InStr(sRet, vbNullChar)
If (iPos > 1) Then
GetString = Left(sRet, iPos - 1)
Else
GetString = sRet
End If
End If
End If
End Function
Public Property Get Ptr() As Long
Ptr = m_lPtr
End Property
Public Property Let Ptr(ByVal lPtr As Long)
Dispose
' This is pointer must be owned elsewhere
' so we have no hMem. No attempt to free
' this pointer will (or can) be made by
' this class
m_lPtr = lPtr
End Property
Private Function IsNt() As Boolean
Dim lVer As Long
lVer = GetVersion()
IsNt = ((lVer And &H80000000) = 0)
End Function
Public Sub Dispose()
If Not (m_hMem = 0) Then
If Not (m_lPtr = 0) Then
LocalUnlock m_lPtr
m_lPtr = 0
End If
LocalFree m_hMem
m_hMem = 0
End If
End Sub
Private Sub Class_Terminate()
Dispose
End Sub
As you can see, the class checks the Windows version: if it is NT then the string is held as Unicode, otherwise it is held as ANSI and VB's Unicode - ANSI conversions are invoked on the string. The class includes methods you can use to get or set the string from the buffer, as well as to either get the pointer to the string or to set the pointer to a new API string. The class also knows how to deallocate the memory, and also not to deallocate things it doesn't own (when the pointer has been externally set). Note that like VB, the "String" associated with the class is immutable: if you change the string then a new buffer is allocated. With this available, we're in a position to make using the SHGetNewLinkInfo easier. Unicode and ANSI Declares with cStringPointerRather than using String at all in the declares, all String parameters are set to ByVal Long:
Private Declare Function SHGetNewLinkInfoA Lib "shell32.dll" ( _
ByVal pszLinkTo As Long, _
ByVal pszDir As Long, _
ByVal pszName As Long, _
ByRef pfMustCopy As Long, _
ByVal uFlags As Long) As Long
Private Declare Function SHGetNewLinkInfoW Lib "shell32.dll" ( _
ByVal pszLinkTo As Long, _
ByVal pszDir As Long, _
ByVal pszName As Long, _
ByRef pfMustCopy As Long, _
ByVal uFlags As Long) As Long
Actually making the call becomes nice an easy:
Public Property Get NewLinkFilename( _
ByVal sFileToLinkTo As String, _
ByVal sLinkDirectory As String, _
Optional ByVal eLinkOptions As EShellLinkFilenameOptions = 0 _
) As String
Dim cLinkTo As New cStringPointer
Dim cLinkDir As New cStringPointer
Dim cFilename As New cStringPointer
Dim lCopyFlag As Long
Dim lR As Long
cLinkTo.SetString sFileToLinkTo
cLinkDir.SetString sLinkDirectory
cFilename.SetString String$(MAX_PATH, 0)
If (IsNt) Then
lR = SHGetNewLinkInfoW( _
cLinkTo.Ptr, cLinkDir.Ptr, cFilename.Ptr, _
lCopyFlag, eLinkOptions)
Else
lR = SHGetNewLinkInfoA( _
cLinkTo.Ptr, cLinkDir.Ptr, cFilename.Ptr, _
lCopyFlag, eLinkOptions)
End If
NewLinkFilename = cFilename.GetString()
End Property
I think you'll agree that that makes handling strings somewhat easier. Here we've accomplished calling a function that has both in and out string values, in both ANSI and Unicode, with only a handful of lines of code. ConclusionThis article demonstrates how to obtain the correct filename for a new Shortcut using the Shell's API. The associated cStringPointer class used to perform this function may well prove useful in your projects too!
|
|
|
|
||