CastError when calling interop Excel functions in a thread? - c#

I have an excel addin class that has this function:
public void testRibbon()
{
Excel.Workbook workbook = this.Application.ActiveWorkbook;
Excel.Worksheet activeSheet = workbook.Sheets[1];
Excel.Range range = activeSheet.get_Range(JOB_ID_FIELD + HEADER_ROW, TOTAL_STATUS_PERCENTAGE_KEY_FIELD + (10 + 1).ToString());
range.get_Range("C4").Value = "test Ribbon complete";
}
In the ribbon I added a button that when pressed will call testRibbon in a thread:
private void process_Click(object sender, RibbonControlEventArgs e)
{
Thread processThread = new Thread(delegate(){
Globals.ExcelAddin.testRibbon();
});
processThread.Start();
}
This causes a cast error:
Unable to cast COM object of type 'System.__ComObject' to interface
type 'Microsoft.Office.Interop.Excel._Workbook'. This operation failed
because the QueryInterface call on the COM component for the interface
with IID '{000208DA-0000-0000-C000-000000000046}' failed due to the
following error: Error loading type library/DLL. (Exception from
HRESULT: 0x80029C4A (TYPE_E_CANTLOADLIBRARY)).
The cast error doesn't happen if I don't use a new thread.
Edit:Tried using task factory, same error:
var task2 = Task.Factory.StartNew(
() =>
{
Globals.BobStats.testRibbon();
});

Using multiple threads in Excel makes no sense because Excel's COM interface is basically single threaded (see this article). There might be further restrictions from within an add-in.
Yet I have to say I don't really understand what the error message is trying to say.

I think at the moment, you are being connected by COMObject to interface type 'Workbook'... it will automatically create a new unmanaged thread and that will crash with your thread when you Start(). To specifically, when you create thread object it will save some data structures to shared memory and did not use Start() so thread was not executing at this time

When you use an Excel interface method from a worker thread then the method call needs to be marshaled. In other words, the call needs to be made from the thread that created the Application object. Same idea as using Control.Invoke() or Dispatcher.Invoke() in a .NET gui app.
To make that work, COM needs to know what the arguments are for the method so it can copy them properly. That is the kind of info that requires the equivalent of Reflection in .NET. Which requires metadata, the metadata that describes a COM method is stored in a type library. The type library for Excel is stored in Excel.exe as a resource.
Finding the type library is what is going wrong here. This info is stored in the registry and it is damaged on your machine for some reason. The most likely key that got whacked is HKLM\SOFTWARE\Classes\TypeLib\{00020813-0000-0000-C000-000000000046}\1.7\0\win32 albeit that it depends on the exact version of Office. You can get more insight from SysInternals' ProcMon utility, you'll see your program searching for the key.
This kind of mishap is rarely limited to just one key. You'll need to get your machine healthy again and reinstall Office.
Oh, and keep in mind that the code doesn't actually run on the worker thread. That requires creating the Application object on that worker thread and calling SetApartmentState() to make it STA before you start it.

Related

Using COM Object in C++ dll

I am writing a Win32 C++ DLL that uses the COM object(B.dll) which is made in C#.
This dll(A.dll) provides CMyComObject class which creates a COM object and access to it.
Here is my code.
void CMyComObject::CMyComObject()
{
HRESULT result = CoInitialize(NULL);
...
result = CoCreateInstance(CLSID_COMDLL, NULL, CLSCTX_INPROC_SERVER, IID_COMDLL, reinterpret_cast<void**>(&MyComObject));
}
void CMyComObject::~CMyComObject()
{
..
CoUninitialize();
..
}
And then, here is a client program that loads A.dll and access to the COM object.
This program creates several threads which load A.dll and create a COM object concurrently.
In this case, Is this correct to use CoInitialize() function or Should I use CoInitializeEx() function with COINIT_MULTITHREADED parameter?
Or Is there any mistake I did?
(I registered B.dll by commanding "reg_asm.exe B.dll B.tlb /codebase")
Sorry for my poor English.
Thanks.
You are supposed to use CoInitialize[Ex]/CoUninitialize before and after any COM activity on that thread, and your choice between CoInitialize and CoInitializeEx with specific parameters depends on whether you prefer STA or MTA mode for your thread.
Having said that, your initialization:
Does not depend on whether the COM object itself creates any threads
Does not depend on other parts of your application possibly having other COM activity, including similar instantiation of the same COM class
Entirely depends on your COM activity on the thread in question
Does not normally happen in class constructor; it is typical to have COM initialization associated with top level thread code such as before windows message pump or at the very beginning of the thread procedure; putting it into constructor is an easy way to get into collision e.g. with another initialization (esp. using different apartment model) or too early uninitialization.
Summing it all once again, your initialization:
looks good if you are OK with COM single thread apartment model and you don't pass obtained pointer between threads
you would be better off moving CoInitialize and CoUninitialize calls out of constructor and associate it with thread code
be sure to check returned value to detect failures, esp. attempt to initialize mismatching apartment on the thread already having COM initialization
the part you missing is that you have to close all your COM activity before CoUninitialize call, including releasing your MyComObject pointer.

Error when accessing a COM dll with Threading Model Apartment from Multithread Apartment

I have to communicate with a third party application and the only way to do so is by accessing the provided COM component. Because the interaction takes about 3 minutes it's mandatory that it takes place in the background.
So what i tried to do is to add a reference the component with option "embedd interop-types" = true and to create a test that reads very basic data through the interface. The documented way to do so is by following Code:
System sys = new System();
if(Convert.ToBoolean(sys.Initialize()) && Convert.ToBoolean(sys.Login("John Smith", out userInstance)))
Project proj = new Project();
if (Convert.ToBoolean(proj.Open(sys, m_projName, m_scenarioName)))
someValue = proj.Name;
this works perfectly until the BackgroundWorker is used. Then I get following error in the first line of code:
Unable to cast COM object of type 'System.__ComObject' to interface type 'ICAPILib.System'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{1F5EB3E2-35F6-11D2-A191-0060083A260B}' failed due to the following error: Error loading type library/DLL. (Exception from HRESULT: 0x80029C4A (TYPE_E_CANTLOADLIBRARY)).
I already tried ReRegistering the Component without any success.
When using the BackgroundWorker the Thread Apartment Type obviously is MTA. The COM component has ThreadingModel set to apartment. If I understood this article
http://msdn.microsoft.com/en-us/library/eaw10et3.aspx
correctly the interop marshalling should take care of accessing the Objects.
Does anybody have a clue what I could try to make this work?
You cannot use BackgroundWorker, its thread is of the wrong type. Which can't be changed, it uses a threadpool thread and that is always MTA. COM automatically creates an STA thread to give the COM server a hospitable home and that is going to cause any calls to get marshaled. Which can't work for that component, it doesn't properly register its type library. Something you want to avoid anyway.
You must create your own Thread instead and call its SetApartmentState() method to switch it to to STA before you start it. It is also important that you create the instance of the COM object on that thread, otherwise the CLR will still try to marshal the calls. Technically you need to pump a message loop (Application.Run) but you might get away with not needing to do so. You'll find out, if a call deadlocks or an expected event doesn't fire then the message loop is required.
What has happenned is that the COM Marshaller was unable to marshal the object.
First answer: Standard marshalling requires a type library. It may be that the object's type library is not correctly registered, hence the error. Are you on x86 or x64? Try registering the library with REGTLB.
Second answer: If that doesn't work, the easy answer is to use a thread of STA apartment type. This may mean you cannot use a BackgroundWorker but may have to use a specially created thread which you destroy when completed. If we are talking about a three minute operation, the additional overhead is negligible.
Note the object must be created on the thread from which it is to be used, and the apartment type msut be compatible with the object's threading model, to avoid marshalling.

COM Interop hang freezes entire COM system. How to cancel COM call

I am using a third party dll that is exposed via a COM Interop wrapper. However, one of the COM calls often freezes (never returns at least). To try to at least make my code a little more robust, I wrapped the call asynchronously (_getDeviceInfoWaiter is a ManualResetEvent)
var backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork +=
(sender, eventArgs) =>
{
var deviceInfo = _myCom.get_DeviceInfo(0);
_serialNumber = deviceInfo.SerialNumber;
_getDeviceInfoWaiter.Set();
};
backgroundWorker.RunWorkerAsync();
var waitFifteenSecondsForGetInfo = new TimeSpan(0, 0, 0, 15);
_getDeviceInfoWaiter.WaitOne(waitFifteenSecondsForGetInfo, true);
if(String.IsNullOrEmpty(_serialNumber))
throw new ArgumentNullException("Null or empty serial number. " +
"This is most likely due to the get_DeviceInfo(0) COM call freezing.");
However, the very next call to any COM component will freeze the code. Is there something I am not thinking of, or is there some way to keep my main thread from dying?
UPDATE
Basically, this is a COM call that is called whenever a new device is plugged into the PC so that we can log the information appropriately. However, as I said, ANY COM component will freeze if this one is waiting (a custom COM of our own locks if the third party locked)
UPDATE 2
The above code DOES work, and delays the hanging of the UI thread until the next COM call. The reason for this attempted workaround was because var deviceInfo = _myCom.get_DeviceInfo(0); was already locking the UI thread. However, this info is not important and is only used for logging, so this approach is to allow for "give up and move on after 15 seconds" scenario
Another workaround here would be to find a way to cancel the COM call after x seconds?
UPDATE - after the second update from the OP
IF you have some problematic component you can always make your usage of it more robust by using the following approach:
Create a process (EXE) which wraps the usage of that component and exposes an API (for example via any IPC mechanism). You can then start that EXE as a separate process (from your main EXE) and use it... IF you need to kill that component after a certain time and/or when some condition is met you can always kill that "wrapper EXE" from your main EXE... depending on the specific component it might even be useful to implement some special "cleanup code" (possibly in a separate thread) within that "wrapper EXE" which gets executed when you need to kill that "wrapper EXE".
Since you are implementing this in .NET you can even have that "wrapper EXE" as "embedded resource" in your main executable and start it even from RAM without writing it to the filesystem...
The third party DLL has some kind of indefinite wait, loop or deadlock inside it. Attempts to work around it like this will not work. You may have farmed out the hanging call to a worker thread, but that thread doesn't go away; it keeps hanging in that call.
The next call to the COM component freezes most likely because the previous one froze. Maybe it's trying to acquire a lock that the previous one acquired before hanging. Or maybe it's hanging for exactly the same reason, rather than a dependent reason.
Better contact the developers/vendors of this third party thing and ask them if you're misusing it in some way. Is there some missing precondition. Some initialization that wasn't performed. Some necessary configuration, etc.

Accessing protected memory in C# via COM interop

I am making a DLL "Plugin" for a EXE. The EXE calls a function in the DLL with an Object as a parameter, and goes from there.
It all works fine and dandy until I split it to a new thread. This error happens
Attempted to read or write protected
memory. This is often an indication
that other memory is corrupt.
when executing this code on the object in the new thread:
protected object GetPropertyValue(object obj, string PropertyName)
{
return obj.GetType().InvokeMember(PropertyName, BindingFlags.GetProperty, null, obj, new object[] { });
}
The above is trying to access a property on a COM object. Changing the function to 'public' doesn't affect it. The code works just fine however if I'm using just one thread.
What's happening is clear: The new thread does not have access to the variable in the EXE. How can I fix this? Not using a thread is not a viable option.
Appreciate any help
Your COM object probably exists in the STA. That means you need to dispatch back to the thread that owns the object, and make the call from there.
If the COM object supports free threading, then it might be running in the STA because your main method is marked with the STA thread attribute.
Alternatively, if you control the COM object you could trying making it an MTA object.
In that case, try removing that attribute. However, if you are using Windows forms, then your forms have to be created from an STA thread.

Cannot call COM object created from STAThread from oher STA threads

I am new to COM and trying to understand the difference between STA and MTA. I tried to create an example that would show that COM can manage calls to object created in STA that is not thread-safe.
MyCalcServer class here is created using ATL Simple Object. The settings used are the same as in this article:
Threading Model: Apartment
Aggregation: No
Interface: Custom
MyCalcServer COM object is used in another C# project which is:
class Program
{
[STAThread]
static void Main(string[] args)
{
MyCOMLib.MyCalcServer instance = new MyCOMLib.MyCalcServer();
string output1;
instance.ChangeValue("Gant", out output1);
Console.WriteLine(output1);
Thread t1 = new Thread(() =>
{
while (true)
{
string output;
instance.ChangeValue("Gant", out output);
Console.WriteLine(output);
}
});
t1.SetApartmentState(ApartmentState.STA);
t1.Start();
// :
// also has t2 and t3 here with similar code
// :
t1.Join(); t2.Join(); t3.Join();
}
}
However, this always results in InvalidCastException (E_NOINTERFACE) raised inside t1's code. I have also tried changing ApartmentState to MTA with no success.
Unable to cast COM object of type
'MyCOMLib.MyCalcServerClass' to
interface type
'MyCOMLib.IMyCalcServer'. This
operation failed because the
QueryInterface call on the COM
component for the interface with IID
'{B005DB8C-7B21-4898-9DEC-CBEBE175BB21}'
failed due to the following error: No
such interface supported (Exception
from HRESULT: 0x80004002
(E_NOINTERFACE)).
Could anybody please explain what I am doing wrong here?
You explicitly ask COM to create instance for main thread, then you pass this to another thread. Of course in some circumstance it is allowed (for example declare MyCalcServer as multithread).
But in your case it looks you need create proxy for another thread. In regular COM clients it is done by CoMarshalInterThreadInterfaceInStream. There is large article to clarify it http://www.codeproject.com/KB/COM/cominterop.aspx
I managed to get this resolve.
As I'm new to COM, I don't know much about Proxy/Stub and that they're needed for marshaling stuffs between STA and STA. After created a new ATL project and make sure I have "Merge Proxy/Stub" ticked. The problem vanished.
I find the info from this page useful: Why would I want to merge Proxy/Stub code with my DLL project.
Proxy/stubs providing standard
marshaling for your component. In many
cases a DLL-based component may not
need proxy/stub because it is running
in the same context of its client, and
this option may seem useless at first.
However, COM uses the marshaling
process to synchronize access to a
component in multi-threaded
situations. So, a DLL-based component
will need a proxy/stub DLL in at least
two cases:
It's running a multi-threaded client and needs to pass interface
pointer between apartments (STA to STA
or MTA to STA).
DCOM can provide a surrogate process for a DLL-based component so
that it can be accessed in a
distributed environment. In this case
a proxy/stub is needed to marshal
between machines.
By merging the proxy/stub code with
your implementation, you don't have to
distribute two DLLs, just the one.
I will mark #Dewfy's answer as accept as he has shed some light on the Proxy topic.

Categories