Calling SendMessage (P/Invoke) keeps crashing - c#

I am having to write an application that communicates with a third-party program (AOL, I'm sorry. :()
Doing a lot of research I found some ways to do this with P/Invoke, and for the most part it works okay, but it crashes upon subsequent trials, specifically with SendMessage. I'm outlining the crashing code below.
All of this was ported to .NET from old, old Visual Basic files. It's archaic as it can be, and I understand if it's not doable - I was just hoping there was a better way than Visual Basic 4.0 to get this done.
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr hwndParent,
IntPtr hwndChildAfter,
string lpszClass,
string lpszWindow);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle,
IntPtr childAfter,
string className,
IntPtr windowTitle);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(HandleRef hWnd,
UInt32 Msg,
IntPtr wParam,
IntPtr lParam);
[DllImport("user32.dll", EntryPoint="SendMessageW")]
public static extern IntPtr SendMessageByString(HandleRef hWnd,
UInt32 Msg,
IntPtr wParam,
StringBuilder lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode , EntryPoint = "SendMessageW")]
public static extern IntPtr SendMessageByString(HandleRef hWnd,
UInt32 Msg,
IntPtr wParam,
String lParam);
public IntPtr FindClientWindow()
{
IntPtr aol = IntPtr.Zero;
IntPtr mdi = IntPtr.Zero;
IntPtr child = IntPtr.Zero;
IntPtr rich = IntPtr.Zero;
IntPtr aollist = IntPtr.Zero;
IntPtr aolicon = IntPtr.Zero;
IntPtr aolstatic = IntPtr.Zero;
aol = Invoke.FindWindow("AOL Frame25", null);
mdi = Invoke.FindWindowEx(aol, IntPtr.Zero, "MDIClient", null);
child = Invoke.FindWindowEx(mdi, IntPtr.Zero, "AOL Child", null);
rich = Invoke.FindWindowEx(child, IntPtr.Zero, "RICHCNTL", null);
aollist = Invoke.FindWindowEx(child, IntPtr.Zero, "_AOL_Listbox", null);
aolicon = Invoke.FindWindowEx(child, IntPtr.Zero, "_AOL_Icon", null);
aolstatic = Invoke.FindWindowEx(child, IntPtr.Zero, "_AOL_Static", null);
if (rich != IntPtr.Zero &&
aollist != IntPtr.Zero &&
aolicon != IntPtr.Zero &&
aolstatic != IntPtr.Zero)
return child;
do
{
child = Invoke.FindWindowEx(mdi, child, "AOL Child", null);
rich = Invoke.FindWindowEx(child, IntPtr.Zero, "RICHCNTL", null);
aollist = Invoke.FindWindowEx(child, IntPtr.Zero, "_AOL_Listbox", null);
aolicon = Invoke.FindWindowEx(child, IntPtr.Zero, "_AOL_Icon", null);
aolstatic = Invoke.FindWindowEx(child, IntPtr.Zero, "_AOL_Static", null);
if (rich != IntPtr.Zero &&
aollist != IntPtr.Zero &&
aolicon != IntPtr.Zero &&
aolstatic != IntPtr.Zero)
return child;
}
while (child != IntPtr.Zero)
;
return child;
}
IntPtr room = IntPtr.Zero;
IntPtr child = IntPtr.Zero;
IntPtr length = IntPtr.Zero;
IntPtr roomHandle = IntPtr.Zero;
child = FindClientWindow();
room = FindChildByClass(child, "RICHCNTLREADONLY");
HandleRef n = new HandleRef(IntPtr.Zero, room);
length = SendMessage(n, 0x000E, IntPtr.Zero, IntPtr.Zero);
// This is the line that keeps crashing on me.
SendMessageByString(n, 0x000D, new IntPtr( length.ToInt32() + 1 ), str);
public IntPtr FindChildByClass(IntPtr parent, string child)
{
return Invoke.FindWindowEx(parent, IntPtr.Zero, child, null);
}

You are using the Wide byte SendMessage..ie for Wide Characters, have you tried the normal Sendmessage..
public static extern int SendMessage(IntPtr hWnd, UInt32 Msg, Int32 wParam, Int32 lParam);
I also notice it's like as if you are trying to change the value based on the handle of the richtextbox's control hence the looking around for the AOL's client window in another process...is that correct?
That could be the source of the problem, directly modifying a control that belongs to a window that is not yours (your program is managed, modifying a unmanaged process's window)...that could explain why it crashed. Can you clarify what is the hex constants for?
Edit: When you use the WM_GETTEXTLENGTH and WM_GETTEXT, they are part of the Windows Messages to retrieve the text length and the actual text from the control. If you look here and see what pinvoke.net has to say about them..When you issue a 'SendMessage', with WM_GETTEXTLENGTH and WM_GETTEXT, you are telling Windows - 'Hey, get me the length of the text in that associated handle which I've given you in the parameter n. Just occurred to me, worth trying out...I would get rid of those SendMessage pinvokes and use just this one..
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, StringBuilder lParam);
//If you use '[Out] StringBuilder', initialize the string builder with proper length first.
child = FindClientWindow();
room = FindChildByClass(child, "RICHCNTLREADONLY");
length = SendMessage(n, 0x000E, IntPtr.Zero, IntPtr.Zero);
StringBuilder sbBuf = new StringBuilder(length);
SendMessageByString(room, 0x000D, new IntPtr( length.ToInt32() + 1 ), out sbBuf); // this is the line that keeps crashing on me.
Try that and get back here ... :)
Hope this helps,
Best regards,
Tom.

Did you manage to solve the "Attempted to read or write protected memory." error? t0mm13b's answer seems to allocate a StringBuilder whose buffer is one character too small (to accommodate the trailing '\0').
Here's code that works for me:
[DllImport("user32.dll", EntryPoint = "SendMessage", SetLastError = true)]
private static extern Int32 SendMessageByString(IntPtr wnd, UInt32 msg, Int32 WParam, StringBuilder output);
const int WM_GETTEXTLENGTH = 0x000e;
const int WM_GETTEXT = 0x000d;
public static string GetText(IntPtr hWnd)
{
int len = SendMessageByString(hWnd, WM_GETTEXTLENGTH, 0, null);
var sb = new StringBuilder(len + 1); // +1 is for the trailing '\0'
SendMessageByString(hWnd, WM_GETTEXT, sb.Capacity, sb);
return sb.ToString();
}

I was getting a crash from Marshal.PtrToStringUni(bf) statement in similar situation where SendMessage was returning a "wrong size" for a text length with WM_GETTEXTLENGTH argument (the control class was "RICHEDIT50W"; multi-line text).
I had tried adding 1, 10, 100 (to text length query result) and still would get an error even though (later on) the text length was equal what was returned from the first call (WM_GETTEXTLENGTH).
My solution was: I multiplied the result with 2 then I trimmed it.
I did use Marshal.AllocHGlobal(sz) and then Marshal.Release(bf), so there was no problem with memory efficiency. My guess is that for multi-line texts Marshal.AllocHGlobal(sz) wasn't making enough space in the memory even with exact text size (+1).
Maybe the return character within the text (vbCr, vbLf) requires more memory: I found nothing to explain this isue, but doubling the size worked for me.

Related

How can I send a string, that has multiple zeros to CMD?

I have a function in WinForms C# app that sends a string (from a textbox) to an active CMD window, using a button.
Unfortunately, if the textbox contains multiple zeros (0000x000F22000), it returns just one zero: 0x0F220
How can I fix this?
private void but_run_Click(object sender, EventArgs e)
{
uint wparam = 0 << 29 | 0;
int i = 0;
for (i = 0; i < textBox1.Text.Length; i++)
{
//PostMessage(child, WM_KEYDOWN, (IntPtr)Keys.Enter, (IntPtr)wparam);
PostMessage(cmdHwnd, WM_CHAR, (int)textBox1.Text[i], 0);
}
PostMessage(cmdHwnd, WM_KEYDOWN, (IntPtr)Keys.Enter, (IntPtr)wparam);
}
You could try using the lParam to specify repeat key presses. Also, pay attention - PostMessage has lParam as the fourth parameter (wParam is before lParam), you're mixing it up in your code.
Next, don't use (int)someChar. You should use the Encoding classes to get byte values from chars.
Use SendMessage instead of PostMessage. PostMessage is asynchronous and can complicate a lot of stuff for you. You don't need the asynchronicity, so don't use it.
Next, why use WM_CHAR? I'd say WM_SETTEXT would be way more appropriate - you can send the whole text at once. Just be careful about using the native resources (eg. the string). To make this as easy as possible, you can make yourself an overload of the SendMessage method:
const uint WM_SETTEXT = 0x000C;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, unit Msg,
IntPtr wParam, string lParam);
You can then simply call:
SendMessage(cmdHwnd, WM_SETTEXT, IntPtr.Zero, textBox1.Text);
I've managed to do it like this:
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindow(IntPtr ZeroOnly, string lpWindowName);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
const int WM_CHAR = 0x0102;
public void sendText(string pText, string pCaption)
{
IntPtr wndHndl = FindWindow(IntPtr.Zero, pCaption);
char[] tmpText = pText.ToCharArray();
foreach (char c in tmpText)
{
System.Threading.Thread.Sleep(50);
PostMessage(wndHndl, WM_CHAR, (IntPtr)c, IntPtr.Zero);
}
}
Where pText is the input string and pCaption is title of the window.

Click A MessageBox button programmatically

As the title suggests, I'm trying to simulate a button-click in a MessageBox programmatically. I earlier tried to close the MessageBox by finding its handle via its caption, and applying WM_CLOSE or SC_CLOSE in SendMessage(). However, due to the presence of Yes/No buttons, that did not work (the X button is grayed out).
Now I'm trying to click the No button as follows -:
List<IntPtr> result = new List<IntPtr>();
GCHandle listHandle = GCHandle.Alloc(result);
try
{
IntPtr Window_hWnd = CloseMessageBox.FindWindowByCaption("#32770", "LastQuestion"); //Could use null as the first argument too. "#32770" represents classname Dialog.
CloseMessageBox.EnumChildWindows(Window_hWnd, (hWnd, lParam) =>
{
StringBuilder sb = new StringBuilder();
foreach (var control in GCHandle.FromIntPtr(lParam).Target as List<IntPtr>)
{
CloseMessageBox.GetWindowText(control, sb, 250);
if (sb.Equals("&No"))
{
CloseMessageBox.PostMessage(hWnd, CloseMessageBox.MouseDown, 0, 0);
CloseMessageBox.PostMessage(hWnd, CloseMessageBox.MouseUp, 0, 0);
}
}
return false;
}, GCHandle.ToIntPtr(listHandle));
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
finally
{
if (listHandle.IsAllocated)
listHandle.Free();
}
Having come this far on the advice of someone from IRC, I find that a few edits earlier, I was getting the button handle (only the "&Yes" button) but not all of them. He then suggested this approach, but the control List is not populated and hence it never goes inside the foreach. What do I do to remedy this?
Here you go.
// A delegate which is used by EnumChildWindows to execute a callback method.
public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
// This method accepts a string which represents the title name of the window you're looking for the controls on.
public static void ClickButtonLabeledNo(string windowTitle)
{
try
{
// Find the main window's handle by the title.
var windowHWnd = FindWindowByCaption(IntPtr.Zero, windowTitle);
// Loop though the child windows, and execute the EnumChildWindowsCallback method
EnumChildWindows(windowHWnd, EnumChildWindowsCallback, IntPtr.Zero);
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
private static bool EnumChildWindowsCallback(IntPtr handle, IntPtr pointer)
{
const uint WM_LBUTTONDOWN = 0x0201;
const uint WM_LBUTTONUP = 0x0202;
var sb = new StringBuilder(256);
// Get the control's text.
GetWindowCaption(handle, sb, 256);
var text = sb.ToString();
// If the text on the control == &No send a left mouse click to the handle.
if (text == #"&No")
{
PostMessage(handle, WM_LBUTTONDOWN, IntPtr.Zero, IntPtr.Zero);
PostMessage(handle, WM_LBUTTONUP, IntPtr.Zero, IntPtr.Zero);
}
return true;
}
[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr i);
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
private static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
[DllImport("user32.dll", EntryPoint = "GetWindowText", CharSet = CharSet.Auto)]
private static extern IntPtr GetWindowCaption(IntPtr hwnd, StringBuilder lpString, int maxCount);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
private static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

How to handle Message Boxes while using webbrowser in C#?

I'm using this webbrowswer feature of C#. Trying to log in a website through my application. Everything goes fine, except when a wrong ID or password is entered there's little message box (that is set on the webpage itself) which pops up and blocks everything until "Ok" is clicked.
So the question is: Is there any possible way to manage this little window (like reading the text inside of it)? If it is then great!
But if there's no way to do that then is there anyway to simply make this message box go away programatically?
You can "manage" the message box dialog by importing some window functions from user32.dll and getting the messagebox dialog's handle by it's class name and window name. For example to click its OK button:
public class Foo
{
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
private void ClickOKButton()
{
IntPtr hwnd = FindWindow("#32770", "Message from webpage");
hwnd = FindWindowEx(hwnd, IntPtr.Zero, "Button", "OK");
uint message = 0xf5;
SendMessage(hwnd, message, IntPtr.Zero, IntPtr.Zero);
}
}
Some reading material from MSDN.
Copy paste from Sires anwser: https://stackoverflow.com/a/251524/954225
private void InjectAlertBlocker() {
HtmlElement head = webBrowser1.Document.GetElementsByTagName("head")[0];
HtmlElement scriptEl = webBrowser1.Document.CreateElement("script");
IHTMLScriptElement element = (IHTMLScriptElement)scriptEl.DomElement;
string alertBlocker = "window.alert = function () { }";
element.text = alertBlocker;
head.AppendChild(scriptEl);
}
here is refined version of Saeb's answer. Saeb's code was not working for me, I added one more step to activate the button and then clicking on it.
using System;
using System.Runtime.InteropServices;
namespace IE_Automation
{
public class IEPoppupWindowClicker
{
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, int wParam, int lParam);
private const int BM_CLICK = 0xF5;
private const uint WM_ACTIVATE = 0x6;
private const int WA_ACTIVE = 1;
public void ActivateAndClickOkButton()
{
// find dialog window with titlebar text of "Message from webpage"
var hwnd = FindWindow("#32770", "Message from webpage");
if (hwnd != IntPtr.Zero)
{
// find button on dialog window: classname = "Button", text = "OK"
var btn = FindWindowEx(hwnd, IntPtr.Zero, "Button", "OK");
if (btn != IntPtr.Zero)
{
// activate the button on dialog first or it may not acknowledge a click msg on first try
SendMessage(btn, WM_ACTIVATE, WA_ACTIVE, 0);
// send button a click message
SendMessage(btn, BM_CLICK, 0, 0);
}
else
{
//Interaction.MsgBox("button not found!");
}
}
else
{
//Interaction.MsgBox("window not found!");
}
}
}
}

notepad character decoding

I am reading text from a notepad opened by my program.and this is my code
const int WM_GETTEXT = 0x000D;
const int WM_GETTEXTLENGTH = 0x000E;
[DllImport("User32.dll", EntryPoint = "SendMessage")]
extern static int SendMessageGetTextLength(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
extern static IntPtr SendMessageGetText(IntPtr hWnd, int msg, IntPtr wParam, [Out] StringBuilder lParam);
[DllImport("user32.dll", EntryPoint = "FindWindowEx")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
public static string GetText(IntPtr hwnd)
{
if (hwnd == IntPtr.Zero)
throw new ArgumentNullException("hwnd");
IntPtr handler = FindWindowEx(hwnd, new IntPtr(0), "Edit", null);
int length = SendMessageGetTextLength(handler, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero);
if (length > 0 && length < int.MaxValue)
{
length++;
StringBuilder sb = new StringBuilder(length);
SendMessageGetText(handler, WM_GETTEXT, (IntPtr)sb.Length, sb);
return sb.ToString();
}
return String.Empty;
}
It is getting the text but in a special encoding.
For example, if the text entered is 'hello' it gets '興梀㇨ȿڳㇺ'.
What is the encoding of this text so I can decode it to ASCII?
Your problem is in fact that you are passing sb.Length in the WM_GETTEXT message, when in fact you should be passing sb.Capacity or even just length.
I would do it like this:
if (length > 0 && length < int.MaxValue)
{
StringBuilder sb = new StringBuilder(length+1);
SendMessageGetText(handler, WM_GETTEXT, (IntPtr)length+1, sb);
return sb.ToString();
}
I'd also point out that WM_GETTEXT will not return more than 64k characters to the length < int.MaxValue isn't what you need.
Of course, in the longer run it may be better to use the Unicode throughout so that you can support international text.
I personally would always opt for using the Unicode APIs and use the following p/invoke declarations:
[DllImport("User32.dll", EntryPoint = "SendMessage",
CharSet = CharSet.Unicode, SetLastError = true)]
extern static int SendMessageGetTextLength(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll", EntryPoint = "SendMessage",
CharSet = CharSet.Unicode, SetLastError = true)]
extern static IntPtr SendMessageGetText(IntPtr hWnd, int msg, IntPtr wParam, StringBuilder lParam);
[DllImport("user32.dll", EntryPoint = "FindWindowEx",
CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
Since you're writing in managed code, you may as well use the managed code automation interfaces, which does all the interop for you. Why reinvent the wheel?
using System.Windows.Automation;
public static string GetText(IntPtr hwnd)
{
IntPtr hwndEdit = FindWindowEx(hwnd, IntPtr.Zero, "Edit", null);
return (string)AutomationElement.FromHandle(hwndEdit).
GetCurrentPropertyValue(AutomationElement.NameProperty);
}
You can even make the automation do the FindWindowEx for you:
public static string GetText(IntPtr hwnd)
{
var editElement = AutomationElement.FromHandle(hwnd).
FindFirst(TreeScope.Subtree,
new PropertyCondition(
AutomationElement.ClassNameProperty, "Edit"));
return (string)editElement.GetCurrentPropertyValue(AutomationElement.NameProperty);
}

How to host an console exe with UI in winform

I am working on a project with some specific requirement: I need to create a program which can monitor and runs exe inside of it using c#. However, an approach of using Console program to actually host another exe seems to be no end. So i used WinForm.
I have been looking and found some quite good solution, which host an UI Application inside the WinForm. But in my case, the exe has no UI, but it is capable of create an UI (OpenGL), so it is not applicable for those solutions. Is there any way to host this kind of exe inside of the WinForm? Which i can run many of it simultaneously?
Thanks
Hosting a process within another one makes no sens. If you want to launch an exe from another one, you can use System.thread.Process and if those process need to interact with each other, well, WCF is made just for that.
Thank you for all your suggestion. However, i did find out another way around to grab the correct Window which created by my console exe, and put it into the correct winform. This is quite a cheat though.
This idea is originally comes from the Window Tabifier from codeproject: http://www.codeproject.com/KB/cs/WindowTabifier.aspx . As my exe does not have the corresponding UI for the process.WaitForInputIdle, i do a cheat on Thread.Sleep(2000) to skip the starting time, and grab the created Window of the console based on the name of it.
Library import:
public delegate bool EnumWindowsProc(IntPtr hWnd, int lParam);
[DllImport("user32.dll", EntryPoint = "GetWindowThreadProcessId", SetLastError = true,
CharSet = CharSet.Unicode, ExactSpelling = true,
CallingConvention = CallingConvention.StdCall)]
private static extern long GetWindowThreadProcessId(long hWnd, long lpdwProcessId);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool MoveWindow(IntPtr hwnd, int x, int y, int cx, int cy, bool repaint);
[DllImport("user32", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private extern static bool EnumThreadWindows(int threadId, EnumWindowsProc callback, IntPtr lParam);
[DllImport("user32", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool EnumChildWindows(IntPtr hwndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam);
[DllImport("user32", SetLastError = true, CharSet = CharSet.Auto)]
private extern static int GetWindowText(IntPtr hWnd, StringBuilder text, int maxCount);
Some methods to find the correct opened window
public IntPtr FindWindowInProcess(Process process, Func<string, bool> compareTitle)
{
IntPtr windowHandle = IntPtr.Zero;
foreach (ProcessThread t in process.Threads)
{
windowHandle = FindWindowInThread(t.Id, compareTitle);
if (windowHandle != IntPtr.Zero)
{
break;
}
}
return windowHandle;
}
private IntPtr FindWindowInThread(int threadId, Func<string, bool> compareTitle)
{
IntPtr windowHandle = IntPtr.Zero;
EnumThreadWindows(threadId, (hWnd, lParam) =>
{
StringBuilder text = new StringBuilder(200);
GetWindowText(hWnd, text, 200);
if (compareTitle(text.ToString()))
{
windowHandle = hWnd;
return false;
}
else
{
windowHandle = FindChildWindow(hWnd, compareTitle);
if (windowHandle != IntPtr.Zero)
{
return false;
}
}
return true;
}, IntPtr.Zero);
return windowHandle;
}
private IntPtr FindChildWindow(IntPtr hWnd, Func<string, bool> compareTitle)
{
IntPtr windowHandle = IntPtr.Zero;
EnumChildWindows(hWnd, (hChildWnd, lParam) =>
{
StringBuilder text = new StringBuilder(200);
GetWindowText(hChildWnd, text, 200);
if (compareTitle(text.ToString()))
{
windowHandle = hWnd;
return false;
}
return true;
}, IntPtr.Zero);
return windowHandle;
}
Last, start the processes:
String fileName = "myexe.exe";
String dir = Path.GetDirectoryName(fileName);
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo.FileName = fileName;
process.StartInfo.WorkingDirectory = dir;
process.Start();
// Wait for process to be created and enter idle condition
Thread.Sleep(5000);
IntPtr appWin = FindWindowInProcess(process, s => s.StartsWith("Built on"));
// Put it into this form
SetParent(appWin, this.Handle);
// Remove border and whatnot
SetWindowLong(appWin, GWL_STYLE, WS_VISIBLE);
// Move the window to overlay it on this window
MoveWindow(appWin, 0, 0, this.Width, this.Height, true);
See Is it possible to log message to cmd.exe in C#/.Net? for information how to create/attach to a console.
Also, look at e.g. Poderosa for an open source terminal emulator that you might be able to embed.

Categories

Resources