I need to automate office documents (Word & Excel) from my .Net4 app.
Since I can't really force my users to use a specific Office version I don't use interop assemblies or tlbimp, so my project doesn't contain any additional references nor will the whole app fail if Office is not installed (feature just won't be available).
Instead I ask the system which COM server can handle "Word.Application" or "Excel.Application":
dynamic app = Activator.CreateInstance(Type.GetTypeFromProgID("Word.Application"));
app.Foo();
I'm concerned how to properly dispose of the "app" object when I'm done with it, since now I have two internal management systems to worry about (COM reference counting and .Net reference tracking). Ideally I should be able to wrap the dynamic app object into a disposable wrapper class and to be sure that underlying COM object is unreferenced when the wrapper is disposed of.
EDIT: Also I'd like to know how to properly make the COM object "live on" when I'm done with it, since Word is in a separate process. I should be able to instantiate Word app, automate it and then free all my references but the Word app should stay open.
I would definitely implement IDisposable and call Marshal.ReleaseComObject within it. Make sure you correctly implement the disposable pattern to avoid releasing the COM object more than once and reducing the reference count further than you should, although this may not matter if you are only going to create one instance of the COM object. I have implemented this pattern successfully in the past so you are definitely heading in the right direction.
I think Word will remain open even when the last COM object is reference counted to 0 in your app, unless you explicitly ask it to exit. ( Word.Application.Close() ?)
Related
I have a C# assembly that is COM-visible. It in turn communicates with a 3rd-party COM application. My problem is that when I release the reference to my C# assembly from VBA, the C# assembly keeps the 3-rd party application open.
' Call assembly from VBA
Dim asm : Set asm = CreateObject("MyCSharpAssembly")
' Get a managed object exposed by the assembly
' managedObject communicates with a 3-rd party COM application
Dim managedObject : Set managedObject = asm.GetManagedObject()
' When I release managedObject from VBA, the 3rd party application stays open.
Set managedObject = Nothing
I have tried implementing the IDisposable pattern in the managedObject and explicitly release the COM object. That works, but I still have to explicitly call the Dispose method from VBA. Just setting the reference to managedObject to Nothing is not enough. This is easily forgotten by VBA coders, who may assume setting the object to Nothing is enough.
Is there any way I can code around that from the C# perspective, or do I have to stick with the explicit Dispose?
You say you implemented the disposal pattern but did you implement the full pattern, not just the public Dispose method, in particular the one with the finalizer, e.g. something similar to below?
~ComplexResourceHolder(){
Dispose(false);
}
Dispose pattern
Please note that this is still non-deterministic; the finalizer may run much much later than the point when VBA consumers sets it to Nothing, but at least it will eventually release the object. If you MUST have a deterministic release, then we need to rethink the approach.
One possible but simplistic option that may simplify the operation is to only take reference to the 3rd party COM component within each of your public method and release it at the end. That might work as long the typical usage don't normally call several methods on same object in succession.
The other simple option is to allow the VBA consumer assign the 3rd party COM object to the managed object so VBA can then manage the lifetime of that component, rather than the .NET.
There may be other options but they will be quite challenging to implement and won't be as foolproof.
I currently have a .NET class library written in C# that exposes its functionaility via COM to a C++ program (pre-.NET).
We now want to move the library out-of-process to free up address space in the main application (it is an image-processing application, and large images eat up address space). I remember from my VB6 days that one could create an "OLE automation server". The OS would automatically start and stop the server .exe as objects were created/destroyed. This looks like the perfect fit for us: as far as I can see nothing would change in the client except it would call CoCreateInstance with CLSCTX_LOCAL_SERVER instead of CLSCTX_INPROC_SERVER.
How would I create such an out-of-process server in C#? Either there is no information online about it, or my terminology is off/out of date!
You can actually do this in .NET (I've done it before as a proof-of-concept), but it's a bit of work to get everything working right (process lifetime, registration, etc).
Create a new Windows application. In the Main method, call RegistrationServices.RegisterTypeForComClients- this is a managed wrapper around CoRegisterClassObject that takes care of the class factory for you. Pass it the Type of the managed ComVisible class (the one you actually want to create- .NET supplies the class factory automatically) along with RegistrationClassContext.LocalServer and RegistrationConnectionType.SingleUse. Now you have a very basic exe that can be registered as a LocalServer32 for COM activation. You'll still have to work out the lifetime of the process (implement refcounts on the managed objects with constructors/finalizers- when you hit zero, call UnregisterTypeForComClients and exit)- you can't let Main exit until all your objects are dead.
The registration isn't too bad: create a ComRegisterFunction attributed method that adds a LocalServer32 key under HKLM\CLSID(yourclsidhere), whose default value is the path to your exe. Run regasm yourexe.exe /codebase /tlb, and you're good to go.
You could always expose your .NET class as COM classes using InteropServices and then configure the library as a COM+ application. The .NET library would run out-of-process and be hosted by a DLLHOST.EXE instance.
Here is an article in MSDN that covers all aspects of how to create COM localserver in c# (.net): link
Your post started a while ago and I had the same problem. The following link is absolute gold and tells you everything
http://www.andymcm.com/blog/2009/10/managed-dcom-server.html
I've got two pieces of technology that plug into excel.
One is a COM Addin that implements the IDTExtensibility2 interface.
Another is an RTD server that is implemented via Excel-DNA.
Both of these objects are instantiated by excel. Each of them needs to access a third object at runtime to get data from and push it to excel.
Since I can't hand this object to the excel plugins I've made it a singleton with the hope that each of them could share the same instance.
Unfortunately when running the code, each of them don't see the instance of the singleton object that the other has created.
How can I get both addins to reference the same object?
Let's work under the assumption that both addins need to remain and I'd rather not go to an interprocess communication setup.
TL/DR
Two excel plugins need to share a 3rd object, but making the third object a singleton doesn't work as each excel plugin doesn't see the instance of the third object that the other plugin created.
What is the solution to such a problem?
You add-ins are loading in separate AppDomains. One option is to integrate the COM Add-In into the Excel-DNA add-in. This might be as easy as:
Add your COM add-in code to your Excel-DNA project,
Change your add-in class to derive from ExcelComAddIn (instead of IDTExtensibility2),
Load that class in your AutoOpen via ExcelComAddInHelper.LoadComAddIn(...).
Otherwise you will need some sort of inter-AppDomain communication. You can set up a Marshal-By-Reference object that you set to the Object property of the COMAddIn object corresponding to your loaded add-in, and retrieve that using the COM interfaces from the Excel-DNA add-in.
Is it possible to force a COM Visible .NET assembly to instantiate as System.__ComObject, rather than it's .NET type? The reason I ask is, part of my app uses 3rd party COM objects, but some of those, when written in .NET, get instantiated as their .NET types and break the object handlers I've created. I have no control over methods used to create the COM objects. Currently I'm using the following to create the objects.
Type comType = Type.GetTypeFromCLSID(objectGUID);
comObject = Activator.CreateInstance(comType);
Thanks!
Actually, no.
The COM activation of managed objects is done in the same AppDomain as the caller. IMHO, This is a nasty issue with COM interop in .NET. There are several questions here on SO with all sorts of attempts at making this work. I can attest that I've tried most of these to no avail. Your COM API provider SHOULD be producing a PIA (Primary Interop Assembly) to ensure forward type compatibility. If they are not, you are going to have some nasty issues.
The only workaround I'm aware of as a client is to only use the 'object' type. Everything would need to go through reflection. Some things you still can't do. For example, you would not be able to implement an interface.
I havn't a clue why this works this way. It's caused me no end of problems producing a viable api for both unmanaged and managed code.
I have existing managed and unmanaged software using an ActiveX component supplied by a third party to perform some communications, but it is now required that this communication be routed through my application.
Ideally I'd be able to install a .NET component which will expose exactly the same interface, and will be usable as a drop-in replacement.
However, I am running into the limits of my understanding of COM, which admittedly is quite minimal.
How best to ensure that my implementation of the interface is 100% binary compatible with the existing object?
How do I ensure that applications use my implementation of the interface instead of the legacy implementation? Is it simply a matter of registering my implementation, and unregistering the legacy one?
How do I ensure it's a "drop-in" replacement, and requires no changes to existing software?
How do I ensure unmanaged code can use it without issue?
Note: I am able to require that .NET 4.0 be used, if that will make things simpler.
Edit: Bounty will be moved here How to debug why a VB6 application using my .NET ActiveX control does not register for events? after 2 days.
Use the type library of the ActiveX component. Import it with Tlbimp.exe to get the interop library, you probably already have it if you use this component yourself. Implement your own code by inheriting the interfaces in that type library.
Your implementation must use the exact same GUIDs and ProgIDs as the ActiveX component. Use OleView.exe, File + View Typelib and select the ActiveX DLL to see the GUIDs. The ProgIDs are more difficult, best thing to do is to watch how the registry is modified with the SysInternals' ProcMon utility when you register the ActiveX DLL with Regsvr32.exe. Ultimately, the exact same changes need to be made by Regasm.exe when you register your replacement.
As point 2.
Same, the registration gets unmanaged code to use yours instead.
To make this work out well, you really have to know what the interfaces do. You cannot make this work if the ActiveX component is actually an out-of-process server (an EXE).
Well, I've gotten a lot further along with this, but I seem to have encountered an intractable problem.
The object I am replacing uses COM events. When one of the client applications (VB6 I believe, as depends.exe tells me it uses msvbvm60.dll) instantiates and uses my replacement, it does not register for any of the events, and unfortunately, the way it works is that after a particular method call has completed, the client application does nothing until an event fires.
Note: My replacement ActiveX control inherits from System.Windows.Forms.Control, and sets MiscOptions of 131457 on the coclass registry entries as suggested by http://ondotnet.com/pub/a/dotnet/2003/01/20/winformshosting.html, the reason being that the thing I am replacing was an honest to goodness ActiveX control, and I could not get these existing clients to instantiate my object successfully without any code changes at all until I inherited from WinForms control.
I have tried the approach where my coclass declares public events with the same name as the interface specified by ComSourceInterfaces, this works 100% from a C# app that uses AxHost, events are triggered.
I have also tried instead to implement IConnectionPointContainer and all supporting interfaces on my replacement control, and this works 100% from a C# app, but in the VB app, it never actually attempts to Advise() the connection point of the client sink interface to call, it only calls Unadvise() with an invalid cookie value of 0.
One issue with the typelib that I have noticed is that I cannot get tlbexp.exe to export one of the properties on the coclass interface as OLE_HANDLE, it just ends up being a long in the TLB generated from the assembly (this TLB is referenced by the TypeLib entry in the registry). Could this cause issues with eventing?
Any ideas how to debug this?