January 30, 2007

Visual Studio Extensibility

http://feeds.delicious.com/rss/learnerplates/extensibility

Extending the Visual Studio IDE means adding custom functionality to the IDE, this could be custom TextEditors, Add Ins to the Tools menu, Writing to the Output Console, basically anything that will require you to add or write to any component in the Visual Studio IDE.

Extending the Visual Studio IDE is not for the faint hearted. It is implemented with a Visual Studio Extensibility SDK downloadable from link 1 below. It's implemented using COM interfaces (not very nice).
There are 5 points of contact for Extensibility issues
1. MSDN Visual Studio SDK homepage.
2. MSDN documentation.
3. MSDN Extensibility Forum.
4. Dr. Ex's Weblog.
5. MZ-Tools.
There are also webcasts available from msdn.

I recommend watching some of the webcasts especially the Managed Package Framework as the explain briefly how some of the Class Attributes work with RegPkg.exe and how important the GUIDs are.

GUIDs
The GUIDs are static and each control and component in the VS environment should have one, if they have a static GUID it means that Visual Studio will persist that same object for all instance of Visual Studio, this means that if you create your own Control, say a Form, if you do not give it a static GUID a new Form object will be created each time it's launch from VS, if you give it a GUID then that same Form object will be used each time, the result of this is that if you change the size or something of that Form object then the next time the Form is shown it will pertain that size set.


Visual Studio Features
Some more detailed features of Visual Studio Extensibility are:
Language Services - Create your own language with Visual Studio TextEditor highlighting and intellisence etc, msdn.
Aaron Martin's Webblog - Managed Language Tools in Visual Studio SDK.

VSPackages - VSPackages allow your application to consume services provided by VSSDK. Your VSPackage implementation must implement the Package base class, this requires implementation of various Interface methods.
To create your own Project type i.e. your own project name type appears in the open dialog is quiet difficult, you'll need to create a VSPackage, see the steps below:

Steps To Create your own Custom Project Type using VSPackages
Steps To Create Your First VSPackage.
1. To create a VSPackage just use the File->New Project->Other Project Types->Visual Studio Integration Package,

2. After this has been created you can immediatly hit F5, this will install this with the Visual Studio Experimental Hive and launch it, you should see a new Project Type listed in the Visual Studio Help, but it probably will not appear in the File->New Project dialog just yet, this requires some more work....

Tip: By hitting F5 you install your application into Visual Studio, the VSPackage template has included some commands in the build an Debug to do this for you. What it actually does in install your project in a developer sandbox called the Experimental Hive. This is is a location in the registry which you can play with, to Reset the Experimental Hive at any time goto the Start Menu->Visual Studio SDK->Tools->Reset Experimental Hive, this removes all memory of Project, add ons, item templates etc that you may have installed.

Tip: Each time you hit F5 it should rebuild you project and install it, sometimes this does not complete, you'll see in the Visual Studio installation there are an number of subfolders, there are 2 in particular we are interested in

C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\ProjectTemplatesExp

C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\ProjectTemplatesCacheExp

The Cache is populated with the unzipped version of what is in the non-cache version, and this is what Visual Studio actually uses to find the project templates. This does not always get updated however, to ensure that it does add this command to you Project's Post Build Event

"C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe" /rootsuffix Exp /setup

or
"C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe" /rootsuffix Exp /InstallVSTemplates

devenv.exe switches (msdn).

3. You now have you own VSPackage but you'll also want your own Project extension (something other than the default .csproj).
You'll need this because if you wish to create Templates to group under your Project Type they'll have a Project Type the same as what you've defined, when you create new project with one of your templates you will want to add Items to the project.
In order for Items to appear in the dialog they will need to also have a type the same as your project type, this depends on the extension of your project i.e. if you leave the default extension .csproj then when you add and item to your project you'll only see the CSharp items, you'll need to change the extension to something like .gtproj, then VS will know that you want to add an item that is the same as the project which manages the extension .gtproj.

Here's a snippet of the Package code

[PackageRegistration(UseManagedResourcesOnly = true)]
[DefaultRegistryRoot("Software\\Microsoft\\VisualStudio\\8.0")]
[InstalledProductRegistration(false, "#110", "#112", "1.0", IconResourceID = 400)]
[ProvideLoadKey("Standard", "1.0", "MyProject", "My Company", 1)]
// This attribute is needed to let the shell know that this package exposes some menus.
[ProvideMenuResource(1000, 1)]
[ProvideProjectFactory(typeof(MyProjectFactory), "MyProject", "MyProject Environment project Files (*.myproj);*.myproj", "myproj", "myproj", ".\\NullPath", LanguageVsTemplate = "MyProject")]

[Guid(GuidList.guidMyProjectPkgString)]
public sealed class MyProject : Package
{

public MyProject()
{
Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));
}
protected override void Initialize()
{
base.Initialize();
base.RegisterProjectFactory(new MyProjectFactory(this));
}
protected override void Dispose(bool disposing)
{
Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Dispose() of: {0}", this.ToString()));
try
{}
finally
{
base.Dispose(disposing);
}
}
}

4. You'll need to need to implement certain interfaces in you VSPackage class, the stubs for these are provided and Visual Studios texteditor allows you to override the methods automatically by right-clickin the interface name and selecting implement from the context menu. You'll need to create a ProjectFactory and register it with your package.
Here's a snippet of the ProjectFactory code

[GuidAttribute(MyProjectFactory.MyProjectFactoryGuid)]
public class MyProjectFactory : Microsoft.VisualStudio.Package.ProjectFactory
{
public const string MyProjectFactoryGuid = "D1E0B0F6-5E61-42bf-ADD0-7CECC5665ED5";

public MyProjectFactory(MyProject package)
: base(package)
{
}
protected override ProjectNode CreateProject()
{
MyProjectNode project = new MyProjectNode();
project.SetSite((IOleServiceProvider)((System.IServiceProvider)Package).GetService(typeof(IOleServiceProvider)));
return project;
}
}

You'll notice a new instance of a class MyProjectNode, this is required, it is an Implementation of ProjectNode, this is required in order to use the Solution explorer in Visual Studio, without this you'll get an error at runtime, see step 7 for the ProjectNode code.

Hit F5 you should now see the new Project Type MyProject appear in the New Project dialog, it's Templates will be empty, so we'll move onto creating a template for your project type.

Tip: If you start to get an error which indicates a null memory address or something like that then make sure the ProjectFactory is being registered, if it is and the error persists then I'd recommend resetting the Experimental Hive with the Start menu utility I mentioned earlier and then F5 your project again.

5. You now have a VSPackage which is associated with your own project type and file type but we've mentioned nothing of your custom language yet. If you want all of the above to appear in Visual Studio from the installation (not from hitting F5 as internal developers would) you'll need to register this language and project type from the installer. A tool is used to fulfill this RegPkg.exe. To share the VSPackage with external users you'll also need a PLK, the PLK is a product key which can be obtained from Microsoft, it allows you to have your package installed on client machines without the requirement of the Visual Studio SDK. Without the PLK clients will not be allowed used your package and will get the following error when they try to create a project that uses your Language Service

RegPkg usage.
Obtain a PLK.
Using a PLK.
Also see Steps To Create Your First VSPackage, this explains how to install the PLK.
If this does not work have a look at step 8 of this post or go here "Debugging Package Load Failure (Dr. Ex)".

5. To create a Project Template file see the Topic at the end of this post "Creation of Project and Item Templates for..".
In order to associate the Project with your VSPackage type MyProject (with extenstion .myproj)
you'll need to edit the the contents of a file within the Project Template's zip file, the .vstemplate file. To do this you'll need to unzip the file. Open the .vstemplate file and edit an entry, the entry is

<ProjectType>CSharp</ProjectType>

The value CSharp could be another .NET language depending on what type of project you created your Template from.
Change the value to that of your Package i.e. MyProject.
Zip the contents back up, make sure the files are in the root of the zip file.
Now place the zip file under a new folder name MyProject, in parallel to the CSharp folder.
This zipping and installing can be done from your project using the MSBuild's ZipProject target.
To implement this you can set the type of the files to add to your template from Content to ZipProject. The files within the same folder under Template\Projects will be added to a Zip template and installed in the Visual Studio.
e.g.


creates a Project Template with the name of the last directory that the item resides in below the Templates folder.
OR

<ZipProject Include="Templates\Projects\Windows\MyProject\projectitem.cs />

results in a project template named MyProject, MyProject.zip, created and installed in the Visual Studio installation.


same for ZipItem.

Oh yes, in order to get the template to appear in the VS installation in an addition subfolder you need to add another attribute to the ZipProject

<ZipProject Include="Templates\Projects\Windows\MyProject\projectitem.cs" >
<OutputSubPath>Windows</OutputSubPath>
</ZipProject>
results in the template appearing in the VS installation at
C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\ProjectTemplatesExp\MyProjectType\Windows
6. Hit F5 you should see your project type MyProject and a project template appear in it's templates panel in the New Project dialog.
Select the project and ok, the contents listed by you in your .vstemplate file should appear in your new project.
You have a Project Type MyProject which has an extension .myproj and a project Template which appears within the Templates under your project type MyProject in the New project dialog.

7. If you want to customize the way Visual Studio handles the files found in your template then you'll need to create a ProjectNode implementation and override some methods in order to tell Visual Studio what to do when a node (file or project) is selected in the Solution Explorer of Visual Studio.
public class MyProjectNode : ProjectNode
{
public MyProjectNode()
{
// use the VS 2005 style property pages (project designer) instead of the old VS 2003 dialog
SupportsProjectDesigner = true;

// We allow destructive deletes on the project
CanProjectDeleteItems = true;
}

public override Guid ProjectGuid
{
get
{
return typeof(MyProjectFactory).GUID;
}
}

public override string ProjectType
{
get
{
return "myproj";
}
}

public override int InitializeForOuter(string filename,
string location,
string name,
uint flags,
ref Guid iid,
out IntPtr projectPointer,
out int canceled)
{
int result = base.InitializeForOuter(filename, location, name, flags, ref iid, out projectPointer, out canceled);
return result;
}

public override bool IsCodeFile(string strFileName)
{
if (String.IsNullOrEmpty(strFileName))
{
return false;
}
return
(String.Compare(Path.GetExtension(strFileName), string.Format("." + .my), StringComparison.OrdinalIgnoreCase) ==
0);
}

public override int GetFormatList(out string formatlist)
{
formatlist = string.Format("My Project File (*.myproj){1}*.myproj{2}",, "\0", "\0");
return VSConstants.S_OK;
}
#region IVsProjectSpecificEditorMap2 Members

public int GetSpecificEditorType(string pszMkDocument, out Guid pguidEditorType)
{
pguidEditorType = Guid.Empty; //typeof(YourEditorFactory).GUID;
return VSConstants.S_OK;
}

public int GetSpecificLanguageService(string pszMkDocument, out Guid pguidLanguageService)
{
pguidLanguageService = Guid.Empty; //typeof(YourLanguageService).GUID;
return VSConstants.S_OK;
}

#endregion
}
8. Debugging Package Load failures
If you were able to create a project of your custom type on your own development machine but failed when you tried it on another machine then this is the spot for you.

Tip : the easiest way to check that your package is registered with Visual Studio is to view the Help->About dialog in Visual Studio.

a. Debug the issue using a Tool called "Package Load Analyzer". It's part of the SDK version 4. It should appear in the Tools menu of Visual Studio if not the installer is present in the SDK installation, C:\Program Files\Visual Studio 2005 SDK\2007.02\VisualStudioIntegration\Tools\Bin\PackageLoader.msi, and when you've installed it it should appear in Visual Studio's Tool menu.
To use it, select Tools->Package Load Analyzer.
A dialog appears, select your Package from the list. Right Click and select "Analyze" from the list.
The information given is only an indicator to what is wrong, either the PLK is at fault of some of the dependencies failed to load.
















b. A problem maybe that you've not installed the language service in the correct Hive. First try devenv /setup again from a command prompt. If this doesnt work have a look at the the registry
HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\8.0\Packages,
your Packages GUID should be here, if not it probably wasn't installed, look in the Experimantal hive's registry
HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\8.0Exp\Packages,
you may see your GUID here instead, this probably means that your installed registered only the Experimental Hive.
Remember back to
RegPkg usage., you generated a registry setting using RegPkg.exe based on your packages assembly,
C:\Program Files\Visual Studio 2005 SDK\2007.02\VisualStudioIntegration\Tools\Bi
n\Regpkg.exe /assembly

do this again but with the first argument as the non-Experimental hive, and the second argument is /regfile to generate a registry settings file which you Import into your Setup project.
C:\Program Files\Visual Studio 2005 SDK\2007.02\VisualStudioIntegration\Tools\Bin\Regpkg.exe /root:Software\Microsoft\VisualStudio\8.0Exp /regfile: /assembly
Reinstall.
If this is not the problem it maybe that the Visual Studio SDK is not installed on the current machine.

c.
If it isn't then you'll have to add a PLK to you VSPackage and reinstall. The PLK allows your custom package to be used on a machine that does not have the SDK, I've described some of this earlier.
According to the experts the most frequent cause of Package Load Failures is with a difference in detail supplied for the PLK and for your Package.
First look at you PLK details on the VSIP website and compare with the details in you VSPackage implementation. The Company name, Product Name and Package GUID must be identical.
If this does not work then it's time for some debugging.

d.
To simulate Visual Studio without having the SDK installed you can launch VS with
devenv /noVSIP
or
devenv /rootsuffix Exp /noVSIP
Now try to create a project of your custom type, if it fails you'll see some warning messages appear in the output console.
You can read this message later using the Visual Studio logger using
devenv /noVSIP /log
or
devenv /rootsuffix Exp /noVSIP /log
an XML log file is created in the Application data folder
C:\Documents and Settings\\Application Data\Microsoft\VisualStudio\8.0
or
C:\Documents and Settings\\Application Data\Microsoft\VisualStudio\8.0Exp
named ActivityLog.xml

e. Problems with your Package not appearing in Visual Studio can be that the Registry settings are incorrect. One problem I came across is that I had failed to generate the .reg file using RegPkg correctly, I had left out the /assembly, without this the registry does not know the Verioning details of the assembly, if you use /assembly you'll also need to let the Registry know where the assembly is this means you'll need to put the Assembly in the GAC! see my post "dotNet Miscellaneous" to see how to install the assembly in the GAC from code (you would do this in you Setup Project's Custom Action).
So now your Assembly is in the GAC (using code in your installer custom action).
You've used RegPkg to create a registry setting and added this to your Setup project.
At install time the Assembly gets added to the GAC, the Registry setting is set to register your Package with Visual Studio using the Assembly in the GAC.
Your setup runs updates Visual Studio with devenv /setup.
Your now good to go...

VSPackage Language Service
This is not the same as the Babel Language Service I mentioned earlier, this type used Flex and Bison directly and uses lots of additional resources in the VSPackage. It too allows you to create Text editors with color coding etc which you can use with a file type of your own and your own Project Types.
This too is available from the VS File->New Project dialog but at: File->New Project->Other Project Types->Visual Studio Language Package.
The wizard will step you through the setup but all is not straight forward, as usual.
The wizard requires you have Flex.exe and Bison.exe installed, the wizard will prompt you for these. Google will turn up installation for these resources.


VSPackage and Projects
To create a VSPackage Project with multiple projects being created when the solution of your type is create you need a to install a MultiFileTemplate in the VS installation along with your VSPackage implementation.
To install the MultiFileTemplate you have to add the contents to a zip file and copy the zip file with the MultiFileTemplate and sub .vstemplate files yourself to the MyDocuments or to the VS installtion directorty, C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\ProjectTemplates\MyProjects, so that VS will pick it up. This can be done from within the source project file which is generating the MultiFileTemplate using the MSBuild ZipItem or the third party Zip build task MSBuildTasks that I've mentioned in another thread MSBuild.


Examples of this functionality are included in the SDK.

Project and Item Templates
Create Reusable Project and Item Templates for the development team.


Editing VSPackage File and Project Icons and Bitmaps

There are usually 2 types of image in a project, the application icon and the filetype bitmap.
The application icon is the image that appears beside the project template in the new project dialog. This is a 32x32 pixel icon with the extension .ico. It is set with the icon element in the vstemplate file.
The second is the bitmap, this used by the VSPackage to display an image beside the file or the project in Visual Studios soulution explorer. The bitmap is created as part of a bitmap file in the Resources of your source project. The bitmap may have many images grouped together but each image should only be 16x16 pixels, this is because they are access by the code and indexed based on 16 pixels. Here's how the image is access
first you add all of the bitmaps to an imageList, remember they are indexed with 16 pixels

FileTypeImageList =
Utilities.GetImageList(typeof(MyProjectNode).Assembly.GetManifestResourceStream("MyNameSpace.Resources.FileTypeImageList.bmp"));

//Store the number of images in ProjectNode so we know the offset of the python icons.
_imageOffset = this.ImageHandler.ImageList.Images.Count;
foreach (System.Drawing.Image img in FileTypeImageList.Images)
{
this.ImageHandler.AddImage(img);
}

You now have a list of all the bitmaps. You now override the ImageIndex() method in both you FileNode implementation and your ProjectNode implementation like so
public override int ImageIndex
{
get
{
return (int)ImageOffset + (int)MyTypeImageIndex.GtProjFile;
}
}
the
MyTypeImageIndex.GtProjFile is just an enum, in the above case the enum returns 1, this indicates the the bitmap for this type, in this case this override is in my ProjectNode implementation so the if a project is created of my type then the bitmap indexed at 1 (pixel 17 to 32) in the bitmap file FileTypeImageList.bmp is used with the project and will appear in solution explorer for that project.