views:

1428

answers:

3

The class mshtml.HTMLDocumentClass in Microsoft.mshtml.dll assembly has a method:

public virtual void write(params object[] psarray);

Avoiding the real question for a moment, what code would you use to call write()? Would you use:

String html = "<html><body>Hello, world!</body></html>";
mshtml.HTMLDocumentClass doc;
...
doc.write(html);

or would you use:

String html = "<html><body>Hello, world!</body></html>";
mshtml.HTMLDocumentClass doc;
...
object[] params = new Object[1];
params[0] = html;
doc.write(params);

Because both of those throw an exception. (Type mismatch. 0x80020005)

The HTMLDocumentClass.write method actually comes from IHTMLDocument2 interface, which is documented as:

IHTMLDocument2::write Method

Writes one or more HTML expressions to a document in the specified window.

Syntax

HRESULT write(
   SAFEARRAY *psarray
);

Parameters

psarray

   [in] A **BSTR** that specifies the text and HTML tags to write.

So in reality the write method needs a pointer to a SAFEARRAY, even though Microsoft's Microsoft.mshtml interop assembly define the write method as taking a regular array:

public virtual void write(params object[] psarray);

Ignoring the mshtml interop declaration, i have to construct a SAFEARRAY object (verses an object array), fill it with a BSTR string (verses a String), and stuff it into a parameter that must be an object array.


Note: i'm unsure of the meaning of the params keyword. It is used to indicate a variable number of parameters.

Does that mean that it can take multiple array parameters?

object[] array1 = new Object[1];
array1 [0] = alpha;
object[] array2 = new Object[1];
array2 [0] = bravo;
object[] array3 = new Object[1];
array3 [0] = charlie;
object[] array4 = new Object[1];
array4 [0] = delta;

doc.write(array1, array2, array3, array4);

Or is object[] the method in which multiple parameters are passed, and you must literally create an array?

object[] params = new Object[4];
params[0] = alpha;
params[1] = bravo;
params[2] = charlie;
params[3] = delta;
doc.write(params);

Or is the array[] just a decoy, and really you pass:

doc.write(alpha, bravo, charlie, delta);


When i originally used this code, from a native Win32 app, the BSTR was placed inside a SAFEARRAY. In IDispatch based automation, everything is inside an array. In this case the late binding code:

doc.write(html);

was converted by the compiler into a SAFEARRAY, where the zero-th element contains a BSTR string (which is a length prefixed unicode string).

My problem becomes one of trying to construct a SAFEARRAY, converting a String into a BSTR, placing the BSTR into the zero-th element of the SAFEARRAY, and passing a variable that contains a SAFEARRAY to one that only accepts an object array (object[]).

This is the real question: how to create a BSTR SAFEARRAY?


Microsoft.mshtml

C:\Program Files\Microsoft.NET\Primary Interop Assemblies\Microsoft.mshtml.dll

A: 

The declaration for the write method on the IHTMLDocument2 created by TLBIMP/VS.NET is incorrect. It should be:

void Write([In, MarshalAs(UnmanagedType.SafeArray)] object[] psarray);

You will have to define this interface in code and then use that.

casperOne
Does that mean that i cannot use Microsoft's Microsoft.mshtml.dll primary interop assembly?
Ian Boyd
Perhaps you can, but I've never used this method, and looking at the IDL for the unmanaged interface, it would seem that the interop definition is incorrect.
casperOne
How are you importing it, when i import from the COM tab "Microsoft HTML Object Library", which is "c:\Windows\System32\MSHTML.TLB", it's still wrong.
Ian Boyd
I'm not importing it, I recommended declaring the interface in code manually and using that.
casperOne
It's a pretty large interface, is the solution you're describing to import all of it, and then change the one signature?
Ian Boyd
You could use reflector to get the interface from the assembly produced by TLBIMP, and then paste that into a code file into your project, and then use THAT definition (do not use the one in MSHTML).
casperOne
+1  A: 

The params keyword indicates that you can supply multiple parameters in this place, and it will group automatically. For example, if I had a function thus:

public int SumNumbers(params int[] value)
{
       //Logic.
}

then I could call it like this:

int myValue = SumNumbers(1,2,3,4,5,6,7,8,9,10);

The array is constructed automagically. So hypothetically, you could call

mshtml.HTMLDocumentClass doc;
...
doc.write('H','I',' ','M','O','M');

And it would work. Not really practical though. I suppose you've tried calling

doc.write(myString.ToCharArray());

? I don't know anything about SAFEARRAYS, but its possible you might not have to know, either, depending on how the compiler helps/hinders here.

GWLlosa
Did you mean to write "params int value[]" rather than "params int[] value"? Your example seems to indicate that it accepts a varialbe number of integers, and the target method receives them as an array. But "int[] value" would be more logical if the final guy receives as array of values.
Ian Boyd
Yes I did, thanks. Corrected now :)
GWLlosa
A: 

It works like a charm this way :

[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); //modified 
    //void write([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)] object[] psarray); //instead of
Is it just a cut/paste snippet, or were you actually able to get away with declaring the interface with only one method?
Ian Boyd