您可能需要通过创建 Visual Studio 扩展 (VSIX) 来遵循不同的方法,下面我详细解释了它,希望它有所帮助。
如何在 Visual Studio 2017 中创建自定义工具或单个文件生成器:
在 VS2017 之前创建自定义工具需要实现接口IVsSingleFileGenerator
以及在系统注册表中注册和取消注册自定义工具的代码,但在 VS2017 中,微软更改了整个注册表结构。变化是,VS 会将注册表项写入私有注册表,这样系统注册表就不会混乱。虽然以前注册表项是在系统注册表中创建的,但现在它们是为了
C:\Users\xyz\AppData\Local\Microsoft\VisualStudio\15.0_xx\privateregistry.bin
Visual Studio 2017 还支持通过从 Visual Studio 本身 (F5) 运行工具来直接测试您的工具,这会启动另一个名为的 Visual Studio 实例Visual Studio实验实例并且您的工具可以在其中进行测试,因为它使注册表项
C:\Users\xyz\AppData\Local\Microsoft\VisualStudio\15.0_xxExp\privateregistry.bin
按照以下步骤在 VS2017 中创建自定义工具:
- 我们需要创建一个 VSIX 扩展
- 添加新的 Visual Studio 包
- 实施
IVsSingleFileGenerator
- 添加注册表项代码
- 在VS2017中编译并运行该工具进行测试
- 双击生成的 .VSIX 文件安装该工具
我们将创建一个名为“CountLines”的扩展/自定义工具作为示例,它将读取一个文件(将自定义工具属性设置为 CountLines)并生成一个包含文件中行数的 XML 文件。例如<LineCount>1050</LineCount>
1. 创建 VSIX 扩展为了创建扩展,您必须安装 Visual Studio 扩展性工具,该工具作为可选功能包含在 Visual Studio 安装程序中。如果未安装,您也可以通过修改 VS 2017 安装程序来安装它。
通过选择创建新的 VSIX(Visual Studio 扩展)项目
新项目 -> 可扩展性 -> VSIX 项目
给它一些名称,例如“CountLinesVSIX”。
2.添加新的Visual Studio包创建 VSIX 项目后,通过选择向其添加新的 Visual Studio 包
添加 -> 新项目 -> 扩展性 -> Visual Studio 包
将其命名为“CountLines.cs”。在CountLines.cs
我们需要删除现有代码并将其替换为我们的代码IVsSingleFileGenerator
执行
3. 实现 IVsSingleFileGenerator编写接口的自定义实现IVsSingleFileGenerator
,我们的示例代码如下
using System;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using System.Text;
namespace CountLinesVSIX
{
[PackageRegistration(UseManagedResourcesOnly = true)]
[InstalledProductRegistration( "CountLines", "Generate XML with line count", "1.0")]
[Guid("202E7E8B-557E-46CB-8A1D-3024AD68F44A")]
[ComVisible(true)]
[ProvideObject(typeof(CountLines))]
[CodeGeneratorRegistration(typeof(CountLines), "CountLines", "{FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}", GeneratesDesignTimeSource = true)]
public sealed class CountLines : IVsSingleFileGenerator
{
#region IVsSingleFileGenerator Members
public int DefaultExtension(out string pbstrDefaultExtension)
{
pbstrDefaultExtension = ".xml";
return pbstrDefaultExtension.Length;
}
public int Generate(string wszInputFilePath, string bstrInputFileContents,
string wszDefaultNamespace, IntPtr[] rgbOutputFileContents,
out uint pcbOutput, IVsGeneratorProgress pGenerateProgress)
{
try
{
int lineCount = bstrInputFileContents.Split('\n').Length;
byte[] bytes = Encoding.UTF8.GetBytes("<LineCount>" + lineCount.ToString() + "</LineCount>" );
int length = bytes.Length;
rgbOutputFileContents[0] = Marshal.AllocCoTaskMem(length);
Marshal.Copy(bytes, 0, rgbOutputFileContents[0], length);
pcbOutput = (uint)length;
}
catch (Exception ex)
{
pcbOutput = 0;
}
return VSConstants.S_OK;
}
#endregion
}
}
我们需要为我们的扩展提供一个唯一的 GUID,例如上面代码中的 GUID[Guid("202E7E8B-557E-46CB-8A1D-3024AD68F44A")]
。可以通过选择从 VS2017 创建 GUID“工具->创建GUID”。选择 GUID 格式作为注册表格式。请注意,上面提到的 GUID 代码没有大括号。
[ComVisible(true)]
COM 互操作需要
[CodeGeneratorRegistration(typeof(CountLines), "CountLines", "{FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}", GeneratesDesignTimeSource = true)]
是一个类属性,带有用于注册工具的代码。参数为 GeneratorType、GeneratorName 和 C# 语言 GUID
您还可以从支持自定义 TextTemplate 格式的“TemplatedCodeGenerator”派生,这可能需要一些额外的代码实现。
4.添加注册表项代码使用以下代码创建新的类文件,将其命名为 CodeGeneratorRegistrationAttribute.cs
using System;
using System.Globalization;
using Microsoft.VisualStudio.Shell;
namespace CountLinesVSIX
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class CodeGeneratorRegistrationAttribute : RegistrationAttribute
{
private string _contextGuid;
private Type _generatorType;
private Guid _generatorGuid;
private string _generatorName;
private string _generatorRegKeyName;
private bool _generatesDesignTimeSource = false;
private bool _generatesSharedDesignTimeSource = false;
public CodeGeneratorRegistrationAttribute(Type generatorType, string generatorName, string contextGuid)
{
if (generatorType == null)
throw new ArgumentNullException("generatorType");
if (generatorName == null)
throw new ArgumentNullException("generatorName");
if (contextGuid == null)
throw new ArgumentNullException("contextGuid");
_contextGuid = contextGuid;
_generatorType = generatorType;
_generatorName = generatorName;
_generatorRegKeyName = generatorType.Name;
_generatorGuid = generatorType.GUID;
}
/// <summary>
/// Get the generator Type
/// </summary>
public Type GeneratorType
{
get { return _generatorType; }
}
/// <summary>
/// Get the Guid representing the project type
/// </summary>
public string ContextGuid
{
get { return _contextGuid; }
}
/// <summary>
/// Get the Guid representing the generator type
/// </summary>
public Guid GeneratorGuid
{
get { return _generatorGuid; }
}
/// <summary>
/// Get or Set the GeneratesDesignTimeSource value
/// </summary>
public bool GeneratesDesignTimeSource
{
get { return _generatesDesignTimeSource; }
set { _generatesDesignTimeSource = value; }
}
/// <summary>
/// Get or Set the GeneratesSharedDesignTimeSource value
/// </summary>
public bool GeneratesSharedDesignTimeSource
{
get { return _generatesSharedDesignTimeSource; }
set { _generatesSharedDesignTimeSource = value; }
}
/// <summary>
/// Gets the Generator name
/// </summary>
public string GeneratorName
{
get { return _generatorName; }
}
/// <summary>
/// Gets the Generator reg key name under
/// </summary>
public string GeneratorRegKeyName
{
get { return _generatorRegKeyName; }
set { _generatorRegKeyName = value; }
}
/// <summary>
/// Property that gets the generator base key name
/// </summary>
private string GeneratorRegKey
{
get { return string.Format(CultureInfo.InvariantCulture, @"Generators\{0}\{1}", ContextGuid, GeneratorRegKeyName); }
}
/// <summary>
/// Called to register this attribute with the given context. The context
/// contains the location where the registration inforomation should be placed.
/// It also contains other information such as the type being registered and path information.
/// </summary>
public override void Register(RegistrationContext context)
{
using (Key childKey = context.CreateKey(GeneratorRegKey))
{
childKey.SetValue(string.Empty, GeneratorName);
childKey.SetValue("CLSID", GeneratorGuid.ToString("B"));
if (GeneratesDesignTimeSource)
childKey.SetValue("GeneratesDesignTimeSource", 1);
if (GeneratesSharedDesignTimeSource)
childKey.SetValue("GeneratesSharedDesignTimeSource", 1);
}
}
/// <summary>
/// Unregister this file extension.
/// </summary>
/// <param name="context"></param>
public override void Unregister(RegistrationContext context)
{
context.RemoveKey(GeneratorRegKey);
}
}
}
上面的代码将确保您的条目已写入 VS 私有注册表
5.在VS2017中编译并运行该工具进行测试您可以在“source.extension.vsixmanifest”中添加“安装目标”,以确保您的扩展支持不同的 VS2017 版本。
在 VS 2017 中运行您的工具来测试它是否按预期工作。运行 VSIX 后,Visual Studio 实验实例将安装扩展并将其注册到注册表“C:\Users\xyz\AppData\Local\Microsoft\VisualStudio\15.0_xxExp\privateregistry.bin”中。您可以通过选择“工具 -> 扩展和更新”来查看已安装的扩展。要测试该工具,我们必须打开一个虚拟项目,在解决方案资源管理器中选择一个文件,转到其属性并将“自定义工具”属性更新为“CountLines”。完成此操作后,VS 将在后台运行该工具并生成输出,在我们的示例中,它将在所选文件下生成一个 xml 文件。或者,设置自定义工具属性后,您可以右键单击该文件并选择“运行自定义工具”
6. 双击生成的 .VSIX 文件安装该工具测试成功后,尝试安装 VSIX,该 VSIX 位于“projectName/bin/debug”位置。双击该文件安装 VSIX,按照安装步骤操作。现在您的工具将可以在 VS2017 中使用。使用工具类似,右键单击要运行自定义工具的文件,然后选择“运行自定义工具”
如果您想卸载扩展,请转到“工具 -> 扩展和更新 -> 选择您的扩展”,然后单击卸载。请注意,在 VS 关闭之前,该工具不会被卸载。关闭后,您将弹出卸载窗口,选择“修改”进行卸载。