I am trying to use an OLE COM object that I don't have any documentation for. I load the com object dynamically by:
dynamic comObj = Activator.CreateInstance(Type.GetTypeFromProgID("The Program ID"));
The Program ID in the registry points to some exe (or at least that is what I think, I cannot tell for sure. Is there a way to know where exactly does it point to). I tried loading the exe in an OLE COM viewer, but I was not able to get any useful information out of it. For example, I was not able to find a method that I knew for sure was there. Actually, after loading the comObject I am able to invoke this method without any problems, but I would like to know if there is a way that I can list/view all the members of this COM object.
Given ProgID you can use API functions and/or look up registry (such as under HKEY_CLASSES_ROOT key) for COM server CLSID and binary that hosts the class, including full path to executable.
If you have a reference to a type library there, you can also load it and check implemented interfaced and interface information. You can also possibly obtain this information from an instance of COM object too, provided that it implements interfaces like IDispatch, IDispatchEx, IProvideClassInfo.
You can enumerate all of the methods through the IDispatch interface, if it supports it.
Here is an MSDN article that uses IDispatch to get member info
I don't remember where I copied this source. I beg a pardon from the author.
using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Runtime.InteropServices.CustomMarshalers;
namespace ConsoleApplication1
{
[
ComImport,
Guid("00020400-0000-0000-C000-000000000046"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)
]
public interface IDispatch
{
void Reserved();
[PreserveSig]
int GetTypeInfo(uint nInfo, int lcid,
[MarshalAs(
UnmanagedType.CustomMarshaler,
MarshalTypeRef = typeof(TypeToTypeInfoMarshaler))]
out System.Type typeInfo);
}
class Program
{
static void Main(string[] args)
{
Type t1 = Type.GetTypeFromProgID("WbemScripting.SWbemDateTime");
Object o1 = Activator.CreateInstance(t1);
IDispatch disp2 = o1 as IDispatch;
if (disp2 != null)
{
Type t3;
disp2.GetTypeInfo(0, 0, out t3);
MemberInfo[] mlist3 = t3.GetMembers();
}
}
}
}
You can find the CustomMarshalers.dll in C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\
Related
I have been given a library written in C# and I need to use it in a C++ project. The C# library has been exported to a .tlb type library, which I can successfully import into my C++ project by using the #import directive.
Being utterly unfamiliar with COM I can't for the life of me figure out how to get at static member functions on any classes. Here's how I access it in C#:
void Function()
{
StaticClass.StaticMethod();
}
And then you get into the C++ side, what gets generated in the .tlh file is:
struct __declspec(uuid("some big long thing"))
/* dual interface */_StaticClass;
//long while later
_COM_SMARTPTR_TYPEDEF(_StaticClass, __uuidof(_StaticClass));
So I'm trying to figure out how to get use of the static class and haven't had any luck with Google. The only example anywhere else in any other project I have access to gives me something similar to this:
_StaticClassPtr s = _StaticClassPtr(__uuidof(_StaticClass));
but the example I have isn't for a static class anyway.
Basically I'm stuck with nowhere to even really start. This fails with "Unhandled exception at in <executable>: Microsoft C++ exception: _com_error at memory location <location>"
Edit: Since #dxiv informed me static methods aren't usable with COM interop, there's another option marked 'obsolete' that does not use static members -- problem is I get exactly the same exception when I construct the instance with similar syntax:
IInstanceClassPtr p = _IInstanceClassPtr(__uuidof(_InstanceClass));
The same exception is thrown, "_com_error at memory location"
Reading your question you would like to use C# using COM Interop. What I done is something like this. Starting from the assumption that COM is the acronym of Component Object Model and to use it you need an instantiable, local or remote, object. The example below creates an "in-proc" instance of the CLR object.
Create an interface which is exposed to COM:
namespace MyNamespace
{
/// <summary>
/// Provides an entry point for COM clients.
/// </summary>
[ComVisible(true)]
[Guid("A9E6D7FE-34FD-4A6B-9EB2-DC91F4AE567B")]
public interface IMyAccessor
{
void ExecuteStaticMethod();
// add anything else like methods, property,
}
}
then implement the interface in a C# class:
namespace MyNamespace
{
/// <summary>
/// The implementation of IMyAccessor.
/// </summary>
[ComVisible(true)]
[Guid("C65A7F81-641C-4F17-B34A-DEB88B4158E8")]
[ProgId("MyCompany.MyAccessor")]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IMyAccessor))]
public sealed class MyAccessor: IMyAccessor
{
public void ExecuteStaticMethod() { StaticClass.StaticMethod(); }
}
}
export the TLB and import it in C++ project (MyAccessor is only a name I used here) in the header file of your C++ class using the following clause:
#import "MyAccessor.tlb"
Within the class header add a line like the following:
MyNamespace::IMyAccessorPtr m_IMyAccessor;
And in the class implementation use the following:
HRESULT hr = m_IMyAccessor.CreateInstance(__uuidof(MyNamespace::MyAccessor));
if (FAILED(hr))
{
// do something if failed
}
m_IMyAccessor->ExecuteStaticMethod(); // this will execute your static method in C#
NOTE: when exporting the TLB use the correct switchs. In an x64 environment (/win64) must be used to have the right pointer size: normally tlbexp returns pointer usable in a 32bit environment. This is important if you want to extend the class with more sofistcated methods.
NOTE 2: if the returned HRESULT from CreateInstance is something like "class not registered", remember to execute the registration of the TLB wih REGTLIB.
We have a .Net assembly, A, that makes use of a class Foo from a COM library, B.
We've created an interop for B (Interop.b.dll) and A makes use of Foo via the interop.
If I decompile Interop.b.dll I can see the following interface defined within it:
using System.Runtime.InteropServices;
namespace Interop.b
{
[Guid("SOME-GUID")]
[CoClass(typeof (FooClass))]
[ComImport]
public interface Foo : _Foo
{
}
}
In the references settings of .Net assembly A, I have the option to embed the interop types for Interop.b.dll. If I set this to true the interfaces defined within Interop.b.dll are embedded within A.dll. If I then decompile A.dll I can see the same interface as I found in the interop:
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Interop.b
{
[CompilerGenerated]
[Guid("SOME-GUID")]
[CoClass(typeof (object))]
[TypeIdentifier]
[ComImport]
public interface Foo : _Foo
{
}
}
Except that it's not the same. In this version we have [TypeIdentifier] as an additional attribute and the parameter of the CoClass attribute has changed from typeof(FooClass) to typeof(object).
I note that Interop.b.dll contains the type FooClass, which I believe is a wrapper class for the COM type Foo that is responsible for marshalling parameter types between .Net and COM, but this type has not been embedded in A.dll.
Within A.dll, Foo is used like this:
using Interop.b;
namespace My.Product
{
public class AClass: IAClass
{
private Foo LocalFoo { get; }
public AClass()
{
LocalFoo = new Foo();
}
}
}
On a clean install, this fails with the following exception:
System.InvalidCastException: Unable to cast COM object of type 'System.__ComObject' to interface type 'Interop.b.Foo'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{SOME-GUID}' failed due to the following error: Error loading type library/DLL. (Exception from HRESULT: 0x80029C4A (TYPE_E_CANTLOADLIBRARY)).
(Interop.b.dll has been installed to the GAC by the product installer)
On a development machine, reinstalling the product and running from installed binaries fails in the same way. Subsequently recompiling the product, which overwrites the installed binaries, and running from either code (in debug) or the fresh binaries then works.
Now, I believe the error is that the code in A.dll should be instantiating FooClass rather than Foo. i.e. something like this:
LocalFoo = new FooClass();
...because Foo is an interface (in the interop) and FooClass is the thing that handles the marshalling of the parameter types between .Net and COM.
So, questions:
1) Am I correct that the code should be instantiating FooClass rather than Foo?
2) Why does it work at all on a dev machine?
3) Why is the embedded interface using typeof(object) instead of typeof(FooClass)?
4) What is the benefit of embedding the interfaces from the interop in A.dll when we will still need Interop.b.dll on the target machine to allow us to make use of FooClass?
I have created an ActiveX dll assembly that launches an application. I have called this dll via JavaScript in browser. The app is launched on click of URL.
For this script to work, I need to go to the IE settings and enable "Initialize and script activex controls not marked as safe". Is there any way to code the ActiveX object to mark it as safe?
I have used the SN tool to generate a key (.snk file), compiled the DLL with this key. So the dll is signed but yet it doesnot run unless I enable "Initialize and script activex controls not marked as safe" in IE settings.
This is the article which I have implemented and would like to mark it as safe:- Writing an ActiveX control in C#.
I have got a reference to the safe controls concept (IObjectSafety) on this microsoft link:-
I am not able to understand as to where this code needs to be placed. Any help would make it more clear..
I picked up pieces of code (and knowledge) from different places and here is my working version
- a interface:
[Serializable, ComVisible(true)]
public enum ObjectSafetyOptions
{
INTERFACESAFE_FOR_UNTRUSTED_CALLER = 0x00000001,
INTERFACESAFE_FOR_UNTRUSTED_DATA = 0x00000002,
INTERFACE_USES_DISPEX = 0x00000004,
INTERFACE_USES_SECURITY_MANAGER = 0x00000008
}
//
// MS IObjectSafety Interface definition
//
[
ComImport(),
Guid("CB5BDC81-93C1-11CF-8F20-00805F2CD064"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)
]
public interface IObjectSafety
{
[PreserveSig]
long GetInterfaceSafetyOptions(ref Guid iid, out int pdwSupportedOptions, out int pdwEnabledOptions);
[PreserveSig]
long SetInterfaceSafetyOptions(ref Guid iid, int dwOptionSetMask, int dwEnabledOptions);
}
and the implementation:
#region safe for scripting
private ObjectSafetyOptions m_options = ObjectSafetyOptions.INTERFACESAFE_FOR_UNTRUSTED_CALLER | ObjectSafetyOptions.INTERFACESAFE_FOR_UNTRUSTED_DATA;
public long GetInterfaceSafetyOptions(ref Guid iid, out int pdwSupportedOptions, out int pdwEnabledOptions)
{
pdwSupportedOptions = (int)m_options;
pdwEnabledOptions = (int)m_options;
return 0;
}
public long SetInterfaceSafetyOptions(ref Guid iid, int dwOptionSetMask, int dwEnabledOptions)
{
return 0;
}
#endregion safe for scripting
Now IE does not nag me with the confirmation dialog.
The following link has a perfect example which is answer to creating as well as marking an activeX control as Safe:-Creating activex objects with c#
Creating activex objects with c#
The purpose of this guide is to provide simple steps for
creating a simple ActiveX Control that can be loaded and used within an IE
hosted web page.
Background
ActiveX is a Microsoft technology used for creating
re-usable components. This technology, also called Object Linking and Embedding
(OLE), along with COM is heavily used within the Microsoft environment and
application development. For the duration of this article, I will use the term
COM to encompass all similar Microsoft related technologies: ActiveX, OLE,
Automation, etc… as it is the framework by which components are created. Anyone
interested in creating reusable components should have some familiarity with
COM. A very good resource is “Essential COM” by Don Box.
The idea behind COM is to allow the creation of components
that can be consumed by a variety of clients. This means that I can create a
component using C++ which can then be used by an application written in VB, or
even a web page which we will be performing shortly. Traditionally, ActiveX
objects were usually written in C++ and VB but can actually be written in any
language as long as they are COM-aware.
This means that we can create COM objects with the .NET
platform. A survey of resources/tutorials on COM and .NET on the internet by and
large only tell you how to consume unmanaged COM objects via Interop. So my aim
with this guide is to show how to use this powerful feature. Also, FYI, there is
a .NET technology very similar to what will be describe below which allows
hosting Windows Forms Controls inside of IE (don’t know the official term/name
for this).
That’s enough babbling lets get going with the example.
Requirements
Familiarity with C#.
Development Environment. I will be using Visual Studio
(2003 should also work. Don’t know about the express editions though.)
HTML, Javascript
IE. ActiveX objects cannot be hosted in any other
browser.
Creating the ActiveX Control
The first step is to create a C# DLL Project. This can be
done with the following steps:
Launch Visual Studio
Select File -> NewProject
Select “Other Language” -> “Visual C#” for the project type
Select “Class Library” template
When the project is created, go ahead and rename Class1.cs
to whatever you want. I renamed it to HelloControl.cs
The ActiveX control we will be creating is the ubiquitous
hello world! The code and explanation follow.
HelloControl.cs
using System;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Collections.Generic;
using System.Text;
namespace csharp.activex.sample
{
/// <summary>
/// A very simple interface to test ActiveX with.
/// </summary>
[
Guid( "E86A9038-368D-4e8f-B389-FDEF38935B2F"),
InterfaceType( ComInterfaceType.InterfaceIsDual),
ComVisible( true)
]
public interface IHello
{
[DispId(1)]
string Hello();
[DispId(2)]
int ShowDialog(string msg);
};
[
Guid("873355E1-2D0D-476f-9BEF-C7E645024C32"),
// This is basically the programmer friendly name
// for the guid above. We define this because it will
// be used to instantiate this class. I think this can be
// whatever you want. Generally it is
// [assemblyname].[classname]
ProgId("csharpAx.CHello"),
// No class interface is generated for this class and
// no interface is marked as the default.
// Users are expected to expose functionality through
// interfaces that will be explicitly exposed by the object
// This means the object can only expose interfaces we define
ClassInterface(ClassInterfaceType.None),
// Set the default COM interface that will be used for
// Automation. Languages like: C#, C++ and VB
// allow to query for interface's we're interested in
// but Automation only aware languages like javascript do
// not allow to query interface(s) and create only the
// default one
ComDefaultInterface(typeof(IHello)),
ComVisible(true)
]
public class CHello : IHello
{
#region [IHello implementation]
public string Hello()
{
return "Hello from CHello object";
}
public int ShowDialog(string msg)
{
System.Windows.Forms.MessageBox.Show(msg, "");
return 0;
}
#endregion
};
}
The HelloControl.cs file consists of one class and one interface.
So let’s start with the interface definition and follow up with the CHello class.
IHello Interface
An interface is generally described as a contract. Once an interface is designed
and published for use it should NEVER change. Let me repeat this, it
should NEVER change.
Several attributes have been defined for this interface.
1) Guid – This is a unique identifier. Use guidgen.exe to generate your own
2) InterfaceType – This determines which base class to expose to COM.
The InterfaceType attribute demands a little further explaination. Here is how it is defined.
public enum ComInterfaceType
{
InterfaceIsDual = 0,
InterfaceIsIUnknown = 1,
InterfaceIsIDispatch = 2,
}
All COM interfaces and objects must at a minimum inherit the IUnknown
interface (see Appendix A.). This interface allows clients to create the COM
objects via CoCreateInstance() and search for other interfaces along with
handling object lifetime management.
The IDispatch interface allows objects to be used in Automation-aware only
languages like javascript (see Appendix B.). This interface, allows for client
applications to query for properties and methods that the object supports at run-
time and execute them. This is very much like Reflection in .Net.
Dual simply means a combination of the two above. The main reason for this is to
allow the interface to be created without all of the overhead that IDispatch has
in situations where it’s not needed.
Lastly, you will notice the DispId(#) attribute for each method in the IHello
interface. This is needed when calling Invoke from the IDispatch interface.
CHello class
The last thing to discuss is the CHello class. This class simple implements
the IHello interface and nothing more. The ShowDialog() simply shows that we can
execute objects that require some sort of UI, in this case a messagebox.
The attributes are described in the comments.
Compiling & Registering
Once the code is written up go ahead an compile the ActiveX assembly.
The assembly will be called [projectname].dll inside the bin folder of the project.
The next step is to register. Do this opening up a command prompt and go to the
location of the dll. To register the assembly type:
C:\Regasm [projectname].dll /codebase /tlb
Make sure replace “[projectname]” with the correct name of your dll.
You should get some output that says the assembly was exported correctly.
Something like the following.
Microsoft ® .NET Framework Assembly Registration Utility 2.0.50727.42
Copyright © Microsoft Corporation 1998-2004. All rights reserved.
Types registered successfully
Assembly exported to '[TLB location]
', and the type library was registered successfully
To unregister the assembly pass the “/unregister” flag to Regasm. You will need to
unregister and close IE when changes need to be made to the assembly. If you don’t
unregister your assembly prior to making code changes you may end up with bogus
registry entries and a pain to clean up. The reason for closing IE is to make sure
that all reference to your assembly have been released.
Creating HTML Test Page
We are just about done. All we need to do now is create a test page.
This should be really simple, but here it is nonetheless. The Javascript
to create the ActiveX object is also provided.
Test.html
<html xmlns="http://www.w3.org/1999/xhtml">
<head> <title>C# ActiveX Test</title> </head>
<body onload="myload();">
<h1>This is Our ActiveX Test Page h1>
The message from the ActiveX Control is [
<div id="axmsg"></div>
]
<script type ="text/javascript">
function myload()
{
var myAx = new ActiveXObject("csharpAx.CHello");
if(myAx != null)
{
myAx.ShowDialog("hello from asp.net");
var d = document.getElementById("axmsg");
var s = myAx.Hello();
d.outerText = s;
}
else
lert("NOOOO... we failed");
}
</script>
</body></html>
To create our ActiveX object in Javascript we use the ActiveXObject(). The parameter
for this is generally the PROGID that we specified in its definition. If a
PROGID was not specified in its definition then I think you can pass in
“AssemblyName.ClassName”. The rest of the javascript simply calls our ActiveX
objects methods.
That’s it!!! We should have a fully functional example. This ability to execute ActiveX
objects from javascript is very powerful. Debates and arguments about security
and safety of ActiveX objects are well known and I will not throw more fodder
to the fire. However, imagine creating a Win32 or even a Winform application
where the UI is written in HTML via IWebBrowser2 and all the UI interaction handled
with your ActiveX object, just food for thought. Though, now that I think of it,
WPF is already doing this. Oh Well.
Hopefully you found this guide on how to create an ActiveX object with C# helpful.
-skaoth-
Further Modifications
The example above, though complete (hopefully error free), has one slight problem. When the html page is
loaded we get a nasty message box saying
“An ActiveX control on this page might be unsafe…blah blah blah”
So how do we fix this? Well it is quit simple really. We need to Implement an interface
called IObjectSafety. To do this, add a new .cs file to the project.
IObjectSafety.cs
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Text;
namespace csharp.activex.sample
{
[
Serializable,
ComVisible(true)
]
public enum ObjectSafetyOptions
{
INTERFACESAFE_FOR_UNTRUSTED_CALLER = 0x00000001,
INTERFACESAFE_FOR_UNTRUSTED_DATA = 0x00000002,
INTERFACE_USES_DISPEX = 0x00000004,
INTERFACE_USES_SECURITY_MANAGER = 0x00000008
};
//
// MS IObjectSafety Interface definition
//
[
ComImport(),
Guid("CB5BDC81-93C1-11CF-8F20-00805F2CD064"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)
]
public interface IObjectSafety
{
[PreserveSig]
long GetInterfaceSafetyOptions( ref Guid iid, out int pdwSupportedOptions, out int pdwEnabledOptions);
[PreserveSig]
long SetInterfaceSafetyOptions( ref Guid iid, int dwOptionSetMask, int dwEnabledOptions);
};
//
// Provides a default Implementation for
// safe scripting.
// This basically means IE won't complain about the
// ActiveX object not being safe
//
public class IObjectSafetyImpl : IObjectSafety
{
private ObjectSafetyOptions m_options =
ObjectSafetyOptions.INTERFACESAFE_FOR_UNTRUSTED_CALLER |
ObjectSafetyOptions.INTERFACESAFE_FOR_UNTRUSTED_DATA;
#region [IObjectSafety implementation]
public long GetInterfaceSafetyOptions( ref Guid iid, out int pdwSupportedOptions, out int pdwEnabledOptions)
{
pdwSupportedOptions = (int)m_options;
pdwEnabledOptions = (int)m_options;
return 0;
}
public long SetInterfaceSafetyOptions(ref Guid iid, int dwOptionSetMask, int dwEnabledOptions)
{
return 0;
}
#endregion
};
}
Then modify the CHello object so that it inherits from the IObjectSafetyImpl
class so that it looks like
public class CHello : IObjectSafetyImpl, IHello
{
…
};
With that, the dialog about the ActiveX object not being safe should disappear.
References
ATL Internals – Brent E. Rector, Chris Sells.
Essential COM – Don Box.
http://msdn2.microso...yw6(VS.80).aspx
http://msdn2.microso...y/aa768224.aspx
http://www.c-sharpco...tiveXInNet.aspx
Appendix A: IUnknown
interface IUnknown
{
virtual HRESULT QueryInterface(REFIID riid, void **ppvObject) = 0;
virtual ULONG AddRef(void) = 0;
virtual ULONG Release(void) = 0;
};
Appendix B: IDispatch
interface IDispatch : public IUnknown
{
virtual ULONG GetTypeInfoCount(unsigned int FAR* pctinfo) = 0;
virtual HRESULT GetTypeInfo(unsigned int iTInfo, LCID lcid, ITypeInfo FAR* FAR* ppTInfo ) = 0;
virtual ULONG GetIDsOfNames(
REFIID riid,
OLECHAR FAR* FAR* rgszNames,
unsigned int cNames,
LCID lcid,
DISPID FAR* rgDispId) = 0;
virtual ULONG Invoke(
DISPID dispIdMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS FAR* pDispParams,
VARIANT FAR* pVarResult,
EXCEPINFO FAR* pExcepInfo,
unsigned int FAR* puArgErr) = 0;
};
I'm creating an add-on system for a shell I'm developing using C#. I've followed this and this. Here is my function to load an add-on:
public void loadAppFromDLL(string assemblyFile)
{
Assembly a = Assembly.Load(assemblyFile);
Type app = a.GetType("App");
MethodInfo loadMethod = app.GetMethod("load");
object appInstance = Activator.CreateInstance(app);
loadMethod.Invoke(appInstance, null);
}
Here is the add-on:
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace App
{
public class App
{
public void load()
{
MessageBox.Show("Application loaded successfully!");
}
}
}
When I build the add-on, I place it in the same directory as the shell executable and call:
LoadExternalApp lea = new LoadExternalApp();
lea.loadAppFromDLL("SampleApp");
(LoadExternalApp contains the DLL loading function)
When I was debugging my shell, I noticed that:
The app didn't start
There was a System.NullReferenceException
What am I not doing right?
This:
Type app = a.GetType("App");
is looking for a type with a namespace-qualified name of App.
Your type is called App in a namespace of App, so Assembly.GetType is returning null, and then you're dereferencing it. Instead, you should use:
Type app = a.GetType("App.App");
However, you shouldn't give a class the same name as its namespace in the first place. Fix that, so that you end up with something more like:
Type app = a.GetType("App.PlugIn");
You should still check whether GetType (or GetMethod) returns null, in order to fail rather more gracefully and with more information.
Additionally, you should start following .NET naming conventions - give methods names in PascalCase. Oh, and you might want to consider a common interface for your add-ins rather than relying on reflection to call methods.
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);