I'm working the open source library EasyHook.
What I'm trying to do, is hook into when a VB6 app calls CoCreateInstance from ole32.dll on a particular CLSID, return my own C# implementation for the object rather than the true COM object. My C# implementation derives from the same interface that tlbimp.exe spits out for the COM object I want to replace.
My hook works, and I'm able to hook into calls, log data about the call, and then p/invoke CoCreateInstance from C# to allow the VB6 application to run as normal.
I'm noticing that the COM object that I want to replace isn't passed through my hook.
Does anyone know how VB6 loads ocx files under the hood? Am I hooking into the proper native api call?
Or is what I'm trying to do impossible due to the nature of .Net?
UPDATE: An alternate solution is to write a COM object to replace the old one, but we cannot get this to work. Here is an old post that I closed on the subject: Replace COM object
UPDATE: After further inspection, we are able to regsvr32 /u the old ocx file and use regasm to register our .Net dll. We put a MessageBox in the constructor of our COM object and the VB6 app loads and pops the box, but it crashes as soon as it makes the first method call on the object.
I suspect that we have some method signatures wrong, also we are using what tlbimp.exe gave us when we ran it on the target ocx we want to replace. Is it possible that tlbimp is making changes to the signatures that is preventing VB6 apps from loading our assembly?
For example sometimes COM signature will look like:
HRESULT MyMethod(IUnknown* ppv);
And tlbimp.exe will give C# something like:
IUnknown MyMethod();
Which looks much cleaner to a C# developer. Does anyone know about this, or a good article that could explain how to write a "binary compatible" COM assembly from C# to replace an ocx file?
Couple of comments: First, VB6 does not use CoCreateInstance on "local" classes, i.e. classes from the same project -- it calls "constructor" directly. Second, you have to hook CoCreateInstance on the import section of every dll/ocx the CLSID can be cocreated from.
A better way is just to register you "upgraded" COM component with the same coclass CLSID. This way it will be automagically used by the client app.
Edit: Or take a look at CoTreatAsClass function.
If you have the source code for the original component, apparently VBMigration Partner can upgrade a VB6 COM component to a VB.Net component that has binary compatibility with the original VB6 component. I don't know whether it supports OCXs.
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 call into a COM component from a C# project, via an interop assembly that I generate from the COM DLL. The COM interface has DispIds defined and I have verified that these appear in the generated interop assembly.
Empirically, if I upgrade the COM component to a newer version, the interop calls go horribly wrong (as if it's calling the wrong COM methods).
Is this expected, i.e. that the interop assembly is tightly bound to the specific version of the COM interface that it was generated for? I had naively assumed that as long as the DispIds and function prototypes matched up in the new COM component, which they do, it would all work OK.
Is there a way of telling the CLR to use the DispIds when calling into the COM component via the interop assembly, i.e. a late binding of sorts? (I know it is possible to use late binding using reflection-style C# code, but this would be less convenient than an interop assembly.)
I found Brian Long's article .NET Interoperability: COM Interop:
The most common requirement will be to use early binding to get compile-time type checking and direct (well, as direct as it gets) vtable calls to the COM objects. The [interop assembly] example above took this approach.
An interop assembly resulting in "direct vtable calls" sounds like it would not work with a new version of the interface (unless, perhaps, new methods were only added to the end of the interface, i.e. to the end of the vtable?).
Perhaps someone can corroborate or provide a more complete answer?
DispIds are only used when the client programmer uses late binding. If that's what you want him to do then you have to enforce it. That's very easy to do, apply the [InterfaceType(ComInterfaceType.InterfaceIsDispatch)] attribute on the interface. If you didn't write an interface but only a class then give it the [ClassInterface(ClassInterfceType.AutoDispatch)] attribute.
Any attempt by the client programmer to use the dangerous early binding will now fail, he must write late-bound code. Don't expect a thank-you note.
The only other way is to remove the [Guid] attribute you now use. It is very dangerous, let the CLR auto-generate the guid so it will automatically be different when you change the interface. And the client programmer gets a decent E_NOINTERFACE error instead of calling the completely wrong method. Well, don't expect a thank-you note :)
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 create a C# COM interop for c++ to invoke.
I have registered the dll and tlb file by regasm.
everything goes well till one day i changed code of C# part (i didn't change the definition of interface, just implementation changed). one interface in COM returns an error 0x80131509. the strange thing is, it is only happened in some computers (my develop PC is works well so i can't debug this problem).
I'm not really clear on how the C# COM worked with C++, after i registered them, i just know they create key value in window registry.(like what regasm /regfile generated). how c++ knows where the COM dll is(search path environment variables)? and what the use of tlb file in run time?
any suggestion?
I got the same error as soon as I introduced a simple inheritance hierarchy to my COM library. A quick resolution was to set the ComVisible attribute to true on the base class. This fixed my problem immediately.
It does make a lot of sense when you think about it - the compiler doesn't allow you to build a hierarchy where the base class is less visible than the inheriting class. So it being the same for COM should come as no surprise - the only difference being, that it is failing at run-time instead of compile-time.
I would venture that the true reason for the error is a broken constructor chain, but I put no further research into it.
I think that your problem is related to the registry... You should try to unregister and register (using regasm) your dll in the computers that are having this problem.
If that doesn't work unregister the dll in those computers, than use regedit to search and delete any missing registry keys that refer to it, after that register your dll again. You could also use one of those registry cleaner programs after deleting the missing keys to guarantee that you didn't miss anything.
Remember that you should allways register a dll in the directory that it will be used by your application and this should happen only once. If you need to unregister a dll, then you should allways do it in the same directory that you used for the registration. In other words, once a dll is registered do not move it.
Note: if your dll is not on the same path as your C++ application it should be in a directory that is referenced in the PATH environment variable.
I got the same error message when I was calling a .Net4 C# COM object from Visual FoxPro.
The method returns object, type of which may be one of several. All the types are derived from an abstract class which implemented an interface with the common stuff for these types.
Eventually I decided to remove the abstract-modifier from the base class and just make it public and ComVisible. This solved the problem for me, even though I would like the base class to be abstract.
I have a 3rd party component, lets say FIPreviewHandler to handle preview, which implements IPreviewHandler. FIPreviewHandler is implemented as a Managed Component, and uses the IPreviewHandler interface and related interfaces through means of an interop. FIPreviewHandler is registered using regasm.exe as COM.
I have a client application which is also Managed. I want to create an instance of FIPreviewHandler as a COM component in my application.
I have an interop assembly that defines IPreviewHandler and related interfaces.
When I create an instance of FIPreviewHandler, using Activator.CreateInstance(), on a type returned by GetTypeByCLSID(), which uses the correct CLSID for FIPreviewHandler, it returns me a managed instance, as it has the actual assembly available, and skips COM. When I try to QI/cast this instance as any of the interfaces, IPreviewHandler for example, it returns null because, it is loaded as a managed object, and although the IPreviewHandler interface implemented by FIPreviewHandler is the same interface as I have in my interop, but its in a difference namespace/assembly, hence null. If it were to return me a COM instance/RCW (System.__ComObject), it would not take namespace into account, and would cast fine, and return a valid instance.
FIPreviewHandler is a 32 bit component, and on a 64bit Win7 machine, if I compile my client application as "Any CPU", Activator.CreateInstance() returns a COM instance/RCW (System.__ComObject), as it cudnt find a 64bit implementation of FIPreviewHandler, hence returns a proxy. In this scenario, my application works fine. But when I compile it for x86, it gets the 32bit implementation, and returns a managed instance of the actual managed class, and not a COM instance, hence fails.
I cannot use the interfaces defined in FIPreviewHandler's assembly, as I have to write a generic client for IPreviewHandler, and my application will work with any component implementing IPreviewHandler, which would work great for C++ based clients accessing FIPreviewHandler as a COM object, but is failing for Managed clients.
I hope I make sense and I would be really grateful for any help.
So clearly this a fail on .NET's part as I'm finding there is no way to use a COM wrapper around a managed COM object.
The "solution" (and I use that term very loosely) is the use of a PIA or 'Primary Interop Assembly'. The PIA provides a single strong-named assembly imported with TlbImp.exe that is GAC registered. Basically I guess we then must rely on GAC publisher policies to force clients to the correct interface assembly.
see also
http://social.msdn.microsoft.com/Forums/en/csharpgeneral/thread/b11a0f90-fcc5-487a-b057-632f5415bfc2
http://www.codeproject.com/KB/COM/BuildCOMServersInDotNet.aspx
Man, if I were you, I would just make the wrapper myself, only when the type is NOT a COM type. To know if the type created is a COM type, use the IsCOMObject from the Type of the object:
myObject.GetType().IsCOMObject
If this is FALSE create a wrapper, that uses reflection to call the managed type. Reflection is slow, but you can cache the MethodInfo objects that you get... otherwise you can generate IL code, and create a wrapper that will have no reflection at all.
Of course there are other methods, of doing it... this is up to you.
As I am in love with dynamic run-time IL generation, I can provide you with a code that does this... if you are interested!
My experience is that Microsoft did it this way by design. They don't want your two managed code assemblies to communicate via COM. If you try to add an assembly which supports COM as a COM reference in your project, the error says as much.
If the COM interfaces are the only way you can get at the functionality you need, a non-managed wrapper should do the trick. Write a new COM server in C++ or VB6 (or any non-managed language which is COM friendly) which wraps you third party managed COM server. Then add this new wrapper DLL to your managed code project as a COM server.
I've tried to do a similar thing with no success. (In my case the existing defined COM interface had a bug that meant it couldn't be implemented in .Net, so I re-defined the COM interface in a separate namespace myself and implemented this interface instead. The resulting object implemented the correct COM interfaces, however I couldn't get a hold of a COM wrapper to cast back to original broken interface).
As far as I am aware there are only 2 solutions:
Declare all COM interfaces in a Primary Interop Assembly, (either using TLBimp or by hand) so that all interfaces are defined in a common namespace. Note that this assembly normally doesn't contain any implementation, and so shouldn't need to reference other assemblies (unless those other assemblies are also interop assemblies that declare dependent COM interfaces).
Create a wrapper (for example in Managed C++) that performs the calls through "traditional" COM, rather than through the .Net COM interop.
Option 1. definitely strikes me as your best option - note that there is no need to register this assembly or place it in the GAC, and the implementation can be in an entirely separate assembly as long as the common interop assembly is referenced.
I can't think of many legitimate situations where a Primary Interop Assembly is not feasible.
It sounds like there are two problems:
Access to the Interfaces
Bitness of the component.
First, why reference it as a COM object in the first place if you know it is managed? Why not make a direct reference to the DLL instead of through interop and in that way, you should have access to the interfaces in the same way the the library has access to them. Thus, you wouldn't need to call Activator.CreateInstance. Instead, you would called var foo = new FIPreviewHandler();. However, that does not solve the bitness problem so the point is moot. Instead...
Coming to the second problem, one solution is to put the COM component into a COM+ Server Application. COM+ applications determine their bitness when they are first loaded. If all libraries in the COM+ application are 32-bit, when it is loaded, it will load into a WOW32 and you can call it from a 64-bit application. I've used this trick for COM libraries that only existed in 32-bit form but I needed the use them on a 64-bit server. The downside is that you are loading the library into a separate process and incurring the marshalling costs due to execution being out of process to your application but once instantiated, it should perform well enough.
Use PInvoke to call the COM function CoCreateInstance(Ex) and pass it the CLSID defined in your interop assembly and the IID of IPreviewHandler. This way .NET gets no opportunity to interfere.
You say you have an interop assembly. It's best if this is a PIA distributed by the author of the third-party assembly, but it's fine if you have to build it yourself. (PIAs do not have to be registered in the GAC, although they often are.) You can get the CLSID by loading the interop assembly in the IL disassembler and looking up the System.Runtime.InteropServices.GuidAttribute attribute on the class.
If I'm reading this right, you need to force it to use COM. Two ideas:
Does the behavior of Activator.CreateInstance() change, if you get the type via GetTypeFromProgID() instead of GetTypeByCLSID()?
How does Microsoft.VisualBasic.Interaction.CreateObject() behave? Yes, I hate to bring VB into this, but I am curious if it returns the COM object instead of the .NET class.