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

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

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

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

目录

一、创建项目框架

[1.1 新建VS项目](#1.1 新建VS项目)

[1.2 添加命令](#1.2 添加命令)

[1.3 测试此框架代码](#1.3 测试此框架代码)

二、引入实际代码

[2.1 添加项目文件](#2.1 添加项目文件)

[2.2 添加依赖项](#2.2 添加依赖项)

三、修改代码

四、测试实际效果

[4.1 创建测试项目](#4.1 创建测试项目)

[4.2 在VSIX项目打开测试项目](#4.2 在VSIX项目打开测试项目)

[4.3 效果](#4.3 效果)

五、代码


一、创建项目框架

1.1 新建VS项目

以下均以VS2022社区版为例。

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

1.2 添加命令

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

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

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

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

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一样,但是已经加载了扩展。

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

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

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

二、引入实际代码

2.1 添加项目文件

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

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

2.2 添加依赖项

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

因为缺少依赖项,在"项目"-"引用"上右键,"添加引用":

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

然后程序就可以编译了。这个依赖项其实只和C++项目功能有关。

如果发生奇怪错误:

cs 复制代码
严重性	代码	说明	项目	文件	行	禁止显示状态
错误		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里面添加如下内容:

cs 复制代码
//文件头添加对共享代码的引用
using VSIXProjectShare;
using Task = System.Threading.Tasks.Task;

//在类里面添加变量,就近放在构造函数前面好了
		private CommandShare commandshare;

//构造函数最后加上这一句
			commandshare = new CommandShare(this.package);


//Execute最后加一句
			commandshare.Execute();

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

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();//第四处修改

		}
	}
}

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

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

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

四、测试实际效果

4.1 创建测试项目

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

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

4.2 在VSIX项目打开测试项目

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

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

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

输出窗口输出如下:

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

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

4.3 效果

看看代码变成了什么样:

Form1.Designer.cs就不贴了。

看看文件比较:

五、代码

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

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:C#项目 重命名所有标识符(Visual Studio扩展开发)代码详解-CSDN博客

(这里是结束)

相关推荐
码农君莫笑12 小时前
使用blazor开发信息管理系统的应用场景
数据库·信息可视化·c#·.net·visual studio
小王爱吃月亮糖18 小时前
QT开发【常用控件1】-Layouts & Spacers
开发语言·前端·c++·qt·visual studio
18号房客1 天前
macOS开发环境配置与应用开发教程(一)
vscode·macos·visualstudio·eclipse·intellij-idea·phpstorm·visual studio
IT规划师1 天前
Visual Studio - API调试与测试工具之HTTP文件
visual studio·http文件
码农君莫笑1 天前
信管通低代码信息管理系统应用平台
linux·数据库·windows·低代码·c#·.net·visual studio
我是一个对称矩阵2 天前
Visual Studio2019基础使用指南
ide·visual studio
码农君莫笑2 天前
Blazor项目中使用EF读写 SQLite 数据库
linux·数据库·sqlite·c#·.netcore·人机交互·visual studio
这是我583 天前
C++打小怪游戏
c++·其他·游戏·visual studio·小怪·大型·怪物
cgsthtm3 天前
Visual studio Debug调试webservice 使用ip代替localhostt配置IIS Express
android studio·visual studio·webservice·真机调试·iis express