Accessing protected memory in C# via COM interop - c#

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.

Related

Keeping a COM object alive to queue longrunning tasks

From what I understand, COM objects can only be used in the thread they were instantiated in. If that thread dies, the object becomes invalid and you can't use it anymore. The error I face when I attempt to is COM object that has been separated from its underlying RCW cannot be used. The advice I've typically seen to deal with this is just to reinitialize a new COM object for every task you want to run.
The problem is that the COM object I'm using handles communication with another program, and re-establishing the connection with the same instance of that program with a new COM object is tricky. The operations I want to run are long winded and require feedback from the user in between, so including all the operations in a single Task is not really feasible afaik.
What I want to do is build a wrapper around that COM object that puts calls to it into a queue to be invoked by the appropriate thread, that would be compatible with async/await, and I would like to know if that's possible. But to avoid the XY problem I'll ask something different:
What is the most elegant way to keep a COM object functioning, off the main thread, so that I can continue to use it for multiple things?
COM object that has been separated from its underlying RCW cannot be used.
To keep the COM object alive you need to declare a corresponding RCW at a global scope, so the reference counter is not declared when the RCW is swiped from the heap. Also you need to pay attention to any Marshal.ReleaseComObject calls that may lead to the issue you faced with. This method is used to explicitly control the lifetime of a COM object used from managed code. You should use this method to free the underlying COM object that holds references to resources in a timely manner or when objects must be freed in a specific order.
For example, if you deal with Office applications like Outlook, it may detect cross-thread calls and throw exceptions in such cases.
The best solution is create a scheduler which can be called from secondary threads and queue such calls on the main thread to communicate with a COM server. Or just consider extracting all the required information (scalar value that don't involve COM objects) which can be simply consumed from secondary threads and then process it in the way you need.

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.

CastError when calling interop Excel functions in a thread?

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.

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.

Has COM object been separated from its RCW?

I'm trying to fix problem with "COM object that has been separated from its underlying RCW cannot be used" error, and I think what's causing it is that COM objects are used on a thread that didn't instantiate them.
I'm not allowed to do much refactoring, and since objects should be available on multiple threads I wonder if there is a way to find out if they have been created on current thread before doing something with them that would cause aforementioned error. And, if they haven't, create them.
Also, I'm new to this interop thing, so if someone would be kind enough to help me understand, I'd much appreciate it:
What happens with the COM object once the thread finishes, and why is RCW still available on the other thread even when it doesn't have the COM object in it anymore (why isn't it null?). Also, why would it cause that error and in the same time return true on Marshal.IsCOMObject?
What happens in the following scenario(s) with reference count and the wrapper and the memory:
Create COM object x on the thread A
Pass it and save it on the thread B
Create another x (alternatively, what would happen if it were y?) on the thread C
Pass it and overwrite x on the thread B
What happens with the COM object once the thread finishes
The COM object gets destroyed automatically by COM. Which will produce the 'COM object that has been separated' exception message when another thread continues to use it. You cannot allow the thread to exit.
Clearly you have an single threaded COM server, by far the most common kind. It has affinity to the STA thread on which it was created. COM makes sure to automatically marshal any calls made on another thread to the thread that created the object. That can no longer work when the thread is gone. Also beware that you don't get any concurrency.
Another way to get this exception is by making the mistake of handling reference counts explicitly with Marshal.ReleaseComObject(). Not unlikely either since you should have gotten an MDA warning.

Categories