Windows has a Share file command in Explorer:
This command brings up a dialog that lets you share the file to nearby people, or to a contact:
How can I add Share command support, invoking the same Windows OS feature, from my application? What is the API, or rundll
command, to invoke the Share dialog?
I tried seeing what process Explorer spawns, and I could use Process Explorer or Process Monitor to spy on the command line, but the dialog is not out-of-process.
Note: Obviously, my application is not a Shell explorer. Nor am I asking how to re-create a feature that already exists. Nor am I asking how to create a shell IContextMenu
.
I am asking how to use this feature of Windows 10 and 11 in my own application so I can share .png
, .jpg
, .pdf
or any other type of file using the Share feature of Windows.
I've tried usual process to invoke a shell command:
IShellItem
IContextMenu
CreatePopupMenu
IContextMenu
to populate our hMenu
It's the last operation, the call to Invoke that fails with error 0x80070057
- Win32 error code 87 - Invalid parameter
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;
I've also tried ShellExecute with a "Share" verb:
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;
And that fails with an error dialog:
[Content]
This file does not have an app associated with it for performing this action. Please install an app or, if one is already installed, create an association in the Default Apps Settings page.[OK]
Which brings us back to our question:
Share in Delphi application
You'd have to use its correspondant Windows API, specifically Windows.ApplicationModel.DataTransfer's DataTransferManager along with the DataPackage class to set the data format to share, like Text, Web Link, Images, Files, etc.
For instance, here is an example that just invokes the Share dialog, without the DataTransferManager object, it should be implemented, specially its event DataRequested
to setup the title and DataPackage to pass the dialog to.
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.
There are many implementations on this at GitHub, like this in Golang (datatransfer_idl_windows.go), yet the official IDL might be worth to look at ShObjIdl_core.idl and about more guidance on implementing it in Delphi, this unit might be useful, as this is for WinAPI.UI.Notifications which has a similar implementation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With