using System; using EasyHook; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels.Ipc; using RenderHookAPI.Interface; using System.Diagnostics; using System.Threading; using RenderHookAPI.Hook; using System.Windows.Forms; namespace RenderHookAPI { public class CaptureProcess : IDisposable { /// /// Must be null to allow a random channel name to be generated /// string _channelName = null; private IpcServerChannel _screenshotServer; private CaptureInterface _serverInterface; public Process Process { get; set; } /// /// Prepares capturing in the target process. Note that the process must not already be hooked, and must have a . /// /// The process to inject into /// Thrown if the does not have a window handle. This could mean that the process does not have a UI, or that the process has not yet finished starting. /// Thrown if the is already hooked /// Thrown if the injection failed - see the InnerException for more details. /// The target process will have its main window brought to the foreground after successful injection. public CaptureProcess(Process process, CaptureConfig config, CaptureInterface captureInterface) { // If the process doesn't have a mainwindowhandle yet, skip it (we need to be able to get the hwnd to set foreground etc) if (process.MainWindowHandle == IntPtr.Zero) { throw new ProcessHasNoWindowHandleException(); } // Skip if the process is already hooked (and we want to hook multiple applications) if (HookManager.IsHooked(process.Id)) { throw new ProcessAlreadyHookedException(); } captureInterface.ProcessId = process.Id; _serverInterface = captureInterface; //_serverInterface = new CaptureInterface() { ProcessId = process.Id }; // Initialise the IPC server (with our instance of _serverInterface) _screenshotServer = RemoteHooking.IpcCreateServer( ref _channelName, WellKnownObjectMode.Singleton, _serverInterface); try { // Inject DLL into target process RemoteHooking.Inject( process.Id, InjectionOptions.Default, typeof(CaptureInterface).Assembly.Location,//"Capture.dll", // 32-bit version (the same because AnyCPU) could use different assembly that links to 32-bit C++ helper dll typeof(CaptureInterface).Assembly.Location, //"Capture.dll", // 64-bit version (the same because AnyCPU) could use different assembly that links to 64-bit C++ helper dll // the optional parameter list... _channelName, // The name of the IPC channel for the injected assembly to connect to config ); var injectionDONE = MessageBox.Show("Injection Finish"); } catch (Exception e) { var injectionFAIL = MessageBox.Show($"Injection FAIL WITH ERROR {e.Message}"); throw new InjectionFailedException(e); } HookManager.AddHookedProcess(process.Id); Process = process; // Ensure the target process is in the foreground, // this prevents an issue where the target app appears to be in // the foreground but does not receive any user inputs. // Note: the first Alt+Tab out of the target application after injection // may still be an issue - switching between windowed and // fullscreen fixes the issue however (see ScreenshotInjection.cs for another option) BringProcessWindowToFront(); } public CaptureInterface CaptureInterface { get { return _serverInterface; } } ~CaptureProcess() { Dispose(false); } #region Private methods /// /// Bring the target window to the front and wait for it to be visible /// /// If the window does not come to the front within approx. 30 seconds an exception is raised public void BringProcessWindowToFront() { if (this.Process == null) return; IntPtr handle = this.Process.MainWindowHandle; int i = 0; while (!NativeMethods.IsWindowInForeground(handle)) { if (i == 0) { // Initial sleep if target window is not in foreground - just to let things settle Thread.Sleep(250); } if (NativeMethods.IsIconic(handle)) { // Minimized so send restore NativeMethods.ShowWindow(handle, NativeMethods.WindowShowStyle.Restore); } else { // Already Maximized or Restored so just bring to front NativeMethods.SetForegroundWindow(handle); } Thread.Sleep(250); // Check if the target process main window is now in the foreground if (NativeMethods.IsWindowInForeground(handle)) { // Leave enough time for screen to redraw Thread.Sleep(1000); return; } // Prevent an infinite loop if (i > 120) // about 30secs { throw new Exception("Could not set process window to the foreground"); } i++; } } #endregion #region IDispose private bool _disposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { // Disconnect the IPC (which causes the remote entry point to exit) _serverInterface.Disconnect(); } _disposed = true; } } #endregion } }