tags:

views:

625

answers:

4

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!

+2  A: 

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\\");
scottm
Thanks. However this doesn't work for me. I'm not sure why. I get a return value of -2147024735...
Christopher Edwards
-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.
Stu Mackellar
A: 

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.

David Morton
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!
Christopher Edwards
I don't think you need to use a StringBuilder because the path is not an [out] variable.
scottm
Also the default marshaling for a string in PInvoke is ANSI. This API requires Unicode. That was my mistake (fixed)
JaredPar
+1  A: 

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
JaredPar
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?
Christopher Edwards
What OS are you using?
JaredPar
Also, what GUID are you using for documents?
JaredPar
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".
Christopher Edwards
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.
JaredPar
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.
Christopher Edwards
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 machines
JaredPar
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?
Christopher Edwards
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 comment
JaredPar
Figured outh te issue. It was a Marshalling problem. I updated the solution nad confirmed it works with RecentFolder: AE50C081-EBD2-438A-8655-8A092E34987A
JaredPar
It works! Brilliant, that has really helped me. Many many thanks.
Christopher Edwards
A: 

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???