Code archives/Miscellaneous/Get Windows 'special folder' paths

This code has been declared by its author to be Public Domain code.

Download source code

Get Windows 'special folder' paths by BlitzSupport2011
Allows you to retrieve the real path of Windows system folders such as "My Documents", the "Program Files" folder, "My Pictures", etc.

Windows only, of course...

[EDIT 11/11/2012 -- Updated to be SuperStrict-compatible.]

[EDIT 29/11/2014 -- Updated to incorporate JoshK's mem-leak fix! Couldn't get the pidl stuff working, so hacked Josh's code in as local function!

This needs to be revisited, though, as it seems SHGetSpecialFolderLocation is now deprecated... not that it's likely to disappear any time soon.
Const CSIDL_INTERNET:Int = $1
Const CSIDL_PROGRAMS:Int = $2
Const CSIDL_CONTROLS:Int = $3
Const CSIDL_PRINTERS:Int = $4
Const CSIDL_PERSONAL:Int = $5 ' Use this instead of CSIDL_MYDOCUMENTS. I don't know why! Ask Microsoft...
Const CSIDL_FAVORITES:Int = $6
Const CSIDL_STARTUP:Int = $7
Const CSIDL_RECENT:Int = $8
Const CSIDL_SENDTO:Int = $9
Const CSIDL_BITBUCKET:Int = $A
Const CSIDL_STARTMENU:Int = $B
Const CSIDL_MYDOCUMENTS:Int = $C
Const CSIDL_MYMUSIC:Int = $D
Const CSIDL_MYVIDEO:Int = $E
Const CSIDL_DESKTOPDIRECTORY:Int = $10
Const CSIDL_DRIVES:Int = $11
Const CSIDL_NETWORK:Int = $12
Const CSIDL_NETHOOD:Int = $13
Const CSIDL_FONTS:Int = $14
Const CSIDL_TEMPLATES:Int = $15
Const CSIDL_COMMON_STARTMENU:Int = $16
Const CSIDL_COMMON_PROGRAMS:Int = $17
Const CSIDL_COMMON_STARTUP:Int = $18
Const CSIDL_COMMON_DESKTOPDIRECTORY:Int = $19
Const CSIDL_APPDATA:Int = $1A
Const CSIDL_PRINTHOOD:Int = $1B
Const CSIDL_LOCAL_APPDATA:Int = $1C
Const CSIDL_ALTSTARTUP:Int = $1D
Const CSIDL_COMMON_ALTSTARTUP:Int = $1E
Const CSIDL_COMMON_FAVORITES:Int = $1F
Const CSIDL_INTERNET_CACHE:Int = $20
Const CSIDL_COOKIES:Int = $21
Const CSIDL_HISTORY:Int = $22
Const CSIDL_COMMON_APPDATA:Int = $23
Const CSIDL_WINDOWS:Int = $24
Const CSIDL_SYSTEM:Int = $25
Const CSIDL_PROGRAM_FILES:Int = $26
Const CSIDL_MYPICTURES:Int = $27
Const CSIDL_PROFILE:Int = $28
Const CSIDL_SYSTEMX86:Int = $29
Const CSIDL_PROGRAM_FILESX86:Int = $2A
Const CSIDL_PROGRAM_FILES_COMMON:Int = $2B
Const CSIDL_PROGRAM_FILES_COMMONX86:Int = $2C
Const CSIDL_COMMON_TEMPLATES:Int = $2D
Const CSIDL_COMMON_DOCUMENTS:Int = $2E
Const CSIDL_COMMON_ADMINTOOLS:Int = $2F
Const CSIDL_ADMINTOOLS:Int = $30
Const CSIDL_CONNECTIONS:Int = $31
Const CSIDL_COMMON_MUSIC:Int = $35
Const CSIDL_COMMON_PICTURES:Int = $36
Const CSIDL_COMMON_VIDEO:Int = $37
Const CSIDL_RESOURCES:Int = $38
Const CSIDL_RESOURCES_LOCALIZED:Int = $39
Const CSIDL_COMMON_OEM_LINKS:Int = $3A
Const CSIDL_CDBURN_AREA:Int = $3B
Const CSIDL_COMPUTERSNEARME:Int = $3D
Const CSIDL_FLAG_PER_USER_INIT:Int = $800
Const CSIDL_FLAG_NO_ALIAS:Int = $1000
Const CSIDL_FLAG_DONT_VERIFY:Int = $4000
Const CSIDL_FLAG_CREATE:Int = $8000
Const CSIDL_FLAG_MASK:Int = $FF00

Function GetSpecialFolder:String (folder:Int)

	?Win32 ' Windows only!

	' Shell32 functions...
	
	Global SHGetSpecialFolderLocation_ (hwndOwner:Byte Ptr, nFolder:Int, pidl:Byte Ptr) "win32"
	Global SHGetPathFromIDList_ (pidl:Byte Ptr, bytearray:Byte Ptr) "win32"
	
	' OLE32 functions...
	
	Global CoTaskMemFree_ (pv:Byte Ptr)
	
	' Assign function pointers...
	
	Local shell32:Int = LoadLibraryA ("shell32.dll")
	Local ole32:Int = LoadLibraryA ("ole32.dll")

	Local result:Int = False
	
	If shell32

		SHGetSpecialFolderLocation_ = GetProcAddress (shell32, "SHGetSpecialFolderLocation")
		SHGetPathFromIDList_ = GetProcAddress (shell32, "SHGetPathFromIDList")

		If (Not SHGetSpecialFolderLocation_) Or (Not SHGetPathFromIDList_)
			DebugLog "Failed to assign shell32 function pointer!"
			Return ""
		EndIf

	Else

		DebugLog "Failed to open shell32.dll!"
		Return ""

	EndIf

	If ole32

		CoTaskMemFree_ = GetProcAddress (ole32, "CoTaskMemFree")

		If Not CoTaskMemFree_
			DebugLog "Failed to assign ole32 function pointer!"
			Return ""
		EndIf

	Else

		DebugLog "Failed to open ole32.dll!"
		Return ""

	EndIf

	Function GetSpecialFolder_Sub:String(folder_id:Int) 

		Local idl:TBank = CreateBank (8) 
		Local pathbank:TBank = CreateBank (260) 
		Local n%
		Local sp$
		Local b:Int

		If SHGetSpecialFolderLocation_ (Null, folder_id, BankBuf (idl)) = 0		

			SHGetPathFromIDList_ Byte Ptr PeekInt (idl, 0), BankBuf (pathbank)

			For n = 0 To 259
				b = PeekByte (pathbank, n)
				If b = 0
					CoTaskMemFree_ (Byte Ptr PeekInt (idl, 0))
					Return sp
				EndIf
				sp$ = sp$ + Chr (b)
			Next
		Else
			Return ""
		EndIf
		
		CoTaskMemFree_ (Byte Ptr PeekInt (idl, 0))
		
		Return sp.Trim ()
		
	End Function

	If SHGetSpecialFolderLocation_ And SHGetPathFromIDList_ And CoTaskMemFree_
		Return GetSpecialFolder_Sub (folder)
	EndIf

	?

End Function

' For Local loop:Int = 1 To 10000000 ' Mem leak test!

	Print GetSpecialFolder (CSIDL_PROGRAM_FILESX86)
	Print GetSpecialFolder (CSIDL_DESKTOPDIRECTORY)
	Print GetSpecialFolder (CSIDL_PERSONAL)
	Print GetSpecialFolder (CSIDL_WINDOWS)
	Print GetSpecialFolder (CSIDL_SYSTEM)
	
	' Handy one: create a folder here in which to store your app settings correctly on Windows Vista upwards...
	
	Print GetSpecialFolder (CSIDL_APPDATA)

'	Delay 10
	
' Next

Comments

Oddball2011
Wow, nice! That's a lot of folder locations. Just a note to anyone wanting a cross platform solution Brucey's Volumes mod can locate some, but not all, of these across all three platforms.


Matty2011
Is that the same for all versions of windows (Win95 upwards)?


BlitzSupport2011
The functions are compatible with Windows 95, but of course some of the folder locations didn't exist back then -- you should get a blank string back in those cases.

(BlitzMax 'officially' doesn't support Windows 9x/Me any more, though I imagine most stuff still works!)


JoshK2014
Your code has a memory leak. It actually caused Leadwerks users quite a lot of trouble, but I finally identified it. Still, thank you for providing this code! Here's the fix:

In your extern block add this function declaration:
Function CoTaskMemFree(pv:Int)

And make sure you free that thing, or you have a leak!:
Function GetSpecialFolder:String(folder_id:Int) 
	Local idl:TBank = CreateBank (8) 
	Local pathbank:TBank = CreateBank (260) 
	Local n%
	Local sp$
	Local b:Int
	If SHGetSpecialFolderLocation(0,folder_id,BankBuf(idl)) = 0		
		SHGetPathFromIDList PeekInt( idl,0), BankBuf(pathbank)		
		For n= 0 To 259
			b=PeekByte(pathbank,n)
			If b=0
				CoTaskMemFree(PeekInt( idl,0))
				Return sp
			EndIf
			sp$ = sp$ + Chr(b)
		Next
	Else
		Return ""
	EndIf
	CoTaskMemFree(PeekInt( idl,0))
	Return sp.Trim()
EndFunction

See MS's note on freeing that thingie:
http://msdn.microsoft.com/en-us/library/windows/desktop/bb762203%28v=vs.85%29.aspx


BlitzSupport2014
Oopsie! Will update the original code soon-ish...

Just out of interest, what was happening while idle that meant this was called so often? I only really expected to use it "now and then" in my own limited use-case scenarios... apologies for any problems this may have caused!


JoshK2014
Well, that part is my fault. I was calling it each frame in our auto-backup routine, and I really should not have, just for performance and stupidity. But yeah it was causing a pretty fast mem leak that led to instability of the editor after a time. Don't mean to blame you or anything, that's just what happened.


BlitzSupport2014
At least you found the bug and the solution -- wouldn't have known it leaked otherwise!


BlitzSupport2014
Finally updated! Seems not to leak now -- thanks, Josh.


Code Archives Forum