Я пытаюсь открыть окно проводника 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 немного сложен, и я, вероятно, сделал что-то не так с вызовами функций.
Потому что подпись func (p *LazyProc) Call(a ...uintptr)
и мне нужно преобразовать параметры в uintptr
. Кроме того, err.Error()
- это "The operation completed successfully."
, где ret
не равно нулю, я это проверял.
uintptr
— неверный тип для второго параметра. Это должно быть 32-битное целое число, независимо от того, используете ли вы 32-битную или 64-битную версию. Кроме того, если ret
не равно 0, функция не удалась, и точка. Эта функция возвращает код ошибки напрямую, она не использует GetLastError()
для сообщения об ошибках (как и ILCreateFromPathW()
), поэтому вы не можете использовать err.Error()
в этой ситуации. Если вам нужно удобочитаемое сообщение с кодом ошибки, вам придется передать код ошибки в FormatMessageW() или его эквивалент.
Как объяснено в официальной 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) неправильно используется с одним параметром там, где требуется два.
да вы правы. Когда я изучал ваш ответ, я заметил, что для этого требуются два параметра. Я только что обновил свой ответ и исправил ошибки. Спасибо за помощь.
Почему в коде Go вы приводите
uint32
кuintptr
во втором параметреSHOpenFolderAndSelectItems()
? Просто передайтеuint32
как есть. Он ожидает 32-битное целое число, а не целое число размером с указатель. Кроме того, почему вы проверяете сообщениеerr.Error()
? Еслиret
не равно 0, то функция не выполнена, иerr.Error()
никогда не должно быть"The operation completed successfully."
, поскольку это текст сообщения об ошибке 0.