Archive: Using System::Call to call SHGetKnownFolderPath


Using System::Call to call SHGetKnownFolderPath
I'm having trouble getting this to work. Here's the MSDN for the function

HRESULT SHGetKnownFolderPath(
REFKNOWNFOLDERID rfid,
DWORD dwFlags,
HANDLE hToken,
PWSTR *ppszPath
)
And what I've got
!define SHGetKnownFolderPath "!insertmacro _SHGetKnownFolderPath"

!macro _SHGetKnownFolderPath nFolder dwFlags outVar
System::Call "shell32::SHGetKnownFolderPath(g ${nFolder}, i ${dwFlags}, i, w .s)"
Pop ${outVar}
!macroend
The problem is the FOLDERID for the function is a GUID, for example for the SendTo folder
{8983036C-27C0-404B-8F08-102D10DCFD74}
So the macro call would look like
${SHGetKnownFolderPath} "{8983036C-27C0-404B-8F08-102D10DCFD74}" "0x1000" $0
But I get nothing in the output. Can someone help?

You aren't setting a value for the hToken parameter. You might try setting the third parameter to 0 (null) or -1 (if the guid is for a user specific folder and you want the current user).

Don


Thanks for the idea, but it doesn't matter if the value for hToken is set of not. It's supposed to be null anyway. I did test it to make sure. But the SHGetFolderPath function, which I can get to work, also works without setting the hToken var.

I'm sure the problem is to do with the rfid var and the proper way to pass the GUID to the function.

PS - this is a Vista only function.


The output is a Unicode string. Are you running the Unicode build, or do you call a function to convert the string to single byte?

The System dll has examples:

!define CLSID_ActiveDesktop {75048700-EF1F-11D0-9888-006097DEACF9}
!define IID_IActiveDesktop {F490EB00-1240-11D1-9888-006097DEACF9}
# create IActiveDesktop interface
System::Call "ole32::CoCreateInstance( \
g '${CLSID_ActiveDesktop}', i 0, \
i ${CLSCTX_INPROC_SERVER}, \
g '${IID_IActiveDesktop}', *i .r0) i.r1"
Your usage of the guid looks similar.

I'm running the ANSI version.

I haven't converted the output yet. Even though, I expected to see some kind of output. It's completely blank. Is that right? Shouldn't I see something even if I haven't converted it yet?


A unicode string will look like it has zeros between its characters. Those would be interpreted as end of string. (I don't recall whether the first byte is the zero, which would make your return string appear completely empty).


I believe the second byte is 00, so I should still get the first character.

Crap, converting the string to ANSI would be a whole other problem. I know how to do the DllCall's in AutoIt for this, but not the System::Call's in NSIS (I don't know how to create DLL structs in NSIS for the string buffers).


I'm not an expert with the System dll, so maybe they will speak up if you don't get it figured out by yourself. Here's a link to CodeGuru on how to convert multibyte to ansi.


I don't have vista, so I could not test this code at all, but I would guess it would look something like

System::Call "shell32::SHGetKnownFolderPath(g '{8983036C-27C0-404B-8F08-102D10DCFD74}', i 0x1000 ,i0, i.r1)i.r0"
${If} $0 == 0
System::Call /NoUnload 'kernel32::lstrlenW(i $1)i.r2'
IntOp $2 $2 * 2
System::Call /NoUnload '*$1(&w$2 .r2)'
MessageBox mb_ok path=$2
System::Call 'ole32::CoTaskMemFree(i $1)'
${endif}

but, why call the Vista function when you can just do:


System::Call 'shell32::SHGetFolderPath(i $hwndparent,i 0x9,i0,i0,t.r0)i'
MessageBox mb_ok $0


and there is $SENDTO also ofcourse

Thanks for the try, but the function crashes if the output data type is an integer (i). It does not crash if output is a unicode string (w), but the output is then just a ?. The return value is still 0 however.

And I realize I can use SHGetFolderPath (or $SENDTO), but SHGetKnownFolderPath has GUIDs with no CSIDL equivalent for the old function. It is also extensible, ie a user can create a GUID for a folder and return it with this function.


ah yes, its PWSTR*, sorry, missed that, try adding a *


Eureka! Thanks soooo much! This was killing me...


It's:

System::Call "shell32::SHGetKnownFolderPath(g '{8983036C-27C0-404B-8F08-102D10DCFD74}', \
i 0x1000 , i0, *w.r1) i.r0"
If that doesn't work, it's because `g` is for GUID pointers and SHGetKnownFolderPath expects the GUID itself on the stack.

Nope, I got it. I had the * on the wrong side of the i ;)

Working code

System::Call "shell32::SHGetKnownFolderPath(g '{8983036C-27C0-404B-8F08-102D10DCFD74}', i 0x1000 ,in, *i.r1)i.r0"
${If} $0 == 0
System::Call /NoUnload 'kernel32::lstrlenW(i $1)i.r2'
IntOp $2 $2 * 2
System::Call /NoUnload '*$1(&w$2 .r2)'
MessageBox mb_ok path=$2
System::Call 'ole32::CoTaskMemFree(i $1)'
${endif}

Originally posted by kichik
`g` is for GUID pointers
Can you explain that a little more? I'm trying to get this to work in AutoIt3 as well, but it doesn't have a GUID data type. Is there some way I can create that data type using others? Are you saying that g is a pointer to a GUID string in a structure?

No, I'm saying for the System plug-in 'g' means pass a pointer to a GUID structure (LPGUID instead of GUID). I don't know how to do that in AutoIt3.


Got it (finally) in AutoIt -

#include <WinAPI.au3>

$tagstruct = DllStructCreate($tagGUID)
$tagptr = DllStructGetPtr($tagstruct)
_WinAPI_GUIDFromStringEx("{8983036C-27C0-404B-8F08-102D10DCFD74}", $tagptr)

So $tagptr has the pointer to the GUID structure.

Now I gotta figure out how to read the buffer....