Click here to Skip to main content
15,888,979 members
Articles / Desktop Programming / WPF
Tip/Trick

Hosting EXE Applications in a WPF Window Application

Rate me:
Please Sign up or sign in to vote.
4.97/5 (20 votes)
25 Oct 2013CPOL1 min read 365.1K   9.1K   32   80
Introduce a simple way to embed an EXE into a WPF window application.

Image 1

Introduction 

This article introduces a way to host an EXE in a WPF window. To make the code easy to reuse, it is set into a WPF user control and also implements the IDisposable interface. A test WPF window app is added as a test project.  

Background 

As a development request I needed to embed a wPython Python edit and compare tool, built by me, into a newly developed WPF application. With the magic power of search engines, I found an article in CodeProject on embedding an EXE into a WinForms program. It's needed to make several changes to become a new WPF control. I also added the IDisposable interface to improve the resource control ability. The links to the reference articles are listed here:

Using the code 

To use the control, we only need to specify the full path of the executable to be embedded and when to dispose it. 

C#
appControl.ExeName = "notepad.exe";
this.Unloaded += new RoutedEventHandler((s, e) => { appControl.Dispose(); }); 

How does it works 

The embedded application is launched with its containing directory as a working directory.

C#
try
{
  var procInfo = new System.Diagnostics.ProcessStartInfo(this.exeName);
  procInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(this.exeName);
  // Start the process
  _childp = System.Diagnostics.Process.Start(procInfo);

  // Wait for process to be created and enter idle condition
  _childp.WaitForInputIdle();

  // Get the main handle
  _appWin = _childp.MainWindowHandle;
}
catch (Exception ex)
{
  Debug.Print(ex.Message + "Error");
}  

Get the container WPF control's handle and set the embedded application's parent window to the container. Change the style for the embedded application. 

C#
// Put it into this form
var helper = new WindowInteropHelper(Window.GetWindow(this.AppContainer));
SetParent(_appWin, helper.Handle);

// Remove border and whatnot
SetWindowLongA(_appWin, GWL_STYLE, WS_VISIBLE);

// Move the window to overlay it on this window
MoveWindow(_appWin, 0, 0, (int)this.ActualWidth, (int)this.ActualHeight, true); 

The embedded application is disposed when the container is disposed.

C#
if (!_isdisposed)
{
  if (disposing)
  {
      if (_iscreated && _appWin != IntPtr.Zero && !_childp.HasExited)
      {
          // Stop the application
          _childp.Kill();

          // Clear internal handle
          _appWin = IntPtr.Zero;
      }
  }
  _isdisposed = true;
}  

Points of Interest 

Some of the embedded applications can't be closed as mentioned in the article: Hosting EXE Application in a WinForms project. So it is changed to use the Process.Kill() method to make sure the embedded application is closed.  

Also the signature of SetWindowLongA is changed a little as the previous one throws an error in the VS2012 compiler.  

History

  • 2013-10-25: Released.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect MicroFocus
China China
Love programming and have fun!

Comments and Discussions

 
GeneralRe: Can Unity3D game file be embedded in WPF application using C# Visual Studio2012? Pin
AZ2Tonez18-May-17 12:13
AZ2Tonez18-May-17 12:13 
QuestionChange the Control location on screen Pin
Member 356206518-Dec-14 3:01
Member 356206518-Dec-14 3:01 
AnswerRe: Change the Control location on screen Pin
Erxin14-Feb-15 14:54
Erxin14-Feb-15 14:54 
AnswerRe: Change the Control location on screen Pin
Erxin14-Feb-15 14:57
Erxin14-Feb-15 14:57 
AnswerRe: Change the Control location on screen Pin
Sacha Barber29-May-15 5:48
Sacha Barber29-May-15 5:48 
QuestionHosting Multiple Applications Pin
kpolecastro10-Nov-14 12:57
professionalkpolecastro10-Nov-14 12:57 
AnswerRe: Hosting Multiple Applications Pin
Erxin17-Nov-14 1:32
Erxin17-Nov-14 1:32 
GeneralRe: Hosting Multiple Applications Pin
kpolecastro18-Nov-14 7:27
professionalkpolecastro18-Nov-14 7:27 
I was trying to do that with excel, but also with other applications like word and such. I was able to find a work around by modifying your code. The second issue that I ran into was when I closed the child window or hosted application, it would prompt the window closing event for the main window. I got around this by forcing a boolean that is only switched when the user closes the main window.

The main window hosts a document previewer which will "host" the application hosting that you created. The hosted application will maintain the size of content presenter that was used in the document previewer.

Declared Global Variables
VB
Public DVHostedApplicationControl As New HostedApplicationViewer
Public CloseApplication As Boolean = False


Closing event on main Window.
VB
Private Sub Window_Closing(sender As System.Object, e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
       'Check if the closing event was propted by the user
       If CloseApplication = False Then
           e.Cancel = True
           DVHostedApplicationControl.Dispose()
       Else
          ...
       End If
   End Sub


Code for loading different files and applications
This code is used in a user control that is a document previewer of the file.
The user can select to edit the file which prompts the application to load in the sub window controller you created.
VB
Private Sub Load_Application()
       Me.DocumentViewer1 = Nothing
       DVHostedApplicationControl = New HostedApplicationViewer(True)
       DVHostedApplicationControl.FileName = _FilePath
       If _FilePath.ToLower Like "*.doc*" Then
           DVHostedApplicationControl.ExeName = "Word.exe"
       ElseIf _FilePath.ToLower Like "*.xls*" Then
           DVHostedApplicationControl.ExeName = "Excel.exe"
       End If

       HostController.Content = DVHostedApplicationControl
   End Sub


Your Modified Code.
I added a file path instead of just the application so that it will load the a file into hosted application. When the hosted Window (application) closes (Disposes), it will also prompt the previewer to show a revised preview.
VB
Imports System
Imports System.Windows
Imports System.Windows.Controls
Imports System.Runtime.InteropServices
Imports System.Diagnostics
Imports System.Windows.Interop
Imports System.IO
Imports System.Threading

''' <summary>
''' This class is used to host different applications in the Document viewer or independently.
''' </summary>
''' <remarks></remarks>
Public Class HostedApplicationViewer
    Inherits UserControl
    Implements IDisposable
    <System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)> _
    Public Structure HWND__
        ''' int
        Public unused As Integer
    End Structure

    Public Sub New(Optional ByVal FromViewer As Boolean = False)
        InitializeComponent()
        AddHandler Me.SizeChanged, AddressOf OnSizeChanged
        AddHandler Me.Loaded, AddressOf OnVisibleChanged
        AddHandler Me.SizeChanged, AddressOf OnResize
        _FromViewer = FromViewer
    End Sub


    Protected Overrides Sub Finalize()
        Try
            Me.Dispose()
        Finally
            MyBase.Finalize()
        End Try
    End Sub

    Private Sub OnChangedInMainThread()
        Dim D As DocViewer = Find_UserControl(Me._Parent)
        If D IsNot Nothing Then
            D.Load_File(Me.FileName, True)
        Else

        End If
    End Sub

    ''' <summary>
    ''' Track if the application has been created
    ''' </summary>
    Private _iscreated As Boolean = False
    Private _FromViewer As Boolean = False

    ''' <summary>
    ''' Track if the control is disposed
    ''' </summary>
    Private _isdisposed As Boolean = False

    ''' <summary>
    ''' Handle to the application Window
    ''' </summary>
    Private _appWin As IntPtr
    Private _childp As Process

    Private _Parent As ContentPresenter

    ''' <summary>
    ''' The name of the exe to launch
    ''' </summary>
    Private m_exeName As String = ""

    Public Property ExeName() As String
        Get
            Return m_exeName
        End Get
        Set(value As String)
            m_exeName = value.ToUpper
            
        End Set
    End Property

    ''' <summary>
    ''' The name of the file to launch
    ''' </summary>
    Private m_FileName As String = ""

    Public Property FileName() As String
        Get
            Return m_FileName
        End Get
        Set(value As String)
            m_FileName = value
        End Set
    End Property


    <DllImport("user32.dll", EntryPoint:="GetWindowThreadProcessId", SetLastError:=True, CharSet:=CharSet.Unicode, ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
    Private Shared Function GetWindowThreadProcessId(hWnd As Long, lpdwProcessId As Long) As Long
    End Function

    <DllImport("user32.dll", SetLastError:=True)> _
    Private Shared Function FindWindow(lpClassName As String, lpWindowName As String) As IntPtr
    End Function

    <DllImport("user32.dll", SetLastError:=True)> _
    Private Shared Function SetParent(hWndChild As IntPtr, hWndNewParent As IntPtr) As Long
    End Function

    <DllImport("user32.dll", EntryPoint:="GetWindowLongA", SetLastError:=True)> _
    Private Shared Function GetWindowLong(hwnd As IntPtr, nIndex As Integer) As Long
    End Function

    <DllImport("user32.dll", EntryPoint:="SetWindowLongA", SetLastError:=True)> _
    Public Shared Function SetWindowLongA(<System.Runtime.InteropServices.InAttribute()> hWnd As System.IntPtr, nIndex As Integer, dwNewLong As Integer) As Integer
    End Function

    <DllImport("user32.dll", EntryPoint:="GetWindowLongA", SetLastError:=True)> _
    Public Shared Function GetWindowLongA(<System.Runtime.InteropServices.InAttribute()> hWnd As System.IntPtr, nIndex As Integer) As Integer
    End Function
   
    <DllImport("user32.dll", SetLastError:=True)> _
    Private Shared Function SetWindowPos(hwnd As IntPtr, hWndInsertAfter As Long, x As Long, y As Long, cx As Long, cy As Long, wFlags As Long) As Long
    End Function

    <DllImport("user32.dll", SetLastError:=True)> _
    Private Shared Function MoveWindow(hwnd As IntPtr, x As Integer, y As Integer, cx As Integer, cy As Integer, repaint As Boolean) As Boolean
    End Function

    Private Const SWP_ASYNCWINDOWPOS As Integer = &H4000 ' If the calling thread and the thread that owns the window are attached to different input queues, the system posts the request to the thread that owns the window. This prevents the calling thread from blocking its execution while other threads process the request. 
    Private Const SWP_FRAMECHANGED As Integer = &H20 'Applies new frame styles set using the SetWindowLong function. Sends a WM_NCCALCSIZE message to the window, even if the window's size is not being changed. If this flag is not specified, WM_NCCALCSIZE is sent only when the window's size is being changed.
    Private Const SWP_NOACTIVATE As Integer = &H10 'Does not activate the window. If this flag is not set, the window is activated and moved to the top of either the topmost or non-topmost group (depending on the setting of the hWndInsertAfter parameter).
    Private Const SWP_NOMOVE As Integer = &H2 'Retains the current position (ignores X and Y parameters).
    Private Const SWP_NOOWNERZORDER As Integer = &H200 ' Does not change the owner window's position in the Z order.
    Private Const SWP_NOREDRAW As Integer = &H8 'Does not redraw changes. If this flag is set, no repainting of any kind occurs. This applies to the client area, the nonclient area (including the title bar and scroll bars), and any part of the parent window uncovered as a result of the window being moved. When this flag is set, the application must explicitly invalidate or redraw any parts of the window and parent window that need redrawing.
    Private Const SWP_NOSIZE As Integer = &H1 ' Retains the current size (ignores the cx and cy parameters).
    Private Const SWP_NOZORDER As Integer = &H4 'Retains the current Z order (ignores the hWndInsertAfter parameter).
    Private Const SWP_SHOWWINDOW As Integer = &H40 'Displays the window.

    Private Const GWL_STYLE As Integer = (-16) ' Sets a new window style.

    Private Const WS_EX_MDICHILD As Integer = &H40L ' The window is a MDI child window.
    'Private Const WS_CAPTION As Integer = &HC00000L ' The window has a title bar (includes the WS_BORDER style).
    'Private Const WS_SYSMENU As Integer = &H80000L 'The window has a window menu on its title bar. The WS_CAPTION style must also be specified.
    'Private Const WS_THICKFRAME As Integer = &H80000L 'The window has a sizing border. Same as the WS_SIZEBOX style.
    Private Const WS_CHILD As Integer = &H40000000 'The window is a child window. A window with this style cannot have a menu bar. This style cannot be used with the WS_POPUP style.
    Private Const WS_VISIBLE As Integer = &H10000000L 'The window is initially visible. This style can be turned on and off by using the ShowWindow or SetWindowPos function.

    ''' <summary>
    ''' Force redraw of control when size changes
    ''' </summary>
    ''' <param name="e">Not used</param>
    Protected Sub OnSizeChanged(s As Object, e As SizeChangedEventArgs)
        Me.InvalidateVisual()
    End Sub


    ''' <summary>
    ''' Create control when visibility changes
    ''' </summary>
    ''' <param name="e">Not used</param>
    Protected Sub OnVisibleChanged(s As Object, e As RoutedEventArgs)
        ' If control needs to be initialized/created
        If _iscreated = False Then

            ' Mark that control is created
            _iscreated = True

            ' Initialize handle value to invalid
            _appWin = IntPtr.Zero

            Try
                Dim procInfo As New System.Diagnostics.ProcessStartInfo()
                procInfo.FileName = m_exeName
                procInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(Me.m_exeName)
                procInfo.WindowStyle = ProcessWindowStyle.Hidden
                procInfo.UseShellExecute = False
                procInfo.CreateNoWindow = False
                If Not m_FileName = "" Then
                    procInfo.Arguments = (Convert.ToString("""") & Me.m_FileName.ToString) + """"
                    'Get the working Directory for the file
                    Dim Str As String = Right(m_FileName, Len(m_FileName) - InStrRev(m_FileName, "."))
                    Dim fp As String = GetAssociatedProgram(Str)
                    procInfo.FileName = fp
                    procInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(fp)
                End If
                
                Try
                    ' Start the process with the info we specified.
                    ' Call WaitForInputIdle and then the using statement will close.
                    _childp = New Process
                    _childp = Process.Start(procInfo)
                    _childp.WaitForInputIdle()
                    ' Get the main handle
                    _appWin = _childp.MainWindowHandle

                    ' Put it into this form
                    Me._Parent = TryCast(Me.VisualParent, ContentPresenter)

                    Dim helper = New WindowInteropHelper(Window.GetWindow(Me))
                    SetParent(_appWin, helper.Handle)

                    ' Remove border and whatnot
                    SetWindowLongA(_appWin, GWL_STYLE, WS_VISIBLE)
                Catch
                End Try

            Catch ex As Exception
                Debug.Print(ex.Message + "Error")
            End Try

            ' Move the window to overlay it on this window
            MoveWindow(_appWin, GetPosition(_Parent).X, GetPosition(_Parent).Y, CInt(Me.ActualWidth), CInt(Me.ActualHeight), True)

        End If
    End Sub

    ''' <summary>
    ''' Update display of the executable
    ''' </summary>
    ''' <param name="e">Not used</param>
    Protected Sub OnResize(s As Object, e As SizeChangedEventArgs)
        If Me._appWin <> IntPtr.Zero Then
            MoveWindow(_appWin, GetPosition(_Parent).X, GetPosition(_Parent).Y, CInt(Me.ActualWidth), CInt(Me.ActualHeight), True)
        End If
    End Sub

    Protected Overridable Sub Dispose(disposing As Boolean)
        If Not _isdisposed Then
            If disposing Then
                If _iscreated AndAlso _appWin <> IntPtr.Zero AndAlso Not _childp.HasExited Then
                    'Load File back into the previewer
                    If _FromViewer Then
                        Try
                            Dim d As Windows.Threading.Dispatcher = Nothing
                            d = Application.Current.Dispatcher
                            If d.CheckAccess() Then
                                OnChangedInMainThread()
                            Else
                                d.BeginInvoke(DirectCast(AddressOf OnChangedInMainThread, Action))
                            End If
                        Catch
                        End Try
                    End If
                    ' Stop the application
                    _childp.Kill()
                    _childp.CloseMainWindow()
                    _childp.Close()

                    ' Clear internal handle
                    _appWin = IntPtr.Zero
                    'If the process has not ended yet, then Completely kill it
                    If _childp IsNot Nothing Then
                        'Process.Start(m_FileName)
                        Dim F As New FileInfo(m_FileName)
                        For Each Proc As Process In Process.GetProcessesByName(Left(m_exeName, Len(m_exeName) - 4))
                            If SimilarText(50, Proc.MainWindowTitle, F.Name) And Not Proc.MainWindowTitle = "" And Not m_FileName = "" Then
                                Proc.Kill()
                            End If
                        Next
                    End If

                End If
            End If
            _isdisposed = True
        End If
    End Sub

    Public Sub Dispose() Implements System.IDisposable.Dispose
        Me.Dispose(True)
        GC.SuppressFinalize(Me)
        Return
    End Sub
End Class


There is still some strangeness with hosting the application. It seems to become an issue when I resize the main window or user control "hosting" the application. At the moment, it is not enough of a concern for me to work on.

Thanks again for your feedback and great code.
QuestionAnother Custom WPF Application Pin
NicholasTan27-Oct-14 13:52
NicholasTan27-Oct-14 13:52 
AnswerRe: Another Custom WPF Application Pin
Erxin30-Oct-14 23:50
Erxin30-Oct-14 23:50 
GeneralRe: Another Custom WPF Application Pin
NicholasTan31-Oct-14 21:04
NicholasTan31-Oct-14 21:04 
GeneralRe: Another Custom WPF Application Pin
Erxin17-Nov-14 1:45
Erxin17-Nov-14 1:45 
GeneralThumbs Frickin Up! Pin
Member 1094594615-Oct-14 11:36
Member 1094594615-Oct-14 11:36 
GeneralRe: Thumbs Frickin Up! Pin
Erxin24-Oct-14 6:07
Erxin24-Oct-14 6:07 
Questionerror, please can helpme Pin
Member 1087520219-Aug-14 2:03
Member 1087520219-Aug-14 2:03 
AnswerRe: error, please can helpme Pin
Erxin20-Aug-14 2:33
Erxin20-Aug-14 2:33 
QuestionType of exe VB6 Pin
andrea giuntoli2-Aug-14 21:13
andrea giuntoli2-Aug-14 21:13 
AnswerRe: Type of exe VB6 Pin
Erxin8-Aug-14 16:52
Erxin8-Aug-14 16:52 
QuestionHosted Buttons Disabled Pin
dokydok27-Jun-14 12:54
dokydok27-Jun-14 12:54 
AnswerRe: Hosted Buttons Disabled Pin
Erxin7-Aug-14 22:22
Erxin7-Aug-14 22:22 
QuestionMultiple Instances in ToolWindow Pin
VishalShah92-Jun-14 0:32
VishalShah92-Jun-14 0:32 
AnswerRe: Multiple Instances in ToolWindow Pin
Erxin2-Jun-14 2:08
Erxin2-Jun-14 2:08 
GeneralRe: Multiple Instances in ToolWindow Pin
VishalShah92-Jun-14 18:44
VishalShah92-Jun-14 18:44 
GeneralRe: Multiple Instances in ToolWindow Pin
Erxin2-Jun-14 19:23
Erxin2-Jun-14 19:23 
GeneralRe: Multiple Instances in ToolWindow Pin
VishalShah92-Jun-14 20:17
VishalShah92-Jun-14 20:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.