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.
Related
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.
i have a compiled dll library but i have no documentation about it. There is a way to get the public interface of a dll (at least function names, params numbers and type).
Thanks
You would have to decompile it and analyze each function, its calling convention, parametrs count, parameters meaning (unless it comes with some PDB, but I doubt it), I've done something like this before, it's complicated work, but it can be done.
In order to retrieve the public symbols (functions and variables) exported by a Dynamic-Link Library, one can use the well-known dependency walker. Parameters and Types are only available when the associated PDB file is available (which does not seems to be your case).
You could use the OLEViewer that comes with Visual Studio to view the TypeLib of the DLL if it is a COM library. This would give you the information you need.
Is it valid to develop a DLL in C++ that returns boost shared pointers and uses them as parameters?
So, is it ok to export functions like this?
1.) boost::shared_ptr<Connection> startConnection();
2.) void sendToConnection(boost::shared_ptr<Connection> conn, byte* data, int len);
In special: Does the reference count work across DLL boundaries or would the requirement be that exe and dll use the same runtime?
The intention is to overcome the problems with object ownership. So the object gets deleted when both dll and exe don't reference it any more.
According to Scott Meyers in Effective C++ (3rd Edition), shared_ptrs are safe across dll boundaries. The shared_ptr object keeps a pointer to the destructor from the dll that created it.
In his book in Item 18 he states, "An especially nice feature of
tr1::shared_ptr is that it automatically uses its per-pointer deleter
to eliminate another potential client error, the "cross-DLL problem."
This problem crops up when an object is created using new in one
dynamically linked library (DLL) but is deleted in a different DLL. On
many platforms, such cross-DLL new/delete pairs lead to runtime
errors. tr1::shared_ptr avoid the problem, because its default deleter
uses delete from the same DLL where the tr1::shared_ptr is created."
Tim Lesher has an interesting gotcha to watch for, though, that he mentions here. You need to make sure that the DLL that created the shared_ptr isn't unloaded before the shared_ptr finally goes out of scope. I would say that in most cases this isn't something you have to watch for, but if you're creating dlls that will be loosely coupled then I would recommend against using a shared_ptr.
Another potential downside is making sure both sides are created with compatible versions of the boost library. Boost's shared_ptr has been stable for a long while. At least since 1.34 it's been tr1 compatible.
In my opinion, if it's not in the standard and it's not an object/mechanism provided by your library, then it shouldn't be part of the interface to the library. You can create your own object to do the reference counting, and perhaps use boost underneath, but it shouldn't be explicitly exposed in the interface.
DLLs do not normally own resources - the resources are owned by the processes that use the DLL. You are probably better off returning a plain pointer, which you then store in a shared pointer on the calling side. But without more info it's hard to be 100% certain about this.
Something to lookout for if you expose raw pointers from a dll interface. It forces you to use the shared dll CRT, memory allocated in one CRT cannot be deallocated in a different CRT. If you use the shared dll CRT in all your modules ( dll's & exe's ) then you are fine, they all share the same heap, if you dont you will be crossing CRT's and the world will meltdown.
Aside from that issue, I agree with the accepted answer. The creation factory probably shouldn't define ownership & lifecycle management for the client code.
No it is not.
The layout of boost::shared_ptr<T> might not be the same on both sides of the DLL boundary. (Layout is influenced by compiler version, packing pragmas, and other compiler options, as well as the actual version of the Boost source code.)
Only "standard layout" (a new concept in C++11, related to the old "POD = plain old data" concept) types can safely be passed between separately-built modules.
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.
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}).