Reputation: 33
I am trying to open a Windows Explorer window with specified items in a particular folder selected.
I tried to implement the program using SHOpenFolderAndSelectItems Win32 API with C#. Following the answer here: https://stackoverflow.com/a/12262552
using System.Runtime.InteropServices;
SelectInFileExplorer("C:\\Users");
void SelectInFileExplorer(string fullPath)
{
if (string.IsNullOrEmpty(fullPath))
throw new ArgumentNullException("fullPath");
fullPath = Path.GetFullPath(fullPath);
IntPtr pidlList = NativeMethods.ILCreateFromPathW(fullPath);
if (pidlList != IntPtr.Zero)
try
{
// Open parent folder and select item
Marshal.ThrowExceptionForHR(NativeMethods.SHOpenFolderAndSelectItems(pidlList, 0, IntPtr.Zero, 0));
}
finally
{
NativeMethods.ILFree(pidlList);
}
}
static class NativeMethods
{
[DllImport("shell32.dll", ExactSpelling = true)]
public static extern void ILFree(IntPtr pidlList);
[DllImport("shell32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern IntPtr ILCreateFromPathW(string pszPath);
[DllImport("shell32.dll", ExactSpelling = true)]
public static extern int SHOpenFolderAndSelectItems(IntPtr pidlList, uint cild, IntPtr children, uint dwFlags);
}
It worked, but now I would like to implement the same thing using Go.
Here is my code:
package main
import (
"fmt"
"path/filepath"
"syscall"
"unsafe"
)
func main() {
SelectInFileExplorer("C:\\Users")
fmt.Println("Hello, World!")
}
func SelectInFileExplorer(fullPath string) {
if fullPath == "" {
panic("fullPath cannot be empty")
}
fullPath, _ = filepath.Abs(fullPath)
pidlList, err := ILCreateFromPathW(fullPath)
if err != nil {
panic(err)
}
defer ILFree(pidlList)
// Open parent folder and select item
err = SHOpenFolderAndSelectItems(pidlList, 0, 0)
if err != nil {
panic(err)
}
}
func ILFree(pidlList uintptr) {
shell32 := syscall.NewLazyDLL("shell32.dll")
proc := shell32.NewProc("ILFree")
proc.Call(pidlList)
}
func ILCreateFromPathW(pszPath string) (uintptr, error) {
shell32 := syscall.NewLazyDLL("shell32.dll")
proc := shell32.NewProc("ILCreateFromPathW")
pszPathPtr, err := syscall.UTF16PtrFromString(pszPath)
if err != nil {
return 0, err
}
ret, _, err := proc.Call(uintptr(unsafe.Pointer(pszPathPtr)))
if ret == 0 {
return 0, err
}
return ret, nil
}
func SHOpenFolderAndSelectItems(pidlList uintptr, cild uint32, children uintptr) error {
shell32 := syscall.NewLazyDLL("shell32.dll")
proc := shell32.NewProc("SHOpenFolderAndSelectItems")
ret, _, err := proc.Call(pidlList, uintptr(cild), children, 0)
if ret != 0 && err.Error() != "The operation completed successfully." {
return err
}
return nil
}
It doesn't work as nothing happened after I executed the code. The Explorer didn't appear.
I read some documents regarding the usage of the API: https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shopenfolderandselectitems
Still not sure what's going on. Maybe it is not because of the programming language itself. I think calling DLL functions is a little bit complex in GO, and I probably did something wrong with the function calls.
Upvotes: 2
Views: 77
Reputation: 33
Here is the complete solution:
package main
import (
"fmt"
"os"
"path/filepath"
"syscall"
"unsafe"
"golang.org/x/sys/windows/registry"
)
var (
shell32 = syscall.NewLazyDLL("shell32.dll")
user32 = syscall.NewLazyDLL("user32.dll")
ole32 = syscall.NewLazyDLL("ole32.dll")
procCoInitializeEx = ole32.NewProc("CoInitializeEx")
procCoUninitialize = ole32.NewProc("CoUninitialize")
procILCreateFromPathW = shell32.NewProc("ILCreateFromPathW")
procSHOpenFolderAndSelectItems = shell32.NewProc("SHOpenFolderAndSelectItems")
procILFree = shell32.NewProc("ILFree")
)
func UTF16String(s string) uintptr {
ptr, err := syscall.UTF16PtrFromString(s)
if err != nil {
return 0
} else {
return uintptr(unsafe.Pointer(ptr))
}
}
func main() {
procCoInitializeEx.Call(0, 0) // nullptr, COINIT_MULTITHREADED
defer procCoUninitialize.Call()
err := SelectInFileExplorer("C:\\Users")
if err != nil {
fmt.Println("Error:", err)
}
}
func SelectInFileExplorer(fullPath string) error {
// Alternative way to open explorer, but the explorer window will not be the front window
// cmd := exec.Command("explorer", "/select,", path)
// cmd.Run()
if fullPath == "" {
return fmt.Errorf("fullPath cannot be null or empty")
}
absPath, err := filepath.Abs(fullPath)
if err != nil {
return err
}
pidlList, err := ILCreateFromPathW(absPath)
if err != nil {
return err
}
defer ILFree(pidlList)
if pidlList != 0 {
err = SHOpenFolderAndSelectItems(pidlList, 0, 0, 0)
if err != nil {
return err
}
}
return nil
}
func ILCreateFromPathW(pszPath string) (uintptr, error) {
ret, _, err := procILCreateFromPathW.Call(UTF16String(pszPath))
if ret == 0 {
return 0, err
}
return ret, nil
}
func ILFree(pidlList uintptr) {
procILFree.Call(pidlList)
}
func SHOpenFolderAndSelectItems(pidlList uintptr, cild uint, children uintptr, dwFlags uint) error {
ret, _, err := procSHOpenFolderAndSelectItems.Call(pidlList, uintptr(cild), children, uintptr(dwFlags))
if ret != 0 {
return err
}
return nil
}
Upvotes: 0
Reputation: 139187
As explained in official SHOpenFolderAndSelectItems
documentation remarks:
CoInitialize or CoInitializeEx must be called before using SHOpenFolderAndSelectItems. Not doing so causes SHOpenFolderAndSelectItems to fail.
So you must add something like this in your main
for example:
func main() {
ole32 := syscall.NewLazyDLL("ole32.dll")
proc := ole32.NewProc("CoInitialize")
proc.Call(0)
SelectInFileExplorer("C:\\Users")
}
It works in .NET because .NET does this automatically
Upvotes: 1