We're writing a simple .NET C# COM applicaton for Photoshop, which is designed to run on all versions from CS2 to CS5 and everything in between. The same application also exists in JavaScript form and it works with all aforementioned versions, as we've avoided to implement version specific functionality.
The problem we've run into is related to the COM interface. For instance, if our application is compiled with the Interop.Photoshop DLL from Photoshop CSx, it does not run on Photoshop CSy. This seems to be because the registry CLSID is specific to each version of Photoshop, causing our application to not find the correct COM interface DLL, if run on a system where a different version of Photoshop than what we compiled for is installed (assuming both applications are 32 bit).
If this is indeed the problem, we are wondering if it would be possible to re-register the COM interface of Photoshop version CSx with the CSy CLSID, ignoring the fact that functionality may differ between versions.
More specific information follow:
Our primary Photoshop.Application CLSID is located here in the registry: HKEY_CLASSES_ROOT\Photoshop.Application\CLSID
This CLSID must match the CLSID for which our application was built for. For instance, this ID differs between CS5 and CS5.1.
Our only solution today is to build specific versions of our application for specific versions of Photoshop, and this is only possible if we have that specific application version installed.
The error code we are getting is 0x80040154, "Retrieving the COM class factory for component with CLSID {116EE066-135E-4F63-8D0E-78F62705FBFC} failed". This application was built with CS5.1 but run on CS5.04 which resulted in the COM interface to not be found. This CLSID is specific to CS5.1. In conclusion, we need to re-register the COM interface to match the CLSID of CS5.04 to be able to run our application on that specific version. Is this possible or is there another way?
Any help or hint we can get on the matter is extremely appreciated.
Changing the GUIDs of COM classes and interface is a hard requirement, it avoids DLL Hell. To invoke the normal problem you are dealing with right now: Version Hell. When you try to use a version of a chunk of code that's not installed on the machine you get a diagnostic message instead of random undiagnosable failure. You cannot reliably re-register, that can seriously mess up your customer's machine.
Yes, you can do it the way Javascript does it: use late binding. You don't use the interop library. It is very painful to do in C# if you use a version earlier than 4, you have to use reflection. But easy to do with VB.NET or with C# version 4's dynamic keyword. This KB article shows late binding in C# using the old way. With the dynamic keyword you can write the code you way you have it written now, other than the object creation syntax. Any kind of version mismatch problem will still cause an exception but only at the property or method call and only at runtime, not compile time.
Related
We have managed to build an outlook addin for 2010,2013 and 2016. We have created an OL2010 vsto project and fiddled with the .csproj file(1*). The problem is that few types were added in 2013/2016 and we can't access them. We only want to access them if the respective OL version is used of course.
As far as i understand the whole thing:
The Interop assemblies are delivered with the addin. So its theoretically possible to inject some IL code or provide additional assemblies with the same namespace that provide those missing types. Since those Interop assemblies are only COM Wrapper and the functionallity relies on unmanaged code which is provided by the installed Outlook version they should get loaded seamlessly when imported correctly.
Is that somehow feasible?
What could go wrong if i try this/deploy the addin to the client?
How could i proceed to determine if i need to use a higher version Interop Type?
How can i load the higher Interop Type?
How can i use it without conflicting existing types?
As i want Intellisense and static typing. I have to predefine those types and decorate them with Guid, CoClass, and TypeLibType. Will that be enough?
(1*)(we changed the DebugInfoExeName and the OfficeVersion in the ProjectName.csproj file and built the respective installer with different virtual machines where the corresponding outlook versions are installed. Maybe there is an easier way? -> let me know!...sadly we can't use addin-express because of some reasons.
I would preferably not use the complete OL 2016 Interop Assemblies since it is throwing exceptions when used from other Threads than the main threads. And we have some synchronization code which has to be run in the background.
Thank you for any answers!
"Creating interops manually: In Visual Studio, just add a COM reference to an application of the Office 2000 suite to the project. This automatically creates the interop you need. But it is the point where your problem begins: a great number of classes and events are inaccessible, because a number of identical bugs in Office type libraries make Studio create the interop that will not work for you. You can disassemble the interop, make unavailable classes and events public, and recompile it (ildasm.exe and ilasm.exe). This is exactly the way Add-in Express version-neutral interops were created."
source: https://social.msdn.microsoft.com/Forums/en-US/a95cd4e3-e619-4846-be2a-ce4c235ff457/is-it-possible-to-use-the-microsoftofficeinteropoutlook-that-comes-from-office-2010-with-all?forum=outlookdev
EDIT
Checkout this project
https://github.com/netoffice/NetOffice-NuGet
It contains version neutral interop assemblies though the structure and namespaces dont match... But still could be useful.
Of course the Embed interop types feature is a great thing, but I can't get it work with me even in simple scenario, so please advice
this is the error I get when I run my project on machine which didn't have powerPoint installed on it:
My code is very simple I just create object from powerPoint, create presentation and slid write something in it.
The libraries I embed is Office and Microsoft.Office.Interop.PowerPoint
converting build configuration to x68 didn't solve it,
I am building windows application and putting the code in button click as follows:
private void button1_Click(object sender, EventArgs e)
{
var pp = new powerpoint.Application();
var oPres=pp.Presentations;
pp.Visible = Office.MsoTriState.msoTrue;
powerpoint.Presentation oPre= oPres.Add(Office.MsoTriState.msoTrue);
powerpoint.Slides oSlides = oPre.Slides;
powerpoint.Slide oSlide = oSlides.Add(1, powerpoint.PpSlideLayout.ppLayoutText);
powerpoint.Shapes oShapes = oSlide.Shapes;
powerpoint.Shape oShape = oShapes[1];
powerpoint.TextFrame oTxtFrame = oShape.TextFrame;
powerpoint.TextRange oTxtRange = oTxtFrame.TextRange;
oTxtRange.Text = "All-In-One Code Framework";
string fileName = Path.GetDirectoryName(
Assembly.GetExecutingAssembly().Location) + "\\Sample1.pptx";
oPre.SaveAs(fileName,
powerpoint.PpSaveAsFileType.ppSaveAsOpenXMLPresentation,
Office.MsoTriState.msoTriStateMixed);
oPre.Close();
pp.Quit();
pp = null;
}
in the top I added
using powerpoint = Microsoft.Office.Interop.PowerPoint;
using Office = Microsoft.Office.Core;
using System.IO;
using System.Reflection;
Note: the program work fine at my end where I have office 2013 installed, but this error show up at my client pc
As already mentioned, the problem is that there is that Powerpoint is not installed on the client machine.
The powerpoint object is implemented in a COM type library and installed and registered when Powerpoint is installed. COM and .NET are totally different technologies. To use COM types from your .NET application, you don't use COM type directly but a special interop .NET assembly. This assembly doesn't contain any PPT functionality, it's just a wrapper that servers as a bridge between your .NET application and a COM type. An interop assembly does all the hard work for you and defines .NET types (from COM types) that you can use as other .NET classes, for example powerpoint.Application.
The interop assembly is just a normal .NET assembly. You can reference it as other .NET references (Embed Interop Types = false). In this case you need to distribute the interop DLL with your application. If you set Embed Interop Types = true, then the interop assembly is compiled and embedded directly to your application assembly. Moreover, only types and functions that are really used are embedded. So using this option has the advantage of optimized and single assembly.
But still, even when embedded, the interop info is just a wrapper over real COM type that must be installed on client machine. If it's not, you get the error.
See more details at https://msdn.microsoft.com/en-us/library/xwzy44e4.aspx
Your option is to force the client to install PPT or avoid Microsoft.Office.Interop.PowerPoint and use some third-party PPT library that you can distribute with your application.
"Embed interop types" does not mean "embed the Office program". You got this error for a very simple reason, Powerpoint is not installed on that machine.
I supposed that the term "interop types" could use an explanation. You automate an Office program by using COM, an interop technology that preceded .NET and has many similarities to .NET. It also has the notion of metadata, just like a .NET assembly has, the type information that a compiler uses when you add a reference to a .NET assembly. That metadata is called a "type library" in COM. The type library for Powerpoint is C:\Program Files (x86)\Microsoft Office\Office15\MSPPT.OLB for the Office 2013 version.
Type libraries are not exactly perfect, they suffer from ambiguity problems that don't matter much in an application written in C++ but do matter in .NET. So the .NET designers decided to not use a type library directly. And specified the Tlbimp.exe utility, it converts the library content to a .NET assembly. Directly usable by the CLR and managed language compilers without having to deal with the headache of interpreting the type library content.
By convention, the .NET assembly generated by Tlbimp.exe is called an "interop library". It is a one-to-one match with the type library. Most importantly in the context of this question, it only contains declarations, it does not contain executable code. The code remains in the COM component, written in a non-.NET language. Like the Office programs, written in C++.
The interop assembly is required at compile-time, the compiler uses it to know what types are implemented by the COM component so it can properly type-check the code you write and tell you when you get it wrong. It is also required at runtime, it is loaded by the CLR as soon as you use one of the COM types.
Having to deploy the interop assembly to the machine that executes your program is a burden. Not in the least because you often need a "Primary Interop Assembly", yet another mystical term that relates to solving the .NET type identity problem. A detail I'll skip here. The PIAs for Office are very large. It is often very murky exactly who is responsible for ensuring the PIAs are deployed on the machine. With programs failing when nobody takes care of it, a very common mishap.
A burden that Microsoft solved in .NET 4, two COM interface types are considered identical when they have the same [Guid], even when they came from a different assemblies. Which permitted a trick, the compiler can now copy the type declarations from the interop assembly into the output assembly. Only the ones you actually use. Solving both the deployment problem and the bulky interop library problem. You no longer need a PIA and don't have to deploy the interop assembly anymore. This is what "Embed interop types" means.
Long story short, metadata is not enough to execute code. You really do have to have the Office program installed on the target machine. And get this exact exception when it is not.
Funny how the most important information in an error message can be buried so deep its very difficult to see.
Class not Registered
This is your error. Basically the object you are trying to create does not exist or has not bee properly installed on the machine you are running your software on. Is powerpoint definitely installed?
I'm working with Visual Studio 2010 and WinForms, .Net 4.0 (C#). I'm building an application with a lot of DLL (150). When I provide the application to my client, it's :
The Executable (.exe)
Dll files (.dll)
Each Dll is related to a module of the application, for example :
Ado.dll (provide access to database)
AccesManagement.dll (this module allows to manage users in the application)
Import.dll (this module allows the user to import data to the application)
etc.
When my client find a bug in the application, I correct it and I provide him impacted DLLs (in order to avoid him to test all the application). It can be for example the Import Dll.
The thing is, after some deliveries, we can have compatibility problems between Dll (a method that doesn't exist anymore in a new DLL for example). To avoid this problem, I would like to find a tool capable of checking compatibility between differents DLL.
I would like something like :
I specify the directory of the program to analyse (executable + Dll)
I launch the analyse
The program tells me for example : Error between Import.dll and Ado.dll, there is a class xxx in Import.dll expecting a method named xxx in the class xxx of Ado.dll
I've found some tools able to compare two versions of a Dll and provide added and removed members (Libcheck, ApiChange), but it's too complicated for me to do that because there are to many changes.
I think you may have a configuration management problem here -- at least as much as you've got a "compatibility" problem.
I'd recommend you find a way to track what versions of which assemblies each of your customers is using so that (1) you know what they're using when you decide what to ship, and (2) when they report bugs, you can replicate their setup (and thus, replicate their bug). If that sounds like a lot of work, it is. This is why a lot of software development shops take steps to ensure that there's a limit to the variation in setups among customers. It's nearly certain that you'll end up with some variation from customer-to-customer, but anything you can do to manage that problem will be beneficial.
Beyond the process implications, if you really need to create a "pluggable" environment, you probably need to create some interfaces for your objects to control the points where they connect, and you should probably look at Microsoft's Managed Extensibility Framework (MEF). MEF can help you manage the way objects "demand" behaviors from other objects.
I finally found a solution to my problem.
Since I'm :
Using SourceSafe and adding labels with the version of the application I'm building
Tagging each of my DLL with the version of the application
I built a program which is capable of :
Opening each Dll of a folder to read the version of the application in it
Getting from SourceSafe each project for the version specified in the DLL (With the functionnality "Get Label")
Then I just have to build the projet. If there is any compilation error, there is a compatibility problem.
This solution can avoid big compatibility problems, but you can still have compatibility problems which can't be seen with a compilation...
I have an image editor user control(c# .net v2.0). Its used in thousands of computers as an activex component. I want the component also be available for windows forms and possible other uses.
For activex usage i add <object> tag in html code and call the component with clsid(a static guid). So if i build and distribute a newer version it works without changing any client code.
I want windows forms to be able use the same distributed libraries. And they should not reference a specific version so i can update the component without changing the programs that use it.
I use regasm to register for com. But i dont know how to reference it from visual studio(like referencing with clsid?)
May be in visual stuido when i choose add reference and select COM tab i shuld see my component in the list.
note: i tried to add the assemly to the global cache using these lines but it did not work out-or i coulnt understand if anything has changed :)
"C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\tlbexp" ImageEditorComp.dll /out:ImageEditorComp.tlb
regasm /tlb:ImageEditorComp.tlb ImageEditorComp.dll
"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\gacutil" /I ImageEditorComp.dll
Any suggestions appreciated,
Regards
This is not possible, you'll invoke the infamous and dreaded DLL Hell problem. A stone cold hard rule in COM is that you have to change the [Guid] attribute values on public interfaces when you make a breaking change in either the publicly visible interfaces or the implementation of them. Changing the guids ensures that you don't overwrite the registry keys of an old version of your component when you use Regasm.exe. Existing programs that use your component and were not recompiled to use the latest version will continue running without problems. The typical outcome of DLL Hell is a nasty hardware exception like AccessViolation, very difficult to troubleshoot.
None of which applies in your specific case here. There is no point in trying to use the component through COM. It is a .NET assembly, just add the reference to it directly. The IDE will in fact stop you from adding a reference to the interop library. But not the .tlb. The GAC keeps you out of DLL Hell, assuming you properly increment [AssemblyVersion].
I figured out a solution.
To explain step by step:
1- Create the component with all needed properties for com.(Sign the assembly, use interfaces for com, make assembly com visible)
On the client machine
2- Register the assembly with regasm(i recommend adding safety flags too).
3- Add the assembly to the global cache using gacutil(or msi installer)
I figured out when you call a specific version of an assembly gac is searched in the first place so if its installed in GAC, referenced codebase path is never used.
When using as activeX you address the component with GUID. Since regasm adds the assembly name and version the GUID is representing, web browser directly uses component from GAC.
When using from a desktop application, reference the assebmly directly and set copylocal property to false. Similarly, in the client machine windows will find the assembly located at GAC itself.
Here is a useful link about the subject.
http://www.simple-talk.com/dotnet/visual-studio/build-and-deploy-a-.net-com-assembly/
Hope it saves other peoples time :)
Scenario
I have two wrappers around Microsoft Office, one for 2003 and one for 2007. Since having two versions of Microsoft Office running side by side is "not officially possible" nor recommended by Microsoft, we have two boxes, one with Office 2003 and the other with Office 2007. We compile the wrappers separately. The DLLs are included in our solution, each box has the same checkout but with either Office 2003 or 2007 "unloaded" so it doesn't attempt to compile that particular DLL. Failure to do that will throw errors on compilation due to the Office COM DLLs not available.
We use .NET 2.0 and Visual Studio 2008.
Facts
Since Microsoft mysteriously changed the Office 2003 API in 2007, renaming and changing some methods (sigh) thus making them not backwards compatible, we need the two wrappers.
We have each build machine with the solution and one Office DLL activated. E.g.: the machine with Office 2003 has the "Office 2007" DLL unloaded, therefore not compiling it. The other box is the same idea but the other way around. All this because we can't have 2 different Office in the same box for programming purposes. (you could technically have two Office together according to Microsoft) but not for programming and not without some issues.
Problem
When we change the Application Version (from 1.5.0.1 to 1.5.0.2 for example) we need to recompile the DLL to match the new version of the application, this is automatically done, because the Office wrapper is included in the solution. Since the wrappers are contained in the solution, those inherit the APP Version, but we have to do it twice and then "copy" the other DLL to the machine that creates the installer. (A Pain…)
Question
Is it possible to compile a DLL that will work with any version of the application, despite being "older"? I've read something about manifests but I have never had to interact with those. Any pointers will be appreciated.
The secret reason for this is that we haven't changed our wrappers in "ages" and neither did Microsoft with their ancient APIs, yet we are recompiling the DLL to match the app version on every release we make. I'd like to automate this process instead of having to rely on two machines.
I can't remove the DLL from the project (neither of them) because there are dependencies.
I could create a third "master wrapper" but haven't thought about it yet.
Any ideas? Anyone else with the same requirement?
UPDATE
Clarifying:
I have 1 solution with N projects.
"Application" + Office11Wrapper.dll + Office12Wrapper.dll.
Both "wrappers" use dependencies for application + other libraries in the solution (datalayer, businesslayer, framework, etc.)
Each wrapper has references for the respective Office package (2003 and 2007).
If I compile and don't have office 12 installed, I get errors from Office12Wrapper.dll not finding the Office 2007 libraries.
So what I have are two building machines, one with Office 2003, one with Office 2007. After a full SVN update + compile on each machine, we simply use office12.dll in the "installer" to have the wrapper compiled against the "same code, same version".
Note: The Office 2007 Build Machine, has the Wrapper for Office 2003 "unloaded" and viceversa.
Thanks in advance.
When the .NET assembly resolver is unable to find a referenced assembly at runtime (in this case, it cannot find the particular wrapper DLL version the application was linked against), its default behavior is to fail and essentially crash the application. However, this behavior can be overridden by hooking the AppDomain.AssemblyResolve event. This event is fired whenever a referenced assembly cannot be found, and it gives you the opportunity to substitute another assembly in place of the missing one (provided that they are compatible). So, for instance, you could substitute an older version of the wrapper DLL that you load yourself.
The best way I've found to do this is to add a static constructor on the main class of the application that hooks the event, e.g.:
using System.Reflection;
static Program()
{
AppDomain.CurrentDomain.AssemblyResolve += delegate(object sender, ResolveEventArgs e)
{
AssemblyName requestedName = new AssemblyName(e.Name);
if (requestedName.Name == "Office11Wrapper")
{
// Put code here to load whatever version of the assembly you actually have
return Assembly.LoadFile("Office11Wrapper.DLL");
}
else
{
return null;
}
}
}
By putting this in a static constructor of the main application class, it is guaranteed to run before any code attempts to access anything in the wrapper DLL, ensuring that the hook is in place ahead of time.
You can also use policy files to do version redirection, but that tends to be more complex.
Just a thought - could you use TlbExp to create two interop assemblies (with different names and assemblies), and use an interface/factory to code against the two via your own interface? Once you have the interop dll, you don't need the COM dependency (except of course for testing etc).
TlbImp has a /asmversion for the version, so it could be done as part of the build script; but I'm sure you even need this: just make sure that "specific version" is false in the reference (solution explorer)?
Also - I know it doesn't help, but C# 4.0 with dynamic and/or "No PIA" might help here (in the future; maybe).
I'm not sure I am completely following everything you stated, but let me try:
It sounds like you have one solution with 2(?) projects. One is the actual application, and the other is a wrapper for the Office API. Your application then has a project reference to your Office API wrapper. I've never programmed for office before, but it sounds like the programming APIs are a common component that you can only have one version of on a machine (ie. 2003 or 2007, not both). And maybe this is where the problem is, but because you have a project reference, the wrapper will be compiled first, copied to the bin directory of your application, where your application will be linked to that build of the wrapper. This will cause the manifest of the application to specifically request that version of the wrapper at run time.
If you had the wrapper in a separate solution, and added a reference to the compiled library rather than the project, you would always link your application to that version of the wrapper and you could avoid the problem.
Another possible choice is Assembly Binding Redirection. This is more advanced, and comes with it's own set of problems, but you can read about it here.
Or similar to Marc's idea, you could extract an interface and pull some common objects into a Framework library, and code your application against the interface and common objects. Then at runtime use reflection to load the assembly and instantiate the wrapper you want.
I think the key is to remove the project dependency if you can. It sounds like the wrapper is pretty stable and isn't changing, otherwise you wouldn't be asking to link to a previous version of it.
Installing Office 2003 and 2007 side-by-side on the same machine is definitely possible - we do it in our organisation even on end-user production workstations.
In that linked article, Microsoft recommend that you don't do this for actual use. But in your case it appears to be just for a single build machine, i.e. you're not going to actually use either version of Office on that machine. In this context, I would try to see if you can make the side-by-side installation work.
My assumption might be wrong, and you're attempting to do this for every developer's machine. In that case, you should ignore this answer :-)
Nice sleuthwork! I just threw together an implementation based on the concept presented above, and it works wonderfully:
static Assembly domain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string partialName = args.Name.Substring(0, args.Name.IndexOf(','));
return Assembly.Load(new AssemblyName(partialName));
}
Of course there is room for enhancement, but this does the trick for me!