最近做的 Java 项目有在 Windows 下创建快捷方式的需求,需要调用 COM 接口去实现,刚好项目里也有使用 JNA,因此记录一下通过 JNA 调用 IShellLinkW
这个 COM 接口的方法。
IShellLinkW
这个接口是直接从 IUnknown
继承的,所以 JNA 不能帮我们自动去寻找接口的方法,因此需要手动去做。
首先创建一个继承 Unknown
的新类。
1
2
3
4
5
6
7
8
| import com.sun.jna.platform.win32.COM.Unknown;
import com.sun.jna.platform.win32.Guid;
public class IShellLink extends Unknown {
/* ShellLink 的 CLSID 以及 IShellLinkW 的 IID */
public static final Guid.GUID CLSID_ShellLink = new Guid.GUID("{00021401-0000-0000-c000-000000000046}");
public static final Guid.GUID IID_IShellLinkW = new Guid.GUID("{000214F9-0000-0000-c000-000000000046}");
}
|
编写 create
方法,用于创建 IShellLink 的实例。
1
2
3
4
5
6
7
8
9
10
| private IShellLink(Pointer ptr) {
super(ptr); // 通过刚才获取的 IShellLink 实例的指针初始化 Unknown 类
}
public static IShellLink create() {
PointerByReference p = new PointerByReference();
WinNT.HRESULT hr = Ole32.INSTANCE.CoCreateInstance(CLSID_ShellLink, Pointer.NULL, WTypes.CLSCTX_INPROC_SERVER, IID_IShellLinkW, p); // 创建 IShellLink 实例
COMUtils.checkRC(hr); // 检查是否成功,若失败则会抛出 COMException 并给出原因
return new IShellLink(p.getValue());
}
|
查找 vtable 索引#
这里还需要准备下 Windows SDK,我们需要其中的 ShObjIdl_core.h
头文件,它将用于查找我们需要用到的 vtable 索引。
可以通过 Visual Studio Installer 获取这些头文件,也可以通过搜索引擎去寻找这些头文件。
在头文件中查找 IShellLinkW
,并在 C style interface
附近查找,它以正确的顺序包含接口所有继承的方法。
以下为 ShObjIdl_core.h
的片段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| #else /* C style interface */
typedef struct IShellLinkWVtbl
{
BEGIN_INTERFACE
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
__RPC__in IShellLinkW * This,
/* [in] */ __RPC__in REFIID riid,
/* [annotation][iid_is][out] */
_COM_Outptr_ void **ppvObject);
ULONG ( STDMETHODCALLTYPE *AddRef )(
__RPC__in IShellLinkW * This);
ULONG ( STDMETHODCALLTYPE *Release )(
__RPC__in IShellLinkW * This);
HRESULT ( STDMETHODCALLTYPE *GetPath )(
__RPC__in IShellLinkW * This,
/* [size_is][string][out] */ __RPC__out_ecount_full_string(cch) LPWSTR pszFile,
/* [in] */ int cch,
/* [unique][out][in] */ __RPC__inout_opt WIN32_FIND_DATAW *pfd,
/* [in] */ DWORD fFlags);
HRESULT ( STDMETHODCALLTYPE *GetIDList )(
__RPC__in IShellLinkW * This,
/* [out] */ __RPC__deref_out_opt PIDLIST_ABSOLUTE *ppidl);
HRESULT ( STDMETHODCALLTYPE *SetIDList )(
__RPC__in IShellLinkW * This,
/* [unique][in] */ __RPC__in_opt PCIDLIST_ABSOLUTE pidl);
HRESULT ( STDMETHODCALLTYPE *GetDescription )(
__RPC__in IShellLinkW * This,
/* [size_is][string][out] */ __RPC__out_ecount_full_string(cch) LPWSTR pszName,
int cch);
HRESULT ( STDMETHODCALLTYPE *SetDescription )(
__RPC__in IShellLinkW * This,
/* [string][in] */ __RPC__in_string LPCWSTR pszName);
HRESULT ( STDMETHODCALLTYPE *GetWorkingDirectory )(
__RPC__in IShellLinkW * This,
/* [size_is][string][out] */ __RPC__out_ecount_full_string(cch) LPWSTR pszDir,
int cch);
HRESULT ( STDMETHODCALLTYPE *SetWorkingDirectory )(
__RPC__in IShellLinkW * This,
/* [string][in] */ __RPC__in_string LPCWSTR pszDir);
|
以 SetWorkingDirectory
方法为例,从 0 开始数,它的 vtable 索引为 9。
关联起来#
知道了我们要调用的方法的 vtable 索引后,就可以去写方法去调用了。
LPCWSTR
实际上就是 const wchar_t*
,对应的 JNA 类型为 WString
。
1
2
3
4
| public void SetWorkingDirectory(WString path) {
int res = this._invokeNativeInt(9, new Object[]{this.getPointer(), path}); // 参数数组必须传入 IShellLinkW 的指针作为第一个参数
COMUtils.checkRC(new WinNT.HRESULT(res)); // 检查是否成功
}
|
获取其他接口#
也可以通过 QueryInterface
去获取其他接口,以 IPersistFile
为例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| /* IPersistFile 的 IID */
public static final Guid.GUID IID_IPersistFile = new Guid.GUID("{0000010B-0000-0000-c000-000000000046}");
public IPersistFile getPF() {
PointerByReference p = new PointerByReference();
WinNT.HRESULT hr = this.QueryInterface(new Guid.REFIID(new Guid.IID(IID_IPersistFile)), p);
COMUtils.checkRC(hr);
return new IPersistFile(p.getValue());
}
public static class IPersistFile extends Unknown {
private IPersistFile(Pointer ptr) {
super(ptr);
}
public void Save(String path) {
int res = this._invokeNativeInt(6, new Object[]{this.getPointer(), new WString(path), true});
COMUtils.checkRC(new WinNT.HRESULT(res));
}
// 其他方法...
}
|
全部准备好后就可以去调用了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| public static void main(String[] args) {
Ole32.INSTANCE.CoInitializeEx(Pointer.NULL, Ole32.COINIT_MULTITHREADED);
try {
IShellLink lnk = IShellLink.create(); // 创建 ShellLink
IPersistFile pf = lnk.getPF(); // 获取 IPersistFile
String dir = System.getProperty("user.home").replaceAll("\"", "\"\""); // 分隔符的转换
lnk.SetPath(new WString("C:\\Windows\\System32\\cmd.exe"));
lnk.SetWorkingDirectory(new WString(dir));
pf.Save(new WString(dir + "\\Desktop\\Opencmd.lnk")); // 保存快捷方式
pf.Release();
lnk.Release(); // 注意资源释放
} finally {
Ole32.INSTANCE.CoUninitialize();
}
}
|
注意有些时候(如使用 JavaFX 的情况下) CoInitializeEx
已经被调用过了,所以不需要再调用一次,也不要调用 CoUninitialize
,可能会产生未知的问题。
Accessing COM Interface with JNA