- What is ShellExecute?
- Historical Context of the Windows ShellExecute API
- Benefits of Using ShellExecute in Delphi Applications
- ShellExecute Parameters in Delphi
- Return Return Value (important for stability)
- Unicode considerations
- How to use ShellExecute: Step-by-Step Guide
- Step 1 – Add the Winapi.ShellAPI Unit
- Step 2 – Prepare the File Path or URL
- Step 3 – Launching Applications with ShellExecute
- Step 4 – Always Check the Result and Handle Errors
- Step 5 – Make Your Code Unicode- and 64-bit Safe
- Delphi ShellExecute Example: Opening Files and URLs
- Error Handling and Best Practices for ShellExecute
- How to read the result correctly:
- Why proper error handling matters:
- Best practices:
- ShellExecute vs ShellExecuteEx: When to Choose What
- Common Use Cases for ShellExecute in Modern Delphi Projects
- Troubleshooting ShellExecute Issues in Delphi
- #1 Error 2 – File Not Found
- #2 Error 5 – Access Denied
- #3 Error 31 – No Association
- #4 Error 1155 – No application registered
- #5 Windows 11 specific issues
- Integrating ShellExecute in Legacy Delphi Modernization
- Alternatives to ShellExecute in Delphi
- CreateProcess
- TProcess (FPC/Lazarus-style)
- Conclusion
If you are asking yourself:
– How do I open this file with its default program without knowing where that program is installed?
– How do I launch common system actions through a simple interface without writing complex, low-level OS-specific code?
– How do I perform basic file operations with minimal code?
There is a solution you can use – it is called ShellExecute.
What is ShellExecute?
ShellExecute is a Windows API function used to perform various operations related to file manipulation or system interactions by launching or executing a specified file or operation associated with the default action for the specified file or application.
This function allows users to work with files, folders, and URLs in Delphi, and perform various actions associated with them, such as open, edit, print, explore, etc., from app’s menu, depending on the specified guidelines. In Delphi, it is used similarly to how it’s used in other programming languages for Windows.
In Delphi, it is used in the same way as in other Windows programming environments. It relies on Windows Shell associations to decide which application handles what file type.
Historical Context of the Windows ShellExecute API
The ShellExecute API has existed since Windows 95. There, it became the standard way for applications to ask the operating system to “do what the user expects” with a file or URL. It is one of the most stable API calls in the Windows Shell.
In Windows 11, it is still fully supported. However, Microsoft recommends to use wrappers or asynchronous patterns (ShellExecuteEx or IAsyncOperation APIs) for non-blocking operations.
It is a versatile function that serves several purposes:
- Launching Applications: It can be used to start external applications. For example, you can use it to open Notepad, launch a web browser, or execute any executable file.
- Opening Files: It can open files using the default program associated with their file types. For instance, it can open text files, documents, images, videos, etc., using their default associated applications.
- Performing Actions on Files: It allows performing various actions associated with files, such as editing, printing, and exploring (opening a folder to view its contents), among others.
- Handling URLs: It can open URLs in the default web browser. For instance, it can open a specific website link using the default web browser.
ShellExecute continues to be one of the simplest ways to initiate an action without manually handling:
- path resolution
- file associations
- protocol handlers
- user-default apps
Developers use this function in Windows programming to initiate these kinds of operations and interactions within their applications. It relies on the default associations set up in the Windows Shell interface, allowing applications to leverage the default applications and behaviors set by the user for different file types and protocols.
Its flexibility makes it a widely used function in programming scenarios where there is a need to trigger various file-related operations or launch applications associated with specific file types or URLs.
Benefits of Using ShellExecute in Delphi Applications
ShellExecute offers multiple advantages for Delphi developers, especially to those working on large and multi-module applications.
- Simplicity and minimal code. You can open a file or an app. You can execute a command using a single line of code. No need for complex WinAPI structures or manual path management.
- Uses Windows defaults. It considers the user’s default applications and system preferences. For example, PDFs automatically open in the user’s chosen viewer.
- Cross-application compatibility. Because the Windows Shell manages the association, your Delphi app doesn’t need to “know” anything about formats or registry settings.
- Reduces code bloat. You avoid writing custom logic for file handling or URL opening. This simplifies maintenance and reduces bugs.
- It works smoothly with Delphi VCL/FMX. You can easily bind ShellExecute to buttons, menus, toolbars, and context actions in both VCL and FMX applications.
- Accelerates modernization and prototyping. For legacy Delphi apps, ShellExecute is an excellent bridge when migrating components, replacing old command handlers, and testing modern workflows. Many modernization teams use it to quickly replace deprecated or custom file-handling code paths during phased refactoring.
ShellExecute Parameters in Delphi
ShellExecute in Delphi uses six core parameters. Understanding each one helps ensure stable, predictable behavior, especially on modern 64-bit Windows builds. Below is a detailed, Delphi-specific explanation:
| Parameter | Meaning | Delphi Notes (2025) |
| hWnd | Handle of the window that owns the launched process. | Use Self.Handle or Application.Handle. Avoid using 0 unless you do not care about message routing. |
| lpOperation | Action to perform – usually “open”, “print”, “explore”, “edit”. | Verb must be a valid PChar/PWideChar. nil defaults to “open”. |
| lpFile | File, folder or URL to act on. | Must be a Unicode string. Use PWideChar for Delphi XE2+ to avoid conversion issues. |
| lpParameters | Arguments passed to the application (optional). | Often nil, but required when launching EXEs with parameters. |
| lpDirectory | Working directory. | Usually nil, but set it when launching command-line tools. |
| nShowCmd | Visibility flag for the launched window. | Common values: SW_SHOWNORMAL, SW_HIDE, SW_MAXIMIZE. |
Additional arguments can be simply passed through the lpParameters parameter when launching executable files.
Return Return Value (important for stability)
ShellExecute returns an HINSTANCE, but for error signaling:
- > 32 → success
- ≤ 32 → error code
Common error codes include:
- 2 – File not found
- 5 – Access denied
- 31 – No association
- 1155 – No application to open this file type
Developers can retrieve further information about a failure by checking the system error message or logging the returned code.
Unicode considerations
Since Delphi XE2, the RTL is Unicode by default. Use:
PWideChar(MyString)for 32/64-bit Windows compatibility. Incorrect casting (PChar instead of PWideChar) is one of the top causes of AVs, unexpected failures, and incorrect argument parsing
especially on Windows 11 ARM64.
How to use ShellExecute: Step-by-Step Guide
Our team has rich expertise in Delphi development services which allowed us to accumulate a lot of valuable insights related to this technology. So, to use this function in Delphi, follow these steps:
Step 1 – Add the Winapi.ShellAPI Unit
Include ShellAPI unit: Ensure that the ShellAPI (or Winapi.ShellAPI in current versions of Delphi) unit is included in your uses clause.
In modern Delphi versions (10.4 Sydney, 11 Alexandria, 12 Athens), you must explicitly include:
Without Winapi.ShellAPI, the compiler cannot resolve ShellExecute and will mark it as undefined.
Note for legacy projects:
Delphi versions prior to XE2 used a flat namespace and the unit was named ShellAPI. When modernizing older Delphi applications, replace ShellAPI with Winapi.ShellAPI. This is required for Unicode and 64-bit compatibility.
Step 2 – Prepare the File Path or URL
You can work with:
2025 Best Practice:
Always normalize the path before using it:
On Windows 11, relative paths frequently lead to Error 2 – “File Not Found” because relative paths are resolved differently depending on execution context, elevation level, and working directory.
Step 3 – Launching Applications with ShellExecute
You can call the function like this:
| 12345678 | ShellExecute(
Self.Handle, // Window owner — recommended instead of 0
'open', // Operation: open/print/explore/edit
PChar(Target), // File or URL
nil, // Parameters
nil, // Default directory
SW_SHOWNORMAL // Window mode
); |
Common verbs you can use:
- open – open file/URL using the default handler
- print – send file to default printer
- edit – open in edit mode
- explore – open folder view in Explorer
Common SW_ window flags:
| Flag | Meaning |
| SW_SHOWNORMAL | Normal window |
| SW_HIDE | Hidden (no UI) |
| SW_MAXIMIZE | Start maximized |
| SW_MINIMIZE | Start minimized |
Step 4 – Always Check the Result and Handle Errors
A successful call returns a value greater than 32. Anything else is an error.
For example:
| 1234567 | var
ResultCode: HINST;
begin
ResultCode := ShellExecute(Self.Handle, 'open', PChar(Target), nil, nil, SW_SHOWNORMAL);
if ResultCode <= 32 then
ShowMessage('Error: ' + SysErrorMessage(GetLastError));
end; |
Here’s the top ShellExecute Errors in 2025:
| Error | Description |
| 2 | File not found |
| 5 | Access denied |
| 31 | No association for file type |
| 740 | Requires elevated privileges (UAC) |
| 1155 | No application to handle the file |
Reusable wrapper
| 12345678 | function SafeShellExecute(hWnd: HWND; Verb, FileName, Params, Dir: string): Boolean;
var
Code: HINST;
begin
Code := ShellExecute(hWnd, PChar(Verb), PChar(FileName),
PChar(Params), PChar(Dir), SW_SHOWNORMAL);
Result := Code > 32;
end; |
Step 5 – Make Your Code Unicode- and 64-bit Safe
Delphi is Unicode by default since XE2, but many legacy codebases still use ANSI and PChar casts that break under modern conditions. Correct approach:
| 12345678 | ShellExecute(
Handle,
'open',
PWideChar(Target), // Ensures correct Unicode handling
nil,
nil,
SW_SHOWNORMAL
); |
Why this matters
- Incorrect casting may cause Access Violations on Windows 11 ARM64.
- 64-bit processes require proper Unicode pointer alignment.
- ANSI-to-Unicode conversion fails for non-Latin paths.
Universal snippet (XE2 → Delphi 12)
| 12345 | {$IFDEF UNICODE}
ShellExecute(Handle, 'open', PWideChar(Target), nil, nil, SW_SHOWNORMAL);
{$ELSE}
ShellExecute(Handle, 'open', PChar(Target), nil, nil, SW_SHOWNORMAL);
{$ENDIF} |
This ensures the same code works on all modern Delphi versions
Delphi ShellExecute Example: Opening Files and URLs
Here’s an example demonstrating how to use this function to open a text file:
| 1234567 | procedure TForm1.Button1Click(Sender: TObject);
var
FilePath: string;
begin
FilePath := 'C:\Path\To\Your\File.txt';
ShellExecute(0, 'open', PChar(FilePath), nil, nil, SW_SHOWNORMAL);
end;
|
This code uses these to open the file located at ‘C:\Path\To\Your\File.txt’ using the default program associated with text files in the system (‘open’ operation). You can change the operation or file path according to your specific use case.
Here’s how to open a URL:
| 1234567 | procedure TForm1.Button1Click(Sender: TObject);
var
URL: string;
begin
URL := 'https://www.softacom.com/';
ShellExecute(Handle, 'open', PWideChar(URL), nil, nil, SW_SHOWNORMAL);
end;
|
This is an example of how to print a file:
| 1234567 | procedure TForm1.Button2Click(Sender: TObject);
var
FileName: string;
begin
FileName := 'C:\Users\User\file.txt';
ShellExecute(Handle, 'print', PWideChar(FileName), nil, nil, SW_SHOWNORMAL);
End;
|
Here’s another example of how to launch an application with parameters:
| 12345678 | ShellExecute(
Handle,
'open',
PWideChar('notepad.exe'),
PWideChar('C:\Temp\log.txt'),
nil,
SW_SHOWNORMAL
);
|
This opens Notepad and loads the target file automatically.
Error Handling and Best Practices for ShellExecute
ShellExecute is simple. But relying on default system associations means you must handle errors carefully. A successful result is any value above 32. Values 32 and below indicate a failure.
How to read the result correctly:
| 1234567 | var
Code: HINST;
begin
Code := ShellExecute(Handle, 'open', PWideChar(Target), nil, nil, SW_SHOWNORMAL);
if Code <= 32 then
ShowMessage('ShellExecute failed: ' + SysErrorMessage(GetLastError));
end;
|
Why proper error handling matters:
Modern Windows versions introduce new failure modes:
- UAC elevation requirements
- Disabled protocols (e.g., FTP)
- Missing app associations
- Controlled folder access denying write/launch
- ARM64 path resolution issues
Handling errors prevents silent failures that confuse users.
Best practices:
- Always validate the path. Use FileExists, DirectoryExists, and ExpandFileName before calling ShellExecute.
- Always check the result. Do not assume “open” works — file associations may not exist.
- Prepare for UAC (error 740). If an operation requires elevation, show a clear user prompt.
- Avoid blocking the UI. ShellExecute is synchronous. For long-running operations, prefer ShellExecuteEx or async wrappers.
- Handle high-DPI scaling. When opening apps or windows from a high-DPI Delphi application, use SW_SHOWNORMAL to avoid incorrect scaling on multi-monitor setups.
- Log failures during modernization audits. In legacy projects, failing ShellExecute calls often reveal missing dependencies or broken associations noted during migrations.
ShellExecute vs ShellExecuteEx: When to Choose What
ShellExecute is easy to use but limited. ShellExecuteEx is more powerful and better suited for modern Windows development.
Here’s a quick comparison table:
| ShellExecute | ShellExecuteEx |
| best for quick actionssynchronouscannot retrieve process handlesno extended controlideal for: open, print, explore, URL launches | asynchronous supportallows retrieving the process handlesupports extended invocation flagsenables waiting for process exitsupports more complex operations (verbs, working folders, elevation prompts) |
When to upgrade in Delphi projects?
Use ShellExecuteEx instead of ShellExecute when:
- You need to wait for a launched process to complete
- You must detect whether an application truly started
- You need elevation (runas) in a controlled way
- You need more reliability in enterprise or multi-user environments
- You are modernizing a legacy application that contains custom shell integrations
For everyday file opening and simple tasks, ShellExecute is still sufficient.
Common Use Cases for ShellExecute in Modern Delphi Projects
ShellExecute remains extremely relevant in 2026 because many workflows still depend on default Windows associations. Common tasks include:
- Opening documents
- Previewing exports
- Email composition
- Displaying reports
- Exploring folders
- Cross-platform notes
This makes ShellExecute an important part of “conditional platform” code.
Troubleshooting ShellExecute Issues in Delphi
When ShellExecute fails, it typically returns a value ≤ 32. Here are the most common problems and their fixes:
#1 Error 2 – File Not Found
It returns this error when the path is not normalized, the file extension is missing, or the current working directory is incorrect.
Fix: Use ExpandFileName and check FileExists.
#2 Error 5 – Access Denied
This error is shown when folder is protected by UAC or attempt to open files in Program Files or restricted directories.
Fix: Request elevation or move files to user space.
#3 Error 31 – No Association
The reason for it is the user has no default app for this file type.
Fix: Prompt user to choose a default application.
#4 Error 1155 – No application registered
This happens often with proprietary extensions.
Fix: Register or map handler during installation.
#5 Windows 11 specific issues
Reasons: Controlled Folder Access, missing legacy handlers (FTP, mail clients), or ARM64 architecture mismatches.
Fix: Add explicit error messaging and compatibility checks.
Integrating ShellExecute in Legacy Delphi Modernization
ShellExecute appears simple, but in large legacy Delphi systems it often reveals deeper architectural issues:
- custom file handlers
- deprecated ShellAPI units
- ANSI string usage
- broken associations
- hard-coded paths from the early 2000s
- silent failures hidden under try/except blocks
During modernization projects, we frequently replace dozens of ShellExecute calls with safer abstractions or with ShellExecuteEx for better process control. We also clean up outdated patterns such as ShellExecute(0, …) or ANSI PChar casts that break 64-bit builds.
A modernization audit helps identify:
- unsafe ShellExecute calls
- missing Unicode handling
- broken UAC elevation paths
- deprecated Windows Shell usage
Alternatives to ShellExecute in Delphi
While ShellExecute is convenient, some scenarios require more control or cross-platform compatibility.
CreateProcess
- Full control over process creation
- Lets you capture output, set environment variables, manage handles
- More complex but essential for advanced tasks
TProcess (FPC/Lazarus-style)
- Used when building cross-platform code
- Offers a higher-level wrapper
- Works on Windows, Linux, macOS
Choose these alternatives when you need deeper process integration than ShellExecute provides.
Conclusion
ShellExecute remains one of the simplest and most reliable ways to interact with Windows from a Delphi application. It helps open documents, launch applications, explore folders, or trigger system actions. It offers an easy-to-use interface with minimal code and maximum flexibility. You should ensure proper Unicode handling and error checking. Then, this API works smoothly across modern Windows versions.
If you’re modernizing or maintaining a legacy Delphi project, reviewing ShellExecute usage is a quick win for improving stability.
Want more Delphi guides? Subscribe to our newsletter or follow Softacom for updates.