EDIT: Scotty2012 and David Morton's answers don't work for me so I have put a bounty on this question. I think I need to change the type of the string to something else before passing it in.
I'm not much cop at P/Invoke and I'm struggling with declaring and calling SHSetKnownFolderPath. I'm using VB9 but if anyone puts answers in C# I should be able to translate.
I have got SHGetKnowFolderPath working. Here is my code.
In VB
Imports System.Runtime.InteropServices
Public Class Form1
<DllImport("shell32.dll")> _
Private Shared Function SHGetKnownFolderPath(<MarshalAs(UnmanagedType.LPStruct)> ByVal rfid As Guid, ByVal dwFlags As UInteger, ByVal hToken As IntPtr, ByRef pszPath As IntPtr) As Integer
End Function
<DllImport("shell32.dll")> _
Private Shared Function SHSetKnownFolderPath(<MarshalAs(UnmanagedType.LPStruct)> ByVal rfid As Guid, ByVal dwFlags As UInteger, ByVal hToken As IntPtr, ByRef pszPath As IntPtr) As Integer
End Function
Public Shared ReadOnly Documents As New Guid("FDD39AD0-238F-46AF-ADB4-6C85480369C7")
Private Sub ButtonSetDocumentsPath_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonSetDocumentsPath.Click
Dim pPath As IntPtr = Marshal.StringToCoTaskMemUni(TextBoxPath.Text)
If SHSetKnownFolderPath(Documents, 0, IntPtr.Zero, pPath) = 0 Then
MsgBox("Set Sucessfully")
End If
End Sub
Private Sub ButtonGetDocumentsPath_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonGetDocumentsPath.Click
Dim pPath As IntPtr
If SHGetKnownFolderPath(Documents, 0, IntPtr.Zero, pPath) = 0 Then
Dim s As String = Marshal.PtrToStringUni(pPath)
Marshal.FreeCoTaskMem(pPath)
TextBoxPath.Text = s
End If
End Sub
End Class
Thanks!
-
I think this should work in C# (I'm not running vista here so I can't check):
[DllImport("shell32.dll")] private static int SHSetKnownFolderPath(ref Guid guid, int flags, IntPtr hToken, string newPath);
you can call it this way
SHSetKnownFolderPath(ref Documents, 0, IntPtr.Zero, "c:\\my new path\\");
Christopher Edwards : Thanks. However this doesn't work for me. I'm not sure why. I get a return value of -2147024735...Stu Mackellar : -2147024735 == 0x800700A1 == "The specified path is invalid". The prototype is correct. Try declaring the string outside of the function call and make sure you're setting a valid path. -
This would be the declaration:
[DllImport("shell32.dll")] static extern int SHSetFolderPath(int csidl, IntPtr hToken, uint dwFlags, StringBuilder path)
You would need to create a StringBuilder, passing in the maximum path of 260 into the constructor (This would be true for Vista/XP.) This is the stringbuilder that would have the new directory for the folder you're trying to set, so append your text into the StringBuilder for your new location. The biggest problem with your implementation, though is that the csidl parameter isn't the same as the Guid specified in Windows. These values are actually the values that are declared in shlobj.h. Follow the link to see the values that are top be passed in. The hToken should always be IntPtr.Zero, unless you have a pointer to a specific user you're trying to change this for. IntPtr.Zero will use the current user. dwFlags should always be set to 0.
Christopher Edwards : Sorry it was SHSetKnownFolderPath that I was interested in, not SHSetFolderPath which is deprecated. I linked to the old version by accident (I will now fix this). That explains the GUIDs. I still can't get it work!scottm : I don't think you need to use a StringBuilder because the path is not an [out] variable.JaredPar : Also the default marshaling for a string in PInvoke is ANSI. This API requires Unicode. That was my mistake (fixed) -
Try this code out. Sorry for the length, but it's all needed to properly PInvoke this particular function. It's a simple console application that includes a definition for both functions and an example usage of SHGetKnownFolderPath.
I went ahead and included the definitions for KNOWN_FOLDER_FLAG as well as a few definitions for the folder ID's. All of the folder Id's are actually just GUIDs. All of the possible ID's can be found at %ProgramFiles%\Windows SDK\v6.0A\Include\KnownFolders.h and added in the same manner that I added in the sample.
I included several wrapper functions that hide all of the evil marashal'ing details for calling the particular functions.
If there is any particular folder id you'd like or explanation please add a comment and I'll update the sample.
EDIT Corrected a mistake in the Marshalling of SHSetKnownFolderPath. I did not add a MarshalAs tag to the String value and it was defaulting to an ANSI string. The API required unicode. The SHSetFolderFunction now works (confirmed with RecentFolder)
Imports System.Runtime.InteropServices Module NativeMethods Public Enum KNOWN_FOLDER_FLAG '''KF_FLAG_CREATE -> 0x00008000 KF_FLAG_CREATE = 32768 '''KF_FLAG_DONT_VERIFY -> 0x00004000 KF_FLAG_DONT_VERIFY = 16384 '''KF_FLAG_DONT_UNEXPAND -> 0x00002000 KF_FLAG_DONT_UNEXPAND = 8192 '''KF_FLAG_NO_ALIAS -> 0x00001000 KF_FLAG_NO_ALIAS = 4096 '''KF_FLAG_INIT -> 0x00000800 KF_FLAG_INIT = 2048 '''KF_FLAG_DEFAULT_PATH -> 0x00000400 KF_FLAG_DEFAULT_PATH = 1024 '''KF_FLAG_NOT_PARENT_RELATIVE -> 0x00000200 KF_FLAG_NOT_PARENT_RELATIVE = 512 '''KF_FLAG_SIMPLE_IDLIST -> 0x00000100 KF_FLAG_SIMPLE_IDLIST = 256 '''KF_FLAG_ALIAS_ONLY -> 0x80000000 KF_FLAG_ALIAS_ONLY = &H80000000 End Enum Public ComputerFolder As Guid = New Guid("0AC0837C-BBF8-452A-850D-79D08E667CA7") Public DesktopFolder As Guid = New Guid("B4BFCC3A-DB2C-424C-B029-7FE99A87C641") Public DocumentsFolder As Guid = New Guid("FDD39AD0-238F-46AF-ADB4-6C85480369C7") <DllImport("shell32.dll")> _ Public Function SHGetKnownFolderPath( _ ByRef folderId As Guid, _ ByVal flags As UInteger, _ ByVal token As IntPtr, _ <Out()> ByRef pathPtr As IntPtr) As Integer End Function <DllImport("shell32.dll")> _ Public Function SHSetKnownFolderPath( _ ByRef folderId As Guid, _ ByVal flags As UInteger, _ ByVal token As IntPtr, _ <[In](), MarshalAs(UnmanagedType.LPWStr)> ByVal path As String) As Integer End Function Public Function SHGetKnownFolderPathWrapper(ByVal folderId As Guid) As String Return SHGetKnownFolderPathWrapper(folderId, 0) End Function Public Function SHGetKnownFolderPathWrapper( _ ByVal folderId As Guid, _ ByVal flags As KNOWN_FOLDER_FLAG) As String Dim ptr = IntPtr.Zero Dim path = String.Empty Try Dim ret = SHGetKnownFolderPath(folderId, CUInt(flags), IntPtr.Zero, ptr) If ret <> 0 Then Throw Marshal.GetExceptionForHR(ret) End If path = Marshal.PtrToStringUni(ptr) Finally Marshal.FreeCoTaskMem(ptr) End Try Return path End Function Public Sub SHSetKnownFolderPathWrapper( _ ByVal folderId As Guid, _ ByVal path As String) SHSetKnownFolderPathWrapper(folderId, 0, path) End Sub Public Sub SHSetKnownFolderPathWrapper( _ ByVal folderId As Guid, _ ByVal flags As KNOWN_FOLDER_FLAG, _ ByVal path As String) Dim ret = SHSetKnownFolderPath(folderId, CUInt(flags), IntPtr.Zero, path) If ret <> 0 Then Throw Marshal.GetExceptionForHR(ret) End If End Sub End Module Module Module1 Sub Main() Dim path = SHGetKnownFolderPathWrapper(NativeMethods.DesktopFolder) Console.WriteLine(path) End Sub End Module
Christopher Edwards : Thanks for that. Unfortunately I am getting "The specified path is invalid. (Exception from HRESULT: 0x800700A1)" thrown by the line Throw Marshal.GetExceptionForHR(ret) in SHSetKnownFolderPathWrapper. This happens even when I set the Documents folder path to that it's existing value. Any ideas?JaredPar : What OS are you using?JaredPar : Also, what GUID are you using for documents?Christopher Edwards : I'm on Vista, but I'll need to test it on XP too. The GUID I'm using is "FDD39AD0-238F-46AF-ADB4-6C85480369C7".JaredPar : Weird, I just verified that GUID works fine on my machine (updated sample to include the Documents folder definition). Do any of the other folders work on your machine? Are you on a 64bit or 32bit machine? Shouldn't matter but I'm struggling to figure out why this isn't working on your box.Christopher Edwards : Well thanks for all your help. If it's working on your box I'll try it on another box in the office. My machine is 32bit Vista Business, I have local admin rights.JaredPar : I poked around in the documentation a bit and it says that not all folder Id's are present in all systems. It's possible your configuration does not have the Documents folder set. Do any of the other folders work? I've unfortunately been unable to repro your issue on any of my machinesChristopher Edwards : Hi, I tried this on another PC with the same issue. I also tried it with the desktop folder instead of MyDocs. I must be doing something differently from you. I am calling SHSetKnownFolderPathWrapper(DesktopFolder, "C:\Users\c.edwards\Desktop"), do I need any other arguments?JaredPar : Oh. I thought you were testing ShGet* version. I haven't played much with the SHSet. I'll check that out tonight and get back to you.JaredPar : I'm a moron, I completely read SHGet in your first commentJaredPar : Figured outh te issue. It was a Marshalling problem. I updated the solution nad confirmed it works with RecentFolder: AE50C081-EBD2-438A-8655-8A092E34987AChristopher Edwards : It works! Brilliant, that has really helped me. Many many thanks. -
Hi guys, great stuff. Unfortunatelly this is not working with Visual Basic 2010, as declaration and variable types have changed (again)
Doe anyone have an idea how this declaration and function calling of SHSetKnownFolderpath works in VB 2010???
0 comments:
Post a Comment