Windows 在资源管理器中有一个共享文件命令:
此命令会弹出一个对话框,让您可以将文件共享给附近的人或联系人:
如何添加 Share 命令支持,从我的应用程序调用相同的 Windows 操作系统功能?用于调用
Share对话框的 API 或
rundll
命令是什么?
我尝试查看生成的进程 Explorer,我可以使用 Process Explorer 或 Process Monitor 来监视命令行,但该对话框不是进程外的。
注意:显然,我的应用程序不是 Shell 浏览器。我也不是问如何重新创建已经存在的功能。我也不是问如何创建 shell
IContextMenu
。
我问如何在我自己的应用程序中使用 Windows 10 和 11 的此功能,以便我可以使用 Windows 的
Share功能共享
.png
、.jpg
、.pdf
或任何其他类型的文件。
我尝试了通常的过程来调用 shell 命令:
IShellItem
IContextMenu
CreatePopupMenu
IContextMenu
填写我们的 hMenu
这是最后一个操作,对 Invoke 的调用失败并出现错误
0x80070057
- Win32 错误代码 87 - 无效参数
procedure InvokeShare(hwndOwner: HWND; const Filename: string);
var
si: IShellItem;
cm: IContextMenu;
menu: HMENU;
command: Cardinal;
info: TCMInvokeCommandInfo;
ICIEx: TCMInvokeCommandInfoEx;
hr: HRESULT;
function GetMenuCommand(const CommandName: string): Integer;
var
i: Integer;
info: TMenuItemInfo;
buffer: array[0..255] of Char;
begin
Result := -1; //default "not found"
FillChar(info, sizeof(info), 0);
info.cbSize := sizeof(info);
info.fMask := MIIM_STRING;
info.dwTypeData := buffer;
info.cch := sizeof(buffer);
for i := 0 to GetMenuItemCount(Menu)-1 do
begin
info.cch := sizeof(Buffer);
if GetMenuItemInfo(Menu, i, True, {var}info) then
begin
if SameText(info.dwTypeData, CommandName) then
begin
Result := i;
Exit;
end;
end;
end;
end;
begin
// Get the IShellItem for the specified file
if not Succeeded(SHCreateItemFromParsingName(PWideChar(Filename), nil, IShellItem, {out}si)) then
RaiseLastOSError;
// Get the IContextMenu for the IShellItem
if not Succeeded(si.BindToHandler(nil, BHID_SFUIObject, IContextMenu, {out}cm)) then
RaiseLastOSError;
// Create a popup menu that the IContextMenu will add its options to
menu := CreatePopupMenu;
try
// Ask the IContextMenu to populate our popup menu
if not Succeeded(cm.QueryContextMenu(menu, 0, 1, $7FFF, CMF_NORMAL)) then
RaiseLastOSError;
// Find the position of the Share command
command := GetMenuCommand('Share'); // Helper function to loop through and find the command
if Command < 0 then
raise Exception.Create('Share command not found.');
// Fill the CMINVOKECOMMANDINFOEX structure
info := Default(CMINVOKECOMMANDINFO);
info.cbSize := sizeof(info);
info.hwnd := hwndOwner;
info.lpVerb := MAKEINTRESOURCEA(command);
info.nShow := SW_SHOWNORMAL;
// Invoke the command
hr := cm.InvokeCommand(info);
OleCheck(hr);
finally
DestroyMenu(menu);
end;
end;
我还尝试了 ShellExecute 和 “共享” 动词:
procedure ExecuteShare(hwndOwner: HWND; Filename: string);
var
sei: TShellExecuteInfo;
begin
ZeroMemory(@sei, SizeOf(sei));
sei.cbSize := SizeOf(TShellExecuteInfo);
sei.Wnd := hwndOwner;
sei.lpVerb := PChar('Share');
sei.lpFile := PChar(Filename); // PAnsiChar;
sei.nShow := SW_SHOWNORMAL; //Integer;
ShellExecuteEx(@sei);
end;
失败并出现错误对话框:
[内容]
此文件没有与之关联的用于执行此操作的应用程序。请安装一个应用程序,或者如果已经安装了一个应用程序,请在“默认应用程序设置”页面中创建关联。[确定]
这让我们回到我们的问题:
在 Delphi 应用程序中分享
您必须使用其相应的 Windows API,特别是 Windows.ApplicationModel.DataTransfer 的 DataTransferManager 以及 DataPackage 类来设置要共享的数据格式,如文本、Web 链接、图像、文件等。
例如,这里是一个仅调用共享对话框的示例,没有DataTransferManager对象,应该实现它,特别是它的事件
DataRequested
来设置标题和DataPackage以将对话框传递到。
unit main;
interface
uses
Winapi.Windows, Vcl.Graphics, System.Win.ComObj, Winapi.Winrt, Winapi.Foundation,
Vcl.Controls, Vcl.StdCtrls, Vcl.Forms, System.Classes;
const
IDataTransferManagerInteropCLSID = 'Windows.ApplicationModel.DataTransfer.DataTransferManager';
IDataTransferManagerInteropGUID: TGUID = '{3A3DCD6C-3EAB-43DC-BCDE-45671CE800C8}';
IDataTransferManagerGUID: TGUID = '{A5CAEE9B-8708-49D1-8D36-67D25A8DA00C}';
type
IDataTransferManagerInterop = interface(IUnknown)
['{3A3DCD6C-3EAB-43DC-BCDE-45671CE800C8}']
function GetForWindow(appWindow: HWND; const riid: TGUID; out dataTransferManager: Pointer): HRESULT; stdcall;
function ShowShareUIForWindow(appWindow: HWND): HRESULT; stdcall;
end;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
function StringToHString(Src: String; var Dest: HSTRING): Boolean;
begin
Result := Succeeded(WindowsCreateString(PWideChar(Src), Length(Src), Dest));
end;
procedure TForm1.Button1Click(Sender: TObject);
var
hs: HSTRING;
insp: IInspectable;
factory: IActivationFactory;
share: IDataTransferManagerInterop;
dtm: Pointer;
begin
if StringToHString(IDataTransferManagerInteropCLSID, hs) then
if Succeeded(RoGetActivationFactory(hs, IDataTransferManagerInteropGUID, insp)) then
begin
share := IDataTransferManagerInterop(insp);
(*TODO: implement DataTransferManager events handler*)
share.GetForWindow(Handle, IDataTransferManagerInteropGUID, dtm);
share.ShowShareUIForWindow(Handle);
end;
end;
end.
GitHub 上有很多关于此的实现,例如 Golang 中的实现 (datatransfer_idl_windows.go),但官方 IDL 可能值得一看 ShObjIdl_core.idl 以及有关在 Delphi 中实现它的更多指导,本单元 可能有用,因为这适用于 WinAPI.UI.Notifications,它具有类似的实现。