I am writing a VSTA 20151 enabled app.
In it, I need to load an assembly, access a well-known method (a constructor, not that it matters), invoke it, and pass it a COM object (wrapped up so it is a .net object naturally).
This works fine. Now, in order to enable debugging, I need the process that the assembly is loaded from be different than the one that interacts with the VSTA subsystem. The application interacts with the VSTA subsystem; which means I need the assembly to be loaded in a distinct process.
Is there a simple way to load a .net assembly in a separate process, passing it a com object (or a .net object wrapping the com object) to a well-known entry point?
The complex method would involve creating a custom .exe loader that loads the assembly, use IPC to get ahold of the COM interface, wrap it up and pass it to the assembly. Then use IPC to return any error messages. Not certain what kind of IPC I should use. But there are a lot of moving parts here (the two IPC systems, the loader), and I'd hope there would be an easy way to do it without them.
I looked at System.Diagnostic.Process.Start, but it doesn't seem able to "open an assembly as a process and pass it this .net object" among the options.
Apartments (and similar) probably won't work, as the separate process is needed so that a debugger can attach to the VSTA "script" process and pause/advance it without locking up the VSTA "hosting" process (which in the docs says it can lead to deadlocks).
The application is mixed managed/unmanaged, if that matters. Most of the managed component is C#, most of the unmanaged is C++, and most of the interface is C++/cli.
1 VSTA 2015 is a Microsoft solution to allow on-the-fly editing of a "script-like" environment of a .net language, where the Visual Studio IDE workflow is integrated with the application workflow. Note that this is different than pre-VSTA 2012 in fundamental ways: pre-VSTA 2012 information or answers are unlikely to be useful here.
Related
I have a number of C# Assemblies which have been built and registered as COM components. They work perfectly fine when invoked from unmanaged code (Fujitsu NetCOBOL for Windows) and I also have no difficulty when invoking COBOL COM components from C#. The unmanaged COM component appears in the VS 2017 Reference Manager COM tab, VS 2017 generates the Interop and it is all excellent.
But if I try to use one of the managed COM components with managed code (a C# .exe), the VS 2017 Reference Manager doesn't show an entry for the .DLL. Instead, it presents the DESCRIPTION of the component instead of the NAME, and it is pointing to the .TLB which was generated when I built the original component. If I select that reference, this happens:
If I go and Browse for the .DLL the reference is added OK, but it forces me to "Copy Local" and I get a copy of the .DLL deployed with my executable.
If I try and make it false, and embed the types, it fails to build and I get a message about some obscure attributes not being set...
If I build it as shown, it deploys and works perfectly, but I am adding a copy of the COM component .dll to every executable I deploy. That kind of defeats the object of COM components where one copy of the code is shared between users. I used COM for years with unmanaged code and never had any problems. Is a different understanding required for use with managed code?
I have spent a number of hours reading the docs on COM and interOP (although InterOP is not applicable here I think, as both the client and the server are managed code.)
Is there something I need to set when I build the component? (I use "Make COM Visible" and it certainly seems to register it OK. However, I noticed that if I unregister it with Regasm it does not clear the registry entries...
Is there a VS 2017 setting I am missing or something in the build? Any help would be gratefully received.
Pete.
After some intensive reading and experimentation I finally solved the problem.
SUMMARY:
To be able to used managed COM components in the same way as we used unmanaged ones (one copy, registered and shared by all users), you SHOULD NOT try and add the .DLL as a reference to your project. Instead, you late bind the component and use reflection to implement its interfaces, methods, and properties.
This means doing the following:
Getting an instance of the component dynamically at run time.
using reflection on that instance to access properties and methods in the normal way.
DETAILS and CODE SAMPLES (to hopefully save someone else from having to do the background I did...):
First thing: DON'T add a reference to the .DLL to your Project.
I set up some global variables as follows:
Now I needed to consider what actions I wanted. I wanted a general solution so that I could use it on ANY COM reference and what I'm posting here is close, but things like
specific error messages have not been replaced with variables yet.
The COM object supports Getting and Setting Properties and invoking Methods. I also need to be able to instantiate it easily.
The methods I have written here return a dynamic value in retVal.
They have been tested and all work very well, so I can continue to use the managed COM components just as I did the unmanaged ones, by simply adding the code samples here to any project that deals with them, and invoking these methods to deal with a given managed COM component. I do not claim to be an expert in C# and I'm sure that more experienced people will find better ways to code than I have, but it does work and that is always a good thing... :-)
I was a little concerned that using reflection might slow everything down, but in practice it is no "slower" than late binding any COM object is.
Here are the methods:
(Sorry for code misalignment; I'm new to this...)
The method to instantiate the COM object. The example uses something called "RAVDesktop.dll"
Now the "Action" methods for properties and methods in the COM object...
Here's an example of instantiating the component, then setting a Property in it called "WebServiceURL", and, if the setting succeeded, retrieving it so it can be checked:
Grateful thanks to all who contributed. Stack Overflow rocks!
Pete.
ActiveX controls or COM components written in .NET languages cannot be referenced by .NET
applications in the form of interop assemblies.
If you "add reference" to such a TLB, or drag & drop such an ActiveX control to your .NET application, you will get an error "The ActiveX type library 'XXXXX.tlb' was exported from a .NET assembly and cannot be added as a reference."Support: Can not add reference a COM in COM client?
The correct way to solve this problem is to add the reference.As follows:Right-click Reference->Add Reference->Browse.
I am creating a web app using ASP.NET. This web app creates an instance of a C# class. This C# class instantiates a COM component written in c++. The C# class calls multiple functions in the COM dll sending a lot of data (read from an ascii file) through the COM interface. The COM dll then does calculations and returns data to the C# application using structures defined in the COM IDL. The COM dll was originally creating us MS C++6.0 but is now built using VS2012.
I see that when I access the web app from 2 different computers, 2 instances of the C# class are created, as I'd expect. Each instance of the C# class creates an instance of the COM component using "new MyCOMInterface". When I look at each instance of the COM interface, they have different addresses, which is what I'd expect. I then start the calculations on both computers. The initial sets of results that get returned from both runs of the web app are correct on both computers. But, if I then press the "Next" button on computer1 (to get the next set of results, which should be sitting in memory in the COM dll), the new results are display on both computers!
I put the C# class within a Session to try to fix this but it did not work. If I then try to get the "Next" set of results from computer2 (by pressing a "Next" button in the web app on comuter2), the results displayed are the next results from computer1.
I am trying to determine if this is a problem in the web app or if my 2 instances of the COM dll are not really independent (are they somehow sharing some memory)? I know that when I run my COM component from two different instances of an .exe, all memory is separate, but I don't know if this is true in a web app.
COM loads and frees libraries using the standard LoadLibrary(Ex) and FreeLibrary routines. These routines load only a single copy of each DLL and maintain a count on the number of times they have been called. When this count reaches 0, the DLL is truly unloaded.
This means that all COM objects served by a DLL (or OCX or other in-process container) will share the global state of their containing library. This is true no matter what isolation is provided by other frameworks like .NET or ASP.NET -- COM doesn't know anything about .NET's AppDomains, for example, and therefore it cannot maintain a separate copy of the DLL per AppDomain.
What you are seeing appears to be bad global state management in your COM DLL. It sounds like it needs to be reimplemented so that the state is managed per object instance.
I am little confused about usage of Application class and AppDomain class.
For example Application.StartupPath is equal to AppDomain.CurrentDomain.BaseDirectory
I usually used Application class and recently discovered AppDomain - Can someone explain to me AppDomain class and its usage?
They have nothing in common, really.
Application is a class specific to Windows Forms, a .NET GUI technology. The Application.StartupPath is handled by the Kernel32 function GetModuleFileName. Through not passing a pointer to a module, the main module's path is returned - that is the exe file, basically.
AppDomain is a core .NET concept for domain isolation. Basically, it allows you to isolate (imperfectly of course) multiple applications in a single native process. Most applications only have a single AppDomain, but you can create as many as you like. The base path of an application domain is handled by Fusion, a .NET assembly loading technology. A very typical example would be ASP.NET applications hosted in IIS - each application has its own AppDomain, but they're all hosted in a single native process ("application pool"). Each logical application can be restarted without touching the others, and they don't have (simple) access to each other, but a process-tearing exception (e.g. StackOverflowException) will still kill the whole pool.
Another interesting class that's somewhat related is Environment. You can use Environment.CommandLine to get the process command line (which includes the path to the executable, including the name of the executable). This is basically a communication interface between the CLR and the underlying system - in this case, it takes care of saving the arguments for the application (which are passed by the OS to the Main function) and making them available at any time in the future.
Environment.CommandLine is somewhat clunky to parse (it's the raw command-line, basically - I assume it will have different conventions on Windows than on Linux, for example), but it's the only way you can always get to the executable. Again, Application.StartupPath is Winforms specific, and you can have more than one AppDomain - and possibly, the AppDomain might not even have a reasonable BaseDirectory.
The .NET Reflection APIs also give you a few ways. For example, Assembly.GetEntryAssembly() will give you the executable assembly - however, this only works for the main AppDomain - other domains will have their own entry assemblies (in fact, they'll usually just return null :)). You can get the path to an assembly through the Assembly.CodeBase property, but do note that this might not always be what you expect. You can also use Assembly.Location, or get the FullyQualifiedName of any of the assembly's modules (again, most assemblies only have a single module; and again, ASP.NET is one of the prime examples of when this isn't the case).
When I try to Convert HTML to XHTML Tag I'm getting the following error...
Error: Retrieving the COM class factory for component with CLSID
{59939390-0E6A-4F1B-A742-20C5459501F7} failed due to the following
error: 80040154.
After googling I found few solutions:
Registering the DLL into regsvr32 "E:Source
Code\bin\Interop.HTML2XHTMLLib.dll"
I'm just tried to register the dll. But E:Source Code\bin\Interop.HTML2XHTMLLib.dll was loaded. But the DllRegisterServer entry point was not found this error message was displayed. Why..?
Recompiled my project for x86 and x64.. no use..
VB.NET Code:
Dim xhtmlUtil As New XHTMLUtilities // Here itself im getting the above error.
sFormattedOutput = xhtmlUtil.convertToXHTML(sInputline) //Send it for conversion
My Operating system is Windows XP 32-bit Service pack 3. My application was done in VS2008. currently I'm working with VS2010.
Here what I'm missing. Could any one help me to figure out this problem?
Thanks in advance.
i'm just tried to register the dll. But E:Source Code\bin\Interop.HTML2XHTMLLib.dll was loaded. But the DllRegisterServer entry point was not found this error message was displayed. why?
The Interop.HTML2XHTMLLib.dll file isn't the library you want to register using regsvr32. It is only the managed interop assembly, generated make COM objects accessable for your .NET application. You actually need to register the type library for the HTML2XHTMLLib.dll.
To do this, you have two options:
Find the redistributeable package, that contains the library and install it together with your application.
On your development system, open the "Add references" dialog of Visual Studio. Choose the COM tab and search for the library (just like you did when you've added the reference). There you will find the absolute path to the library. Copy the library to the client system and register it using regsvr32.
Since I do not know the source of the HTML2XHTMLLib, I can only suggest those ways. You should prefer the first one.
Since you've started a bounty on this, I want to go a little bit more into detail on COM and the InterOp.
Differences between COM and .NET assemblies
There are two types of servers in COM: InProc-servers and OutProc-servers. InProc (In Process) are servers we usually know als DLL. OutProc (Out of Process) servers are standing alone, running in their own process. We know them as EXEcutables.
You want to consume an InProc-server. Your COM-server (HTML2XHTMLLib) consists out of two parts:
A type library (.tlb), that contains meta-information about the server, it's contained objects and their accessability.
A library, containing the code where all the objects are implemented. The library also exports the following static functions:
DllGetClassObject – Tries to create an instance of an object, defined inside the server
DllCanUnloadNow – Tells the COM environment, whether or not the server can be released, because it isn't used by any other process any more.
DllRegisterServer – Called by regsvr32 to register the previously mentioned type library in the Windows Registry, to make it visible to clients and the COM environment.
DllUnregisterServer – Does the exact opposite, when called through regsvr32 -u.
The type library can also a resource of the DLL or EXE file, so that there's only one file. For C# developers this seems somehow confusing, since meta-information is directly compiled into a.NET assembly and accessable through reflection.
The InterOp: A wrapper between .NET and COM
So basicly type libraries describe everything that is needed by the .NET reflection to access the objects exposed through COM. But the problem is, that COM-components are stored in a different format:
Usually they are directly compiled into machine code: You cannot link a .NET assembly, compiled with AnyCPU against a COM-server. COM-servers are directly compiled to either x86-assembler, or x86-64-assembler. They have fixed pointer sizes and thus are only compatible with one of the compilation-models.
COM defines rules for memory management. Each COM-object must implement the IUnknown-interface. This interface defines three methods. The methods AddRef and Release are for memory management purposes. Whenever a client accesses an COM object it needs to call AddRef. This increases a counter by one. When the client does not need the object anymore, it calls Release instead of deleting the object, resulting in a counter decrement. If the pointer reaches 0, the object delete's itself. This is different from how .NET manages memory. In .NET the garbage collector visits each object on the heap in a non-deterministic manner (you cannot determinate the exact point of time an object get's deleted) and releases the object, when there are no references left to it.
COM defines rules for identity. Whenever you only want to access an base interface of an object, you have to call the QueryInterface method, defined by IUnknown. This method is guaranteed to allways return the same pointer, when a specific interface get's queried. This might be also true for .NET (besides you are overloading some operators), but the way .NET ensures object identity is different.
COM defines rules for object relations. Crazy stuff like Aggregation and Containment, which do also exist in .NET, but are implemented differently.
COM defines different multithreading rules, like Single Threaded Appartments and Multi Threaded Appartments. Those threading models define how objects interact, when they are coexisting in different manners. In .NET you have to perform each synchronisation process manually.
This list may not be complete, neither I want to go into detail any further, because it is only incidental for your question, but you see, that there are some big differences between .NET and COM. And to manage those differences there is a layer between both worlds: the COM InterOp.
If you are calling a COM server from .NET, the InterOp is nothing more than a .NET assembly, that does all the hard work under the hood. It get's created using the tlbimp.exe tool. Visual Studio typically calls it for you whenever you are referencing a library from the COM tab. The result is the library you wanted to register: InterOp.Libary.dll. This library redefines all types of the type library of the COM server, implements the rules required by COM and performs the actual calls for you. However it is a managed .NET library which does not define the methods, described earlier. This is why regsvr32 cannot find the DllRegisterServer entry point.
The way described above is only a one-way with an unmanaged COM server and a managed .NET client. There is also the other way, with the counterparts tlbexp.exe and regasm.
I have a 3rd party COM object(32 bit) that I need to call from my c# application (64 bit).
I know I have to run the COM object in a separate process.
This COM object has many classes implemented in it, so I'm trying to avoid writing my own remoting wrapper that exposes all the methods. COM+ seems to be the most straightforward solution. I opened the Component Services menu, created a new COM+ Application, added my COM object as a component to this application. Everything seemed to import beautifully.
In my C# application, I added the original COM object as a reference (which automatically generates the type library). Using the type library reference, I can create objects from from the COM+ component (I see them begin to spin in the Component Services window), but when I try to access on of the methods of the object, I get an error saying the interface is not registered.
Does anyone have a clue? I went back and ran regsvr32 on the COM object, but I don't think it was necessary, and it didn't help.
Is my usage in C# correct? VS2008 autocomplete had no problem seeing those methods.
The exact exception is:
"Interface not registered (Exception from HRESULT:0x80040155)"
Unclear about exactly what the permissions and roles are about in the Component Services, I tried setting up the COM+ object identity to run under the System Account, both as a local service and as interactive user. I've added Everyone as a user in the Roles.
Everything is running locally, so there shouldn't be an issue with file privileges or anything like that.
I also want to reiterate that this COM object contains many classes. I successfully instantiated one class object in my client and set some property values.
I also successfully instantiated another class object, but received this exception when attempting to call a method of this second object .... so I don't think there is an issue with which registry my COM object is registered in.
We had a similar situation, working with a COM dll from VFP.
It all depends on rights and permissions, like Yahia says.
We got it working by doing this:
Install VFP oledb 9 drivers (dunno what you have so probably not required).
give Network Service IIS_IUSR full control on the COM folder (required so the DLL can do some logging in its own folder, when called from the website).
run regsvr32.exe "c:\xxx\yourfile.dll" -> this should be successful!
Create COM+ application, and add the DLL as a part
Set the application COM+ credentials on a user wigh sufficient rights
and we had to do some more settings on rights in application pool / IIS, but thats not required for you I guess.
Anyways, just make sure you have enough logging, make sure the dll is registered, and after that its all about rights rights rights..
Good luck with it!
Sorry to use the "Answer" to respond to comments, but it seems to be my only avenue.
The whole purpose of moving to a 64bit operating system was to gain the extra addressable memory space, so running the entire application in 32bit mode is not an option.
It might be relevant to the problem that after successfully creating three class objects, I was able to set properties in one, call a method with no arguments in the second, but it was calling a method in the third, which took the other two objects as arguments that threw the exception.