WebBrowser control memory leak - c#

I have problem with memory leak in webBrowser control.
I have found this thread:
How to get around the memory leak in the .NET Webbrowser control?
and this:
//dispose to clear most of the references
this.webbrowser.Dispose();
BindingOperations.ClearAllBindings(this.webbrowser);
//using reflection to remove one reference that was not removed with the dispose
var field = typeof(System.Windows.Window).GetField("_swh", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var valueSwh = field.GetValue(mainwindow);
var valueSourceWindow = valueSwh.GetType().GetField("_sourceWindow", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(valueSwh);
var valuekeyboardInput = valueSourceWindow.GetType().GetField("_keyboardInputSinkChildren", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(valueSourceWindow);
System.Collections.IList ilist = valuekeyboardInput as System.Collections.IList;
lock(ilist)
{
for (int i = ilist.Count-1; i >= 0; i--)
{
var entry = ilist[i];
var sinkObject = entry.GetType().GetField("_sink", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (object.ReferenceEquals(sinkObject.GetValue(entry), this.webbrowser.webBrowser))
{
ilist.Remove(entry);
}
}
}
But I'm using Windows.Forms no WPF window and i have problem with converting this code to my needs. Can somebody help me?

We have used Chromium in a couple of applications. This allowed us to run HTML 5 in WinXP. Since the webBrowser control uses the installed IE of the OS you can't use most of the better HTML/Javascript. Microsoft doesn't support WinXP's IE so the application only can access older versions of IE.
If you use the CEFSharp version of Chromium you can even compile in further mods and aids for your navigation which gives you improved embedded communication that isn't supported by IE.
The code is really simple and there are several examples but just look:
InitializeComponent();
Text = "CefSharp";
web_view = new WebView("https://github.com/perlun/CefSharp", new BrowserSettings());
web_view.Dock = DockStyle.Fill;
toolStripContainer.ContentPanel.Controls.Add(web_view);
//even setup the console to log to a Textbox for debugging by setting up a Handler.
web_view.ConsoleMessage += new CefSharp.ConsoleMessageEventHandler(ConsoleMessageHandler);

We faced that problem some time ago... to no avail.
To work around the problem and keep our application's memory consumption at a reasonable level we decided to split our application in two kind of processes, one for the main window and N child processes to host the WebBrowserControl. Then, design a pipe protocol (or RMI/RPC-like) to communicate events from the main window to the child processes and vice versa.
Doing that, you can design a recycle strategy using a pool of browser processes and a background kill-and-spawn policy to get the memory consumption at a controlled level.

Have you considered using other web browser controls? There are a lot out there! I feel like chromium is a good bet, check out this question for alternatives.

Related

WPF Window not following mouse when expanding

On a new blank WPF app, I noticed that the resizing the window with mouse does not work properly.
When I'm shrinking the window, it works fine. But when I'm expanding it, the window does not follow the mouse until I leave the button, and then the window suddenly jumps to the new size.
It seems that if I move the cursor really slowly while expanding, the window follows the cursor; but a bit faster than that and it doesn't anymore.
You can see the issue in a video I recorded here.
I tried .NET versions 4.7.2, 4.7.1 and 4.6. All are having the same issue.
I've tried to look this up, but found nothing about this issue anywhere. Does anyone know what is causing this, and how can one fix it?
There's a possibility that this issue is somehow isolated to my machine, and therefore caused by something other than the framework itself. So another relevant question might be whether anyone else sees this behavior on a new WPF project or not.
Update: This is the behavior seen in a new blank WPF project, with no code changes at all. And the expected behavior is obviously for the window to follow the cursor while expanding, just like it already does when shrinking.
Update 2: As #gore85 mentioned, this might be an issue with Windows 10 version 1903. I was on 1903 insider preview builds when I posted this question (it's out now) and he's seeing the same thing after updating to 1903.
There is a fix for this issue now.
https://support.microsoft.com/en-us/help/4517211/windows-10-update-kb4517211
Addresses an issue with resizing Windows Presentation Foundation (WPF)
applications; they may not respond to being resized using the mouse
until you release the mouse button.
It seems to stem from a faulty tablet input support. Someone devised a workaround (without using the update):
public static void DisableWPFTabletSupport()
{
// Get a collection of the tablet devices for this window.
TabletDeviceCollection devices = System.Windows.Input.Tablet.TabletDevices;
if (devices.Count > 0)
{
// Get the Type of InputManager.
Type inputManagerType = typeof(System.Windows.Input.InputManager);
// Call the StylusLogic method on the InputManager.Current instance.
object stylusLogic = inputManagerType.InvokeMember("StylusLogic",
BindingFlags.GetProperty | BindingFlags.Instance |
BindingFlags.NonPublic,
null, InputManager.Current, null);
if (stylusLogic != null)
{
// Get the type of the stylusLogic returned
// from the call to StylusLogic.
Type stylusLogicType = stylusLogic.GetType();
// Loop until there are no more devices to remove.
while (devices.Count > 0)
{
// Remove the first tablet device in the devices collection.
stylusLogicType.InvokeMember("OnTabletRemoved",
BindingFlags.InvokeMethod |
BindingFlags.Instance | BindingFlags.NonPublic,
null, stylusLogic, new object[] { (uint)0 });
}
}
}
}

System.Windows.Automation is very slow at enumerating table rows vs. UIAutomationCore

I am trying to do automated testing of my application via UI Automation (mainly using TestStack.White to provide a friendly interface; it uses System.Windows.Automation as a back-end). I have a table with ~200 rows that I need to test the values of (actually I only want to test the first and last couple rows). I have discovered that using COM-interop UIAutomationCore by itself, I can enumerate the rows in a fraction of a second, but only when I don't use White or System.Windows.Automation. As soon as System.Windows.Automation initializes, future UI Automation actions to enumerate rows are slow:
First COM run: it took 0.04 seconds to get 102 rows!
First System.Windows.Automation run: it took 7.18 seconds to get 102 rows!
Second COM run: it took 7.87 seconds to get 102 rows!
I created a simple WinForms test application (TableTest.exe to verify that it was System.Windows.Automation and not something to do with my application:
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var form = new Form() { Text = "TableTest", WindowState = FormWindowState.Maximized };
var dgv = new DataGridView() { Name = "DGV", Dock = DockStyle.Fill, AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill };
dgv.Columns.Add("i", "i");
dgv.Columns.Add("2i", "2i");
dgv.Columns.Add("i^2", "i^2");
dgv.Columns.Add("i^i", "i^i");
for (int i = 0; i < 100; ++i)
dgv.Rows.Add(i, i * 2, i * i, Math.Pow(i, i));
form.Controls.Add(dgv);
Application.Run(form);
}
Then I created another test app to test the first one. It works as either a console app or a WinForms app. First I test with COM automation, then with System.Windows.Automation, then again with COM automation. As you can see from the output I quoted above, the first block executes very quickly, the next two blocks execute excruciatingly slowly. If I comment out the System.Windows.Automation block code then both COM blocks execute quickly.
using UIA = Interop.UIAutomationCore;
static void Main(string[] args)
{
var process = System.Diagnostics.Process.Start("TableTest.exe");
System.Threading.Thread.Sleep(500);
var uia = new UIA.CUIAutomation();
var rootCom = uia.GetRootElement();
var windowCom = rootCom.FindFirst(UIA.TreeScope.TreeScope_Children, uia.CreatePropertyCondition(UIA.UIA_PropertyIds.UIA_NamePropertyId, "TableTest"));
var dgvCom = windowCom.FindFirst(UIA.TreeScope.TreeScope_Descendants, uia.CreatePropertyCondition(UIA.UIA_PropertyIds.UIA_AutomationIdPropertyId, "DGV"));
var start = DateTime.Now;
var rowCount = dgvCom.FindAll(UIA.TreeScope.TreeScope_Children, uia.CreatePropertyCondition(UIA.UIA_PropertyIds.UIA_ControlTypePropertyId, UIA.UIA_ControlTypeIds.UIA_CustomControlTypeId)).Length;
var elapsed = (DateTime.Now - start).TotalSeconds;
Console.WriteLine(String.Format("It took {0} seconds to get {1} rows!", elapsed.ToString("f2"), rowCount));
process.Kill();
process = System.Diagnostics.Process.Start("TableTest.exe");
System.Threading.Thread.Sleep(500);
var root = AutomationElement.RootElement;
var window = root.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "TableTest"));
var dgv = window.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, "DGV"));
start = DateTime.Now;
rowCount = dgv.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom)).Count;
elapsed = (DateTime.Now - start).TotalSeconds;
Console.WriteLine(String.Format("It took {0} seconds to get {1} rows!", elapsed.ToString("f2"), rowCount));
process.Kill();
process = System.Diagnostics.Process.Start("TableTest.exe");
System.Threading.Thread.Sleep(500);
uia = new UIA.CUIAutomation();
rootCom = uia.GetRootElement();
windowCom = rootCom.FindFirst(UIA.TreeScope.TreeScope_Children, uia.CreatePropertyCondition(UIA.UIA_PropertyIds.UIA_NamePropertyId, "TableTest"));
dgvCom = windowCom.FindFirst(UIA.TreeScope.TreeScope_Descendants, uia.CreatePropertyCondition(UIA.UIA_PropertyIds.UIA_AutomationIdPropertyId, "DGV"));
start = DateTime.Now;
rowCount = dgvCom.FindAll(UIA.TreeScope.TreeScope_Children, uia.CreatePropertyCondition(UIA.UIA_PropertyIds.UIA_ControlTypePropertyId, UIA.UIA_ControlTypeIds.UIA_CustomControlTypeId)).Length;
elapsed = (DateTime.Now - start).TotalSeconds;
Console.WriteLine(String.Format("It took {0} seconds to get {1} rows!", elapsed.ToString("f2"), rowCount));
process.Kill();
}
What the heck is System.Windows.Automation doing that kills the performance of UI Automation? I've looked at the White source code and I don't see anything obvious. I can't profile System.Windows.Automation itself because I can't find any PDB for it. I'm not very familiar with UI Automation so maybe it'll be obvious to someone else. The White is: 0.13.0.0 and I'm testing on 64-bit Windows 7.
I cannot answer your question. But many people will come here from Google who search for the keywords "uiautomation slow" and the first result in Google is your question. (You wrote a bestseller)
For all those coming from Google and struggling with slow UIAutomation I post this answer.
System.Windows.Automation is EXTREMELY slow. Obtaining 30 child elements may take 1000ms on a very fast computer! I have even seen it hanging forever while getting the child elements of a Tree in a QT application.
Apart from that the implementation is not even thread safe.
System.Windows.Automation is deprecated. Do not use it!
In the MSDN you find the following note:
UI Automation was first available in Windows XP as part of the
Microsoft .NET Framework. Although an unmanaged C++ API was also
published at that time, the usefulness of client functions was limited
because of interoperability issues. For Windows 7, the API has been
rewritten in the Component Object Model (COM).
Although the library functions introduced in the earlier version of
UI Automation are still documented, they should not be used in new
applications.
The solution to slow performance is to use the new IUIAutomationElement COM interface instead of the old System.Windows.Automation C# interface. After that the code will be running lightning fast!
Apart from that the new interface offers much more patterns and Microsoft is extending it continously. In the Windows 10 SDK (UIAutomationClient.h and UIAutomationCore.h) several patterns and properties have been added which are not available in the .NET Automation framework.
The following patterns are available in the COM version of UIAutomation which do not exist in System.Windows.Automation:
IUIAutomationLegacyIAccessiblePattern
IUIAutomationObjectModelPattern
IUIAutomationAnnotationPattern
IUIAutomationTextPattern2
IUIAutomationStylesPattern
IUIAutomationSpreadsheetPattern
IUIAutomationSpreadsheetItemPattern
IUIAutomationTransformPattern2
IUIAutomationTextChildPattern
IUIAutomationDragPattern
IUIAutomationDropTargetPattern
IUIAutomationTextEditPattern
IUIAutomationCustomNavigationPattern
Additionally the following Control types have been added:
AppBar
SemanticZoom
Additionally the following Element's have been added:
IUIAutomationElement2
IUIAutomationElement3
IUIAutomationElement4
The examples you posted do not use White... FWIW, White uses recursive calls to Automation.Find requesting the children each time. This returns valid results but is slower than requesting the subtree from the appropriate parent node - note that the root node is never the 'appropriate' node from which to request the subtree (see the note on MSDN). Since your example only requests the children once, that is not the issue.

Use a proxy with webBrowser control C#/.net 3.5

I need some help from someone who has already use the webBrowser control along with a proxys.
What I need is the following.
1 - Set a proxy for a webBrowser control.
2 - Load a specific site.
3 - Execute a routine over the site.
4 - Set a diferent proxy for the webBrowser control.
5 - Load another site.
6 - Execute the same routine from point number 3.
And the process keeps in that way, looping from a list of proxys, until all of them had been used.
But. I'm having some problems with the app. to do that:
1 - I'm using the code attached to set the proxy into the webBrowser control, but seems to work only once during the execution, when I call it again in the loop it just doesn't work, I can get t ounderstand why.
2 - I'm having problems to determine when the page has loaded completely, I mean, when I set the first site to load, I need the program to wait until it has finish to load, and after that execute the routine over it, and continue with the process.
Hope some one could help me with this...
/// The function that I'm using -----------------------------
private void SetProxy(string Proxy)
{
MessageBox.Show("Setting :" + Proxy);
string key = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
RegistryKey RegKey = Registry.CurrentUser.OpenSubKey(key, true);
RegKey.SetValue("ProxyServer", Proxy);
RegKey.SetValue("ProxyEnable", 1);
}
// The app logic --------------------------------------
SetProxy("190.97.219.38:80");
webBrowser1.Navigate("http://www.whatismyip.com/");
ExecuteRoutine();
SetProxy("187.93.77.235:80");
webBrowser1.Navigate("http://www.whatismyip.com/");
ExecuteRoutine();
SetProxy("109.235.49.243:80");
webBrowser1.Navigate("http://www.whatismyip.com/");
ExecuteRoutine();
Perhaps this link is useful:
http://blogs.msdn.com/b/jpsanders/archive/2011/04/26/how-to-set-the-proxy-for-the-webbrowser-control-in-net.aspx
I tested the code and it seemed to work. But two points are important:
It's not compatible to projects in compile mode "Any CPU" (x86 works fine)
JUST for HTTP proxy servers ; not for SOCKS
1- I guess webBrowser control checks the proxy only while its is created, so create a new control after setting the proxy
2- Navigate is not a blocking call and does not wait till page it loaded, use webBrowser.DocumentCompleted event
Below code should work (Not tested)
void Exec(string proxy,string url)
{
var th = new Thread(() =>
{
SetProxy(proxy);
using (WebBrowser wb = new WebBrowser())
{
wb.DocumentCompleted += (sndr, e) =>
{
ExecuteRoutine();
Application.ExitThread();
};
wb.Navigate(url);
Application.Run();
}
});
th.SetApartmentState(ApartmentState.STA);
th.Start();
th.Join();
}
I had a somewhat similar question in the past. The accepted answer for the question suggests to take a look at this Microsoft Knowledge Base article:
"How to programmatically query and set proxy settings under Internet Explorer"
Basically, you have to do some P/Invoke and call some WinInet DLL functions. Although I never tried it in a real-world project, I strongly assume that this is the way to go.
Just to let you all know, this guy has posted 5 question, all asking the same thing, and based on his first question and how badly he was knocked down, it seems he is trying to commit some type of cybercrime. Now, based on my reading of his intellect, he'll probably end up in prison really quickly, but I'm just thinking perhaps we can save him from that by letting him know that it's not possible to provide an imaginary IP address to services you are communicating with (since if you did, the service will not be able to reach you to provide a response). Here is his entertaining list:
https://stackoverflow.com/questions/12045317/how-to-hide-my-ip-address-c-net-3-5
Use a proxy with webBrowser control C#/.net 3.5
how to pass ip-address to webBrowser control
how to use custom ip address to browse a web page c#/.net
https://stackoverflow.com/questions/12019890/how-to-load-webpage-using-user-provided-ipaddress-webbrowser-control-c-net
And now, I think he has created a new username, user1563019, with more proxy/settings questions below:
https://stackoverflow.com/users/1563019/user1563019

Should I call Application.EnableVisualStyles() on terminal services?

In a terminal services/citrix environment, should I call
Application.EnableVisualStyles() in my .NET 3.5 WinForms app when my program
starts? Or, is it better to refrain from doing that?
I am looking for the option that gives the best performance, and do not need any controls drawn with
themes.
Visual styles are the colors, fonts, and other visual elements that form an operating system theme. Controls will draw with visual styles if the control and the operating system support it. To have an effect, EnableVisualStyles() must be called before creating any controls in the application; typically, EnableVisualStyles() is the first line in the Main function.
So, if you need to have your application look in line with the current OS theme, you need to call this. If the classic Windows look is enough for you, you can skip this. I personally never enable visual styles for my server-only apps (like control panels, etc.).
Below is a configurator tool without the visual styles enabled. It's good looking for me this way so EnableVisualStyles was skipped:
A quick look into Application.EnableVisualStyles() method with reflector revealed below code in the method EnableVisualStyles -> EnableVisualStylesInternal -> CreateActivationContext:
if (!contextCreationSucceeded && OSFeature.Feature.IsPresent(OSFeature.Themes))
{
enableThemingActivationContext = new ACTCTX();
enableThemingActivationContext.cbSize = Marshal.SizeOf(typeof(ACTCTX));
enableThemingActivationContext.lpSource = dllPath;
enableThemingActivationContext.lpResourceName = (IntPtr) nativeResourceManifestID;
enableThemingActivationContext.dwFlags = 8;
hActCtx = CreateActCtx(ref enableThemingActivationContext);
contextCreationSucceeded = hActCtx != new IntPtr(-1);
}
If OSFeature.Feature.IsPresent(OSFeature.Themes) returns false, EnableVisualStyles has absolutely no effect so calling it or not makes no difference.

Better way to "dock" a third party running application inside a windows.forms panel?

I am currently doing this as follows:
// _Container is the panel that the program is to be displayed in.
System.Diagnostics.Process procTest = new System.Diagnostics.Process();
procTest.StartInfo.FileName = "TEST.EXE";
procTest.StartInfo.CreateNoWindow = false;
procTest.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
procTest.Start();
procTest.WaitForInputIdle();
SetParent(procTest.MainWindowHandle, _Container.Handle);
MoveWindow(procTest.MainWindowHandle,
0, 0, _Container.Width, _Container.Height, true);
The problem I am having with this code is that some parts of the application UI no longer function properly once I change the MainWindowHandle (ie: buttons missing text).
Is there a way to do this without causing issues with the docked application? (Either through .net or user32)?
First of all, instead of simply waiting 1.5 seconds, try calling procTest.WaitForInputIdle to wait until its message loop is free. You already are.
In general, I don't think it's possible to do this without modifying the program that you're hosting.
EDIT: You could try to keep the other program above your hosting area by hiding in from the taskbar, removing its title bar, moving it as your program moves, etc. However, this still wouldn't work perfectly; I recommend that you try to find some alternative.
Try contacting the original developers of the third-party application and asking for their advice.

Categories

Resources