VSIX:C项目 重命名所有标识符(Visual Studio扩展开发)

VSIX:C项目 重命名所有标识符(Visual Studio扩展开发)VSIX C 项目重命名所有标识符 VisualStudio 扩展开发 vsix

大家好,欢迎来到IT知识分享网。

初级代码游戏的专栏介绍与文章目录-CSDN博客

我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。

这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。


        出于某种目的(合法的,真的合法的,合同上明确指出可以这样做),我准备了一个重命名所有标识符的VS扩展,用来把一个C#库改头换面,在简单的测试项目上工作很满意,所有标识符都被准确替换。我还尝试用在C++项目上,问题就比较多了,因为VS并不能准确识别代码,这说明,C#比C++好用太多了。

        当然,最终合同被放弃了,所以这个东西也没派上用场,纯粹成了我的个人练习(因为没有人指派我做这个程序)。

        本文涉及的代码支持2017、2019和2022,为了稳妥起见,项目本身用不同版本的VS创建,实际代码则放在一个共享文件中,只需要在生成的框架之中加入一句调用代码即可。

目录

一、创建项目框架

1.1 新建VS项目

1.2 添加命令

1.3 测试此框架代码

二、引入实际代码

2.1 添加项目文件

2.2 添加依赖项

三、修改代码

四、测试实际效果

4.1 创建测试项目

4.2 在VSIX项目打开测试项目

4.3 效果

五、代码


        

一、创建项目框架

1.1 新建VS项目

        以下均以VS2022社区版为例。

VSIX:C项目 重命名所有标识符(Visual Studio扩展开发)

        项目类型过滤选择“扩展”,项目类型为“VSIX Project”。

        如果找不到这个,是因为安装的时候没有装,打开安装程序增加选项即可。

        在开始菜单里面找到“Visual Studio Installer”,修改安装选项:

VSIX:C项目 重命名所有标识符(Visual Studio扩展开发)

1.2 添加命令

        创建以后在项目上右键,“添加”-“新建项”:

VSIX:C项目 重命名所有标识符(Visual Studio扩展开发)

        “Command”就是一个菜单命令,会出现在VS的“工具”菜单下面。

        添加之后会看到增加了一个文件:Command1.cs,当然如果你改了命令名就是另外一个文件。

        文件不长,直接拉到最后,看最后一个方法的代码:

 /// <summary> /// This function is the callback used to execute the command when the menu item is clicked. /// See the constructor to see how the menu item is associated with this function using /// OleMenuCommandService service and MenuCommand class. /// </summary> /// <param name="sender">Event sender.</param> /// <param name="e">Event args.</param> private void Execute(object sender, EventArgs e) { ThreadHelper.ThrowIfNotOnUIThread(); string message = string.Format(CultureInfo.CurrentCulture, "Inside {0}.MenuItemCallback()", this.GetType().FullName); string title = "Command1"; // Show a message box to prove we were here VsShellUtilities.ShowMessageBox( this.package, message, title, OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); } 

        好简单的,猜也能猜到就是显示一个消息框。

1.3 测试此框架代码

        编译项目,应该没什么问题(全是开发工具生成的代码嘛)。

        调试或者直接运行不调试(“调试菜单”的“开始执行(不调试)”),会打开一个新的VS2022窗口,像普通VS一样,但是已经加载了扩展。

        选择项目,或者不选择项目直接进入。不选择项目直接进入(点击“继续但无需代码”):

VSIX:C项目 重命名所有标识符(Visual Studio扩展开发)

        工具菜单下会出现“Invoke Command1”(图中还有另外一个相似菜单,是我的正式项目创建的),点击出现:

VSIX:C项目 重命名所有标识符(Visual Studio扩展开发)

        这个框架就算完成了,剩下的就是修改命令代码。

二、引入实际代码

2.1 添加项目文件

        在项目上右键,“添加”-“现有项”,找到实际代码文件添加进来,当然你也可以直接放在项目里面,但是因为VS扩展项目是依赖VS版本的,最好把通用部分独立出来。

VSIX:C项目 重命名所有标识符(Visual Studio扩展开发)

        这个位置是在项目之外的,可以由多个项目共享。

2.2 添加依赖项

        文件加进来之后不能编译:

VSIX:C项目 重命名所有标识符(Visual Studio扩展开发)

VSIX:C项目 重命名所有标识符(Visual Studio扩展开发)

        这是因为缺少依赖项,在“项目”-“引用”上右键,“添加引用”:

VSIX:C项目 重命名所有标识符(Visual Studio扩展开发)

        在“程序集”-“扩展”里面找到“Microsoft.VisualStudio.VCCodeModel”,选中,确定。

        然后程序就可以编译了。这个依赖项其实只和C++项目功能有关,删除C++项目相关代码也可以不要这个依赖项。

        如果发生奇怪错误:

严重性 代码 说明 项目 文件 行 禁止显示状态 错误 CreatePkgDef : error : ArgumentException: No Visual Studio registration attribute found in this assembly. The assembly should contain an instance of the attribute 'Microsoft.VisualStudio.Shell.RegistrationAttribute' defined in assembly 'Microsoft.VisualStudio.Shell.Framework' version '17.0.0.0' 在 Microsoft.VisualStudio.Tools.CreatePkgDef.ProcessAssembly(String fileName, Hive hive, PkgDefContext context, Boolean register, RegistrationMode mode) 位置 D:\a\_work\1\s\src\product\vssdk\tools\CreatePkgDef\CreatePkgDef.cs:行号 383 在 Microsoft.VisualStudio.Tools.CreatePkgDef.DoCreatePkgDef(InputArguments inputArguments) 位置 D:\a\_work\1\s\src\product\vssdk\tools\CreatePkgDef\CreatePkgDef.cs:行号 202 在 Microsoft.VisualStudio.Tools.CreatePkgDef.Main(String[] arguments) 位置 D:\a\_work\1\s\src\product\vssdk\tools\CreatePkgDef\CreatePkgDef.cs:行号 91 VSIXProject1 

        不要尝试任何解决方案,删除刚才添加的东西也没用,整个过程删掉重来。这可能是VS的BUG。

三、修改代码

        现在我们可以将代码引入,在Command1.cs里面添加如下内容:

//文件头添加对共享代码的引用 using VSIXProjectShare; using Task = System.Threading.Tasks.Task; //在类里面添加变量,就近放在构造函数前面好了 private CommandShare commandshare; //构造函数最后加上这一句 commandshare = new CommandShare(this.package); //Execute最后加一句 commandshare.Execute(); 

        最终的Command1.cs是这样的(四处修改在里面已经注明):

using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using System; using System.ComponentModel.Design; using System.Globalization; using System.Threading; using System.Threading.Tasks; using VSIXProjectShare;//第一处修改 using Task = System.Threading.Tasks.Task; namespace VSIXProject1 { /// <summary> /// Command handler /// </summary> internal sealed class Command1 { /// <summary> /// Command ID. /// </summary> public const int CommandId = 0x0100; /// <summary> /// Command menu group (command set GUID). /// </summary> public static readonly Guid CommandSet = new Guid("16dcb30d-2f74-4781-bde4-c21c60716ac8"); /// <summary> /// VS Package that provides this command, not null. /// </summary> private readonly AsyncPackage package; private CommandShare commandshare;//第二处修改 /// <summary> /// Initializes a new instance of the <see cref="Command1"/> class. /// Adds our command handlers for menu (commands must exist in the command table file) /// </summary> /// <param name="package">Owner package, not null.</param> /// <param name="commandService">Command service to add command to, not null.</param> private Command1(AsyncPackage package, OleMenuCommandService commandService) { this.package = package ?? throw new ArgumentNullException(nameof(package)); commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); var menuCommandID = new CommandID(CommandSet, CommandId); var menuItem = new MenuCommand(this.Execute, menuCommandID); commandService.AddCommand(menuItem); commandshare = new CommandShare(this.package);//第三处修改 } /// <summary> /// Gets the instance of the command. /// </summary> public static Command1 Instance { get; private set; } /// <summary> /// Gets the service provider from the owner package. /// </summary> private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider { get { return this.package; } } /// <summary> /// Initializes the singleton instance of the command. /// </summary> /// <param name="package">Owner package, not null.</param> public static async Task InitializeAsync(AsyncPackage package) { // Switch to the main thread - the call to AddCommand in Command1's constructor requires // the UI thread. await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; Instance = new Command1(package, commandService); } /// <summary> /// This function is the callback used to execute the command when the menu item is clicked. /// See the constructor to see how the menu item is associated with this function using /// OleMenuCommandService service and MenuCommand class. /// </summary> /// <param name="sender">Event sender.</param> /// <param name="e">Event args.</param> private void Execute(object sender, EventArgs e) { ThreadHelper.ThrowIfNotOnUIThread(); string message = string.Format(CultureInfo.CurrentCulture, "Inside {0}.MenuItemCallback()", this.GetType().FullName); string title = "Command1-d";//修改这里以确认版本 // Show a message box to prove we were here VsShellUtilities.ShowMessageBox( this.package, message, title, OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); commandshare.Execute();//第四处修改 } } } 

        然后编译执行(仍然用“继续但无需代码”):

VSIX:C项目 重命名所有标识符(Visual Studio扩展开发)

        看,版本正确,这是一个小技巧,debug发生困惑的时候先确认版本,不要编译失败执行旧版本。

        点击【确定】后又弹出新的消息框:

VSIX:C项目 重命名所有标识符(Visual Studio扩展开发)

        这就对了,因为没有打开项目,共享代码需要项目来操作。这说明共享代码也正确进去了。后面就可以测试实际效果,共享代码放在本文最后。

四、测试实际效果

4.1 创建测试项目

        创建一个C#项目,一个对话框好了:

VSIX:C项目 重命名所有标识符(Visual Studio扩展开发)

        一个对话框,有个静态文本,窗口初始化设置了一下文本。

4.2 在VSIX项目打开测试项目

        先创建好这个项目,然后回到VSIX项目,调试或运行,在新打开的VS启动时选择新建的这个项目,打开后是和普通VS一样操作的,只不过多了扩展菜单项。

        现在从工具菜单执行我们的命令,运行时会在输出窗口输出内容,最后会得到一个消息框:

VSIX:C项目 重命名所有标识符(Visual Studio扩展开发)

        这就表示正确完成,提示信息是共享代码最后的版本,就是日期和时间。

        输出窗口输出如下:

VSIX:C项目 重命名所有标识符(Visual Studio扩展开发)

        注意:此时修改的文件还没保存,要点击“全部保存”来保存文件。

        然后我们看看效果如何,现重新编译程序确认测试项目是正常的。

4.3 效果

        看看代码变成了什么样:

VSIX:C项目 重命名所有标识符(Visual Studio扩展开发)

VSIX:C项目 重命名所有标识符(Visual Studio扩展开发)

        Form1.Designer.cs就不贴了。

        看看文件比较:

VSIX:C项目 重命名所有标识符(Visual Studio扩展开发)

五、代码

        共享代码在此,文件名CommandShare.cs:

using System; using System.ComponentModel.Design; using System.Globalization; using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Task = System.Threading.Tasks.Task; using EnvDTE; using EnvDTE80; using Microsoft.VisualStudio; using Microsoft.VisualStudio.VCCodeModel; using System.IO; using Microsoft.Internal.VisualStudio.PlatformUI; namespace VSIXProjectShare { public sealed class CommandShare { private readonly AsyncPackage package; enum ProjectType { VC,CSharp,OTHER};//项目类型 ProjectType projectType; private Random r ;//随机数 private string new_name_title;//新名称标题 private long count = 0;//顺序编号 public CommandShare(AsyncPackage _package) { package = _package; Log("初始化插件"); r = new Random(); new_name_title = "_ASDFGHJKL_" + r.Next().ToString() + "_"; } //显示消息对话框 private void ShowMessageBox(string title, string message) { VsShellUtilities.ShowMessageBox( package, message, title, OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); } //输出日志 private void Log(string msg) { Log(0,msg); } private void Log(int level,string msg) { ThreadHelper.ThrowIfNotOnUIThread(); IVsOutputWindowPane pane = (IVsOutputWindowPane)Package.GetGlobalService(typeof(SVsGeneralOutputWindowPane)); int tmp = pane.Activate(); if (VSConstants.S_OK != tmp) { ShowMessageBox("注意", "未能激活输出窗口 " + tmp.ToString()); } for (int i = 0; i < level; ++i) { pane.OutputStringThreadSafe(" "); } pane.OutputStringThreadSafe(msg + "\r\n"); } private void AddFunction_myToString(int level, VCCodeElements codeElements) { ThreadHelper.ThrowIfNotOnUIThread(); IVsOutputWindowPane pane = (IVsOutputWindowPane)Package.GetGlobalService(typeof(SVsGeneralOutputWindowPane)); string px = new string(' ', level * 4); string px2 = new string(' ', 4); px = level.ToString() + px; string fun_name = "myToString"; string fun_type = "stringstream &"; vsCMFunction fun_kind = vsCMFunction.vsCMFunctionFunction | vsCMFunction.vsCMFunctionConstant; foreach (VCCodeElement element in codeElements) { Log(px + "Kind " + element.Kind.ToString() + " Name " + element.Name); if (0 != element.Children.Count) { AddFunction_myToString(level + 1, element.Children as VCCodeElements); } if (element.Kind == vsCMElement.vsCMElementClass || element.Kind == vsCMElement.vsCMElementStruct) { VCCodeElement Found = null; string bodytext = ""; //基类必须首先处理 foreach (VCCodeElement chileren in element.Children) { if (chileren.Kind == vsCMElement.vsCMElementVCBase) { Log(px + px2 + "基类 " + chileren.Name); bodytext += "\t\t " + chileren.Name + "::myToString(ss) << \" \";\r\n"; } } foreach (VCCodeElement chileren in element.Children) { Log(px + px2 + chileren.Name + " Kind " + chileren.Kind); if (chileren.Kind == vsCMElement.vsCMElementVariable) { VCCodeVariable variable = (VCCodeVariable)chileren; Log(px + px2 + "变量 Name " + variable.Name + " TypeString " + variable.TypeString + " StartPoint " + variable.StartPoint.Line + " " + variable.StartPoint.LineCharOffset + " EndPoint " + variable.EndPoint.Line + " " + variable.EndPoint.LineCharOffset); if (variable.TypeString.EndsWith(")")) { bodytext += "\t\t ss << \"函数指针 " + variable.Name + " \" << " + variable.Name + " << \" \";\r\n"; } else if (variable.TypeString.EndsWith("]")) { bodytext += "\t\t ss << \"数组 " + variable.Name + " \" << " + variable.Name + " << \" \";\r\n"; } else { bodytext += "\t\t Template_" + fun_name + "(" + variable.Name + ", ss) << \" \";\r\n"; } } else if (chileren.Kind == vsCMElement.vsCMElementFunction) { if (chileren.Name == fun_name) { Found = chileren; Log(px + px2 + fun_name + " 已存在,重新创建"); } } } VCCodeFunction codeFunction; if (element.Kind == vsCMElement.vsCMElementClass) { VCCodeClass codeClass = (VCCodeClass)element; codeClass.RemoveMember(Found); codeFunction = (VCCodeFunction)codeClass.AddFunction(fun_name, fun_kind, fun_type, -1, vsCMAccess.vsCMAccessPublic); } else { VCCodeStruct codeClass = (VCCodeStruct)element; codeClass.RemoveMember(Found); codeFunction = (VCCodeFunction)codeClass.AddFunction(fun_name, fun_kind, fun_type, -1, vsCMAccess.vsCMAccessPublic); } codeFunction.AddParameter("ss", "stringstream &"); codeFunction.Comment = "自动生成的代码"; bodytext += "\t\t return ss;"; codeFunction.BodyText = bodytext; } } } private void CSharp_Rename(int level, CodeElements codeElements) { ThreadHelper.ThrowIfNotOnUIThread(); foreach (CodeElement _element in codeElements) { Log(level, "Kind " + _element.Kind.ToString()); string name = "未知";//Name属性不是每个都有 if (_element.Kind == vsCMElement.vsCMElementImportStmt) { name = "vsCMElementImportStmt"; } else { name = _element.Name;//这个竟然不是每个都支持 } CodeElement2 element = (CodeElement2)_element; Log(level, "Kind " + element.Kind.ToString() + " Name " + name + " type " + element.GetType().ToString()); //处理子项 if (0 != element.Children.Count) { CSharp_Rename(level + 1, element.Children); } bool skip = false;//是否需要跳过 //检查是否已经处理过 if (name.StartsWith(new_name_title)) { skip = true; } if (element.Kind == vsCMElement.vsCMElementVariable) { CodeVariable variable = (CodeVariable)element; Log(level + 1, "变量 Name " + variable.Name + " StartPoint " + variable.StartPoint.Line + " " + variable.StartPoint.LineCharOffset + " EndPoint " + variable.EndPoint.Line + " " + variable.EndPoint.LineCharOffset); } else if (element.Kind == vsCMElement.vsCMElementFunction) { Log(level + 1, "函数 " + name); if (name.Equals("Main")) { Log(level + 1, "Main函数(跳过) " + name); skip = true; } if (name.Equals("Dispose")) { Log(level + 1, "Dispose函数(跳过) " + name); skip = true; } } else if (element.Kind == vsCMElement.vsCMElementNamespace) { Log(level + 1, "命名空间 " + name); //skip = true; } else if (element.Kind == vsCMElement.vsCMElementAttribute) { Log(level + 1, "属性(跳过) " + name); skip = true; } else if (element.Kind == vsCMElement.vsCMElementImportStmt) { Log(level + 1, "导入语句(跳过) " + name); skip = true; } else if (element.Kind == vsCMElement.vsCMElementOther) { Log(level + 1, "vsCMElementOther(跳过) " + name); skip = true; } if (!skip) { Log(level, "重命名 " + name + "(" + element.Kind.ToString() + ") 为 " + new_name_title + count.ToString()); element.RenameSymbol(new_name_title + count.ToString()); count++; Log(level, "重命名完成"); } } } private void ProcessProjectItem(int level, ProjectItem projectItem) { ThreadHelper.ThrowIfNotOnUIThread(); //项目下的筛选器 Log(level, "===============目录:" + projectItem.Name + " 项目子项FileCount:" + projectItem.FileCount.ToString()); for (short i = 0; i < projectItem.FileCount; i++) { Log(3, "文件名:" + projectItem.FileNames[i]); } if (projectType == ProjectType.CSharp && projectItem.Name == "Properties") { Log(3, "C#项目忽略属性目录"); return; } if (null != projectItem.FileCodeModel) { String language = "未知语言"; switch (projectItem.FileCodeModel.Language) { case CodeModelLanguageConstants.vsCMLanguageVC: language = "VC"; AddFunction_myToString(5, projectItem.FileCodeModel.CodeElements as VCCodeElements); break; case CodeModelLanguageConstants.vsCMLanguageIDL: language = "IDL"; Log(3, "未支持的语言 " + language); break; case CodeModelLanguageConstants.vsCMLanguageVB: language = "VB"; Log(3, "未支持的语言 " + language); break; case CodeModelLanguageConstants.vsCMLanguageMC: language = "MC"; Log(3, "未支持的语言 " + language); break; case CodeModelLanguageConstants.vsCMLanguageCSharp: language = "CSharp"; Log(3, "语言 " + language); if (null == projectItem) Log(3, "语言1" + language); if (null == projectItem.FileCodeModel) Log(3, "语言 2" + language); if (null == projectItem.FileCodeModel.CodeElements) Log(3, "语言 3" + language); Log(3, "语言 " + language); CSharp_Rename(5, projectItem.FileCodeModel.CodeElements); break; } } foreach (ProjectItem current_project_item_item in projectItem.ProjectItems) { ProcessProjectItem(level + 1, current_project_item_item); } } public void Execute() { ThreadHelper.ThrowIfNotOnUIThread(); string message = string.Format(CultureInfo.CurrentCulture, "Inside {0}.MenuItemCallback()", this.GetType().FullName); string title = "Command1 2023-04-20 1720"; // Show a message box to prove we were here //VsShellUtilities.ShowMessageBox( // this.package, // message, // title, // OLEMSGICON.OLEMSGICON_INFO, // OLEMSGBUTTON.OLEMSGBUTTON_OK, // OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); Log(title); try { DTE2 dte = (DTE2)Package.GetGlobalService(typeof(SDTE)); Log("DTE:" + dte.Version); Log("DTE:" + dte.Name); Log("DTE:" + dte.Edition); Log("DTE:" + dte.Mode); var solution = dte.Solution; var SolutionName = Path.GetFileName(solution.FullName); //解决方案名称 var SolutionPath = Path.GetDirectoryName(solution.FullName);//解决方案路径 Log("解决方案:" + solution.ToString()); Log("解决方案FileName:" + solution.FileName); Log("解决方案FullName:" + solution.FullName); Log("解决方案GetFileName:" + SolutionName); Log("解决方案GetDirectoryName:" + SolutionPath); Log("解决方案Count:" + solution.Count); Log("解决方案Projects.Count:" + solution.Projects.Count); foreach (Project current_project in solution.Projects) { //解决方案下的项目 Log(1, "--------------------------Language:" + current_project.CodeModel.Language); if (current_project.CodeModel.Language == "{B5E9BD34-6D3E-4B5D-925E-8A43B79820B4}") { projectType = ProjectType.CSharp; } else if (current_project.CodeModel.Language == "{B5E9BD32-6D3E-4B5D-925E-8A43B79820B4}") { projectType = ProjectType.VC; } else { projectType = ProjectType.OTHER; } Log(1, "--------------------------项目:" + current_project.Name + " 类型 " + projectType + " 项目子项个数:" + current_project.ProjectItems.Count.ToString()); foreach (ProjectItem current_project_item in current_project.ProjectItems) { ProcessProjectItem(2, current_project_item); } } ShowMessageBox(title, "操作完成"); } catch (Exception ex) { ShowMessageBox("", ex.Message); } } } } 

        这个代码对C#项目执行CSharp_Rename,对C++项目则执行AddFunction_myToString,功能是给所有结构添加toString函数,尚不完善,所以无视即可(其实我最开始折腾VSIX就是为了写这个功能的,我挺希望别人能把这个功能做出来)。

        代码分析看这里:VSIX:C#项目 重命名所有标识符(Visual Studio扩展开发)代码详解-CSDN博客。


(这里是结束)

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/126588.html

(0)
上一篇 2025-09-17 17:33
下一篇 2025-09-17 17:45

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信