I am trying to create a WPF - hybrid application. This application should have the option to be started from command prompt, where in this case, it would not show any window but will only start some process and then quit.
For example (made up example) in my WPF application I enable users to create body of an email message that will be send to any user (sort of a template).
Afterwards when user wants to send the email, he can do it via cmd like following (meaning GUI will not even start, it will only call email connector and send message to selected recipient and then quit program):
MyProgram.exe -recipient john#doe.com -send=true
My Main() looks like this (created based on the following website:http://www.jankowskimichal.pl/en/2011/12/wpf-hybrid-application-with-parameters/)
public static class Program
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AllocConsole();
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool FreeConsole();
[DllImport("kernel32", SetLastError = true)]
static extern bool AttachConsole(int dwProcessId);
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
[STAThreadAttribute]
[System.Diagnostics.DebuggerNonUserCodeAttribute]
public static void Main(string[] args)
{
//App.Main();
if (args.Length == 0)
{
IP_DynamicMailings.App app = new IP_DynamicMailings.App();
app.InitializeComponent();
app.Run();
}
else
{
// Get uppermost window process
IntPtr ptr = GetForegroundWindow();
int u;
GetWindowThreadProcessId(ptr, out u);
Process process = Process.GetProcessById(u);
// Check if it is console?
if (process.ProcessName == "cmd")
{
// Yes – attach to active console
AttachConsole(process.Id);
}
else
{
// No – create new console
AllocConsole();
}
// Program actions ...
foreach (var item in args)
{
Console.WriteLine(item);
}
FreeConsole();
}
}
}
What happens is, that I need to press Enter in order to quit application.
I am also open to rewrite my hybrid logic, shall you have better solution (this was so far the best I could find).
Related
Guys I'm stuck at the final part of a functional deskband implementation for my application,
For context, I'm currently running a visual studio solution with 2 projects in them,
one is the c++ deskband dll and another is a simple c# console program.
The DeskBand code is Microsoft's DeskBand sample
Currently, I can register the dll when the console app runs by calling the "DllRegisterServer" with GetProcAddress(). After this I'll simply right-click the taskbar and enable the registered deskband.
My question comes from here on,
Once registered, let's say the OnPaint event in the Deskband implementation paints a text variable called "Hello world", What I want to do is simply send a string from c# to the running dll and make it paint "Sent from c#".
The register class:
public class Registrar
{
private IntPtr hLib;
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
internal static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool FreeLibrary(IntPtr hModule);
internal delegate int PointerToMethodInvoker();
public Registrar(string filePath)
{
hLib = LoadLibrary(filePath);
if (IntPtr.Zero == hLib)
{
int errno = Marshal.GetLastWin32Error();
throw new Win32Exception(errno, "Failed to load library.");
}
}
public void RegisterComDLL()
{
CallPointerMethod("DllRegisterServer");
}
public void UnRegisterComDLL()
{
CallPointerMethod("DllUnregisterServer");
}
private void CallPointerMethod(string methodName)
{
IntPtr dllEntryPoint = GetProcAddress(hLib, methodName);
if (IntPtr.Zero == dllEntryPoint)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
PointerToMethodInvoker drs =
(PointerToMethodInvoker)Marshal.GetDelegateForFunctionPointer(dllEntryPoint,
typeof(PointerToMethodInvoker));
drs();
}
public void FreeLib()
{
if (IntPtr.Zero != hLib)
{
FreeLibrary(hLib);
hLib = IntPtr.Zero;
}
}
}
The simple console program:
static void Main(string[] args)
{
Console.WriteLine("Start");
Registrar reg = new Registrar("DeskBandDLL.dll");
if (reg != null)
{
reg.RegisterComDLL();
//send data to the registered dll to start painting new text
Console.ReadLine();
reg.UnRegisterComDLL();
reg.FreeLib();
}
}
Btw, I do know that Microsoft discontinued deskband in win11, but I really want to implement this feature for only the win10 platform and I feel like I'm close to finishing this.
And if you need the full code for clarification, I can host it on GitHub.
I am doing a console app that I change in start up the width, is it possible to do it before the console launch? right now the console launches at default then changes.
I wish it to launch after the configuration was applied.
using C# in Visual Studio.
Thanks
It is sadly (as far as I can tell) impossible.
At first I thought "Hey, let's hide the window at start, set the size, and show the window again" like so:
[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
private const int SwHide = 0;
private const int SwShow = 5;
public static void Main()
{
_consoleHandle = GetConsoleWindow();
ShowWindow(_consoleHandle, SwHide);
Console.SetWindowSize(100, 30);
ShowWindow(_consoleHandle, SwShow);
}
But that didn't work. Even when I tried hiding the console window in a static constructor (so it gets executed before main). And by "didn't work" I mean, the console showed up, vanished, and reappeared with a different size.
Then I stumbled on a Q&A here that said you could hide the console window by setting the output type to "Windows Application" instead of "Console Application". So I thought "hey, I could attach a console after the fact, let's try it":
[DllImport("kernel32", SetLastError = true)]
static extern bool AttachConsole(uint dwProcessId);
[DllImport("kernel32")]
static extern IntPtr GetConsoleWindow();
[DllImport("user32")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
private const int SwHide = 0;
private const int SwShow = 5;
private const uint AttachParentProcess = 0x0ffffffff;
public static void Main()
{
AttachConsole(AttachParentProcess);
var handle = GetConsoleWindow();
Console.SetWindowSize(100, 30);
ShowWindow(handle, SwShow);
Console.WriteLine(Console.WindowHeight);
Console.WriteLine(Console.WindowWidth);
}
But that didn't work either, for some reason it can't attach a console and it returns an invalid handle meaning I can't do anything.
all!
Please, help me with any advice with my problem: I build GUI WinForms application and now I want to attach console to it. I found this is not as much easy as it seems before. But I found good solution here: How do I show a console output/window in a forms application? Below the code from rag answer.
using System;
using System.Runtime.InteropServices;
namespace SomeProject
{
class GuiRedirect
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AttachConsole(int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetStdHandle(StandardHandle nStdHandle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetStdHandle(StandardHandle nStdHandle, IntPtr handle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern FileType GetFileType(IntPtr handle);
private enum StandardHandle : uint
{
Input = unchecked((uint)-10),
Output = unchecked((uint)-11),
Error = unchecked((uint)-12)
}
private enum FileType : uint
{
Unknown = 0x0000,
Disk = 0x0001,
Char = 0x0002,
Pipe = 0x0003
}
private static bool IsRedirected(IntPtr handle)
{
FileType fileType = GetFileType(handle);
return (fileType == FileType.Disk) || (fileType == FileType.Pipe);
}
public static void Redirect()
{
if (IsRedirected(GetStdHandle(StandardHandle.Output)))
{
var initialiseOut = Console.Out;
}
bool errorRedirected = IsRedirected(GetStdHandle(StandardHandle.Error));
if (errorRedirected)
{
var initialiseError = Console.Error;
}
AttachConsole(-1);
if (!errorRedirected)
SetStdHandle(StandardHandle.Error, GetStdHandle(StandardHandle.Output));
}
}
This code works as charm except one downside: non-latin letters outputs to console in strange encoding (but if redirected to file, they are in right encoding). I need to redirect both StdOut and StdErr, and if I change any part of the code it stops redirecting.
Thanks to all who share their wisdom with me in comments!
SetConsoleOutputCP was the answer.
Don't forget put this to other definitions of the class.
[DllImport("kernel32.dll")]
static extern bool SetConsoleOutputCP(uint wCodePageID);
And than add call of the SetConsoleOutputCP(desired codepage); to Redirect() method.
My issue is PostMessage windows API is not working properly as it works when running from console application.
Working code:
I have 2 application [1] is console application [2] Windows Forms application.
Requirement is I want to send message to all the running instances of application.
console application code:
class Program
{
#region Dll Imports
public const int HWND_BROADCAST = 0xFFFF;
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32")]
public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
[DllImport("user32")]
public static extern int RegisterWindowMessage(string message);
#endregion Dll Imports
public static readonly int WM_ACTIVATEAPP = RegisterWindowMessage("CLOSE");
static void Main(string[] args)
{
//we tried to create a mutex, but there's already one (createdNew = false - another app created it before)
//so there's another instance of this application running
Process currentProcess = Process.GetCurrentProcess();
//get the process that has the same name as the current one but a different ID
foreach (Process process in Process.GetProcessesByName("ClientApp1"))
{
if (process.Id != currentProcess.Id)
{
IntPtr handle = process.MainWindowHandle;
//if the handle is non-zero then the main window is visible (but maybe somewhere in the background, that's the reason the user started a new instance)
//so just bring the window to front
//if (handle != IntPtr.Zero)
//SetForegroundWindow(handle);
//else
//tough luck, can't activate the window, it's not visible and we can't get its handle
//so instead notify the process that it has to show it's window
PostMessage((IntPtr)HWND_BROADCAST, WM_ACTIVATEAPP, IntPtr.Zero, IntPtr.Zero);//this message will be sent to MainForm
break;
}
}
}
}
Windows Forms application code:
public partial class Form1 : Form
{
#region Dll Imports
public const int HWND_BROADCAST = 0xFFFF;
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32")]
public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
[DllImport("user32")]
public static extern int RegisterWindowMessage(string message);
#endregion Dll Imports
public static readonly int WM_ACTIVATEAPP = RegisterWindowMessage("CLOSE");
public Form1()
{
InitializeComponent();
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
//someone (another process) said that we should show the window (WM_ACTIVATEAPP)
if (m.Msg == WM_ACTIVATEAPP)
this.Close();
}
}
Above code is working as expected.
My issues start from here. I want to run the same code from windows service instead of console application. Need immediate guidance.
It seems when I run this code from windows service its not getting hold of the process or service runs in different account so message is not getting delivered.
Most probably you run your service as Local System account in session 0 and it is rather isolated for good reasons. For example you don't have access to other desktops/sessions.
You have to implement a different IPC method, e.g. pipes or memory mapped files.
What I'm trying to do is activate another application and send a key input to it to trigger a button. However this code doesn't seem to work. It looks like it can find the application but it does not activate it, and does not send the key.
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
private void debugButton_Click(object sender, EventArgs e)
{
//GetProcess by Class
IntPtr rightNowHandle = FindWindow("WindowsForms10.Window.8.app.0.24dc298_r17_ad2", null);
//Get Handle by Process
Process proc = Process.GetProcessesByName("RightNow.CX")[0];
IntPtr ptrFF = proc.MainWindowHandle;
//Get a handle for the Calculator Application main window
if (rightNowHandle == IntPtr.Zero)
{
MessageBox.Show("Could Not Find Right Now");
return;
}
SetForegroundWindow(rightNowHandle); //Activate Handle By Class
//SetForegroundWindow(ptrFF); //Activate Handle By Process
SendKeys.SendWait("{F1}");
}
And here is what I pull with Window Spy
Any help would be greatly appreciated. Thanks.
Figured out my issue.
SetForegroundWindow does not show the application if it's minimized, which is what I was anticipating.
Next I used Windows Input Simulator, to send the input instead of SendKeys.
Here's the code I ended up using.
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
private const int SW_RESTORE = 9;
private void debugButton_Click(object sender, EventArgs e)
{
//GetProcess by Class
IntPtr rightNowHandle = FindWindow("WindowsForms10.Window.8.app.0.24dc298_r17_ad2", null);
//Get Handle by Process
Process proc = Process.GetProcessesByName("RightNow.CX")[0];
IntPtr ptrFF = proc.MainWindowHandle;
//Get a handle for the Calculator Application main window
if (rightNowHandle == IntPtr.Zero)
{
MessageBox.Show("Could Not Find Right Now");
return;
}
SetForegroundWindow(ptrFF); //Activate Handle By Process
ShowWindow(ptrFF, SW_RESTORE); //Maximizes Window in case it was minimized.
//SetForegroundWindow(rightNowHandle); //Activate Handle By Class
InputSimulator.SimulateModifiedKeyStroke(VirtualKeyCode.LMENU, VirtualKeyCode.VK_A); //Send Left Alt + A
}