在Go中调用SHGetImageList

时间:2016-07-04 20:41:47

标签: windows winapi go

我试图在Go中调用SHGetImageList,以便从.EXE文件中提取图标。问题是我不知道如何创建/传递" IImageList2接口"在Go中,这是SHGetImageList所需要的。

我已经尝试了几个小时的杂项,但这都导致了同样的E_NOINTERFACE错误。基本上他们是在黑暗中拍摄的所有照片" ([]字节数组,以查看我是否可以接收任何"数据"在Go中的实际接口{}包含与MSDN定义的IImagelist2接口相同的功能,等等。如果它有任何相关性,我在C#中使用http://www.pinvoke.net/default.aspx/shell32.shgetimagelist的某些内容确实有一个这样的工作版本,但我根本没有真正的线索如何"翻译"去吧。任何帮助将不胜感激。

示例转到下面的代码,其中包含一些信息以及评论中指向MSDN的链接。

package main

import (
    "fmt"
    "syscall"
    "unsafe"
)

var (
    shell32 = syscall.MustLoadDLL("shell32.dll")

    // https://msdn.microsoft.com/en-us/library/windows/desktop/bb762179(v=vs.85).aspx
    procSHGetFileInfo = shell32.MustFindProc("SHGetFileInfoW")

    //https://msdn.microsoft.com/en-us/library/windows/desktop/bb762185(v=vs.85).aspx
    procSHGetImageList = shell32.MustFindProc("SHGetImageList")
)

func main() {
    someExeFile := `c:\windows\explorer.exe`

    iconIndex := GetIconIndex(someExeFile)

    // The problem:
    HRESULT, _, _ := procSHGetImageList.Call(
        uintptr(SHIL_JUMBO),
        uintptr(unsafe.Pointer(&IID_IImageList2)),

        // I don't know how pass/create an "IImageList interface" in Go,
        // or if it's even possible without relying on CGO.
        // IImageList interface:
        // https://msdn.microsoft.com/en-us/library/windows/desktop/bb761419(v=vs.85).aspx

        // Currently there's just a pointer to an empty []byte so that the code will compile.
        // HRESULT naturally contains the error code E_NOINTERFACE (2147500034),
        // which makes sense seeing as I'm not passing a valid interface.
        uintptr(unsafe.Pointer(&[]byte{})),
    )

    fmt.Println(iconIndex, HRESULT)
}


const SHIL_JUMBO = 0x4

const shGetFileInfoLen = 3
const shGetFileInfoFlags = 16400 //(SysIconIndex|LargeIcon|UseFileAttributes)
// use SHGetFileInfo to get the icon index (only value we care about)
func GetIconIndex(fileName string) int {
    buf := make([]uint16, shGetFileInfoLen)
    ret, _, _ := procSHGetFileInfo.Call(
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(fileName))),
        0,
        uintptr(unsafe.Pointer(&buf[0])),
        shGetFileInfoLen,
        shGetFileInfoFlags,
    )

    if ret != 0 && buf[2] > 0 {
        return int(buf[2])
    }

    return 0
}

// From: "192B9D83-50FC-457B-90A0-2B82A8B5DAE1"
var IID_IImageList2 = &GUID{0x192b9d83, 0x50fc, 0x457b, [8]byte{0x90, 0xa0, 0x2b, 0x82, 0xa8, 0xb5, 0xda, 0xe1}}

// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373931.aspx
type GUID struct {
    Data1 uint32
    Data2 uint16
    Data3 uint16
    Data4 [8]byte
}

更新

现在的问题是,当调用Syscall获取实际的图标指针时,它会以错误(0xC0000005)退出。

package main

import (
    "fmt"
    "syscall"
    "unsafe"
)

var (
    shell32 = syscall.MustLoadDLL("shell32.dll")

    // https://msdn.microsoft.com/en-us/library/windows/desktop/bb762179(v=vs.85).aspx
    procSHGetFileInfo = shell32.MustFindProc("SHGetFileInfoW")

    //https://msdn.microsoft.com/en-us/library/windows/desktop/bb762185(v=vs.85).aspx
    procSHGetImageList = shell32.MustFindProc("SHGetImageList")

    ole32 = syscall.MustLoadDLL("ole32.dll")
    procCoInitialize = ole32.MustFindProc("CoInitialize")
)

func main() {
    someExeFile := `c:\windows\explorer.exe`

    procCoInitialize.Call()

    iconIndex := GetIconIndex(someExeFile)

    var imglist *IImageList
    hr, _, _ := procSHGetImageList.Call(
        uintptr(SHIL_JUMBO),
        uintptr(unsafe.Pointer(&IID_IImageList)),
        uintptr(unsafe.Pointer(&imglist)),
    )

    // These look OK
    fmt.Println(iconIndex, hr, imglist.Vtbl.GetIcon)

    var hIcon uintptr
    // GetIcon: https://msdn.microsoft.com/en-us/library/windows/desktop/bb761463(v=vs.85).aspx
    hr, _, _ = syscall.Syscall(imglist.Vtbl.GetIcon,
        uintptr(unsafe.Pointer(imglist)),
        uintptr(iconIndex),
        getIconFlags,
        uintptr(unsafe.Pointer(&hIcon)),
    )

    // Errors: "Process finished with exit code -1073741819 (0xC0000005)"

    fmt.Println("hIcon:", hIcon) // Never reaches this
}

// ILD_TRANSPARENT | ILD_IMAGE
const getIconFlags = 0x00000001 | 0x00000020

const SHIL_JUMBO = 0x4

const shGetFileInfoLen = 3
const shGetFileInfoFlags = 16400 //(SysIconIndex|LargeIcon|UseFileAttributes)
// use SHGetFileInfo to get the icon index (only value we care about)
func GetIconIndex(fileName string) int {
    buf := make([]uint16, shGetFileInfoLen)
    ret, _, _ := procSHGetFileInfo.Call(
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(fileName))),
        0,
        uintptr(unsafe.Pointer(&buf[0])),
        shGetFileInfoLen,
        shGetFileInfoFlags,
    )

    if ret != 0 && buf[2] > 0 {
        return int(buf[2])
    }

    return 0
}


// From: "46EB5926-582E-4017-9FDF-E8998DAA0950"
var IID_IImageList = GUID{0x46eb5926, 0x582e, 0x4017, [8]byte{0x9f, 0xdf, 0xe8, 0x99, 0x8d, 0xaa, 0x09, 0x50}}

// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373931.aspx
type GUID struct {
    Data1 uint32
    Data2 uint16
    Data3 uint16
    Data4 [8]byte
}

type IImageList struct {
    Vtbl *IImageListVtbl
}

type IImageListVtbl struct {
    Add                uintptr
    ReplaceIcon        uintptr
    SetOverlayImage    uintptr
    Replace            uintptr
    AddMasked          uintptr
    Draw               uintptr
    Remove             uintptr
    GetIcon            uintptr
    GetImageInfo       uintptr
    Copy               uintptr
    Merge              uintptr
    Clone              uintptr
    GetImageRect       uintptr
    GetIconSize        uintptr
    SetIconSize        uintptr
    GetImageCount      uintptr
    SetImageCount      uintptr
    SetBkColor         uintptr
    GetBkColor         uintptr
    BeginDrag          uintptr
    EndDrag            uintptr
    DragEnter          uintptr
    DragLeave          uintptr
    DragMove           uintptr
    SetDragCursorImage uintptr
    DragShowNolock     uintptr
    GetDragImage       uintptr
    GetItemFlags       uintptr
    GetOverlayImage    uintptr
}

2 个答案:

答案 0 :(得分:2)

哦,我现在看到了实际问题。

        uintptr(unsafe.Pointer(&IID_IImageList2)),
...
var IID_IImageList2 = &GUID{0x192b9d83, 0x50fc, 0x457b, [8]byte{0x90, 0xa0, 0x2b, 0x82, 0xa8, 0xb5, 0xda, 0xe1}}

您的IID_IImageList2已经是指针。在您的调用中,您将获取指向该指针的指针,这意味着该地址将用作GUID。你应该做

        uintptr(unsafe.Pointer(&IID_IImageList2)),
...
var IID_IImageList2 = GUID{0x192b9d83, 0x50fc, 0x457b, [8]byte{0x90, 0xa0, 0x2b, 0x82, 0xa8, 0xb5, 0xda, 0xe1}}

        uintptr(unsafe.Pointer(IID_IImageList2)),
...
var IID_IImageList2 = &GUID{0x192b9d83, 0x50fc, 0x457b, [8]byte{0x90, 0xa0, 0x2b, 0x82, 0xa8, 0xb5, 0xda, 0xe1}}

这样,GUID本身就被用作GUID,而不是它在内存中的位置。

答案 1 :(得分:1)

COM与Go的接口将是痛苦

像IImageList2这样的COM接口是一个函数指针列表,你不能直接使用Go的那些C函数指针;你必须使用syscall.Syscall()及其兄弟(取决于各个函数所用的参数数量)来调用它们。

COM接口实例的核心是一个结构,其第一个也是唯一一个字段是指向此方法列表的指针。

这就是它的内容
type IImageList2Vtbl struct {
    QueryInterface uintptr
    AddRef         uintptr
    Release        uintptr
|

type IImageList2 struct {
    Vtbl *IImageList2Vtbl
}

当您将指向IImageList2类型的变量的指针传递给Windows API函数时,您调用以创建该对象,无论是SHGetImageList()CoCreateInstance()D3D11CreateDeviceAndSwapChain(),或者你有什么,系统将用只读系统内存中包含功能列表的指针填充Vtbl条目。

因此,您要做的第一件事是确保方法的顺序正确,因此Go结构IImageList2Vtbl和Windows将为您提供的列表匹配。不幸的是,MSDN并不擅长这个;你将不得不通过头文件。这是我得到的:

type IImageList2Vtbl struct {
    QueryInterface       uintptr
    AddRef               uintptr
    Release              uintptr
    Add                  uintptr
    ReplaceIcon          uintptr
    SetOverlayImage      uintptr
    Replace              uintptr
    AddMasked            uintptr
    Draw                 uintptr
    Remove               uintptr
    GetIcon              uintptr
    GetImageInfo         uintptr
    Copy                 uintptr
    Merge                uintptr
    Clone                uintptr
    GetImageRect         uintptr
    GetIconSize          uintptr
    SetIconSize          uintptr
    GetImageCount        uintptr
    SetImageCount        uintptr
    SetBkColor           uintptr
    GetBkColor           uintptr
    BeginDrag            uintptr
    EndDrag              uintptr
    DragEnter            uintptr
    DragLeave            uintptr
    DragMove             uintptr
    SetDragCursorImage   uintptr
    DragShowNolock       uintptr
    GetDragImage         uintptr
    GetItemFlags         uintptr
    GetOverlayImage      uintptr
    Resize               uintptr
    GetOriginalSize      uintptr
    SetOriginalSize      uintptr
    SetCallback          uintptr
    GetCallback          uintptr
    ForceImagePresent    uintptr
    DiscardImages        uintptr
    PreloadImages        uintptr
    GetStatistics        uintptr
    Initialize           uintptr
    Replace2             uintptr
    ReplaceFromImageList uintptr
}
  

我确定我弄错了;请报告任何错误。 :)

这来自commoncontrols.h。使用C风格,而不是C ++风格的接口定义(如果有多个),因为它将具有所有方法,包括IImageList2派生自的接口(IUnknown,所有接口派生自,和IImageList) )。

等等,我撒谎:Windows不希望你给它IImageList2的内存。那是因为COM接口就像Go接口:它们是任何实现都可以实现的一组方法。所以实际上你必须让Windows给你一个IImageList2的指针,而不是IImageList2Vtbl

那我们该怎么办?我们将每个实例存储为接口的指针,然后将指向 的指针传递给创建函数。

因此,我们有

var imglist *IImageList2
hr, _, _ := procSHGetImageList.Call(
    uintptr(SHIL_JUMBO),
    uintptr(unsafe.Pointer(&IID_IImageList2)),
    uintptr(unsafe.Pointer(&imglist)),
)

请注意,我们将指针传递给IImageList2。我将其称为实例,将Vtbl成员称为 vtable

现在,当您想调用方法时,您必须添加额外的第一个参数:实例本身。您可以在MSDN上查看返回类型和其他参数:

// HRESULT EndDrag(void);
hr, _, _ = syscall.Syscall(instance.Vtbl.EndDrag,
    uintptr(unsafe.Pointer(imglist)))
// HRESULT SetOverlayImage(int, int);
hr, _, _ = syscall.Syscall(instance.Vtbl.SetOverlayImage,
    uintptr(unsafe.Pointer(imglist)),
    4,
    2)

请注意,我们这次通过了imglist,而不是&imglist

现在因为C和C ++不像Go那样支持多个返回,所以函数通常会返回一个HRESULT(相当于Go的error的COM)并且你将指针传递给其余的返回值。参数列表的结尾。对于其他COM接口,我们遵循上述规则。对于其他类型,我们refer to the Windows Data Types page查看每个命名类型代表的内容,并记住在Windows上C int总是Go int32,即使在64位系统上也是如此。

// HRESULT AddMasked(HBITMAP, COLORREF, (OUT) int *);
// we'll use uintptr for HBITMAP and int32 for COLORREF
var index int32
hr, _, _ = syscall.Syscall(instance.Vtbl.AddMasked,
    uintptr(unsafe.Pointer(instance)),
    hbitmap,
    mask,
    uintptr(unsafe.Pointer(&index)))

最后,COM要求我们在完成对象时调用Release()方法。这不需要额外的参数,其返回值也无关紧要。如果您愿意,可以将其填入defer

syscall.Syscall(instance.Vtbl.Release,
    uintptr(unsafe.Pointer(instance)))
  

注意:我不知道SHGetImageList()是否需要初始化COM。如果没有,请忽略此部分,直到您需要其他COM接口为止。

哦,但这还不够,因为你还需要初始化COM。 COM可以在许多线程模型中运行。只有两个是重要的:单线程单元用于与GUI相关的任何内容,多线程单元用于特殊用途。

CoInitialize()CoInitializeEx()CoUninitialize()函数都处理COM初始化和未初始化。你已经知道如何打电话给他们;它们只是普通的DLL函数。

但要注意!如果你需要一个单线程的公寓,你必须使用runtime.LockOSThread()来确保Go不会将当前goroutine移动到你下面的另一个OS线程。如果你不这样做,事情会以奇怪的方式破裂。

这是一大堆工作,您必须为将要使用的每个接口执行此操作。你也许可以使用一个已经为你做过繁重的工作包。有几个包,例如go-ole,它们可以完成基本工作。我没有看到提供IImageList2的人,但你可能能够背负现有的。{1}}。 There seems to be only one reference for IImageList.

祝你好运!