Code: Select all
class SHInstanceExplorer {
static RefCount := 0
static AddRef := this => (++this.RefCount)
static Release := this => (--this.RefCount, this.Released())
static Released := this => 0
static Call() => this
static __new() {
static meth := A_PtrSize = 8 ? [
[0xB8481675003A8348, 0x46000000000000C0, 0x8949067508423948, 0x0000C749C3C03308, 0x80004002B8000000, 0xC3], ; QueryInterface
[0x4C10518B28EC8348, 0x01B941C18B4CD18B, 0x4108498B48000000, 0x00000001B81852FF, 0xC328C48348], ; AddRef
[0x4810518B28EC8348, 0xC93345C18B4CC18B, 0x331850FF08498B48, 0xC328C48348C0] ; Release
] : [
[0x410B018B08244C8B, 0x00C0087981217504, 0x000C798118750000, 0x24448B0F75460000, 0x33088904244C8B0C, 0x0C24448B000CC2C0, 0x02B80000000000C7, 0x000CC2800040], ; QueryInterface
[0xFF50016A0424448B, 0x0C408B0470FF0870, 0xC200000001B8D0FF, 0x0004], ; AddRef
[0xFF50006A0424448B, 0x0C408B0470FF0870, 0x0004C2C033D0FF] ; Release
]
static code := Buffer((meth[1].Length + meth[2].Length + meth[3].Length) * 8)
static vtbl := Buffer(meth.Length * A_PtrSize)
offset := 0
for m in meth {
NumPut("ptr", code.ptr + offset, vtbl, (A_Index-1)*A_PtrSize)
for i64 in m {
NumPut("int64", i64, code, offset)
offset += 8
}
}
DllCall("VirtualProtect", "ptr", code, "ptr", code.size, "uint", 0x40, "uint*", 0)
static objb := Buffer(A_PtrSize*4)
NumPut(
"ptr", vtbl.ptr,
"ptr", A_ScriptHwnd,
"ptr", msg := DllCall("RegisterWindowMessage", "str", "SHIE"),
"ptr", DllCall("GetProcAddress", "ptr", DllCall("GetModuleHandle", "str", "user32", "ptr"), "astr", "PostMessageW", "ptr"),
objb
)
this.RefCount := 0
OnMessage(msg, this.AddRefRelease.Bind(this))
DllCall("shell32\SHSetInstanceExplorer", "ptr", objb)
}
static AddRefRelease(pobj, add, nMsg, hwnd) {
if hwnd != A_ScriptHwnd
return
(add) ? this.AddRef() : this.Release()
return 1
}
}
Shell extensions that create worker threads need to call the SHGetInstanceExplorer function so that Explorer will not exit while the worker thread is still running. When your worker thread finishes, you release the IUnknown that you obtained to tell the host program, “Okay, I’m done now, thanks for waiting.”
You can read this contract from the other side. Instead of thinking of yourself as the shell extension running inside a host program, think of yourself as the host program that has a shell extension running inside of it.
...
Source: Reading a contract from the other side: SHSetInstanceExplorer and SHGetInstanceExplorer - The Old New Thing
Each time a component calls SHGetInstanceExplorer, the system calls the AddRef method before returning the interface pointer to the calling component. The component then calls the IUnknown::Release method when processing is complete. The process that calls SHSetInstanceExplorer must not terminate while the reference count of the provided interface pointer is nonzero.
Source: SHSetInstanceExplorer function (shlobj_core.h) - Win32 apps | Microsoft Learn
By default, AddRef increments SHInstanceExplorer.RefCount while Release decrements it and calls SHInstanceExplorer.Released() if RefCount reaches 0.
The script is free to redefine the following methods via assignment or DefineProp:
- SHInstanceExplorer.AddRef()
- SHInstanceExplorer.Release()
- SHInstanceExplorer.Released()
The function uses machine code because the "instance explorer" object is required to be "free threaded"; in other words, the actual interface methods are called from shell worker threads, so cannot be safely implemented directly with CallbackCreate.
C++ source code and disassembly
Example #1: When you use Run to show a file's Properties dialog, it remains open only while the script is running. It's easy enough to wait for the window(s) to close, but SHInstanceExplorer provides an alternative.
Code: Select all
Run 'properties "' A_ScriptFullPath '"'
Run 'properties "' A_ScriptDir '"'
while SHInstanceExplorer.RefCount
Sleep 100
Example #2: Adding files to a zip via the shell API usually requires a bit of guesswork to determine when the operation has completed, since the API returns while the operation is still in progress. SHInstanceExplorer.Released can be redefined so that the script is notified when the operation is completed. In this example, I just use it to keep the script running.
Code: Select all
; Make the script persistent...
Persistent
; ...until the shell file operation is complete.
SHInstanceExplorer.Released := this => Persistent(this.RefCount > 0)
zipPath := A_WorkingDir "\~test.zip"
sourceDir := A_MyDocuments
try {
CreateZipFile(zipPath)
shell := ComObject("Shell.Application")
itemToZip := shell.Namespace(sourceDir).Items ; The folder's contents
; itemToZip := shell.Namespace(0).ParseName(sourceDir) ; The folder itself
shell.Namespace(zipPath).CopyHere(itemToZip)
}
catch {
Persistent false
throw
}
CreateZipFile(path) {
f := FileOpen(path, "w")
f.Write("PK")
f.WriteInt64(0x0605)
f.WriteInt64(0)
f.WriteInt(0)
f.Close()
}