InternetExplorer being set up as __ComObject instead of SHDocVw.InternetExplorer - c#

I'm having a problem with the InternetExplorer in SHDocVw.dll. I also have mshtml.tlb referenced (while googling, I did read 1 comment that said I should have mshtml.dll referenced, but that this couldn't be done in Microsoft Visual Studio Express, I don't know how true this is though). Here's one small function in its most basic form that won't work for me:
public static HtmlElement GetDocumentControlByID(
ref SHDocVw.InternetExplorer IEObj,
string ControlID)
{
HtmlElement ReturnElement = IEObj.Document.GetElementById(ControlID);
return ReturnElement;
}
The problem is that when I create the IEObj instance, it is considered type System.__ComObject instead of SHDocVw.InternetExplorer, and all subparts are also of type System.__ComObject. When I try any of the following statements...
Document WebDoc = IEObj.Document;
HtmlElement ReturnElement = IEObj.Document.GetElementById(ControlID);
...I keep getting the same error message:
Cannot implicitly convert type 'System.__ComObject' to 'System.Windows.Forms.HtmlElement'
(obviously the convert-to type is different for the IEObj.Document).
I am new to c# (coming from VBA, so I am familiar with programming), but in VBA, the equivelant works perfectly without needing to convert it in any way.
Is it something I'm doing wrong? In case it's my creation of the object, the following is (roughly) the code I used to test the function:
public static void Main(String [] args)
{
SHDocVw.InternetExplorer IEObj = new SHDocVw.InternetExplorer();
IEObj.Navigate("http://sports.ladbrokes.com/");
while (IEObj.ReadyState != 4)
{
}
// There is a textbox that definitely exists
HtmlElement NetControl = GetDocumentControlByID(ref IEObj, "username");
// I was goint to manipulate it after this, but it crashes in the above function.
}
All I really want to do is latch onto various elements so that I can enter text into textboxes, click buttons etc. I would also need to be able to use the Document variables as well (like Document.Body.InnerHtml, etc). The whole project is to be a bunch of functions to be contained in a DLL to be referenced by other projects.

You're trying to use the WinForms HtmlElement class, which isn't a COM object.
You can't mix the native InternetExplorer COM object with the managed classes in WinForms.
You should use the WinForms classes (the WebBrowser control) instead.
In most cases, you don't need COM at all.

Related

Adding project dependencies to a solution file programmatically [duplicate]

Is there anyway that a reference can be added to a solution programmatically?
I have an add-in button, when the user presses it, I want a reference to be added.
The reason is, I have created a piece of software that I want to be integrated into any given VS program (if the developer wants it), they would simply click the add-in button and the reference would be loaded in the current solution.
Is this possible?
Something like this I haven't tested it
get the environment
EnvDTE80.DTE2 pEnv = null;
Type myType = Type.GetTypeFromProgID("VisualStudio.DTE.8.0");
pEnv = (EnvDTE80.DTE2)Activator.CreateInstance(myType, true);
get the solution.
Solution2 pSolution = (Solution2)pEnv.VS.Solution;
get the project that you want
Project pProject = pSolution.Projects[0];
add the reference
pProject.References.Add(string referenceFilePath);
There is an example on CodeProject.
The functionality is contained within a single class elRefManager and the method to call is CheckReferences. The code can be looked at here by selecting the elRefManager.cs file on the left hand side.
As seen in the article you could do...
private void button1_Click(object sender, System.EventArgs e)
{
int ec;
ec=elRefManager.CheckReferences(null, new string[] {textBox1.Text});
if (ec<0)
MessageBox.Show("An error occurred adding this reference");
if (ec>0)
MessageBox.Show("Could not add " + textBox1.Text +
"\nCheck its spelling and try again");
}
System.Assembly.load Allows you to call functions in a library that were not built with your program.
If you want to add a reference to the project so that its in the solution you can use the following. Basically the same as #Scots answer.
I did it in a macro which is vb but I'm sure you can get the idea
DTE.Windows.Item(Constants.vsWindowKindSolutionExplorer).Activate()
Dim objProject As EnvDTE.Project
Dim i As Long
i = DTE.Solution.Projects.Count
For Each objProject In DTE.Solution.Projects
If (objProject.Name() = "csCA") Then
Dim vsproj As VSLangProj.VSProject
vsproj = objProject.Object
vsproj.References.Add("C:\Users\test.dll")
End If
Next

CreateObject C#

I already have a native code COM object, and am trying to instantiate it from C#. The Com is registered in DCOM components and in the registry.
This is what I'v try to do:
public static void Main(string[] args)
{
Type t = Type.GetTypeFromProgID("CaseConsole.Application");
dynamic app = Activator.CreateInstance(t);
dynamic c = app.Case;
c.OpenCase("17-16", 0);
}
If I try to instantiate the com-type then, I get the:
NotImplementedException
The execption is thrown at line:
dynamic c = app.Case;
As I set a breakpoint at this line, I'v looked into "app" and the error was already present
I'v looked in the Type t and it shows: IsCOMObject = true
As VBS it works great:
Dim App
Dim c
Set App = CreateObject ("CaseConsole.Application")
Set c = App.Case
c.OpenCase "17-16", 0
As VB.net it works
Sub Main()
Dim App
Dim c
App = CreateObject("CaseConsole.Application")
c = App.Case
c.OpenCase("17-16", 0)
End Sub
but not in C#
For the C# example I looked at the sources from
http://www.codeproject.com/Articles/990/Understanding-Classic-COM-Interoperability-With-NE
https://msdn.microsoft.com/en-us/library/aa645736%28v=vs.71%29.aspx
Equivalent code of CreateObject in C#
My guess is that i must invoke the methods with InvokeMember or a security thing...
Please can you help to get the c# example working?
Update rename case to c but that wasn't the fault.
Your C# program is subtly different from the VBS and VB.NET programs. The apartment state of the thread that runs this code is different. The exception comes from the proxy, it should be interpreted as "running this code from a worker thread is not supported". Nicely done. Fix:
[STAThread]
public static void Main(string[] args)
{
// etc..
}
Note the added [STAThread] attribute. It is technically not correct to do this, a console mode app does not provide the correct kind of runtime environment for single-threaded COM objects but if the VB.NET app works then you have little to fear. If you ever notice deadlock then you'll know what to look for. It worked when you used late-binding through InvokeMember because that uses IDispatch and it has a different proxy.
Great thank you Sami Kuhmonen, the solution was:
object c = t.InvokeMember("Case", BindingFlags.GetProperty, null, app, null);
t.InvokeMember("OpenCase", BindingFlags.InvokeMethod, null, c, new object[] { "17-16", 0 });
so I didn't realized the fact, that "Case" ís a property.
Solved

Convert c# windows panel to c HWND

I have a dll which accepts HWND,
(code in the dll);
void VideoCapture::SetVideoWindow(HWND VidWind)
{
VideoWindow = VidWind;
}
i am calling the above dll in a sample c#.net application by adding the dll in references, in c#.net i have a form with Panel, is it possible to pass that panel to the dll?
i gave code as below in c#
VidCapWrapper.ManagedVideoCapture cc = new VidCapWrapper.ManagedVideoCapture();
cc.SetVideoWindow( panel1);
i am getting errors as below:
'Error 2 The best overloaded method match for 'VidCapWrapper.ManagedVideoCapture.SetVideoWindow(HWND__)' has some invalid arguments D:\DirectShow_Capture_GUI\DirectShow_Capture_GUI\Form1.cs 44 13 DirectShow_Capture_GUI
Error 3 Argument 1: cannot convert from 'System.Windows.Forms.Panel' to 'HWND__' D:\DirectShow_Capture_GUI\DirectShow_Capture_GUI\Form1.cs 44 32 DirectShow_Capture_GUI`
Can any one please tell me how to pass panel to dll, (any example will be good)?
(sorry i am very new to .net, but trying to create a sample app which shows available devices like integrated webcam... and then shows preview on c#.net form panel)
EDIT:
Thanks to Both #Blachshma and #Hans Passant, Now i am able to pass c# windows form's panel to my c++ dll.
I changed my func in dll as
void VideoCapture::SetVideoWindow(IntPtr windowHandle)
{
VideoWindow = (HWND)windowHandle.ToPointer();
}
and in c# i am calling it as
cc.SetVideoWindow(panel1.Handle);
You must be careful to not expose fundamentally unmanaged types like HWND to C# code. The C# compiler will not allow you to pass a value of such a type. The proper interop type here is IntPtr, it can store a handle value. So make your C++/CLI method look like this:
void VideoCapture::SetVideoWindow(IntPtr windowHandle)
{
VideoWindow = (HWND)windowHandle.ToPointer();
}
You can now simply pass panel1.Handle to the method, also of type IntPtr.
The panel has a Handle property you can use:
Panel p = new Panel();
IntPtr handle = p.Handle;
This should be enough if you set the SetVideoWindow signature to allow IntPtr i.e.
void VideoCapture::SetVideoWindow(IntPtr VidWind)
If you MUST use a HWND, you'll need to use an unsafe context, and cast it like this:
HWND hwnd=(HWND)this.Handle.ToPointer();
Here is an link with more information

Determining interop function caller

I'm exposing a C# class to COM using these attributes:
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
[GuidAttribute("2325EBEB-DB5F-4D29-B220-64845379D9C5")]
[ComSourceInterfaces(typeof(WrapperEvents))]
in this class I have a function:
public void shutdownService()
This function is meant to be called just once from a VB6 client via COM Interop. Everything works fine. But somehow, it's being called more than once. My C# codes doesn't call this function directly. So I'm guessing the problem is in VB6 code. Unfortunately, that's not what the VB6 team thinks.
Is there a way to determine the caller of this function, ie. from my C#code or the VB6 code?
Right now I'm using a simple function to get the stacktrace:
public void LogStack()
{
var trace = new System.Diagnostics.StackTrace();
foreach (var frame in trace.GetFrames())
{
var method = frame.GetMethod();
if (method.Name.Equals("LogStack")) continue;
logger.Debug(string.Format("LogStack: {0}::{1}",
method.ReflectedType != null ? method.ReflectedType.Name : string.Empty, method.Name));
}
}
Obviously, I got somthing like this on the log:
2011-12-23 08:28:40,067 1 DEBUG (null) LogStack: Service::shutdownService
Since the only line of LogStack is the COM exposed function, I assume it's being called from vb6. But that's not enough proof for the VB6 team. Any idea how to really prove where function ?
You can try several things:
set a breakpoint in your code to trigger the debugger, then look at the call stack.
You could do an application dump here from visual studio and send it to them or screenshot the stack.
ex. Debugger.Break
http://www.netsplore.com/PublicPortal/blog.aspx?EntryID=12
Dump with "Savre Dump As"
http://msdn.microsoft.com/en-us/library/d5zhxt22.aspx
Use the com tracing
from a system level see
http://support.microsoft.com/kb/926098
I also recall a tool being installed with visual studio 6 do to this as well

.NET2.0 C# Interop: How to call COM code from C#?

In my last development environment, I was able to easily interact with COM, calling methods on COM objects. Here is the original code, translated into C# style code (to mask the original language):
public static void SpawnIEWithSource(String szSourceHTML)
{
OleVariant ie; //IWebBrowser2
OleVariant ie = new InternetExplorer();
ie.Navigate2("about:blank");
OleVariant webDocument = ie.Document;
webDocument.Write(szSourceHTML);
webDocument.close;
ie.Visible = True;
}
Now begins the tedious, painful, process of trying to interop with COM from managed code.
PInvoke.net already contains the IWebBrower2 translation, the relavent porition of which is:
[ComImport,
DefaultMember("Name"),
Guid("D30C1661-CDAF-11D0-8A3E-00C04FC9E26E"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch),
SuppressUnmanagedCodeSecurity]
public interface IWebBrowser2
{
[DispId(500)]
void Navigate2([In] ref object URL, [In] ref object Flags, [In] ref object TargetFrameName, [In] ref object PostData, [In] ref object Headers);
object Document { [return: MarshalAs(UnmanagedType.IDispatch)] [DispId(0xcb)] get; }
}
I've created the COM class:
[ComImport]
[Guid("0002DF01-0000-0000-C000-000000000046")]
public class InternetExplorer
{
}
So now it's time for my actual C# transaction:
public static void SpawnIEWithSource(String szHtml)
{
PInvoke.ShellDocView.IWebBrowser2 ie;
ie = (PInvoke.ShellDocView.IWebBrowser2)new PInvoke.ShellDocView.InternetExplorer();
//Navigate to about:blank to initialize the browser
object o = System.Reflection.Missing.Value;
String url = #"about:blank";
ie.Navigate2(ref url, ref o, ref o, ref o, ref o);
//stuff contents into the document
object webDocument = ie.Document;
//webDocument.Write(szHtml);
//webDocument.Close();
ie.Visible = true;
}
The careful readers notice that IWebBrowser2.Document is a late-bound IDispatch.
We're using Visual Studio 2005, with .NET 2.0 on our, and our customer's, machines.
So what's the .NET 2.0 method to invoke methods on an object that, on some level, only supports late-bound IDispatch?
A quick search of Stack Overflow for using IDispatch from C# turns up this post saying what I want is not possible in .NET.
So is it possible to use COM from C# .NET 2.0?
The question is that there is an accepted design pattern that I want to use in C#/.NET. It involves launching Internet Explorer out of process, and giving it HTML content, all the while not using temporary files.
A rejected design idea is hosting Internet Explorer on a WinForm.
An acceptable alternative is launching the system registered web browser, giving it HTML to display, without using a temporary file.
The stumbling block is continuing to use COM objects in the .NET world. The specific problem involves performing late-binding calls to IDispatch without needing C# 4.0. (i.e. while using .NET 2.0)
Update: Based on question updates, I have removed the portions of my answer that are no longer relevant to the question. However, in case other readers are looking for a quick and dirty way to generate HTML in a winforms app and do not require an in-process IE, I will leave the following:
Possible Scenario 1: The ultimate goal is to simply display HTML to your end user and are using Windows Forms
System.Windows.Forms.WebBrowser is the painstakingly easy .NET wrapper for the interface you are trying to manually implement. To get it, Drag and drop an instance of that object from your toolbar (listed as "Web Browser" under the "All Windows Forms" section) onto your form. Then, on some suitable event handler:
webBrowser1.Navigate("about:blank");
webBrowser1.Document.Write("<html><body>Hello World</body></html>");
On my test app, this correctly displayed the haunting message we all have learned to fear and loath.
Late bound IDispatch called is relativly easy in .NET, although piss-poor:
public static void SpawnIEWithSource(String szHtml)
{
// Get the class type and instantiate Internet Explorer.
Type ieType = Type.GetTypeFromProgID("InternetExplorer.Application");
object ie = Activator.CreateInstance(ieType);
//Navigate to the blank page in order to make sure the Document exists
//ie.Navigate2("about:blank");
Object[] parameters = new Object[1];
parameters[0] = #"about:blank";
ie.GetType().InvokeMember("Navigate2", BindingFlags.InvokeMethod | BindingFlags.IgnoreCase, null, ie, parameters);
//Get the Document object now that it exists
//Object document = ie.Document;
object document = ie.GetType().InvokeMember("Document", BindingFlags.GetProperty | BindingFlags.IgnoreCase, null, ie, null);
//document.Write(szSourceHTML);
parameters = new Object[1];
parameters[0] = szHtml;
document.GetType().InvokeMember("Write", BindingFlags.InvokeMethod | BindingFlags.IgnoreCase, null, document, parameters);
//document.Close()
document.GetType().InvokeMember("Close", BindingFlags.InvokeMethod | BindingFlags.IgnoreCase, null, document, null);
//ie.Visible = true;
parameters = new Object[1];
parameters[0] = true;
ie.GetType().InvokeMember("Visible", BindingFlags.SetProperty | BindingFlags.IgnoreCase, null, ie, parameters);
}
The referenced SO question that originally said "not possible until C# 4.0" was amended to show how it is possible in .NET 2.0.
Does C# .NET support IDispatch late binding?
The answers in the post that you link to are actually incorrect. It is generally very easy to deal with IDispatch based objects in .Net. Basically you go through three steps:
Most automation objects (probably well over 90%) that are exposed as IDispatch interfaces have other interfaces that can be used by non-scripting type COM clients (either the IDispatch interface is actually a full COM interface derived from IDispatch or the object supports one or more other IUnknown derived interfaces). In this case, you would simply import the appropriate COM interface definition and then cast the object to the appropriate interface. The cast calls QueryInterface under the covers and returns a wrapped reference to the interface you want.
This is the technique you would use in the scenario that you presented above. The Document object that is returned from the IE automation object supports the IHTMLDocument, IHTMLDocument2, IHTMLDocument3, IHTMLDocument4 and IHTMLDocument5 interfaces (depending on the version of IE you are using). You should cast to the appropriate interface and then call the appropriate method. For example:
IHTMLDocument2 htmlDoc = (IHTMLDocument2)webDocument;
htmlDoc.Write(htmlString);
htmlDoc.Close();
In the rare case where the automation object does not support an alternative interface. Then you should use VB.Net to wrap that interface. With Option Strict set to off (for the wrapper class only) you can use VB's built in support for late bound calls to simply call the appropriate IDispatch methods under the covers. In rare cases with unusual argument types you may need to fiddle a bit with the call but, in general, in VB you can just do it! Even with the dynamic additions to C# v4 VB will still probably have significantly better support for late-bound COM calls.
If for some reason you can't use VB to wrap the automation interface then you can still make any necessary calls from C# using reflection. I won't go into any details since this option should basically never be used but here is a small example involving Office automation.
See this article :
http://www.codeproject.com/KB/cs/IELateBindingAutowod.aspx
Internet Explorer Late Binding Automation
By yincekara
Internet Explorer automation sample code using late binding, without Microsoft.mshtml and shdocvw dependency.
for htmlDoc.write(htmlString);
modify
[Guid("332C4425-26CB-11D0-B483-00C04FD90119")]
[ComImport]
[TypeLibType((short)4160)]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)]
internal interface IHTMLDocument2
{
[DispId(1054)]
void write([MarshalAs(UnmanagedType.BStr)] string psArray);
//void write([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)] object[] psarray);

Categories