Threading problem

  • Thread starter Thread starter Ulf Bietz
  • Start date Start date
U

Ulf Bietz

I need some help to understand a specific threading problem :
I wrote a function "BrowserObjects()" for testing webcontent.

....
IHTMLDocument3 currentDoc = (IHTMLDocument3)ObjectFromLResult(lRes, ref
IID_IHTMLDocument,0);
IHTMLElementCollection frameCol = currentDoc.getElementsByTagName("FRAME");
foreach (IHTMLElement element in frameCol)
{
if (element is IHTMLElement)
{
DispHTMLFrameElement frameElement = (DispHTMLFrameElement)element;
IHTMLWindow2 newWindow = frameElement.contentWindow;
//InvalidCastException, if called in separate thread?
....
}
}
....

no problem, except when I try the following :

Thread secThread = new Thread(new ThreadStart(RunWebTest);
secThread.Start();

public void RunWebTest()
{
...
BrowserObjects();
...
}

as soon as I call the function in a separate thread, it raises a
"InvalidCastException : Schnittstelle wird nicht unterstützt." (german VS
..NET, would be Interface not supported/implemented I guess)

Could someone explain me why this Exception is only raised when I called in
a new thread?
And if it's a general interface/threading problem, why does it never happen
on IHTMLDocument3 ?
Only the call the IHTMLWindow2 raises the exception.

thx :)

Ulf
 
Ulf,

Seems you are trying to to use a COM object created on one thread in
another. It is not allowed to pass COM interfaces between threads - they
should be marshaled instead.
In the unmanaged world, this is done through CoMarshalInterfaceToThread. Not
sure if .NET handles this automatically behind the scenes, so this might be
the case.

Try to create the HTMLDocument instance from scratch on your worker thread.
Also, try to mark your Main method as [MTAThread]
 
Hi Dmitriy,

thx for your suggestions. I tried MTAThread already, but with no success.
From my understanding, I create and use the COM object in the same thread.
Here is the code :

private void Form1_Load(object sender, System.EventArgs e)
{
Thread secThread = new Thread(new ThreadStart(StartTest));
secThread.Priority = ThreadPriority.Lowest;
secThread.CurrentCulture = new CultureInfo("en-GB",false);
secThread.Start();

//if I run directly
// StartTest() ;
// everything works fine
}

public void StartTest()
{
const int SMTO_ABORTIFHUNG = 0x00000002;

WarObj w = new WarObj(); //library for automated tests

Process proc = new Process();
proc.StartInfo.FileName = "iexplore";
proc.StartInfo.Arguments = "any website";
proc.Start();

int lMsg, lRes = 0, hWnd;

Guid IID_IHTMLDocument = new Guid("626FC520-A41E-11CF-A731-00A0C9082637");

hWnd = w.GetHandle(1,"Internet Explorer_Server");
if (hWnd == 0)
{
MessageBox.Show("Could not find Internet Explorer_Server");
return;
}

lMsg = Win32.RegisterWindowMessage("WM_HTML_GETOBJECT");
Win32.SendMessageTimeout(hWnd,lMsg,0,0,SMTO_ABORTIFHUNG,1000, ref lRes);
if(lRes == 0)
{
MessageBox.Show("Could not send WM_HTML_GETOBJECT message");
return;
}


IHTMLDocument3 myInstance =
(IHTMLDocument3)Win32.ObjectFromLresult(lRes,ref IID_IHTMLDocument,0);
IHTMLElementCollection elements = myInstance.getElementsByTagName("FRAME");

foreach(IHTMLElement element in elements )
{
if(element is IHTMLFrameElement)
{
DispHTMLFrameElement frameElement = (DispHTMLFrameElement)element;
IHTMLWindow2 window = frameElement.contentWindow;
IHTMLDocument2 frameDoc = window.document;
IHTMLElementCollection allTag = frameDoc.all;
foreach(IHTMLElement myTag in allTag)
{
if ((myTag.tagName.Equals("INPUT") || myTag.tagName.Equals("A"))&&
myTag.outerHTML.IndexOf("login")>=0)
{
MessageBox.Show("Login found");
}
}
}
}
proc.Kill();
}

Any hints where I'm wrong ?

thx :)

Ulf
 
Hi again,


I simply added the ApartmentState :

private void Form1_Load(object sender, System.EventArgs e)
{
Thread secThread = new Thread(new ThreadStart(StartTest));
secThread.Priority = ThreadPriority.Lowest;
secThread.CurrentCulture = new CultureInfo("en-GB",false);
secThread.ApartmentState = ApartmentState.STA;
secThread.Start();
}

works fine now, but I'll still have to spend some hours compulsing MSDN to
understand why :)

Ulf
 
Ulf,

I am really glad you've resolved the issue. The explanation seems to be as
follows:

Any thread talking to COM should register with the COM subsystem by calling
CoInitialize(). When you start an unmanaged thread and plan to use COM from
it, one of the first calls in the thread routine should be a call to
CoInitialize(). For a .NET Thread, you instruct it to register with COM by
setting its ApartmentState property.
 
Back
Top