How does .NET/COM work with multiple versions registered via Regasm? - com

I have a .NET DLL (that happens to be written in C++/CLI). Parts of it I want to expose via COM. I do this and register it using "regasm my.dll /codebase". So far so good. But then I change some things and the version number of the assembly changes plus I move the dll to a different folder. I register it again and look at my COM object in OLE/COM Viewer. I see something like this
InprocServer32 [Codebase] = file://c://foo/bar/my.dll
7.0.0.0 [Class] = My.Blah.Class
7.0.0.0 [Assembly] = Sync, Version=7.0.0.0, Culture=neutral, PublicKeyToken=1dd19234234
7.0.0.0 [RuntimeVersion] = v2.0.50727
7.0.0.0 [CodeBase] = file://c:/dooby/do/my.dll
7.0.0.27397 [Class] = My.Blah.Class
7.0.0.27397 [Assembly] = Sync, Version=7.0.0.27397, Culture=neutral, PublicKeyToken=1dd19234234
7.0.0.27397 [RuntimeVersion] = v2.0.50727
7.0.0.27397 [CodeBase] = file://c://foo/bar/my.dll
Questions about multiple versions:
So I think that the last COM object that was registered wins. It doesn't matter if I have my old 7.0.0.0 COM object registered, the 7.0.0.27397 is the one that will be created when I instantiate my COM object because I registered it last. Is that correct?
Oops I didn't keep around the 7.0.0.0 object. Is there any way to get rid of it? Is there any way to remove all versions of a COM object other than going into the registry and whacking it by hand?
Just out of curiosity, if I specifically wanted to instantiate a particular version of my COM object is there any way to do that? (I'm using C++ if you wanted to give a code example).
Is there any way I can just tell regasm to not store the version number because it just seems to be cluttering things up and I can't see what the benefit is. If my COM object went through significant API change I'd just change the GUID and progid, right? What if I don't want to register multiple versions (I don't).

I always set my COM visible assemblies up with a static AssemblyVersion for just this reason. If you want to have binaries tagged with a version, use AssemblyFileVersion instead.
Last registered object wins: yep
Not really. You can put stuff in your assembly's ComRegisterFunction/ComUnregisterFunction attributed methods to automate the cleanup, but if you leave old version droppings around, this is about the only way.
You'd do it with a different coclass GUID and/or ProgID (eg, MyCoClass.1, .2, etc). CoCreateInstance doesn't know anything about the version values- they're used by the CLR's activator to ensure it loaded the right assembly.
No- best thing to do is never change your assembly version (see above).

Components with the same CLSID should be compatible, especially if you've only changed the build number between assemblies. Here's the only relevant thing I found to confirm this by googling quickly.
To answer your questions directly:
Correct.
regasm /unregister
Look into Binding redirects.
Probably not.

Related

WinSCP .NET assembly registration error in Dolphin Smalltalk

I'm trying to use a 3rd party DLL (WinSCP .NET assembly) in Dolphin 6.1b2. I've registered the DLL and generated a TypeLib in Windows 7.
In Dolphin I successfully used the component wizard to generate the interfaces but when I try to register the control and TypeLib I get errors. On the registering the control I get
WinSCPnet.dll was loaded but DllRegisterServer entry point could not be found.
Does anyone have any idea why it's failing? I have also asked the author of the DLL and he's leaning toward a Dolphin problem since the registration worked in Windows.
The DLL is a .NET assembly, import the generated TLB.
Downloaded ".NET assembly/automation package" from: https://winscp.net/eng/download.php
Unpacked, registered as per included readme_automation.txt.
See also Downloading and Installing WinSCP .NET Assembly
Started fresh Dolphin, imported the .tlb, generated with WinSCP prefix (so the classes wouldn't start with _).
Opened workspace, imported the WinSCP_Constants Pool, converted start of the C# example (https://winscp.net/eng/docs/library#example):
opts := WinSCP_SessionOptions new
protocol: Protocol_Sftp;
hostName = 'example.com';
userName: 'user';
password: 'mypassword';
sshHostKeyFingerprint: 'ssh-rsa 2048 ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff';
yourself.
Got working object back ...
EDIT: Your WinSCP forums notion "in order to use it within dolphin you need to have its tools register the dll and tlib" is wrong. The COM "source" needs to be registered only once (In case of "old-school" COM server, you can either use regsvr32 or dolphin - both does the same; in case of .NET assembly you have to use the .NET incantation). Only thing really needed on dolphin side is to import previously registered library.
If there is .TLB, I'd go for .TLB, otherwise try my luck with .DLL. Sadly, for some standard COM interfaces Microsoft never made typelibs available, so it's even worse there (use C/C++, or create struct/interface tables by hand).
Edit 2 - further questions:
1) can you explain the relationship between the typelib and the library class which "i create" ( i.e. dolphin tutorial in help)
Dolphin creates smalltalk classes to mirror the COM types / structures. You use these to instantiate COM types from Smalltalk, call their methods, pass them (and also primitive types such as strings, integers, ...) as arguments and get Smalltalk types for returned values (Dolphin does all the conversions for you, so you can +- forget you are calling foreign code).
2) an example of the method you implemented mapping the library class to the winscp interface.
I implemented nothing, I just used the generated wrapper (in background, WinSCP COM object - SessionOptions - got created, and had some properties set).
basically, i just said:
var opts = new WinSCP.SessionOptions().
opts.Protocol = Protocol.Sftp;
opts.HostName = .........
Just look at WinSCP Automation documentation / examples, and then convert it to smalltalk-speak (and hopefully, it should auto-magically work ;-).
3) where are the smalltalk methods protocol:, hostName:, etc defined? i searched the image and they are not there. how did you know to use those method names?
Since SessionOptions (represented by [PREFIX]_SessionOptions class in Dolphin) is an IDispatch interface (subclass of IDispatch in Dolphin), all the method calls are dynamic in nature. You just do the right things (& catch possible failures at necessary granularity), and it will "just work (tm)".
Smalltalk sibbling is the #doesNotUnderstand: aMessage method.

how to ildasm/ilasm round trip a dll exposing COM objects

I want to round trip a dll that exposes COM objects (interop).
With ildasm i dumped the dll.
In the ildasm dump i renamed all occurances of the class name.
An Ilasm with DLL switch and inclusion of the resources produced the new dll.
I dont know how to register this new dll (Win7).
Regasm/Regedit complains about a strong name.
What else do i have to adjust?
Thank you.
Seppe
A strong name for an assembly explicitly prevents you from doing this. Strong names were designed to detect somebody tampering with the code of an assembly, useful when the assembly is stored in an insecure location like a website. You are definitely tampering with the DLL so you'll break the strong name. Resigning it is required and that requires access to the private key that was used originally.
That said, there are some mitigating factors in the specific case of a [ComVisible] assembly. There isn't any way for an application to verify the strong name since the client of such an assembly is native code that doesn't know beans about strong names. The strong name is only required to allow the assembly to be registered in the GAC. Which in general is a good place for such assemblies since it helps to avoid DLL Hell.
So there are two things you can do to solve this problem:
Read the regasm.exe message carefully. It probably only displays a warning if you use the /codebase option. "RegAsm : warning RA0000 : Registering an unsigned assembly with /codebase can cause your assembly to interfere with other applications that may be installed on the same computer. The /codebase switch is intended to be used only with signed assemblies. Please give your assembly a strong name and re-register it." Which really means "You are about to commit yourself to DLL Hell". There are various degrees of DLL Hell, the name+location of the DLL is a fairly mild one. The far more important one is that the interfaces and classes get a new [Guid]. Which they'll automatically get since you changed the names, assuming that there is not a [Guid] attribute present in the original code.
Just sign the assembly with your own key, using sn.exe. Since nobody can actually check the strong name, any is good enough.
And do keep your eye on the ball, the actual name of the [ComVisible] interface and class names is immaterial in COM. Only the [Guid] matters, that's what the COM client uses to find the types back.

Discovering registered COM components

Is there a way to determine if a registered COM component is creatable as a stand-alone component simply by parsing the information available in the registry? In particular, by the information found in HKCR/ClsId?
My system has over 12,000 entries in this key, and I am already excluding any items that do not have an InProcServer32 or LocalServer32 key, but this only eliminates about half of the items. I believe there are still another couple thousand that are not creatable objects. I really don't want to have to attempt to do a CreateObject() on every one of them to distinguish the ones that can be created from the ones that cannot. Is there a more efficient way?
Oleview
I used Oleview
for this purpose (back in the day :))
Manual/programmatic
If I remember correctly (no Windows PC nearby):
the class should link to a typelibrary
the typelib will point to a binary (dll, ocx, exe)
this binary contains the physical typelibrary, which you should parse
the midl compiler can do that (generate stubs/C headers)
oleview can do that (extract IDL)
tlbimp can do that
you can do it with Win32 API
any creatable objects should be marked coclass (not interface or source; there were also global modules which I suppose are creatable too: I'm just not sure whether they are defined as coclasses
Show me the code
It is possible to read the information within a type library with the ITypeLib and ITypeInfo interfaces. They can be created with the ICreateTypeLib and ICreateTypeInfo interfaces. However, the Microsoft IDL compiler (MIDL) is probably the only application to ever use ICreateType and ICreateTypeInfo.
A quick google turned up this useful page: Reading Type Libraries with C++.
It contains just the code to get started. Just to see whether it was worth anything, I fired up a cloud Windows instance, grabbed all the sources and compiled it.
In contrast with the options mentioned on the site, I simply compiled on windows with
cl.exe *.cpp /EHs ole32.lib oleaut32.lib
Just for fun, I compiled the stuff on Linux (64 bit) using MingW:
i586-mingw32msvc-g++ *.cpp -loleaut32 -lole32 -o Typelib.exe
To save you the work I have put a zip-file up for download containing:
win32_Unicode.cpp - sources by René Nyffenegger
win32_Unicode.h
TestTypelib.cpp
Typelib.cpp
Typelib.h
VariantHelper.cpp
VariantHelper.h
TestTypelib.exe - binary compiled on windows
A test run:
# linux: ./a.exe ~/.wine/drive_c/windows/system32/msxml6.dll
C:\Games\Stacko>TestTypelib.exe c:\Windows\System32\msxml6.dll
MSXML2: Microsoft XML, v6.0
Nof Type Infos: 149
IXMLDOMImplementation
----------------------------
Interface: Dispatch
functions: 8
variables: 0
Function : QueryInterface
returns : VT_VOID
flags :
invoke kind: function
params : 2
params opt : 0
Parameter : riid type = VT_PTR (VT_USERDEFINED (GUID)) in
Parameter : ppvObj type = VT_PTR (VT_PTR) out
Function : AddRef
returns : VT_UI4
flags :
invoke kind: function
params : 0
params opt : 0
(snip) and 15499 lines more
Concluding
I hope this gives you a good starting point in scanning your system for installed, creatable, COM components
Depends what you mean by "createable". If it has a LocalServer32 or InprocServer32 key it should be locally creatable. It may also be creatable remotely if it has an AppID and the AppID has either LocalService or RemoteServer keys.
However consulting the registry will only answer the question "does it look like it ought to be creatable".
You might still not be able to create it:
The registration might be broken, or "fossil" registry entries from uninstalled components.
The component might be an internal Windows component of some sort that you have no idea how to use since it is intentionally not documented.
The component might be an internal component of an installed application which has additional requirements not documented.
You might not have permission.
There may be other components you could create:
There might be registration-free COM components, such as WSC scriptlets.
there might be registration-free COM DLLs. There is no law saying you have to be registered to be a COM component. Registration is an optional service that most people opt into.
So I guess the answer is you should be able to get a mostly complete list using the registry, but what is the list for?
Without knowing what you want the list for, it is impossible to know if the list is good enough.

When To Change a GUID on a Type Library

I know that when you add/change/remove methods in a COM interface you're supposed to change the interface/coclass GUID but what about type libraries. When should you change the type library's GUID? Do you change it if a GUID inside the type library has changed? Or should you only change it when something that doesn't have its own GUID within the type library changes.
The basic principle is that COM interfaces and Type Libraries should be immutable (that is, they shouldn't ever change). If you change one item inside a COM interface, then the new version needs to be a completely separate entity from the previous version. The only way to do this is to change the GUID for every interface in the library and the GUID for the type library itself. It's also a good idea (for your own personal sanity) to change the name of the type library.
Ideally you shouldn't ever change a COM interface. Instead create a new derived COM interface and publish in a new type library.
I've got a similar question.
I had an original control with CLSID_A that implemented interface IID_A in some 1.0 type library with GUID_A
Later on, I decided to add a new interface to the original control. It would then implement both IID_A and IID_B interfaces. I figured that I should probably keep the same CLSID but didn’t knew much what to do with the typelib itself. I was mostly doing VC++ programmatic-by-the-book stuff which involved QueryInterface and didn’t cared much about versioning and typelib. You wanted to create an object with a specific CLSID, you just asked CoCreated instance...and then Queried interface for potential support of the new interface...
Now when I get into fancier environments like LabVIEW or design-time drop-in development environments like Microsoft .NET, MFC stuff that seems to break.
You are mentioning in your answer to change all of the GUID. Is the whole paradigm of adapting an application based on available functionality dead, that a newer application could still use its basic functionality with the older version of a control? Maybe I didn’t catch the later wave that is: No point in adapting an application to run using old control version, it simply requires a specific control version. That would be reason M$ also came out with the ASSEMBLY thing.

CLSIDFromProgID is successful but CreateInstace fails! Why?

I am trying to create an instance of a COM object. I have the class name that implements the interface and I get a CLSID by using CLSIDFromProgID(). So since I am getting a CLSID I thought everything should be fine from now on. However when I do a call to CreateInstance and pass in the CLSID, I get an error saying "Class not registered". Also I get this error only in some computers. It runs error free on several computers. I don't understand where the problem could be. Is my registry dirty? Does anyone know what is going on here? Thanks for your help!
I just want to add that this is a .NET COM class. The appropriate entries are in the registry and the DLL is in the GAC.
CLSIDFromProgId is simply looking up the ProgId's name in the registry and translating it to a CLSID, it doesn't have to look at anything beyond the registry or even check that something is actually implementing that CLSID.
When you call CreateInstance on the CLSID, Windows will look up in the registry to find out how the object should be instantiated (usually a exe or dll). It will then try to load the dll (or start up the exe) and create the object from it.
There is a lot of documentation in MSDN on the processes involved, for example see "COM Class Objects and CLSIDs", and if you do a lot of COM work it is worthwhile learning the process from first principals since it can save a lot of time and hassle when debugging this type of issue.
It's a two step process in the registry. You used the ProgID to get the CLSID. Then, when you call CreateInstance, COM then uses the CLSID to find the path to the dll. You can used regedit yourself to lookup the CLSID and see what that entry looks like.
Thanks for your answers. The .Net assemblies were registered properly and were present in the GAC. One application that absolutely confirmed this was Process Explorer. You can view the dlls that are loaded by each application. So from here I was able to see if the application that was instantiating the COM objects was actually able to load the DLLs or not. I found out that this was indeed happening. The problem was due to different Regional settings. We found that the application threw an exception when the region was not set to US. This issue was fixed. The error message "Class not registered" was not very helpful. Thankfully it was a quick fix.
Using shell32 as an example, you can create a new instance like so;
var shl = (Shell) Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
This will aquire a refernce to an existing component;
var shl2 = (Shell) Marshal.GetActiveObject("Shell.Application");
Here's a reference to how to do the same in IronPython.
** Note, this used the progid, clsid would be nearly identical, just use Type.GetTypeFromCLSID({GUID}).