SHOpenFolderAndSelectItems Win32 API не работает на Go, но работает на C#

Я пытаюсь открыть окно проводника Windows с выбранными указанными элементами в определенной папке.

Я попытался реализовать программу, используя SHOpenFolderAndSelectItems Win32 API с C#. Следуя ответу здесь: 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);
}

Это сработало, но теперь я хотел бы реализовать то же самое с помощью Go.

Вот мой код:

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
}

Это не работает, поскольку после выполнения кода ничего не произошло. Эксплорер не появился.

Я прочитал некоторые документы, касающиеся использования API: https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shopenfolderandselectitems

Все еще не уверен, что происходит. Возможно, дело не в самом языке программирования. Я думаю, что вызов функций DLL в GO немного сложен, и я, вероятно, сделал что-то не так с вызовами функций.

Почему в коде Go вы приводите uint32 к uintptr во втором параметре SHOpenFolderAndSelectItems()? Просто передайте uint32 как есть. Он ожидает 32-битное целое число, а не целое число размером с указатель. Кроме того, почему вы проверяете сообщение err.Error()? Если ret не равно 0, то функция не выполнена, и err.Error() никогда не должно быть "The operation completed successfully.", поскольку это текст сообщения об ошибке 0.

Remy Lebeau 26.07.2024 22:50

Потому что подпись func (p *LazyProc) Call(a ...uintptr) и мне нужно преобразовать параметры в uintptr. Кроме того, err.Error() - это "The operation completed successfully.", где ret не равно нулю, я это проверял.

Jerry Lum 27.07.2024 04:57
uintptr — неверный тип для второго параметра. Это должно быть 32-битное целое число, независимо от того, используете ли вы 32-битную или 64-битную версию. Кроме того, если ret не равно 0, функция не удалась, и точка. Эта функция возвращает код ошибки напрямую, она не использует GetLastError() для сообщения об ошибках (как и ILCreateFromPathW()), поэтому вы не можете использовать err.Error() в этой ситуации. Если вам нужно удобочитаемое сообщение с кодом ошибки, вам придется передать код ошибки в FormatMessageW() или его эквивалент.
Remy Lebeau 27.07.2024 05:49
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
55
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Как объяснено в официальной SHOpenFolderAndSelectItems документации примечаниях:

CoInitialize или CoInitializeEx должны быть вызваны перед использованием. SHOpenFolderAndSelectItems. Невыполнение этого требования приводит к SHOpenFolderAndSelectItems потерпит неудачу.

Итак, вы должны добавить что-то вроде этого в свой main, например:

func main() {
    ole32 := syscall.NewLazyDLL("ole32.dll")
    proc := ole32.NewProc("CoInitialize")
    proc.Call(0)

    SelectInFileExplorer("C:\\Users")
}

Это работает в .NET, потому что .NET делает это автоматически.

Вот полное решение:

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
}

Обратите внимание, что вам не следует вызывать CoInitialize(Ex) и CoUninitialize в «библиотечном коде» или в методе. Их должен вызывать разработчик, создающий работающий поток (и неявно запускающий исходный код в первом потоке процесса). Вот почему я поместил CoInitialize в main, а не рядом с вызовами Shell API. Также вы вызываете CoInitializeEx, который 1) здесь не нужен и 2) неправильно используется с одним параметром там, где требуется два.

Simon Mourier 27.07.2024 16:10

да вы правы. Когда я изучал ваш ответ, я заметил, что для этого требуются два параметра. Я только что обновил свой ответ и исправил ошибки. Спасибо за помощь.

Jerry Lum 27.07.2024 17:36

Другие вопросы по теме