Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows 11 share in Delphi application

Tags:

windows

delphi

Windows has a Share file command in Explorer:

image

image

This command brings up a dialog that lets you share the file to nearby people, or to a contact:

image

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.

What I've Tried So Far

I've tried usual process to invoke a shell command:

  • Parse the file displayName into an IShellItem
  • Ask the shell item for it's context menu handler IContextMenu
  • Create a popup menu CreatePopupMenu
  • Ask the IContextMenu to populate our hMenu
  • Find the menu item with the name "Share"
  • Call IContextMenu.Invoke to invoke the option

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

like image 235
MohsenB Avatar asked Oct 18 '25 11:10

MohsenB


1 Answers

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.

share

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.

like image 121
vhanla Avatar answered Oct 20 '25 03:10

vhanla



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!