使用 protobuf-net 的 C# 项目中的协议缓冲区 - 代码生成的最佳实践

发布于 2024-07-11 16:29:53 字数 3222 浏览 5 评论 0原文

我正在尝试在 C# 项目中使用 protobuf-net,并且想知道将其组织到 Visual Studio 项目结构中的最佳方法是什么。

当手动使用 protogen 工具生成 C# 代码时,生活看起来很轻松,但感觉不太对劲。

我希望 .proto 文件被视为主要源代码文件,生成 C# 文件作为副产品,但在 C# 编译器介入之前。

选项似乎是:

  1. 原型工具的自定义工具(尽管我不知道从哪里开始)
  2. 预构建步骤(调用 protogen 或执行此操作的批处理文件)

我一直在努力解决上面的 2),因为它不断给出我“系统找不到指定的文件”,除非我使用绝对路径(并且我不喜欢强制显式定位项目)。

对此有(尚未)约定吗?


编辑: 根据 @jon 的评论,我重试了预构建步骤方法并使用了此方法(目前对 protogen 的位置进行了硬编码),使用 Google 的地址簿示例:

c:\bin\protobuf\protogen "-i:$(ProjectDir)AddressBook.proto" 
       "-o:$(ProjectDir)AddressBook.cs" -t:c:\bin\protobuf\csharp.xslt

Edit2: 按照 @jon 的建议,如果 .proto 文件没有更改,则不处理它们,从而最大限度地减少构建时间,我已经拼凑了一个基本工具来检查我(这可能会扩展为完整的自定义构建工具):

using System;
using System.Diagnostics;
using System.IO;

namespace PreBuildChecker
{
    public class Checker
    {
        static int Main(string[] args)
        {
            try
            {
                Check(args);
                return 0;
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                return 1;
            }
        }

        public static void Check(string[] args)
        {
            if (args.Length < 3)
            {
                throw new ArgumentException(
                    "Command line must be supplied with source, target and command-line [plus options]");
            }

            string source = args[0];
            string target = args[1];
            string executable = args[2];
            string arguments = args.Length > 3 ? GetCommandLine(args) : null;

            FileInfo targetFileInfo = new FileInfo(target);
            FileInfo sourceFileInfo = new FileInfo(source);
            if (!sourceFileInfo.Exists) 
            {
                throw new ArgumentException(string.Format(
                    "Source file {0} not found", source));
            }

            if (!targetFileInfo.Exists || 
                sourceFileInfo.LastWriteTimeUtc > targetFileInfo.LastAccessTimeUtc)
            {
                Process process = new Process();
                process.StartInfo.FileName = executable;
                process.StartInfo.Arguments = arguments;
                process.StartInfo.ErrorDialog = true;

                Console.WriteLine(string.Format(
                     "Source newer than target, launching tool: {0} {1}",
                     executable,
                     arguments));
                process.Start();
            }
        }

        private static string GetCommandLine(string[] args)
        {
            string[] arguments = new string[args.Length - 3];
            Array.Copy(args, 3, arguments, 0, arguments.Length);
            return String.Join(" ", arguments);
        }
    }
}

我的预构建命令现在是(全部在一行):

$(SolutionDir)PreBuildChecker\$(OutDir)PreBuildChecker 
    $(ProjectDir)AddressBook.proto 
    $(ProjectDir)AddressBook.cs 
    c:\bin\protobuf\protogen 
      "-i:$(ProjectDir)AddressBook.proto" 
      "-o:$(ProjectDir)AddressBook.cs" 
      -t:c:\bin\protobuf\csharp.xslt

I'm trying to use protobuf in a C# project, using protobuf-net, and am wondering what is the best way to organise this into a Visual Studio project structure.

When manually using the protogen tool to generate code into C#, life seems easy but it doesn't feel right.

I'd like the .proto file to be considered to be the primary source-code file, generating C# files as a by-product, but before the C# compiler gets involved.

The options seem to be:

  1. Custom tool for proto tools (although I can't see where to start there)
  2. Pre-build step (calling protogen or a batch-file which does that)

I have struggled with 2) above as it keeps giving me "The system cannot find the file specified" unless I use absolute paths (and I don't like forcing projects to be explicitly located).

Is there a convention (yet) for this?


Edit:
Based upon @jon's comments, I retried the pre-build step method and used this (protogen's location hardcoded for now), using Google's address-book example:

c:\bin\protobuf\protogen "-i:$(ProjectDir)AddressBook.proto" 
       "-o:$(ProjectDir)AddressBook.cs" -t:c:\bin\protobuf\csharp.xslt

Edit2:
Taking @jon's recommendation to minimise build-time by not processing the .proto files if they haven't changed, I've knocked together a basic tool to check for me (this could probably be expanded to a full Custom-Build tool):

using System;
using System.Diagnostics;
using System.IO;

namespace PreBuildChecker
{
    public class Checker
    {
        static int Main(string[] args)
        {
            try
            {
                Check(args);
                return 0;
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                return 1;
            }
        }

        public static void Check(string[] args)
        {
            if (args.Length < 3)
            {
                throw new ArgumentException(
                    "Command line must be supplied with source, target and command-line [plus options]");
            }

            string source = args[0];
            string target = args[1];
            string executable = args[2];
            string arguments = args.Length > 3 ? GetCommandLine(args) : null;

            FileInfo targetFileInfo = new FileInfo(target);
            FileInfo sourceFileInfo = new FileInfo(source);
            if (!sourceFileInfo.Exists) 
            {
                throw new ArgumentException(string.Format(
                    "Source file {0} not found", source));
            }

            if (!targetFileInfo.Exists || 
                sourceFileInfo.LastWriteTimeUtc > targetFileInfo.LastAccessTimeUtc)
            {
                Process process = new Process();
                process.StartInfo.FileName = executable;
                process.StartInfo.Arguments = arguments;
                process.StartInfo.ErrorDialog = true;

                Console.WriteLine(string.Format(
                     "Source newer than target, launching tool: {0} {1}",
                     executable,
                     arguments));
                process.Start();
            }
        }

        private static string GetCommandLine(string[] args)
        {
            string[] arguments = new string[args.Length - 3];
            Array.Copy(args, 3, arguments, 0, arguments.Length);
            return String.Join(" ", arguments);
        }
    }
}

My pre-build command is now (all on one line):

$(SolutionDir)PreBuildChecker\$(OutDir)PreBuildChecker 
    $(ProjectDir)AddressBook.proto 
    $(ProjectDir)AddressBook.cs 
    c:\bin\protobuf\protogen 
      "-i:$(ProjectDir)AddressBook.proto" 
      "-o:$(ProjectDir)AddressBook.cs" 
      -t:c:\bin\protobuf\csharp.xslt

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

忆离笙 2024-07-18 16:29:53

作为 Shaun 代码的扩展,我很高兴地宣布 protobuf-net 现在通过自定义工具实现了 Visual Studio 集成。 msi 安装程序可从项目页面获取。 更完整的信息在这里: protobuf-net; 现在添加了逆戟鲸

As an extension of Shaun's code, I am pleased to announce that protobuf-net now has Visual Studio integration by way of a Custom Tool. The msi installer is available from the project page. More complete information here: protobuf-net; now with added Orcas.

Visual Studio with protobuf-net as a Custom Tool

永言不败 2024-07-18 16:29:53

调用预构建步骤但使用项目变量(例如 $(ProjectPath))来创建绝对文件名,而不将它们实际包含在您的解决方案中,对我来说似乎是一个合理的选择。

根据我过去使用代码生成器的经验,您可能需要考虑一件事:您可能需要为 protogen 编写一个包装器,它将代码生成到不同的位置,然后检查新生成的代码是否与旧代码相同,并且如果是的话,不会覆盖它。 这样,Visual Studio 将意识到没有任何更改,并且不会强制重建该项目 - 这在过去大大缩短了我的构建时间。

或者,您可以保留上次执行 protogen 时 .proto 文件的 md5 哈希值,并且仅在 .proto 文件发生更改时才执行 protogen - 每次构建时执行的操作更少!

不过,感谢您提出这个问题 - 它清楚地表明我应该找到一种方法,使其成为我自己的端口的简单预构建步骤。

Calling a pre-build step but using project variables (e.g. $(ProjectPath)) to create absolute filenames without having them actually in your solution would seem a reasonable bet to me.

One thing you might want to consider, based on my past experience of code generators: you might want to write a wrapper for protogen which generates code to a different location, then checks whether the newly generated code is the same as the old code, and doesn't overwrite it if so. That way Visual Studio will realise nothing's changed and not force that project to be rebuilt - this has cut build times dramatically for me in the past.

Alternatively, you could keep an md5 hash of the .proto file the last time protogen was executed, and only execute protogen if the .proto file has changed - even less to do on each build!

Thanks for raising this as a question though - it clearly suggests I should work out a way to make this an easy pre-build step for my own port.

你对谁都笑 2024-07-18 16:29:53

将以下预构建事件添加到项目设置中,以便仅在 .proto 文件发生更改时生成 C# 文件。 只需将 YourFile 替换为 .proto 文件的基本名称。

cd $(ProjectDir) && powershell -Command if (!(Test-Path YourFile.proto.cs) -or (Get-Item YourFile.proto).LastWriteTimeUtc -gt (Get-Item YourFile.proto.cs).LastWriteTimeUtc) { PathToProtoGen\protogen -i:YourFile.proto -o:YourFile.proto.cs }

这适用于任何最新版本的 Visual Studio,与 protobuf-net Custom-Build 工具不同,根据问题 338413

Add the following pre-build event to your project settings to generate the C# file only when the .proto file has changed. Just replace YourFile with the name of base name of your .proto file.

cd $(ProjectDir) && powershell -Command if (!(Test-Path YourFile.proto.cs) -or (Get-Item YourFile.proto).LastWriteTimeUtc -gt (Get-Item YourFile.proto.cs).LastWriteTimeUtc) { PathToProtoGen\protogen -i:YourFile.proto -o:YourFile.proto.cs }

This works in any recent version of Visual Studio, unlike the protobuf-net Custom-Build tool, which does not support Visual Studio 2012 or Visual Studio 2013, according to issues 338 and 413.

醉梦枕江山 2024-07-18 16:29:53

将其添加到相关的项目文件中。

优势,增量构建。

缺点,添加文件时需要手动编辑。

<ItemGroup>
    <Proto Include="Person.proto" />
    <Compile Include="Person.cs">
        <DependentUpon>Person.proto</DependentUpon>
    </Compile>
</ItemGroup>
<PropertyGroup>
    <CompileDependsOn>ProtobufGenerate;$(CompileDependsOn)</CompileDependsOn>
</PropertyGroup>
<Target Name="ProtobufGenerate" Inputs="@(Proto)" Outputs="@(Proto->'$(ProjectDir)%(Filename).cs')">
    <ItemGroup>
        <_protoc Include="..\packages\Google.Protobuf.*\tools\protoc.exe" />
    </ItemGroup>
    <Error Condition="!Exists(@(_protoc))" Text="Could not find protoc.exe" />
    <Exec Command=""@(_protoc)" "--csharp_out=$(ProjectDir.TrimEnd('\'))" @(Proto->'%(Identity)',' ')" WorkingDirectory="$(ProjectDir)" />
</Target>

Add this to the relevant project file.

Advantage, incremental build.

Disadvantage, you need to edit manually when adding files.

<ItemGroup>
    <Proto Include="Person.proto" />
    <Compile Include="Person.cs">
        <DependentUpon>Person.proto</DependentUpon>
    </Compile>
</ItemGroup>
<PropertyGroup>
    <CompileDependsOn>ProtobufGenerate;$(CompileDependsOn)</CompileDependsOn>
</PropertyGroup>
<Target Name="ProtobufGenerate" Inputs="@(Proto)" Outputs="@(Proto->'$(ProjectDir)%(Filename).cs')">
    <ItemGroup>
        <_protoc Include="..\packages\Google.Protobuf.*\tools\protoc.exe" />
    </ItemGroup>
    <Error Condition="!Exists(@(_protoc))" Text="Could not find protoc.exe" />
    <Exec Command=""@(_protoc)" "--csharp_out=$(ProjectDir.TrimEnd('\'))" @(Proto->'%(Identity)',' ')" WorkingDirectory="$(ProjectDir)" />
</Target>
﹏半生如梦愿梦如真 2024-07-18 16:29:53

好吧,这给了我一个想法(关于重新发明轮子的东西)...

  • 创建简单的 Makefile.mak,类似
.SUFFIXES : .cs .proto

.proto.cs:
    protogen\protogen.exe -i:$? -o:$@ -t:protogen\csharp.xlst

(显然,不要忘记替换 protogen 和 csharp.xlst 的路径)。 重要 - protogen\protogen.exe 命令从 TAB 字符开始,而不是 8 个空格

  • 如果您不想一直指定需要构建的文件,您可以使用类似于
.SUFFIXES : .cs .proto

all: mycs1.cs myotherfile.cs

.proto.cs:
    protogen\protogen.exe -i:$? -o:$@ -t:protogen\csharp.xlst
  • 预构建步骤中的 内容添加
cd $(ProjectDir) && "$(DevEnvDir)..\..\vc\bin\nmake" /NOLOGO -c -f Makefile.mak mycs1.cs myotherfile.cs

,或者,如果您的路径中有 nmake,则可以使用

cd $(ProjectDir) && nmake /NOLOGO -c -f Makefile.mak mycs1.cs myotherfile.cs

well, that gave me an idea (something about reinventing the wheel)...

  • create simple Makefile.mak, something like
.SUFFIXES : .cs .proto

.proto.cs:
    protogen\protogen.exe -i:$? -o:$@ -t:protogen\csharp.xlst

(obviously, don't forget to replace paths to protogen and csharp.xlst). IMPORTANT - protogen\protogen.exe command starting from TAB character, not 8 spaces

  • If you don't want to specify files needed to be build all the time, you might use something like
.SUFFIXES : .cs .proto

all: mycs1.cs myotherfile.cs

.proto.cs:
    protogen\protogen.exe -i:$? -o:$@ -t:protogen\csharp.xlst
  • in pre-build step to add
cd $(ProjectDir) && "$(DevEnvDir)..\..\vc\bin\nmake" /NOLOGO -c -f Makefile.mak mycs1.cs myotherfile.cs

or, if you have nmake in your path, one can use

cd $(ProjectDir) && nmake /NOLOGO -c -f Makefile.mak mycs1.cs myotherfile.cs

千笙结 2024-07-18 16:29:53

我已将 ProtoGen.exe 周围的快速而肮脏的 Visual Studio 自定义工具包装器附加到针对此问题的 Google 代码页面 (http://code.google.com/p/protobuf-net/issues/detail?id=39)。 这使得将 .proto 文件添加到 C# 项目变得非常容易。

有关详细信息,请参阅附件中的自述文件。

I have attached a quick and dirty Visual Studio Custom Tool wrapper around ProtoGen.exe to the Google Code page for this issue (http://code.google.com/p/protobuf-net/issues/detail?id=39). This makes adding .proto files to C# projects extremely easy.

See the readme in the attachment for more info.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文