How to create a COM dll using Visual Studio 2008 - visual-studio

How do I create a COM dll using Visual Studio 2008? What are the custom settings needed for creating the dll? That dll should be used in Microsoft Navision(ERP PACKAGE).

Simply create a class library. In the project properties, open the dialog where you can change assembly information and mark the "Make assembly COM-visible" checkbox (sorry, don't know the exact name of the option, using German VS 2008).
Then, add the following attributes to any class that should be used from Navision:
[ClassInterface(ClassInterfaceType.AutoDual)]
[ProgId("YOUR-ID-GOES-HERE")]
[ComVisible(true)]
[Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")]
public class ComVisbleClass
{
....
}
I suggest that you also manually assign Disp-IDs to the public methods and properties using the DispId attribute. Otherwise, inserting new public methods or properties may break Navision functionality, as the Disp-IDs may be changed upon compilation.
Navision would then refer to the old Disp-IDs which may now "point" to different methods. This is a PITA to debug and solve, so use the DispId attribute from the start.

Related

COMVisible class in VS 2010 assembly still not visible through com

I created a VS 2010 class library. Marked the assembly for Com Visibility. Signed the assembly with a strong key. Created my class, have my entry point method available.
The library works fine from a test project in C#.
I regasm the class library to gac, via:
c:\windows\microsoft.net\framework\v4.0.30319\regasm testdll.dll /tlb: testdll.tlb /codebase
Include the tlb file as a reference in my VB6 project. I find it through resources 'browse' so its there.
When i try to instantiate the class... its empty. the public method that should be available via the public class doesn't show.
Dim objTest as testdll.testclass
set objTest = new testdll.testclass
objTest.testmethod <--- this 'testmethod' doesn't display in intellisense... nothing does.
In addition i tried calling the 'testdll.testclass' via CreateObject, i get the error "ActiveX component can't create object"
Now i have other projects i've done COM visibility for and i've tried comparing the difference, but i don't see any. I can't understand why it isn't working.
Any clues??? tx very much.
Just use an interface... one you define or to use the [ClassInterface(ClassInterfaceType.AutoDual)]
there are comments online you can find that indicate not to use autodual, but if you control the complete usage of your library, it seems like an 'ok' way to go.
I tried all sorts of ways to simulate / understand why my one project didn't need an interface to be visible by an vb project, without success. i had originally thought perhaps possible that it was because that project implemented an IDisposable Interface (the ONLY interface used in the C# projects that is com visible) but that didn't turn out to be the reason. Anyway I don't want to waste anyone else's time on this. thanks for the responses.
this link provides ample information on the subject:
http://anturcynhyrfus.blogspot.com/2011/03/creating-com-visible-c-component.html

How to compile Shadows events using MSBuild

I am trying to use set up a CI server that doesn't depend on a Visual Studio installation but uses msbuild instead. Our solution includes an interop project. Visual Studio handles this project just fine, but msbuild seems to have a problem with events like this one, taken from an interop user control which inherits from System.Windows.Forms.UserControl:
Public Shadows Event Click() 'Event must be marked as Shadows since .NET UserControls have the same name.
The error I get from MSBuild is this:
error BC30029: Derived classes cannot raise base class events.
I read this, but my Event statement and RaiseEvent statements are already in the same class (the one from which the above error is being thrown). I don't think this solution applies to my situation.
What's the best way to get around this? Let me know if you need more information.
Thanks.

Visual Studio Extension MEF class not instantiated when installed via an MSI

I am writing a VS 2012 plug-in, which contains a Package as well as an IWpfTextViewCreationListener MEF extension class.
It is installed using a WiX project created MSI, placing all of the files in C:\Program Files\...[Application]; which creates the Packages registry entry to point to the DLL containing the Package implementation.
When debugging the plug-in in an experimental Visual Studio instance, everything runs correctly.
When running the MSI installer, the Package code runs fine, but the MEF class is not instantiated.
Note: if I install the package using the VSIX (which I do not use for MSI install) everything also works fine.
The MEF class (in same Assembly as Package):
[Export(typeof (IWpfTextViewCreationListener))]
[ContentType("text")]
[TextViewRole(PredefinedTextViewRoles.Document)]
internal sealed class HighlightAdornerFactory : IWpfTextViewCreationListener
{
[Import]
public IClassificationTypeRegistryService ClassificationRegistry = null;
[Import]
public IClassificationFormatMapService FormatMapService = null;
[Export(typeof (AdornmentLayerDefinition))]
[Name(HighlightAdornment.Name)]
[Order(After = PredefinedAdornmentLayers.Selection, Before = PredefinedAdornmentLayers.Text)]
public AdornmentLayerDefinition editorAdornmentLayer = null;
public void TextViewCreated(IWpfTextView textView)
{ /** ... **/ }
}
I used VisualMEFX to examine the DLL that this is packaged in. It reports that no exports match IClassificationTypeRegistryService. This doesn't explain why it works when installed with VSIX, or debugged via the IDE. But it might be useful to know in troubleshooting the problem.
[Export] MySolution.HighlightAdornerFactory (ContractName="Microsoft.VisualStudio.Text.Editor.IWpfTextViewCreationListener")
[Export] MySolution.Adornment.HighlightAdornerFactory.editorAdornmentLayer (ContractName="Microsoft.VisualStudio.Text.Editor.AdornmentLayerDefinition")
[Import] MySolution.Adornment.HighlightAdornerFactory.ClassificationRegistry (ContractName="Microsoft.VisualStudio.Text.Classification.IClassificationTypeRegistryService")
[Exception] System.ComponentModel.Composition.ImportCardinalityMismatchException: No exports were found that match the constraint:
ContractName Microsoft.VisualStudio.Text.Classification.IClassificationTypeRegistryService
RequiredTypeIdentity Microsoft.VisualStudio.Text.Classification.IClassificationTypeRegistryService
at System.ComponentModel.Composition.Hosting.ExportProvider.GetExports(ImportDefinition definition, AtomicComposition atomicComposition)
at System.ComponentModel.Composition.Hosting.ExportProvider.GetExports(ImportDefinition definition)
at Microsoft.ComponentModel.Composition.Diagnostics.CompositionInfo.AnalyzeImportDefinition(ExportProvider host, IEnumerable`1 availableParts, ImportDefinition id) in C:\Temp\MEF_Beta_2\Samples\CompositionDiagnostics\Microsoft.ComponentModel.Composition.Diagnostics\CompositionInfo.cs:line 157
[Import] MySolution.HighlightAdornerFactory.FormatMapService (ContractName="Microsoft.VisualStudio.Text.Classification.IClassificationFormatMapService")
[Exception] System.ComponentModel.Composition.ImportCardinalityMismatchException: No exports were found that match the constraint:
ContractName Microsoft.VisualStudio.Text.Classification.IClassificationFormatMapService
RequiredTypeIdentity Microsoft.VisualStudio.Text.Classification.IClassificationFormatMapService
at System.ComponentModel.Composition.Hosting.ExportProvider.GetExports(ImportDefinition definition, AtomicComposition atomicComposition)
at System.ComponentModel.Composition.Hosting.ExportProvider.GetExports(ImportDefinition definition)
at Microsoft.ComponentModel.Composition.Diagnostics.CompositionInfo.AnalyzeImportDefinition(ExportProvider host, IEnumerable`1 availableParts, ImportDefinition id) in C:\Temp\MEF_Beta_2\Samples\CompositionDiagnostics\Microsoft.ComponentModel.Composition.Diagnostics\CompositionInfo.cs:line 157
I have tried adding all the referenced libraries from my assembly into the MSI, but this did not help. I am not seeing any exceptions being thrown, the IWpfTextViewCreationListener class simply does not get loaded.
The behavior you are seeing is by design; assemblies are included in the MEF composition only if they're listed in a .vsixmanifest somewhere. When you added the Package registry keys through your MSI, this will not add your assemblies to the MEF composition. When were you were testing in the experimental hive or installing the extension directily, you were including your assembly into the MEF composition as you expected.
You have a few ways to fix this, in my order of recommendation:
Don't use an MSI. I strongly encourage this if possible. VSIXes are quite powerful, and offer many advantages for the user: they are easier to install and uninstall, and they integrate with the extension manager and Visual Studio gallery. Unless you can prove that a VSIX is unacceptable for your scenario, please use them.
Use WiX 3.6, which has a VsixPackage element which will install your VSIX for you. The documentation is available here.
Have your MSI install the contents of your VSIX into [Program Files]\Microsoft Visual Studio 11.0\Common7\IDE\Extensions\[Your Product Name] and then run devenv /setup as a part of a custom action. This is very tricky to do and requires a bunch of manual WiX authoring. Option #2 exists for a reason -- the WiX folks actually know what they're doing. ;-)

Why are "Extracted Interfaces" Internal rather than Public?

Visual Studio includes a refactoring function called "Extract Interface" that generates an interface based on a class implementation.
The extracted interfaces are Internal by default. Problem is, we end up changing nearly all of them to Public.
Does anyone know why it's Internal by default? Better yet, is there a way to customize this to default to Public?
It might work if you change the Visual Studio template for interfaces (I haven't tried that but assume this should work).
For Visual Studio 2008 the template is stored at
"C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\ItemTemplates
\CSharp\Code\1033\Interface.zip"
This is described in more detail in this answer.
I don't have a reference, but I have a wild guess as to why it is internal by default.
Let's say you have 3 projects / assemblies: log4net (a 3rd party API), MyApp.Util, and MyApp.Web (a Web project). Web references Util, which references log4net. Web does not reference log4net, and you want to keep it that way.
Inside of DAL, say you have an internal class, and one of its members references a type defined in log4net. It could be the return type or one of the parameter types of a method, or the type of a property.
Let's say you extract an interface from the aforementioned class, including the aforementioned member that references log4net. Well, if you make that member public (part of a public interface) and reference a type that implements it, you then require that the Web project reference log4net.
By making the interface internal, Web may continue to be ignorant of log4net.

Typelib Generation and Installation with WiX

After asking about what Visual Studio does to register a COM Library, it became clear that VS did two things for COM registration:
Registered the COM Library
Creates and registers a Type Library
Visual Studio seems to do this registration using regasm.exe. For the first part (the direct COM registration) using tallow or heat (WiX 2.0 or WiX 3.0) seems to get all of the basic COM registration information correct.
However, what tallow/heat doesn't seem to do is set up a type library installation. It would be possible to create a custom action to do this with a WiX installer and regasm.exe, but invoking custom actions are not best practices when it comes to Microsoft installer based installers.
Upon further research, it looks like an msi has the ability to generate the type library upon installation. In fact, WiX seems to have direct support for it! In a file element, you can add a Typelib element. In fact, an article over here on wix has an example of filling out the TypeLib element with Interface elements.
It seems there's at least two required attributes to an Interface element:
Id
Name
Larry Osterman speaks about the other parts of the interface that need to be registered for a TypeLib in general, and this Interface entry seems to take care of the individual parts. Larry says we need to specify the ProxyStubClassId32 as "{00020424-0000-0000-C000-000000000046}", so we can easily add that.
Where to go from there and what to fill out for the various Interface elements has me stumped. I've gone ahead and added the TypeLib element to my wix file, and it successfully compiles. I'm a bit clueless as to how to set up the Interface elements though. What do we need to do to properly fill out the TypeLib element, and what apps or tools can I use to get it?
The answer below by wcoenen looks promising...I'm going to give it a shot.
Update: Posted my final solution below as an answer.
Here's the lazy man's way of solving this problem: Use heat from WiX 3.0.
If you have a type library generated automatically and installed via regasm, heat can take the .tlb as an argument in
heat file c:\my\path\to\my.tlb -out tlb.wxs
It will generate all the typelib and interface elements you need to register. This won't solve the problem of needing to know them ahead of time, and it won't solve the problem of GUIDs changing when the version of the assembly changes (even if the interface doesn't - which is the only time you're supposed to change it) but it will get you partway there.
The following trick can help with harvesting any registry changes and turning them into a wxs file, including the typelib element you're after.
First, bring your registry back in a state where the type library was not registered:
c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm.exe /tlb /u mylib.dll
Export this clean state of the registry to hklm-before.reg:
c:\WINDOWS\system32\reg.exe export HKLM hklm-before.reg
Register the type library again:
c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm.exe /tlb mylib.dll
Export the new state of the registry to hklm-after.reg:
c:\WINDOWS\system32\reg.exe export HKLM hklm-after.reg
Now we have two text files, hklm-before.reg and hklm-after.reg. Create a diff.reg file which only holds the relevant differences between these. You can find the differences easily with a diffing tool. I like to use the diff tool included in TortoiseSVN since I already use that every day. (WinDiff doesn't seem to work well in this case because of text-encoding issues.)
We can now convert diff.reg into a .wxs by calling heat.exe with the reg command. (Requires wix 3.5 or newer.)
heat reg diff.reg -out typelib.wxs
It looks like to register a Type library, the best way would be to generate your own IDL or ODL file, which will contain your GUIDs. The Typelibs generated directly from the Assembly are [i]dependent[/i] on the assembly version numbers : the GUIDs are generated based on that information, even if the interface hasn't changed. Visual Studio uses regasm to register and generate the typelib. Underneath that, it uses RegisterTypeLib, a win32 call. Using the typelib element seems to do something similar. No good.
However! Creating the type library by hand is painful. It is possible to get those GUIDs another way: digging them out of the typelib and creating the elements yourself.
Larry Osterman has the information that's needed: there's certain registry keys that need to be set. You can do those with the Registry table (and in Wix3, that means RegistryValue elements.) The trick here is getting the GUIDs: any old GUID will not work. Normally, getting the GUIDs is simply a matter of looking in the IDL for your library (you wrote your own IDL, right? :) ).
If you didn't write an IDL or ODL file to compile into a typelib, they still exist, in the file. Microsoft provides several handy tools: LoadTypeLibEx and the ITypeLib interface. With these interfaces, you can browse the type library and get all sorts of information. How do we browse the library?
I simply took a look at how Regasm did it! A quick dissassemble later, and we find that regasm is written in C# too. Glory day. I started up a project, and with a few using statements and a PInvoke later, we have:
using System.Runtime.InteropServices; // for struct marshaling
using System.Runtime.InteropServices.ComTypes; // for the ITypeLib + related types
// TYPELIBATTR lives in two places: Interop and ComTypes, but the one
// in Interop is deprecated.
using TYPELIBATTR = System.Runtime.InteropServices.ComTypes.TYPELIBATTR;
/// <summary>
/// The registry kind enumeration for LoadTypeLibEx. This must be made
/// here, since it doesn't exist anywhere else in C# afaik. This is found
/// here: http://msdn.microsoft.com/en-us/library/ms221159.aspx
/// </summary>
enum REGKIND
{
REGKIND_DEFAULT,
REGKIND_REGISTER,
REGKIND_NONE
}
// and this is how we get the library.
[DllImport("oleaut32.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
private static extern void LoadTypeLibEx(string strTypeLibName, REGKIND regKind, out ITypeLib TypeLib);
Whew! Once we have this out, we have to navigate the structure. This is interacting with unmanaged resources, so get ready to be Marshaling stuff around.
ITypeLib lib = null;
LoadTypeLibEx(Value, REGKIND.REGKIND_NONE, out lib);
IntPtr libInfoPtr = IntPtr.Zero;
lib.GetLibAttr(out libInfoPtr);
TYPELIBATTR libInfo =
(TYPELIBATTR) Marshal.PtrToStructure(libInfoPtr, typeof(TYPELIBATTR));
int typeCount = lib.GetTypeInfoCount();
for (int i = 0; i < typeCount; ++i)
{
ITypeInfo info;
lib.GetTypeInfo(i, out info);
IntPtr typeDescrPtr = IntPtr.Zero;
info.GetTypeAttr(out typeDescrPtr);
TYPELIBATTR type =
(TYPELIBATTR)Marshal.PtrToStructure(typeDescrPtr, typeof(TYPELIBATTR));
// get GUID, other info from the specific type
}
lib.ReleaseTLibAttr(libInfoPtr);
libInfoPtr = IntPtr.Zero;
Whew. So, you have to write some code to extract the information. Once you do, you have to fill that information into Registy Entries, as specified By Larry Osterman.
Of course, you could avoid that step by simply writing your own IDL file to begin with. The choice in pain: it's up to you!

Resources