还没有写好…
还没有写好…
起因 最近做的 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
起因 最近做的 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
通过修改 CSS,给 Nautilus 加个背景。 准备 首先准备一张你喜欢的图片,我这里使用的是澄闪的立绘。 前期处理 用 GIMP 打开图片,点击 图像 -> 缩放图像,将图片缩放到一个合适的大小。我这里缩放到 512 x 512。 然后调整透明度,根据个人喜好调整。这里调到75%。 最后导出为 PNG 格式,并将图片放在 ~/.config/gtk-4.0/assets 目录下。(若没有请自行新建) 注意勾选 保存透明像素的颜色值。 添加 CSS 在 ~/.config/gtk-4.0 下创建 gtk.css 文件,并写入以下内容: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 .nautilus-window tabview { /* Nautilus 标签页的 CSS 选择器 */ background-image: url("assets/gdglow2.png"); /* 背景图的路径 */ background-position: right bottom; /* 背景图的位置,这里是右下 */ /* 其他可用的值: center 居中 left top 左上 left bottom 左下 right top 右上 */ background-repeat: no-repeat; /* 不要平铺 */ } .nautilus-window tabview statuspage /* Nautilus 提示页(“文件夹为空”等)的 CSS 选择器 */ { background-color:transparent; /* 移除白色背景 */ } 然后在终端执行以下命令,完全退出 Nautilus: 1 nautilis --quit 最后重新打开 Nautilus,看看效果吧!
通过修改 CSS,给 Nautilus 加个背景。 准备 首先准备一张你喜欢的图片,我这里使用的是澄闪的立绘。 前期处理 用 GIMP 打开图片,点击 图像 -> 缩放图像,将图片缩放到一个合适的大小。我这里缩放到 512 x 512。 然后调整透明度,根据个人喜好调整。这里调到75%。 最后导出为 PNG 格式,并将图片放在 ~/.config/gtk-4.0/assets 目录下。(若没有请自行新建) 注意勾选 保存透明像素的颜色值。 添加 CSS 在 ~/.config/gtk-4.0 下创建 gtk.css 文件,并写入以下内容: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 .nautilus-window tabview { /* Nautilus 标签页的 CSS 选择器 */ background-image: url("assets/gdglow2.png"); /* 背景图的路径 */ background-position: right bottom; /* 背景图的位置,这里是右下 */ /* 其他可用的值: center 居中 left top 左上 left bottom 左下 right top 右上 */ background-repeat: no-repeat; /* 不要平铺 */ } .nautilus-window tabview statuspage /* Nautilus 提示页(“文件夹为空”等)的 CSS 选择器 */ { background-color:transparent; /* 移除白色背景 */ } 然后在终端执行以下命令,完全退出 Nautilus: 1 nautilis --quit 最后重新打开 Nautilus,看看效果吧!