Calling COM Interop API from a Background Thread? - c#

I instantiate a service class that contains a COM interop component that does terminal automation. I am using Task Library (TPL) from Microsoft. I want to make calls to the COM object from a TPL task (background thread) so my UI doesn't freezes while the COM object is working.
However when I call my first function from the background thread (which receives an IntPtr) an COM Exception is thrown detailing HRESULT: 0xC0000005.
I know this is an access violation exception and I think I'm not mashaling my object the right way.
How can I call methods from a COM object created in the main thread from a background thread?
public void Button1_Click(object sender, EventArgs e)
{
var comWrapper = new COMWrapper(); // A simple wrapper for a COM object
Task.Factory
.StartNew(() => LoadStuff(comWrapper))
.ContinueWith(() => {
// Output results...
});
}
int LoadStuff(COMWrapper w)
{
return w.LoadStuffFromCOM();
}
Method that calls the COM object:
int LoadStuffFromCOM()
{
string buffer;
IntPtr pointer = Marshal.StringToHGlobalUni(buffer);
return comObject.GetValue(pointer); // Exception here...
}

Many legacy COM objects were made to run inside of a desktop application. That means they expected to run on the UI thread, with the Windows message pump as the only synchronizing method.
You're now trying to run that code in an environment it may never have "heard of". There's a good chance that you have violated the assumptions the author made when he wrote the code.
The code may work if you don't violate the assumptions, but if you do, then you're going to have a problem (or two, or two dozen).

It is possible to have COM / OLE Interop objects running on background, but they must be compiled with correct Threading Model
In case of Delphi for MTA it should be compiled
initialization
TTypedComObjectFactory.Create(ComServer, TSomeLogic, Class_SomeLogic,
ciMultiInstance, tmFree);
end.
In case of STA by default it uses
initialization
TTypedComObjectFactory.Create(ComServer, TSomeLogic, Class_SomeLogic,
ciMultiInstance, tmApartment);
end.
It should be simmilar in C/C++ and other unmanaged languages
More information can be found here:
http://msdn.microsoft.com/en-us/library/ff647812.aspx#scalenetchapt07 _topic11

IF you are desperate you could spawn a whole separate process which executed the com code. Then you would only have to write the ipc

Related

OS Loader Lock when doing managed-to-native interop

I am loading a native control (C++) into a WPF control using HwndHost. The HwndHost is defined as follows:
class ControlHost : System.Windows.Interop.HwndHost
{
public IntPtr Handle;
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
{
// instantiate the native control
Handle = control.Handle;
return new HandleRef(this, control.Handle);
}
...
}
My WPF project has a System.Windows.Controls.Border named ControlHostElement. The general pattern is to get the handle for the ControlHostElement, instantiate the native control and and set it as a child element of the WPF control. This pattern is prescribed by MSDN here. I am triggering this with a button on the WPF page:
private void btnHwndHost_OnClick(object sender, RoutedEventArgs e)
{
myControlHost = new ControlHost();
ControlHostElement.Child = myControlHost;
}
The problem is that when I instantiate my native control, I get an OS Loader Lock error at the line where Child is assigned:
DLL 'my.dll' is attempting managed execution inside OS Loader lock. Do
not attempt to run managed code inside a DllMain or image
initialization function since doing so can cause the application to
hang.
I'm not sure how I'm inside the loader thread at this point, but I figure I should just spin up a new thread to perform the initialization and window handle assignment:
private void btnHwndHost_OnClick(object sender, RoutedEventArgs e)
{
Thread loadControlHostThread = new Thread(
new ThreadStart(this.loadControlHostThread_DoWork));
loadControlHostThread.SetApartmentState(ApartmentState.STA);
loadControlHostThread.Start();
}
void loadControlHostThread_DoWork()
{
myControlHost = new ControlHost();
ControlHostElement.Child = myControlHost;
}
No dice:
An unhandled exception of type 'System.InvalidOperationException'
occurred in WindowsBase.dll
Additional information: The calling thread cannot access this object
because a different thread owns it.
Fair enough. Maybe I should try asking the UI thread to do this work:
void loadControlHostThread_DoWork()
{
this.Dispatcher.Invoke((Action)(() =>
{
myControlHost = new ControlHost();
ControlHostElement.Child = myControlHost;
}));
}
That results in the same OS Loader Lock error. What is the correct way for me to initialize my native control?
I get an OS Loader Lock error
It is not an error, it is a warning. From an MDA, a Managed Debugger Assistant. They are little slivers of code that Microsoft inserted into the CLR and the debugger to produce warnings when it looks like your program is doing something wrong. The kind that doesn't produce an exception but makes your program hang-up or fail in a very difficult to diagnose way.
Loader lock certainly fits that pattern, it is a deadlock buried inside Windows internals. Associated with the loader, the part of the operating system that's responsible for loading DLLs and calling their DllMain() entrypoint. It takes an internal lock to ensure that the DllMain() functions are called one-at-a-time. It prevents re-entrancy problems, pretty comparable to the kind of trouble Application.DoEvents() causes. A deadlock on that lock is pretty hard to debug, the code is completely buried inside operating system as well as mysterious DllMain() functions you don't know anything about. Very high odds that a real deadlock would get you to tear your head-hair out in major clumps with little to show for it than a bald spot without that MDA.
Unfortunately the MDA tends to produce false warnings. It is not always aware that deadlock cannot actually happen. It is over-eager, a side-effect of it having to predict, crystal-ball style, that it might happen. Without otherwise being able to wire itself into the operating system internals to give you a guaranteed warning. The Windows group at Microsoft hasn't ever been that happy about accommodating managed code, Longhorn was a sore spot for quite a while. Loader lock was a big, big issue in .NET 1.0
It almost certainly is a false warning in your case, you can be dead-sure that the CLR is already loaded, your program could not possibly start otherwise.
Fortunately it is very simple to make it stop bugging you: Debug + Exceptions, open the Managed Debugging Assistants node and untick the "LoaderLock" checkbox. Very high odds that it will leave you in peace from there, allowing you to focus on testing your program.

Why cannot I cast my COM object to the interface it implements in C#?

I have this interface in the dll (this code is shown in Visual Studio from metadata):
#region Assembly XCapture.dll, v2.0.50727
// d:\svn\dashboard\trunk\Source\MockDiagnosticsServer\lib\XCapture.dll
#endregion
using System;
using System.Runtime.InteropServices;
namespace XCapture
{
[TypeLibType(4160)]
[Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")]
public interface IDiagnostics
{
[DispId(1)]
void GetStatusInfo(int index, ref object data);
}
}
So I created a COM server with such class:
[ComVisible(true)]
[Guid(SimpleDiagnosticsMock.CLSID)]
[ComDefaultInterface(typeof(IDiagnostics))]
[ClassInterface(ClassInterfaceType.None)]
public class SimpleDiagnosticsMock : ReferenceCountedObject, IDiagnostics
{
public const string CLSID = "281C897B-A81F-4C61-8472-79B61B99A6BC";
// These routines perform the additional COM registration needed by
// the service. ---- stripped from example
void IDiagnostics.GetStatusInfo(int index, ref object data)
{
Log.Info("GetStatusInfo called with index={0}, data={1}", index, data);
data = index.ToString();
}
}
Server seems to work fine, and I am able to use the object from a VBScript. But then I try to use it from another C# client:
[STAThread]
static void Main(string[] args)
{
Guid mockClsId = new Guid("281C897B-A81F-4C61-8472-79B61B99A6BC");
Type mockType = Type.GetTypeFromCLSID(mockClsId, true);
IDiagnostics mock = (IDiagnostics)Activator.CreateInstance(mockType);
//var diag = mock as IDiagnostics;
object s = null;
mock.GetStatusInfo(3, ref s);
Console.WriteLine(s);
Console.ReadKey();
}
And it fails with
Unable to cast COM object of type 'System.__ComObject' to interface
type 'XCapture.IDiagnostics'. This operation failed because the
QueryInterface call on the COM component for the interface with IID
'{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}' failed due to the following
error: No such interface supported (Exception from HRESULT: 0x80004002
(E_NOINTERFACE)).
What am I doing wrong?
I have also tried to use InvokeMember, and that kinda worked except that I wasn't able to get the ref-returned data parameter.
EDIT: added STAThread attribute to my Main procedure. This does not solve the issue, but you really should use STAThread with COM unless you're absolutely sure you don't need it. See Hans Passant's answer below.
This exception can be a DLL Hell problem. But the simplest explanation is for what's missing from your snippet. Your Main() method is missing the [STAThread] attribute.
That's an important attribute that matters when you use COM objects in your code. Most of them are not thread-safe and they require a thread that's a hospitable home for code that cannot support threading. The attribute forces the state of the thread, the one you can set explicitly with Thread.SetApartmentState(). Which you can't do for the main thread of an app since Windows starts it, so the attribute is used to configure it.
If you omit it then you the main thread joins the MTA, the multi-threaded apartment. COM is then forced to create a new thread to give the component a safe home. Which requires all calls to be marshaled from your main thread to that helper thread. The E_NOINTERFACE error is raised when COM cannot find a way to do that, it requires a helper that knows how to serialize the method arguments. That's something that needs to be taken care of by the COM developer, he didn't do that. Sloppy but not unusual.
A requirement of an STA thread is that it also pumps a message loop. The kind you get in a Winforms or WPF app from Application.Run(). You don't have one in your code. You might get away with it since you don't actually make any calls from a worker thread. But COM components tend to rely on the message loop to be available for their own use. You'll notice this by it misbehaving, not raising an event or deadlocking.
So start fixing this by applying the attribute first:
[STAThread]
static void Main(string[] args)
{
// etc..
}
Which will solve this exception. If you have the described event raising or deadlock problems then you'll need to change your application type. Winforms is usually easy to get going.
I cannot otherwise take a stab at the mocking failure. There are significant deployment details involved with COM, registry keys have to be written to allow COM to discover components. You have to get the guids right and the interfaces have to be an exact match. Regasm.exe is required to register a .NET component that's [ComVisible]. If you try to mock an existing COM component, and got it right, then you'll destroy the registration for the real component. Not so sure that's worth pursuing ;) And you'll have a significant problem adding a reference to the [ComVisible] assembly, the IDE refuses to allow a .NET program to use a .NET assembly through COM. Only late binding can fool the machine. Judging from the COM exception, you haven't gotten close to mocking yet. Best to use the COM component as-is, also a real test.
So, the problem was that my DLL with IDiagnostics interface was generated from a TLB, and that TLB never got registered.
Since the DLL was imported from the TLB, RegAsm.exe refuses to register the library. So I used the regtlibv12.exe tool to register the TLB itself:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\regtlibv12.exe "$(ProjectDir)\lib\Diagnostics.tlb"
Then everything magically started to work.
Since regtlibv12 is not a supported tool, I still don't know how to do this properly.

Refer to COM object in the worker thread in C#

I have a COM object SerInterface objCANinterface; which is created in VC++ 6.0 ATL
which i was able to successfully call the methods from my C# GUI.(ThreadingModel is Both when checked in registry)
But i have created a thread which has to use this object opened inside this thread to continues the process.
so i have added the following statements in my constructor class as
public MyClass()
{
// Start the thread with a ParameterizedThreadStart.
ParameterizedThreadStart start = new ParameterizedThreadStart(callBackLoad);
threadFlash = new Thread(start);
threadFlash.SetApartmentState(ApartmentState.STA);
...
}
void myFun()
{
threadFlash.Start(objCANinterface);
threadFlash.Join(); //Wai
}
[STAThread]
public void callBackLoad(object refinterface)
{
//I am not able to access objCANinterface inside my thread so i have passed the object as a parameter and trying to cast
SerInterface objSrinterface = (SerInterface)refinterface;
}
This is not successful Could you please let us know how do i solve this issue. I am getting the following error
An unhandled exception of type 'System.InvalidCastException' occurred in MyClass.exe
Additional information: Unable to cast COM object of type 'System.__ComObject' to interface type 'MYINTERFACELib.SerInterface'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{37C34F3C-0082-46F5-9974-37CEB2E1C2EE}' failed due to the following error: Error loading type library/DLL. (Exception from HRESULT: 0x80029C4A (TYPE_E_CANTLOADLIBRARY)).
Please let me know how to solve this issue. Thanks in advance.
# oberfreak.........
have you tried it by using "as" as well? Wen you step down with a debugger, which type is it, is it the expected type? Are your interops included in the assembly or in a seperate file?
# oberfreak, how do i use "As", could you please let me know how to use that. My interops are included along with my exe. I was able to perfectly work out of the thread.
# Hans Passant:
You forgot to register the type library in your .rgs script and you also didn't create the proxy/stub project. A threading model of Both still requires an object to be marshaled when it was created on an STA thread and used on another STA thread. Which tends to make it pointless to create a new thread. Get ahead by creating the object on the worker thread instead.
# Hans Passant : I have registered my type library and interop also created in the C# which is making me to use inside my C# code.I cannot create a new object as I need to use the same object to continue the CAN sequence commands. Could you please provide any sample for better marshalling inside a thread.
I have unregistered/removed the reference from the Exe and have registered and added the reference back to the tool. Then it worked with the parameter thread passing which made as STA thread. Thanks for the answers proposed.

Keep a Form running after COM visible DLL terminates

I created a COM Visible DLL in C# that should show a Form after some inputs from the User in the host application (unmanaged). It works fine with ShowDialog(), but ideally the Form should keep running even after the DLL finishes. Because the Form need some Data a separated Project with Main(string[] args) is not an option.
How can I accomplish this? I tried something like that but it didn't worked.
public class FormManager : ApplicationContext
{
FormMain frmMain;
public FormManager()
:base(new FormMain())
{
frmMain = (FormMain)this.MainForm;
frmMain.Closed += new EventHandler(OnFormClosed);
}
public void SetData(object o1, object o2)
{
if (frmMain != null)
{
frmMain.SetData(o1, o2);
frmMain.Show();
}
}
private void OnFormClosed(object sender, EventArgs e)
{
ExitThread();
}
}
I have no idea where Application.Run should be inserted.
I, too, am a little unclear as to what exactly you are trying to do here... but normally, if you are placing the Application.Run somewhere in that code, it would be in place of this line:
frmMain.Show();
Of course, by using Application.Run you will be freezing this code (the thread that calls Application.Run) until the form in question closes... So maybe that doesn't really accomplish what you want (it is, indeed, unclear).
Edit After Clarification of Question
Here's the thing about COM in .NET that was not true about previous iterations of Microsoft languages. When you call an assembly in .NET via COM (OLE) the calling assembly subsumes the COM exposed code into its runtime. In other words, when you look in the Task Manager, you won't see both of your assemblies running! You'll only see the one that did the calling. Thus, when you close the main assembly, you close any running code attached to it, including your COM code.
There is one way around this, but it's not simple. In short, you would need to:
Launch your second process (you could, for instance, use a Process.Start())
Use the first process to look inside the ROT (Running Objects Table) and locate the second assembly
Communicate freely via COM (OLE) and pass your data
At this point, the two assemblies are running in separate runtimes, which will allow you to produce forms in the second assembly that will not close when the first assembly closes. That, as I understand it, is what you're looking for.
If you want to try this route, do a little Googling for the ROT and try some sample code. If you have questions about that let me know!

WPF Thread: "COM object that has been separated from its underlying RCW cannot be used."

I am getting following error:
"COM object that has been separated from its underlying RCW cannot be used."
I am sure the problem is because COM object is being called not on the thread it has been created - STA. I tried to implement IDisposable but it has not worked for me.
There is a couple of posts dealing with similar problem but which still do not solve my issue:
Is it safe to call an RCW from a finalizer?
Release Excel Object In My Destructor
Could anyone post an example/explain how COM object should be correctly accessed from another thread?
Here is minimal code which shows the problem:
using System;
using System.Threading;
namespace Test.ComInterop
{
public class Program
{
MyCom _myCom;
[STAThread]
static void Main( string[] args )
{
new Program();
}
public Program()
{
_myCom = new MyCom();
// this method call works
string version = _myCom.ComMethod();
StartThread();
}
private void StartThread()
{
Thread t = new Thread( UIRun );
t.SetApartmentState( ApartmentState.STA );
t.Start();
}
void UIRun()
{
TestUI window = new TestUI();
window.Show();
// this method call fails
window.Title = _myCom.ComMethod();
window.Closed += ( sender2, e2 )
=> window.Dispatcher.InvokeShutdown();
System.Windows.Threading.Dispatcher.Run();
}
}
class MyCom
{
private dynamic _com;
public MyCom()
{
_com = Activator.CreateInstance(
Type.GetTypeFromProgID( "Excel.Application" ) );
}
public string ComMethod()
{
return (string) _com.Version;
}
}
}
The problem is your program's startup thread. It creates the COM object, starts a thread, then exits. As part of the cleanup of that main thread, .NET calls CoUninitialize() and that's the end of the COM object. Getting that error is the expected result.
There's just no point in letting your main startup thread exit like that. Let it do the work now done by your own thread, problem solved.
Sorry for maybe not answering directly your question. This is merely an advice for handling it in a different way. Hope it helps.
With COM interop with Excel there are quite a few pitfalls - not directly related to COM I think, but to how Excel COM is implemented.
I struggled a lot with COM interop with Excel (and also MsProject). For Excel the only good solution was a dedicated thread for handling the whole Excel communication from creation until termination. There are a few design flaws in the Excel API. Some method calls are not stateless, meaning two threads will have a hard time to make the stuff work. It would be safer to delegate all the communication to one thread and handle the communication with other threads yourself.
Beside this, the thread you are using for communication MUST also have the en/US culture (LCID issues). This usually results in an other message:
Old format or invalid type library
but might be useful to you to know.
Usually this is because the underlying COM object has been released from its wrapper - this happens when you manually release it via Marshal.Release or the managed wrapper is disposed. Using it on the wrong thread will simply cause any calls to the COM object to actually occur on the thread it was created on - I was stung by this in the past, it has thread affinity for execution.
You don't appear to be disposing of the wrapper, but I'm not sure what the affect of the dynamic variable will be.
Have you tried changing your thread apartment state to MTA?
Try making your MyCom class inherit for DispatcherObject. After you start up your other thread, do a _myCom.Dispatcher.Run(). When you want to talk to your COM object, just do a _myCom.Dispatcher.BeginInvoke/Invoke.

Categories

Resources