VS 2005 Shared Add-In and ShadowCopy
VS 2005 Shared Add-In and ShadowCopy
VS 2005 Shared Add-In and ShadowCopy
VS 2005 Shared Add-In and ShadowCopy
VS 2005 Shared Add-In and ShadowCopy
VS 2005 Shared Add-In and ShadowCopy VS 2005 Shared Add-In and ShadowCopy VS 2005 Shared Add-In and ShadowCopy VS 2005 Shared Add-In and ShadowCopy VS 2005 Shared Add-In and ShadowCopy VS 2005 Shared Add-In and ShadowCopy VS 2005 Shared Add-In and ShadowCopy VS 2005 Shared Add-In and ShadowCopy
VS 2005 Shared Add-In and ShadowCopy VS 2005 Shared Add-In and ShadowCopy
VS 2005 Shared Add-In and ShadowCopy
Go Back  Xtreme Visual Basic Talk > > > VS 2005 Shared Add-In and ShadowCopy


Reply
 
Thread Tools Display Modes
  #21  
Old 05-31-2007, 03:41 PM
topry topry is offline
Newcomer
 
Join Date: May 2007
Posts: 23
Default


I did try copying the DLL to the temp folder, invoking Assembly.LoadFrom on that file - but when I use Assembly.CreateInstance, it locks the original (source) DLL. I did confirm the lock is occurring on the .CreateInstance, not .LoadFrom - as to why, that will have to wait for another day
Reply With Quote
  #22  
Old 05-31-2007, 04:43 PM
Mike Rosenblum's Avatar
Mike Rosenblum Mike Rosenblum is offline
Microsoft Excel MVP

Forum Leader
* Guru *
 
Join Date: Jul 2003
Location: New York, NY, USA
Posts: 7,848
Default

I trust you, you've clearly done a lot of work here. But I think I might not have explained myself 100% clearly, above...

As an example, if you were to copy the original into a temp folder, and then do the Assembly.LoadFrom() and Assembly.CreateInstance(), accessing the assembly in the temp folder location, I don't see how the original DLL can get "locked"? The CLR won't even know where the original is.

As another example, what if you downloaded it from a web server, saved it to a temp folder, and then loaded from there. The CLR wouldn't be able to "lock" the file found on the web server, right?

I hope that I'm helping and not confusing things!
__________________
My Articles:
| Excel from .NET | Excel RibbonX using VBA | Excel from VB6 | CVErr in .NET | MVP |
Avatar by Lebb

Last edited by Mike Rosenblum; 06-01-2007 at 05:49 AM.
Reply With Quote
  #23  
Old 05-31-2007, 06:49 PM
topry topry is offline
Newcomer
 
Join Date: May 2007
Posts: 23
Default

Oh I agree with you, and I have verified that the one loaded is from the temp folder (via step tracing through a sample app) - but the sucker most definitely is being locked. I made some changes, to use late binding vs early and that seems to work (at least in my test Winform app - will try it in the AddIn later). Long day - will look at it again later.

No need to be so diplomatic Its definitely one of those 'have you lost your mind' moments...O:
Reply With Quote
  #24  
Old 06-01-2007, 06:22 AM
Mike Rosenblum's Avatar
Mike Rosenblum Mike Rosenblum is offline
Microsoft Excel MVP

Forum Leader
* Guru *
 
Join Date: Jul 2003
Location: New York, NY, USA
Posts: 7,848
Default

Honestly, I think you're doing great, this is definately a head-banger. And advice is cheap, believe me. Getting your hands dirty, as you are, to actually make this thing work is another story!

Ok, so the one loaded from the temp folder is being locked, right? I think this is ok...

If you need the add-in to be updatable on the fly, no questions asked, I was thinking that you could create a new, randomly named temp folder for the dynamically loaded assembly each time the add-in is loaded. This way, the original (located on the network) can be swapped out any time you want because the original is never locked. (Only the locally-created temp folder version is locked.)

Note that in this scenario the existing running add-ins are not updated because they are running and the temp folders that they are running from are locked. However, the original can be updated and new instances of the same application type will run with the new version of the add-in, loaded into a different temp folder location.

Ok, but I just thought of two other minor complexities that you might want to deal with once you have the basic version working:

(1) There could be an issue if a program attempts so load your late-loaded assembly at the exact same time you attempted to copy in a new version. To protect against this, you might want to add version numbering for which your front-end loading Managed COM Add-in looks. This way you could add new versions without having to delete an older one. The text or XML file that has the version numbering and effectively states that "this version is number XXXX and is ready for use" should be copied in last or else an add-in could attempt to load the assembly before all the files were there.

(2) Another issue is that this appoach is doing a file copy operation every time the add-in is loaded, which is expensive. If it's not noticeable, then fine, but I suspect that this could visibly slow down the application's load time. A finesse would be to have a mechanism for remembering and using an existing temp folder location from a previous load. Then when the Managed COM Add-in loads, it checks the original file location (on the network) against the most recent temp folder location and if it's a version match, it does not bother with the copy-paste operation and simply loads off the temp folder.

But as I said, advice is cheap, I hope you can get this going!

I've got my fingers crossed...
Mike
__________________
My Articles:
| Excel from .NET | Excel RibbonX using VBA | Excel from VB6 | CVErr in .NET | MVP |
Avatar by Lebb
Reply With Quote
  #25  
Old 06-01-2007, 03:04 PM
topry topry is offline
Newcomer
 
Join Date: May 2007
Posts: 23
Default

The problem was that even though I was calling:
MyAssembly.LoadFrom("c:\temp\mydll.dll"), MyAssembly.Location would show the original DLL. Which is why it is being locked. This only happened with my AddIn, when I test in a Winforms app, .LoadForm dutifully loads the one requested.

Then I found this: http://blogs.msdn.com/suzcook/archiv...-loadfrom.aspx

While I was hoping it was just a bone-headed error on my part, not a lot of code involved. After reading the above, I switched to .LoadFile....and the results, now MyAssembly.Location shows the correct file in the temporary folder AND locks the temporary file ...but... the addin still also locks the original file TOO - when I invoke .CreateInstance.

As to why... seems to be due to having a reference to the class within the AddIn.
To use early binding, I have to pass a reference to the AddIn object within the Office apps COMAddIn object (ComAddIn.Object). Will test switching to this to late binding and using .LoadFile next go round.

Last edited by topry; 06-01-2007 at 03:18 PM.
Reply With Quote
  #26  
Old 06-01-2007, 03:58 PM
Mike Rosenblum's Avatar
Mike Rosenblum Mike Rosenblum is offline
Microsoft Excel MVP

Forum Leader
* Guru *
 
Join Date: Jul 2003
Location: New York, NY, USA
Posts: 7,848
Default

Wow, I didn't expect that... Sorry, clearly I've never done this before.

Ok, well a couple of more thoughts then:

(1) There must be absolute addresses to the dependencies or something to the original file locations. So I wonder if providing a Manifest telling it exactly where to find the necessary dependencies (and listing them as local path locations) would do the trick?

(2) In theory, you could do Reflection.Emit() here, in which case you don't need a physical assembly at all. Just a string, really. But replicating an entire DLL via Reflection.Emit() sounds daunting. I'm not that strong at reflection though, so for those that really know what they are doing this might be fine.

(3) My third thought is, the original is locked, "so what?" All this prevents us from doing is overwriting an older version with a new one. But this was potentially problematic anyway. For example, what if you attempted an update (copying in a new, updated assembly) at exactly the same moment that another Word or Excel application launched, loading your Add-in? Could be big trouble.

So a thought here would be to always bring in new back-end assemblies into a new folder. Use a naming scheme for the Folder (say, "Assembly_0001", "Assembly_0002" or the like, or maybe date-stamped) so that the front-end loader can always locate the most recent one. It can either load directly from that location (since we no longer care about locking it) or copy to a temp folder and then run off the temp folder (which I'm no longer sure we need anymore, since the original gets locked anyway. But if you cannot run two instances from this same location, then, yes, the copy-to-temp-folder approch will be needed.).


(4) But this might be easiest: Don't have the original anywhere. Keep it out of reach. For example, keep it compressed, for example, or named incorrectly. (Say name the .DLL ".XYZ" instead.) Then the copy-paste mechanism would copy in from the original location to the temp-folder location. But then it must make it valid by changing the DLL extensions from ".XYZ" to ".DLL" and then calling Assembly.LoadFrom(), etc.

Now this last idea might crash, I don't know. It "sounds" good, but if it normally locks the original, this new approach either safely skirts the issue, or, ah, "makes it angry", lol. I don't know which. If it fails, you might have to provide a manifest instead to explicitly tell it where to find the dependencies...

Oy, sorry, none of this sounds easy.

Mike
__________________
My Articles:
| Excel from .NET | Excel RibbonX using VBA | Excel from VB6 | CVErr in .NET | MVP |
Avatar by Lebb

Last edited by Mike Rosenblum; 06-01-2007 at 04:04 PM.
Reply With Quote
  #27  
Old 06-01-2007, 04:20 PM
topry topry is offline
Newcomer
 
Join Date: May 2007
Posts: 23
Default

Actually, I think I have an easy way.
I get the version number of the DLL and append it to the file (eg: MyDll.dll1.0.0.3)
Check the temp folder if it already exists, load it and go, if it doesn't, copy, load it and go.

Since the original (the updateable one) is the one I will be overwriting, as long as it is not locked, I can update it at will. Each time the AddIn is loaded it checks to see if what is in the install folder + version is in the temp foler.

To resolve the reference issue and without dropping back to late binding (which complicates my life on the front end) - I create a reference to the interface in the AddIn, then after .CreateInstance, use DirectCast to convert the object to one of my interface and voila!.

Quick tests look promising - thanks for all of your input and help. It is much appreciated.
Reply With Quote
  #28  
Old 06-02-2007, 05:27 AM
Mike Rosenblum's Avatar
Mike Rosenblum Mike Rosenblum is offline
Microsoft Excel MVP

Forum Leader
* Guru *
 
Join Date: Jul 2003
Location: New York, NY, USA
Posts: 7,848
Default

Yes, that sounds right!

I like your idea of obfuscating thhe name of the original DLL with the version number of the DLL itself. Two birds with one stone. I'm still a little surprised that the original verision needs to be protected from being locked like this, but if that's what's needed then your idea is a very clean way to do it.

As for the interface, yes that is exactly the way to avoid the need for late binding. Be aware that I think you will have to define the interface in a third assembly so that it's definition does not change depending on were you've copied it to. (The assembly with the interface should not move or be installed in the GAC.) Then both the front end shared add-in and back end late-loaded assembly would both reference the assembly that holds the interface.

Good luck, let us know how it goes...
,
Mike
__________________
My Articles:
| Excel from .NET | Excel RibbonX using VBA | Excel from VB6 | CVErr in .NET | MVP |
Avatar by Lebb
Reply With Quote
  #29  
Old 06-04-2007, 02:52 PM
topry topry is offline
Newcomer
 
Join Date: May 2007
Posts: 23
Default

Well, I was almost there or so I thought- but after a couple more days of teeth gnashing, hair pulling (which I cannot afford!), I finally have it completed, tested and implemented! The following is a synopsis of the process that I hope will save others a bit of time. I surely don't claim this the best nor even the only way, so if anyone knows how to do this 'better', please pass it along.

[This hit the message limt, so I have split it into two]

Environment: Small business with internally developed applications running on two W2k3 servers running MS Terminal Services with Office 2003.

Primary Goal: To create an integrated document management system that controls all Excel and Word documents, including the creation, viewing, printing, emailing, faxing, and prevents unauthorized access including cut and paste.

Requirements:
The automation of the Office COM objects are straight forward and well documented using the COM Interop PIA’s and/or the .Net VSTO templates. However some of the goals in this project could only be accomplished through the use of shared add-ins. While VS .Net 2005 has a standard template to create a shared add-in, I could not find any documentation on how to accomplish two requirements.
1) the ability to interact with the Add-In from a separate process (the .Net application that uses the Interop PIA automation), ideally using early binding (late binding is very straight forward)
2) being able to dynamically update the Add-In without requiring all of the users on the Terminal Server to close all instances of Word and Excel


Item1: To obtain a late bound object reference requires the addition of only two lines of code to the template skeleton that is created by .Net. In the .OnConnection event, add the following lines at the end of the event handler:
Dim AddIn As Core.COMAddIn = CType(addInInst, Core.COMAddIn)
Addin.Object = Me

While late binding worked fine and considering the interop overhead, I did not expect to see any performance improvement through early binding, it was still my preference for the purpose of making the code easier to understand and overall ease of use. I finally found a single reference to this topic in an MSDN forum and the method to accomplish it is straight forward – create a public interface within the shared add-in module and then another public class, also within this module that implements the interface. http://forums.microsoft.com/MSDN/Sho...59557&SiteID=1
Then instead of setting:
Addin.Object = Me
We create an instance of our new public class that implements our interface:
Dim MyExposedClass As New clsExternalObject
and then set the .Object property to that:
Addin.Object = MyExposedClass

The external application will need a reference to your AddIn (remember it’s a COM object so you must browse to the DLL).
Import a reference to the object and the interface;
Imports MyOfficeAddIn = HITOfficeAddIn.Connect.MyHITOffice
Imports MyAddInInterface = AddInInterface.clsInterface.IAddInExposedClass

Create an object that implements the same public interface:
Private MyAddInObject As AddInInterface.clsInterface.IAddInExposedClass

To create a reference to the AddIn and implement properties and methods on that interface do the following:
Dim itm As Core.COMAddIn = MyAddIns.Item("MyAddInName.Connect")
Now cast your declared interface to the object reference
MyAddInObject=CType(itm.object, MyAddInInterface)

You now have an early bound reference to your Add-In and can invoke methods/properties as required. Keep in mind that each instance of an Office application that loads a shared add-in loads its own copy of that object. However, simply double-clicking on a desktop shortcut to launch Word/Excel may appear to run separate instances, in many cases it’s the same instance (check Task Manager). Depending on your functionality, this may not matter – but in our case, each document must be treated separately / uniquely so we had to insure that each document was loaded in a separate instance of Word/Excel.

Since our primary business application runs under Terminal Services and is shared by upto 50 employees at a time, we needed a way to dynamically update our .Net applications whenever necessary, without requiring them to close the application, which is both time consuming and disruptive. Accomplishing this with a .Net executable is simple and requires nothing more than creating a small launcher executable which everyone runs, which in turn creates a new AppDomain that enables ShadowCopy. This is an article documenting that methodology: http://www.devx.com/dotnet/Article/10045. We needed this same capability with our share add-in since multiple users have one or more instances of Word/Excel opened throughout the day. My first thought was to use ShadowCopy as we did with our .Net application. Unfortunately, I could find no information on how to accomplish this in pure managed code, much less in managed code that was registered as a COM object.

After considerable time and experimentation, I found that simply creating a single new AppDomain was insufficient, that I needed to create two AppDomains in two separate objects. The second one would be shadow copied. While I was able to get this work within the Add-In itself, it does not appear that you can pass an addressable reference to it for external applications as this object was a transparent proxy and I was never able to find a way to create a reference to an object I could use externally. I do not know if this is possible or not, but after many frustrating days, I moved on to what I thought was a simpler way to accomplish the same thing, which was to use Assembly.LoadFrom to load a copy of the ‘worker’ assembly that would be copied to the users temporary folder, leaving the one in the installation folder updateable.

First create a new object/assembly that is simply the public interface, don’t forget to make it COM visible – you will also need to create a strong name for the assembly.
Second create an assembly that will ‘do the work’ – this is the one that you will be updating and should be the only item changed going forward (unless you change your interface). This assembly must also be COM visible and have a strong name.

My first attempt was to:
a) create a reference to the ‘worker’ assembly within the Add-In assembly
b) Get the version of the ‘original’ DLL, the one in the install folder, append the version info to the filename and see if that file existed in the users temp folder, if not, copy it.
c) Use Assembly.LoadFrom to load that assembly into memory
d) Use CreateInstance to create an instance of the ‘worker’ class and pass that reference in the AddIn.Object property.

Unfortunately, this did not work as declaring a reference to the ‘worker’ class caused it to be locked when the .CreateInstance method was invoked. The solution to this, was to create a reference to the interface and then DirectCast the object to the interface. For a few moments, I thought I had it resolved – the DLL in the temporary folder was locked, as expected and the original in the install folder could be updated upon demand… then I realized that Assembly.LoadFrom locks the file for exclusive use until the AppDomain terminates. This prevents any other process from even reading the object – meaning only one instance of one office application per user could load the Add-In, which was worse than having an add-in that could not be updated dynamically.

After many more hours of research, I found this was expected behavior for the Assembly.Load methods when passing a filename, and that the solution was to load the DLL into a byte array and create the assembly from that. This also has the added benefit of no longer needing to make a copy of the DLL in each users temporary folder.
Dim bin As Byte() = File.ReadAllBytes(MyDll)
Dim myasm As Assembly = Assembly.Load(bin)

However, the Assembly.CreateInstance method was returning ‘Nothing’ for my object, which means that it could not be found…or could it… For whatever reason, the Try/Catch within the Add-In was throwing no error on the invocation of .CreateInstance – simply returned nothing. However, I did get an error when I tried to enumerate the objects from .GetTypes. The problem was that Assembly.Load on the binary byte array was ‘incomplete’ and .CreateInstance could not find the public interface. The solution – install the interface assembly into the GAC.

The DLL could now be replaced as required and directly from the installation folder as well! – life was grand…or was it. When I attempted to reference my now working Add-In from an external application, I found that I could not. I had hit a similar remoting issue that I ran into with my ShadowCopy tests. Fortunately, the resolution to this one was a bit easier – I simply created a proxy class within my Add-In class. It implements the same public interface, the reference in the Add-In class must be shared and then this new proxy class simply invokes the same methods on the interface to this shared object.
Reply With Quote
  #30  
Old 06-04-2007, 02:53 PM
topry topry is offline
Newcomer
 
Join Date: May 2007
Posts: 23
Default

[Continued]

Other considerations: The default setup project created by the VS .Net 2005 template works very well. I recommend these articles for more information:
http://support.microsoft.com/kb/302896/
http://support.microsoft.com/kb/908002/en-us (important - this patch IS required)
http://excelkb.com/default.aspx?cNode=4K2U2U
The two items missing from the default install package is the TypeLib registration for your Add-In and installing the interface assembly into the GAC. You will need to add these two items to the package, or if installing yourself, simply run RegAsm on the main add-in DLL with the /TLB comand line switch for the typelib registration and use GACUTIL /I myinterface.dll to install the assembly into the GAC.

Like many things in programming, the resulting solution is relatively simple and something I could now replace from scratch in less than 1 hour – unfortunately getting here took well over 100 hours of research and experimentation. Hopefully, this will save a few people some of that frustration and wasted time.
Reply With Quote
  #31  
Old 06-05-2007, 01:19 AM
DennisW's Avatar
DennisW DennisW is offline
Junior Contributor
 
Join Date: Mar 2006
Location: Östersund, Sweden
Posts: 268
Default

Many thanks for Your kindness to share Your experience and solutions with the forum. Whenever a solution to a problem is achieved it should be considered as good. In my experience progress usually means that new (better) solutions may be available but only the future can tell us

(BTW, I'm a power user of Add-In Express but that's another story )
__________________
Kind regards,
Dennis

.NET & Excel | 2nd edition PED | MVP
Reply With Quote
  #32  
Old 06-06-2007, 03:35 PM
topry topry is offline
Newcomer
 
Join Date: May 2007
Posts: 23
Default

Add-In Express is an impressive product - well worth the price. While I am not using it for this current project, I likely will in the near future. I purchased it originally only for the ShadowCopy implementation, but needed a different out of process interface than PostMessage. Before I open a controlled document I must query the AddIn. Using COM, I can use a function result, trying to do that with PostMessage is a bit...ugly.
Reply With Quote
  #33  
Old 06-07-2007, 09:25 AM
Mike Rosenblum's Avatar
Mike Rosenblum Mike Rosenblum is offline
Microsoft Excel MVP

Forum Leader
* Guru *
 
Join Date: Jul 2003
Location: New York, NY, USA
Posts: 7,848
Default

Hey topry,

That is a really nice writeup of the steps you had to take and the thought process you used to get shadow copying to work for a shared add-in. If you cleaned it up a bit I think it would make for an excellent tutorial or walkthrough. Just something to keep in mind if you wanted to write a killer tutorial in the future...

Thanks for sharing, I think that the detailed information you provided will definately be of value to others in need of a similar solution.

Thanks for contributing ,
Mike
__________________
My Articles:
| Excel from .NET | Excel RibbonX using VBA | Excel from VB6 | CVErr in .NET | MVP |
Avatar by Lebb
Reply With Quote
  #34  
Old 06-11-2007, 12:06 PM
topry topry is offline
Newcomer
 
Join Date: May 2007
Posts: 23
Default

If I cleaned it up.. A LOT!
Stream of consciousness writing can be difficult to follow.
Considering the dearth of info on the issues, I'm not sure there would be many/any interested. What I thought were 'common questions' apparently were not so common after all.

Once I get caught up, if there is any activity on the topic I will try and put something together.
Reply With Quote
  #35  
Old 06-11-2007, 12:16 PM
Mike Rosenblum's Avatar
Mike Rosenblum Mike Rosenblum is offline
Microsoft Excel MVP

Forum Leader
* Guru *
 
Join Date: Jul 2003
Location: New York, NY, USA
Posts: 7,848
Default

Actually, "stream of consciousness" or not, I think what you wrote flows very nicely. It reads like a how-to, explaining every bump in the road and how you got around it. Yes, it would need some cleanup, but I think it's actually very good as-is.

I agree somewhat with what you say about attempting to estimate the level of interest for this. To some degree you may be right, but I think that one of the key issues with any article covering an advanced topic is the same: how big is your audience? The tougher the issue, the smaller is your audience. On the other hand, achieving a first-class write up on an advanced topic is an achievement unto itself and very well could be worth doing... If one has the time!

Anyway, I personally appreciate your having shared your detailed thoughts and experiences with this.

-- Mike
__________________
My Articles:
| Excel from .NET | Excel RibbonX using VBA | Excel from VB6 | CVErr in .NET | MVP |
Avatar by Lebb

Last edited by Mike Rosenblum; 06-11-2007 at 12:21 PM.
Reply With Quote
Reply


Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off

Forum Jump

Advertisement:





Free Publications
The ASP.NET 2.0 Anthology
101 Essential Tips, Tricks & Hacks - Free 156 Page Preview. Learn the most practical features and best approaches for ASP.NET.
subscribe
Programmers Heaven C# School Book -Free 338 Page eBook
The Programmers Heaven C# School book covers the .NET framework and the C# language.
subscribe
Build Your Own ASP.NET 3.5 Web Site Using C# & VB, 3rd Edition - Free 219 Page Preview!
This comprehensive step-by-step guide will help get your database-driven ASP.NET web site up and running in no time..
subscribe
VS 2005 Shared Add-In and ShadowCopy
VS 2005 Shared Add-In and ShadowCopy
VS 2005 Shared Add-In and ShadowCopy VS 2005 Shared Add-In and ShadowCopy
VS 2005 Shared Add-In and ShadowCopy
VS 2005 Shared Add-In and ShadowCopy
VS 2005 Shared Add-In and ShadowCopy VS 2005 Shared Add-In and ShadowCopy VS 2005 Shared Add-In and ShadowCopy VS 2005 Shared Add-In and ShadowCopy VS 2005 Shared Add-In and ShadowCopy VS 2005 Shared Add-In and ShadowCopy VS 2005 Shared Add-In and ShadowCopy
VS 2005 Shared Add-In and ShadowCopy
VS 2005 Shared Add-In and ShadowCopy
 
VS 2005 Shared Add-In and ShadowCopy
VS 2005 Shared Add-In and ShadowCopy
 
-->