我试图在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
}
答案 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
.
祝你好运!