Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Getting the IShellBrowser from a dialog

308 views
Skip to first unread message

lhaymehr

unread,
Nov 1, 2007, 11:43:18 AM11/1/07
to
I'm trying to get the IShellBrowser interface from a Explorer window,
Open/Save dialog and a SHBrowseForFolder dialog. From what I could make out
out of numerous google searches, explorer windows expose a IShellBrowser
interface via its' hidden "SHELLDLL_DefView" classed window. The reason why
I need this is to set the current directory of those dialogs/explorer
windows to a path I want to specify. If I'm correct, I can do this with
IShellBrowser.BrowseObject Method.

Im doing it like:

var
ShellBrowser: IShellBrowser;
const
CWM_GETISHELLBROWSER = WM_USER + 7;
begin
ShellBrowser := nil;
ShellBrowser := IShellBrowser(SendMessage(aDialog, CWM_GETISHELLBROWSER,
0, 0));

This was dug out of EasyNSE source.

Is this correct? Now I will not get Access Violations as this is called from
a DLL that is injected into the process for which the IShellBrowser is
queried.

What I don't know how to do is supply the BrowseObject method with a valid
ITEMIDLIST structure, or better yet, how to simply convert a path of string
into this data. To be honest, i still haven't ranished all the EasyNSE code
for a solution so I'm hoping someone can supply me with a quicker solution
:).

Thanks,
Vedran.
--
np: Jean Jaques Perrey & Luke Vibert - Analog Generique / Moog Acid


Chris Morgan

unread,
Nov 1, 2007, 1:09:01 PM11/1/07
to
> var
> ShellBrowser: IShellBrowser;
> const
> CWM_GETISHELLBROWSER = WM_USER + 7;
> begin
> ShellBrowser := nil;
> ShellBrowser := IShellBrowser(SendMessage(aDialog, CWM_GETISHELLBROWSER,
> 0, 0));

OK so far, but add this:

ShellBrowser._AddRef; // interface is not automatically AddRef'd by
Microsoft

This avoids a crash when Delphi releases the interface at the end of the
method.

Now you need to convert your directory path into a PIDL
using the IShellFolder::ParseDisplayName method:

something like

var
Desktop: IShellFolder;
pidl: PItemIDList;
i: cardinal;

...

OleCheck(SHGetDesktopFolder(Desktop));
Desktop.ParseDisplayName(0,nil,StringToOLEStr(dir),i,pidl,i);

ShellBrowser.BrowseObject(0,pidl); // change dir

CoTaskMemFree(pidl);

end;

That should do it.

SHGetDesktopFolder and CoTaskMemFree are windows APIs, (uses ShlObj,
ActiveX)
StringToOLEStr is a Delphi function in the System unit

Cheers,

Chris

lhaymehr

unread,
Nov 1, 2007, 2:32:48 PM11/1/07
to
Chris Morgan sayz:

> That should do it.

Yeah, that did it. :)
Thanks.


Remy Lebeau (TeamB)

unread,
Nov 1, 2007, 3:28:57 PM11/1/07
to

"lhaymehr" <my.name.@google.email.service.xxx> wrote in message
news:4729...@newsgroups.borland.com...

> The reason why I need this is to set the current directory of
> those dialogs/explorer windows to a path I want to specify.

Open/Save dialogs already provide a native way to do that. Before
displaying the dialog, set the lpstrInitialDir member of the OPENFILENAME
structure (or set the InitialDir property if using the
TOpenDialog/TSaveDialog components).

SHBrowseForFolder() also provides a native way to do that as well. Inside
its callback function, you can issue a BFFM_SETSELECTION message to the HWND
that is passed to the callback.

> What I don't know how to do is supply the BrowseObject method
> with a valid ITEMIDLIST structure, or better yet, how to simply
> convert a path of string into this data.

Use SHGetDesktopFolder() to get the IShellFolder interface for the top-level
Desktop namespace, then pass the full path to the
IShellFolder.ParseDisplayName() method. When passing that ITEMIDLIST to
IShellBrowser.BrowseObject(), be sure to specify the SBSP_ABSOLUTE flag.

With that said, here is a gotcha for you to consider - BrowseObject() in
those dialogs does not seem to work in Vista, so if you need to support that
OS, you will likely have to find an alternative approach.


Gambit


Remy Lebeau (TeamB)

unread,
Nov 1, 2007, 3:34:08 PM11/1/07
to

"Chris Morgan" <chris.nospam at lynxinfo dot co dot uk> wrote in message
news:472a...@newsgroups.borland.com...

> ShellBrowser._AddRef; // interface is not automatically
> AddRef'd by Microsoft

The interface returned by WM_GETISHELLBROWSER does not need to be Addref'ed
or Release'd if you are not going to hold on to it for very long.

Besides that, in Delphi, assigning an interface to an interface variable
already AddRef's and Release's it automatically for you.

> Desktop.ParseDisplayName(0,nil,StringToOLEStr(dir),i,pidl,i);

StringToOleStr() returns a BSTR that needs to be freed manually with
SysFreeString(), otherwise the memory is leaked.

> ShellBrowser.BrowseObject(0,pidl); // change dir

You need to specify the SBSP_ABSOLUTE flag when passing an absolute PIDL to
BrowseObject().


Gambit


lhaymehr

unread,
Nov 1, 2007, 8:38:39 PM11/1/07
to
Remy Lebeau (TeamB) sayz:

> Open/Save dialogs already provide a native way to do that.

But that isn't of much use to me in this case. Read on.

> With that said, here is a gotcha for you to consider - BrowseObject() in
> those dialogs does not seem to work in Vista

I've found a post in MS boards on that one. A "MSVP" replied that
BrowseObject is used even more in Vista, but that they've actually changed
something. However, I just tested it under Vista and it works. I did notice
annother bug though..

Still, this approach won't work for me, as it doesn't work on
SHBrowseForFolder dialogs and they were the reason i tried using this.

Please see this:
http://snjezanat.net.amis.hr/EvilUtilities.wmv (460 KB (471.040 bytes))

The first app that i run is the app that initially sets the system wide
hook. The dialog sizing and the popup menus on the dialog system menus are
provided by the hook and the path setting/app executing is done via the hook
also.

There is another way of doing this; changing the directories of the
Open/Save dialogs by setting the text of the filename edit boxes to a path
and simulating the OK button click, but I wanted something more universal
which could be reused on SHBrowseForFolder dialogs also.

And that "text-setting way" won't work for SHBrowseForFolder because those
dialogs need to have their EditBox shown in order for it to work, and that
isn't always the case. This IShellBrowser approach is actually only usable
on Explorer windows and Open/Save dialogs because they expose the
"SHELLDLL_DefView" window, and the new SHBrowseForFolder dialogs have
something else, which is "SHBrowseForFolder ShellNameSpace Control" classed
window. Old-style Browse dialogs don't have anything like that at all. So
all this is a real "show-stopper".
--
foobar is offline


Remy Lebeau (TeamB)

unread,
Nov 1, 2007, 9:12:44 PM11/1/07
to

"lhaymehr" <my.name.@google.email.service.xxx> wrote in message
news:472a...@newsgroups.borland.com...

> But that isn't of much use to me in this case. Read on.

When exactly are you trying to change the working folder in each scenerio?

> Still, this approach won't work for me, as it doesn't work
> on SHBrowseForFolder dialogs and they were the reason
> i tried using this.

As I stated earlier, the official and preferred way to change the working
folder in that dialog is to use the BFFM_SETSELECTION message instead. It
supports paths specified as both strings and PIDLs. All you need to do is
specify a callback when calling SHBrowseForFolder() so that you can interept
the property HWND to send the message to when desired.


Gambit


Rudy Velthuis [TeamB]

unread,
Nov 1, 2007, 9:13:44 PM11/1/07
to
Remy Lebeau (TeamB) wrote:

>
> "Chris Morgan" <chris.nospam at lynxinfo dot co dot uk> wrote in
> message news:472a...@newsgroups.borland.com...
>
> > ShellBrowser._AddRef; // interface is not automatically
> > AddRef'd by Microsoft
>
> The interface returned by WM_GETISHELLBROWSER does not need to be
> Addref'ed or Release'd if you are not going to hold on to it for very
> long.

But Delphi will _Release it, though, so an _AddRef would be a good
idea. <g>
--
Rudy Velthuis [TeamB]

"We've all heard that a million monkeys banging on a million
typewriters will eventually reproduce the entire works of
Shakespeare. Now, thanks to the Internet, we know this is not
true." -- Robert Wilensky

lhaymehr

unread,
Nov 2, 2007, 6:41:30 AM11/2/07
to
Remy Lebeau (TeamB) sayz:

> As I stated earlier, the official and preferred way to change the working
> folder in that dialog is to use the BFFM_SETSELECTION message instead.

Again you're right. I tried that before but I passed the path in an
incorrect way. After fiddling with it now it works as expected.

SendMessage(aDialog, BFFM_SETSELECTION, 1,
integer(@Favorites[aIndex].FileName[1]));

So the path setting now WORKS on all the dialogs and i can move on :)!
--
np: Cepia - Mr. Projectile - You Need (Cepia Mix) / Atlantic Blood


Remy Lebeau (TeamB)

unread,
Nov 2, 2007, 1:28:05 PM11/2/07
to

"lhaymehr" <my.name.@google.email.service.xxx> wrote in message
news:472a...@newsgroups.borland.com...

> SendMessage(aDialog, BFFM_SETSELECTION, 1,


> integer(@Favorites[aIndex].FileName[1]));


Use PChar() instead of indexing into the string like that, ie:

SendMessage(aDialog, BFFM_SETSELECTION, 1,
Longint(PChar(Favorites[aIndex].FileName)));

With that said, the docs for BFFM_SETSELECTION say that the string has to be
a Unicode string. Personally, I have aways used Ansi strings with
BFFM_SETSELECTION. It probably has to do with whether IsUnicodeWindow()
returns True or False for the dialog. Besides that, the dialog has some
weird parsing requirements for path strings anyway, so I would suggest using
PIDLs instead, ie:

var
Pidl: PItemIDList;
Desktop: IShellFolder;
WideName: WideString;
begin
if Succeeded(SHGetDesktopFolder(Desktop)) then
begin
WideName := Favorites[aIndex].FileName;
if Succeeded(Desktop.ParseDisplayName(0, nil,
PWideChar(WideName), ULONG(nil^), Pidl, ULONG(nil^))) then
begin
SendMessage(aDialog, BFFM_SETSELECTION, 0, Longint(Pidl));
CoTaskMemFree(Pidl);
end;
end;
end;


Gambit


Remy Lebeau (TeamB)

unread,
Nov 2, 2007, 1:12:47 PM11/2/07
to

"Rudy Velthuis [TeamB]" <newsg...@rvelthuis.de> wrote in message
news:xn0fd7f6y...@newsgroups.borland.com...

> But Delphi will _Release it, though, so an _AddRef
> would be a good idea. <g>

My point was that Delphi should have already been calling _AddRef()
automatically when the interface variable is assigned.


Gambit


Chris Morgan

unread,
Nov 3, 2007, 10:02:25 AM11/3/07
to

> My point was that Delphi should have already been calling _AddRef()
> automatically when the interface variable is assigned.

It does not, because this is a non-standard way of obtaining
an interface reference.
Using an 'as' cast does not work, because the return value
of SendMessage is not an IInterface (or derived type).
So we need to manually bump-up the ref count to avoid
an AV from the _Release which Delphi automatically
adds when the interface goes out of scope.

> StringToOleStr() returns a BSTR that needs to be freed manually with
> SysFreeString(), otherwise the memory is leaked.

Wasn't aware of that - thanks.

cheers,

Chris

Remy Lebeau (TeamB)

unread,
Nov 3, 2007, 4:20:27 PM11/3/07
to

"Chris Morgan" <chris....@lynxinfo.co.uk> wrote in message
news:472c7f6d$1...@newsgroups.borland.com...

> It does not, because this is a non-standard way of
> obtaining an interface reference.

It doesn't matter how the interface is obtained. An interface is an
interface, and assigning an interface to a variable is supposed to AddRef it
and then Release it when the variable goes out of scope. That is core
Delphi interface behavior.

> Using an 'as' cast does not work, because the return value
> of SendMessage is not an IInterface (or derived type).

Yes, it is. All COM interfaces derive from IUnknown, and IUnknown is an
alias for IInterface in Delphi. The signatures of IUnknown and IInterface
are identical. Anything that can be done on an IUnknown can be done on an
IInterface and vice versa.


Gambit


Rudy Velthuis [TeamB]

unread,
Nov 4, 2007, 8:45:33 AM11/4/07
to
Chris Morgan wrote:

> > Yes, it is. All COM interfaces derive from IUnknown, and IUnknown
> > is an alias for IInterface in Delphi. The signatures of IUnknown
> > and IInterface are identical. Anything that can be done on an
> > IUnknown can be done on an IInterface and vice versa.
>

> SendMessage returns an LRESULT.
> An LRESULT cannot be used with the Delphi 'as' cast.

But you can do:

MyBrowser := IInterface(SendMessage(...)) as IShellBrowser;

Or simply:

MyBrowser := IShellBrowser(SendMessage(...));

The assignment to MyBrowser should cause an _AddRef.
--
Rudy Velthuis [TeamB]

"While we are postponing, life speeds by."
-- Seneca (3BC - 65AD)

Chris Morgan

unread,
Nov 4, 2007, 9:40:10 AM11/4/07
to
> Yes, it is. All COM interfaces derive from IUnknown, and IUnknown is an
> alias for IInterface in Delphi. The signatures of IUnknown and IInterface
> are identical. Anything that can be done on an IUnknown can be done on an
> IInterface and vice versa.

SendMessage returns an LRESULT.


An LRESULT cannot be used with the Delphi 'as' cast.

I'm not an expert in the internals of Delphi, or how how
interface types are instantiated, but I *do* know that unless
you add the explicit _AddRef to the retrived IShellBrowser
variable, you will get a nasty access violation at some later point.
There are numerous posts on the interweb about this unique
un-documented way of retrieving the IShellBrowser interface from a
SendMessage call, and the workarounds which are necessary in languages
which automatically reference count.
With respect, C++ works differently from Delphi in this matter.

Cheers,

Chris

Chris Morgan

unread,
Nov 4, 2007, 9:49:09 AM11/4/07
to

> But you can do:
>
> MyBrowser := IInterface(SendMessage(...)) as IShellBrowser;

I'm sure I tried this, and it didn't work, but I may be wrong.

>
> Or simply:
>
> MyBrowser := IShellBrowser(SendMessage(...));

This definitely didn't work, at least in D6.

cheers,

Chris

Rudy Velthuis [TeamB]

unread,
Nov 4, 2007, 9:07:33 AM11/4/07
to
Chris Morgan wrote:

What do you mean with "didn't work"?


--
Rudy Velthuis [TeamB]

"Support your local Search and Rescue unit -- get lost."
-- Unknown

Chris Morgan

unread,
Nov 4, 2007, 10:14:33 AM11/4/07
to
>>> MyBrowser := IInterface(SendMessage(...)) as IShellBrowser;
>> I'm sure I tried this, and it didn't work, but I may be wrong.
>>
>>> Or simply:
>>>
>>> MyBrowser := IShellBrowser(SendMessage(...));
>> This definitely didn't work, at least in D6.
>
> What do you mean with "didn't work"?

Caused an access violation later on.

Cheers,

Chris

Rudy Velthuis [TeamB]

unread,
Nov 4, 2007, 9:20:03 AM11/4/07
to
Chris Morgan wrote:

Hmmm... then an _AddRef was missing, or there was one too many.
--
Rudy Velthuis [TeamB]

"O'Toole's Corollary of Finagle's Law: The perversity of the
Universe tends towards a maximum."

Chris Morgan

unread,
Nov 4, 2007, 10:30:51 AM11/4/07
to
>>>>> MyBrowser := IShellBrowser(SendMessage(...));
>>>> This definitely didn't work, at least in D6.
>>> What do you mean with "didn't work"?
>> Caused an access violation later on.
>
> Hmmm... then an _AddRef was missing, or there was one too many.

Which was precisely why I added an explicit _AddRef!
And the reason that the implicit _AddRef is not added by the compiler
(at least in earlier Delphi versions - not tested recently),
is because of the unorthodox way in which the IShellBrowser
interface has to be retrieved from the dialog.

This may have been fixed with a new API which was not available
in the D6/W2000-timeframe. I investigated this a while ago,
when creating a customised open/save dialog with an extra
button to allow quick switching to a list of favourite folders.
I haven't revisited the code recently, since it appeared to work ok.

cheers,

Chris

Remy Lebeau (TeamB)

unread,
Nov 5, 2007, 1:37:29 AM11/5/07
to

"Chris Morgan" <chris....@lynxinfo.co.uk> wrote in message
news:472dd9c5$1...@newsgroups.borland.com...

> SendMessage returns an LRESULT.

The data type may be an LRESULT, but its value is an IUnknown pointer.

> An LRESULT cannot be used with the Delphi 'as' cast.

Look at lhaymehr's code again. It is typecasting the LRESULT to an
interface pointer before then assigning it to an interface variable. That
is enough to invoke Delphi's automated reference counting of the interface
that is returned by SendMessage(). Again, it doesn't matter where the
interface originally comes from. If the compiler sees an interface pointer
being assigned to an interface variable (which it does in this situation),
it will emit the calls to both AddRef and Release.


Gambit


Chris Morgan

unread,
Nov 5, 2007, 4:49:34 AM11/5/07
to
>> SendMessage returns an LRESULT.
>
> The data type may be an LRESULT, but its value is an IUnknown pointer.
>
>> An LRESULT cannot be used with the Delphi 'as' cast.
>
> Look at lhaymehr's code again. It is typecasting the LRESULT to an
> interface pointer before then assigning it to an interface variable. That
> is enough to invoke Delphi's automated reference counting of the interface
> that is returned by SendMessage(). Again, it doesn't matter where the
> interface originally comes from. If the compiler sees an interface
> pointer
> being assigned to an interface variable (which it does in this situation),
> it will emit the calls to both AddRef and Release.

It may have been a bug in an earlier version of the Delphi compiler,
or an idiosyncracy in how MS allocates/assigns/returns the IShellBrowser
interface, but the retrieved interface is *not* automatically AddRef'd.
The extra _AddRef call is required to avoid the interface being released
too soon.

Cheers,

Chris


0 new messages