I created a C# COM accessible dll that I want to consume in VB6
I was able to consume in VB6 my COM object with a hard reference to the TLB.
What I am trying to do now is to remove this reference and load it dynamically
I am creating it as follows:
Dim keylok As Object
Set keylok = CreateObject("MyClassLib.MyObject")
I get the Run-time error 424 "Object Required" once I hit the second line.
But when I create it as follows:
Dim keylok As MyObject
Set keylok = CreateObject("MyClassLib.MyObject")
It works fine.
I am not sure why would that make a difference. Anyway I cannot use the second one because I would still need to have the physical reference.
I tried also as a sort of debugging to write to file in my COM object constructor to if it really gets called. And yes it does, I'm even able to call other methods in my COM object sucessfully inside the constructor.
I was even able to load dynamically and consume it from another C# app using:
dynamic myObj = Activator.CreateInstance(Type.GetTypeFromProgID("MyClassLib.MyObject"));
Did any one encounter something like that before?
I found the solution with the help of #rskar input. So, I thought I'm gonna answer my question, in case any one faces the same problem.
My object didn't impelement IDsipatch. So all I had to do it to decorate my C# COM interface with InterfaceType(ComInterfaceType.InterfaceIsDual) So it implements both IUnknown and IDispatch.
Originally it was decorated with InterfaceType(ComInterfaceType.InterfaceIsIUnknown)
I think you are going to require the .tlb anyway. COM objects need to be capable of being marshalled as the .Net hosting runs on a different thread to the VB6 runtime. The default marshalling uses information from the typelibrary to do this. IDIspatch has 4 methods and 2 of these are to do with accessing type information. So possibly if you removed the .tlb, when you create the IDispatch COM attempts to call up the ITypeInfo from this and dies failing to load the registered typelibrary. If you eliminate the .tlb you will become unable to be marshalled and likely you would have to provide a custom marshaller for your interface.
Related
I'm quite struggling when trying to to what follows.
I'm currently using an ActiveX, through an interop dll generated by Visual Studio (more precisely tlbimp, implicitly).
Different versions of this ActiveX exist, that sometimes add, remove, properties in the interfaces. If I want to change the version, I have to change the reference, recompile and so on.
I'd like to have a single version of my code. I remember that to drive Word for example, one can instantiate a word object via CreateInstance on Word.Application, and call methods, whatever the version. Calls being linked only at runtime. I'd like to do the same with the activeX I use, but I don't really know how to do that (btw, it's sage objets100c dll). I don't find it in the list of ActiveX, thus I'm not even sure I can do like for Word.Application.
Has someone a clue about how I could do that ? (might be a completely different solution, as my need is: having one code with no need to recompile).
Thank you in advance
If you have a corresponding *.tlb file, reference this one, not the dll. In the properties window of the reference, you can set the Specific Version property to False and EmbedInterop Types to True (makes it more stable).
If this does not help, you can try to create and access the object dynamically instead of adding a reference. You need either the ProgID of the COM type …
Type comType = Type.GetTypeFromProgID("MyProg.ProgId");
… or the CLSID of the COM type (you may find them in the registry)
Type comType = Type.GetTypeFromCLSID(new Guid("EA2BACD6-9D0F-4819-98BC-88E8173D3D16"));
Then you can create and object with Activator.CreateInstance and assign it to a dynamic to do late binding.
dynamic sage100 = Activator.CreateInstance(comType);
You will get no IntelliSense for this dynamic object. If you specify non-existing members (method or property names), you can still compile and run your C# code; however, a run-time exception will be thrown when you attempt to access these members.
I have a simple C# library that I have registered for COM interop. I have added a reference for this to my vb6 app. I ran my vb application and everything works fine. What I would like to know is how does this work. I checked the task mamager and I see VB6.exe in the processes but I cannot see anything relating to .net.
code: vb6
Dim a As CsharpdllForVBHack.ComAdder
Private Sub Command1_Click()
Set a = New CsharpdllForVBHack.ComAdder
a.Add 1, 4
End Sub
code: C#.net
[ComVisible(true)]
public class ComAdder
{
[ComVisible(true)]
public void add (int a,int b)
{
TestForm testForm = new TestForm(a+b);
testForm.ShowDialog();
}
}
I would also like to know how would I handle disposing of this com object once I am done
We noticed that each time we click on the button and close the form the memory used goes up by a few 100 kb even adding set a= Nothing
In your case, VB instantiates a COM Callable wrapper class (CCW) which lives inside the .NET assembly. The usual COM-type things happen here. First of all, COM looks up the GUID for the class in the registry, and finds the assembly DLL, which it loads into the process of the VB component. COM tries to find a function which retrieves a pointer to a standard COM interface, which you use to instantiate the COM class. You now have a COM object.
But that is not the whole story. When you instantiate the CCW, it also ensures that the .NET runtime is loaded, and then creates an instance of the .NET class. Your COM object has an interface which is based on the .NET interface. The CCW essentially forwards all calls from the COM interface to the .NET interface, converting the COM data types to .NET data types, and back again if you have return values and out parameters.
As for your second point - in this particular case, don't bother. When VB gets to the end of the procedure (or Exit Sub, or raises an error), it jumps to a subroutine which clears down all procedure level variables. If object variables are cleared, the reference count to the COM object is decremented. If the reference count is zero, the COM instance kills itself.
In your case, when the COM class kills itself, it takes extreme measures to ensure that the .NET object is destroyed, but you cannot rely on this behaviour, as with all .NET objects.
The first part of your question is too broad to be answered here have a look at COM Interop for much more information on this subject.
The second part of your question is answered as follows:
To dispose of the object in VB6 you do the following:
Set a = Nothing
Making sure there are no other references left around.
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.
Say I've got a load of COM types and I'd like to check if a particular type has been registered. I can use Activator.CreateInstance to actually try and create the class, but I was wondering if there's a simpler way actually check in advance if the class is actually registered.
I'd prefer not to go to the registry directly - I'm looking for something easy to use like the Activator.CreateInstance call above.
Thanks
NB. Activator.CreateInstance doesn't directly create COM class instance, it requires an interop assembly to have been generated and installed (e.g. by tlbimp.exe)—but this makes no difference here.
The simplest way to check without reading the registry is to try creating an instance and catch the possible exceptions due to the type not being available. (E.g. COMException if the underlying component is not installed correctly, TypeLoadException (IIRC) if the interop assembly is missing.)
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.