Search code examples
c#gowinapiwindows-shell

SHOpenFolderAndSelectItems Win32 API doesn't work on Go, but works on C#


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.


Solution

  • 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