65.9K
CodeProject 正在变化。 阅读更多。
Home

在编译时将 C/C++ 常量、枚举和结构体传递给 C#

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (24投票s)

2014年7月27日

CPOL

12分钟阅读

viewsIcon

80947

downloadIcon

2057

CppHeader2CS:一个编译时工具,用于将 C/C++ 头文件中的常量、枚举和结构体转换为 C#。

引言

这个命令行工具可以从 C/C++ 头文件中提取 C/C++ 常量、预定义宏、结构体和枚举,然后输出到 C# 文件。这个工具不是一个功能齐全的 C/C++ 到 C# 的转换器——它不转换函数到 C#。这个工具旨在将 C/C++ 文件中的头信息复制到 C# 文件中,以便在 C/C++ 项目和 C# 项目之间共享常量、预定义宏、结构体和枚举。这种转换通常通过 Visual Studio 的预构建命令行事件来完成。目标是为 C/C++ 和 C# 项目维护一套相同的常量和枚举。这些常量和枚举从给定的 C/C++ 文件中提取、转换,然后写入一个通常已添加到 C# 项目的生成的 C# 文件中。转换速度非常快,对总编译时间的影响不大。

由于 C# 不支持与 C/C++ 同等级别的预定义宏,程序会将 #define 值转换为 C# 中的常量,并将其放在默认类中。鉴于 C/C++ 中的 #define 值是无类型的,CppHeader2CS 会尝试找出最适合用于 C# 输出的常量类型。简而言之,它首先尝试解析为 int,然后是 float,接着是 bool,如果所有方法都失败,它就将其保存为 string。例如:#Define cat 5 会被翻译成 public const int cat = 5;,而 #define cat 5.0 会被翻译成 public const float cat = 5;。通过在 #define 后面的注释中附加 C2CS_TYPE 命令,也可以强制指定任何类型。例如, #Define cat 5 // C2CS_TYPE: float 会被翻译成  public const float cat = 5;

为了实现转换,程序使用正则表达式来收集信息,然后将数据转换为其 C# 等效项,并保存到三个目标区域之一。这三个 C# 目标区域是顶部区域、命名空间区域和类区域(参见图中的橙色框)。StringBuilder 用于收集转换后的三个区域。第一个“顶部区域”StringBuilder 写入 C# 文件的最顶部。这可以是类似 using system.IO; 的内容,并且可以通过在 C/C++ 源文件中使用特殊命令 //C2CS_Top_Write using system.IO; 来添加。第二个 StringBuilder 输出到命名空间区域,通常包含结构体和枚举。最后一个位置是默认类区域,主要存放常量。最后,这些内容合并到一个文档中,并输出到文件或控制台。下面您将看到一张带有橙色框的输出图片。每个橙色框代表一个 StringBuilder。

Sample Image - maximum width is 600 pixels

灵感

过去,我曾多次需要在 C/C++ 项目和 C# 项目之间传递常量、枚举或简单的结构体。在网上搜索,我找不到任何能做到我想要的事情的工具。其他解决方案都是完整的 C/C++ 到 C# 的转换,速度太慢,无法在每次编译时运行。我确实找到了一个不错的 T2 模板项目,它做了一些我想要的功能,但总体而言,T2 模板很难阅读,并且不支持我需要的功能。我决定采用正则表达式的方法。RegEx 有时会让人恼火且难以阅读,但对于某些问题它们效果很好。

使用代码

使用说明

  1. CppHeader2CS.exe 文件复制到某个位置
  2. 打开 C# 项目的项目属性,然后导航到“生成事件”部分。
  3. 在“预构建事件命令行”中输入类似以下内容:
    C:\[工具位置]\CppHeader2CS.exe "$(ProjectDir)MyInput.h" "$(ProjectDir)myCppSharedItems.cs"
    根据您的需求,您需要调整上面提到的文件名和位置。Visual Studio 使用宏按钮来帮助完成此操作。

命令行用法

CppHeader2CS.exe input_file [output_file]
  input_file - 这是 C/C++ 源头文件。它应该是一个用于 C/C++ 与 C# 共享的简单文件。
  output_file - (可选) 这是 C# 输出文件的名称。如果未提供名称,它将使用输入文件名并加上 .cs 扩展名。

命令列表 (位于 C/C++ 源文件中的注释中)

此程序支持许多可以在源文件中输入的命令。命令通过像注释一样添加。例如:// C2CS_TYPE MyType 这些命令可用于写入或调整输出。三个 C2CS_*_Write 命令可用于使用 C/C++ 源文件中的注释在 C# 文件中写入任何内容。其他选项可用于设置自定义命名空间或类名,以及强制指定类型或完全跳过源文件中的一行。

// C2CS_TOP_Write 要写入的文本 - C2CS_TOP_Write 是一个直接传递,将文本写入命名空间之上。当需要将内容打印在输出文件的顶部时,这非常有用。例如://C2CS_TOP_Write using System.IO;

// C2CS_NS_Write 要写入的文本 - C2CS_NS_Write 是一个直接传递,将文本写入默认类之上,但在命名空间区域。此区域通常包含枚举和结构体。

// C2CS_Class_Write 要写入的文本 - C2CS_Class_Write 是一个直接传递,将文本写入类区域。CppHeader2CS 仅写入一个类。

// C2CS_Set_Namespace MyNsName - C2CS_Set_Namespace 设置命名空间名称。这是可选的,如果未指定,则默认为 C2CS。

// C2CS_Set_ClassName MyClass - C2CS_Set_ClassName 设置要使用的类名。这是可选的,如果未指定,则默认为 Constants。

// C2CS_TYPE MyType - C2CS_TYPE 用于 #define 后面的注释中,以指定要使用的类型。如果自动检测不符合预期或程序员想强制指定类型,则需要此项。例如:#Default mySum (2+1) //C2CS_TYPE int;

// C2CS_SKIP - 在任何注释中添加 C2CS_SKIP 都会强制 CppHeader2CS 忽略当前行。

示例

示例 1 - #define -> int

#DEFINE Cars 10

...将添加到类区域,如...

public const int Cars = 10;
关注点
  • 自动识别为 int。

示例 2 - #define -> float

#DEFINE MyDouble 3.2

...将添加到类区域,如...

public const double MyDouble = 3.2f;
关注点
  • #DEFINE 始终会转换为小写。
  • #DEFINE MyFloat 3.2f 带“f”后缀将导致输出为 float。
  • #DEFINE MyFloat 3.2d 带“d”后缀将导致输出为 double。
  • 当没有后缀时,输出将是 double。
  • 科学计数法得到支持。

示例 3 - #define -> 支持非常简单的表达式

假设 my_float 是一个已存在的 float...

#DEFINE expr1 my_float + 2 

...将添加到类区域,如...

public const float expr1 = my_float + 2;
关注点
  • 转换器识别出这必须是一个 float。但它的功能非常有限,仅支持 +、-、/、* 以及逻辑 true、false、&&、||。
  • 如果未按预期自动检测,则始终可以使用命令 // C2CS_TYPE MyType

示例 4 - #define -> string

#DEFINE MyString New World Test!

...将添加到类区域,如...

public const string MyString = "New World Test!";
关注点
  • 如果 int.TryParsefloat.TryParsebool.TryParse 都失败,则使用 string 类型。

示例 5 - 简单的 #define,没有值

#DEFINE FullDebug

稍作修改并移至顶部...

#define FullDebug
关注点
  • 未显示,但 #define 已移至 C# 文件顶部附近(C# 要求)。
  • 大小写已更改为小写。

示例 6 - Struct

// C2CS_NS_Write  [StructLayout(LayoutKind.Sequential)]
struct SomeStruct1{unsigned char a; unsigned char b;}; 

...添加到命名空间区域,如...

[StructLayout(LayoutKind.Sequential)]
public struct SomeStruct1
{
    public byte a;
    public byte b;
}
关注点
  • 此处我们添加了前缀 // C2CS_NS_Write [StructLayout(LayoutKind.Sequential)]。这将使结构体布局更像 C/C++ 结构体。仅在需要时添加。
  • 单行结构体得到支持,但会转换为标准格式。
  • 此处仅支持基本项,如 int、short、long、long long、char、char*、unsigned、float 和 double。

示例 7 - Struct #2

struct SomeStruct1{
   unsigned long long test123; //some comments
   public long test124;
} someStruct1Instance; // my instance notes

...添加到命名空间区域,如...

public struct  SomeStruct1
{
  public ulong  test123; //some comments
  public long  test124;
}

...并且以下内容也添加到类部分...

public SomeStruct1  someStruct1Instance;  // my instance notes
关注点
  • “//”样式的注释会被保留,如果它们在同一行。
  • 位字段目前不支持。

 示例 8 - Enum

public enum MyEnum {
        test1 = 33,
        test2 = 5,
        test3 = test2 };

...添加到命名空间区域,如...

[Flags]
enum MyEnum
{
    test1 = 33,
    test2 = 5,
    test3 = test2,
};
关注点
  • 仅当有赋值时,才会添加 [Flags] 前缀。这样做是为了保持枚举的含义不变。这将来可能会更改。

示例 9 - 常量/静态

protected static char myChar = -100;  // my notes

...将添加到类区域,如...

public const SByte myChar = -100;  // my notes
关注点
  • 输出始终为 public const...
  • protected 和 static 被忽略(private 和 const 也被忽略)。
  • 值写入类部分。

性能

性能是此项目的重要组成部分。由于此工具通常会在每次编译时调用,因此保持其速度很重要。我使用了一些 RegEx 技巧,StringBuilders,一个快速的空格移除器,以及一个 Dictionary 来提供帮助。转换相对较快。对于 224 行的演示文件,整个读取、转换和写入过程大约需要 12 到 30 毫秒,具体取决于使用的系统。较短的文件,约 40 行,大约需要 5 毫秒。

我在网上的一些“RegEx 技巧”中学到的一件事是,要确保没有发生灾难性回溯。简而言之,最好在 RegEx 表达式中更具体,并避免使用通配符“.*”。我没有遇到重大问题,但确实发现更具体的表达式可以将演示文件的性能提高约 50%。我还实现了一些其他技巧,这些技巧很容易在网上找到。然而,在构建过程中,我有几次机会可以稍微提高性能,但我为了更易读的代码而放弃了它们。

该文件也只有 37kb,这应该有助于提高其性能。39 个文件可以装在一张 1986 年的 1.44MB 软盘上。=)

限制

  • 仅适用于预处理指令、常量、结构体和枚举。
  • 不转换任何函数(它不是一个完整的 C/C++ 到 C# 的转换器)。
  • 任何 /* ... */ 风格的注释都不会被带过来。在程序的第一个传递中,它们被简单地删除。它们可以在源文件的任何位置使用,但请注意,它们永远不会出现在输出文件中。
  • typedef 不受支持。
  • bitfields 部分支持。
  • char 数组不受支持。

未来愿望清单

  • 添加对简单函数转换的支持,例如:int add(int a, int b){ return (a+b);}
  • 添加 typedef、char 数组和额外的位字段支持。

如果您想做出贡献,请随时在评论中发布更改或在 GitHub 上提交拉取请求。要发布到 GitHub:(1) 点击 Fork 按钮将存储库 fork 到您的帐户。(2) 进行更改并将您的更改推送到您自己的分支。(3) 创建一个拉取请求,将这些更改包含在原始存储库中。

关注点

能够如此轻松快速地识别和解析 C/C++ 文件是对正则表达式的绝佳证明。虽然一个非 RegEx 的 C/C++/C# 解析器可能具有更好的性能和控制力,但 RegEx 解决方案更容易、更快地投入使用。当程序员需要快速可靠地完成任何模式匹配时,RegEx 是一个强大的工具。没有 RegEx,代码将花费更长的时间来构建,而且我可能在完成项目之前就失去了兴趣!=)

其他值得尝试的工具

P/Invoke Interop Assistant - 这是引起我注意的一个工具,它做了与 cppHeader2CS 类似的事情。P/Invoke Interop Assistant 是一个工具,可以从 c++ 文件生成 C# 或 VB P/Invoke 代码,但它也生成结构体、枚举、#defines 和其他项。根据您的需求,此工具可能更适合。以下是两者的优点:

P/Invoke Interop Assistant 相对于 cppHeader2CS 的优点:

  • windows.h 类型
  • p/invoke 头文件生成(该工具的主要目的)
  • 它支持 char 数组、typedef 和 include 文件。
  • 输出到 VB。
  • 总的来说,输出更适合 p/invoke 用途。
  • 可能还有我遗漏的其他方面(它是一个更强大/更复杂的工具)。

总结:如果需要上述任何一项,请使用 Interop Assistant。此工具可以在构建时运行,但速度较慢,并且会始终更新目标文件。更新目标文件可能会很烦人,尤其是在文件打开的情况下,因为 Visual Studio 会提示您重新加载它。

cppHeader2CS 的优点:

  • 支持更多预定义宏,IA 仅支持 #define。
  • 支持全局常量。
  • 支持自定义命名空间和类名。
  • 允许通过注释中的命令进行引导式输出。
  • #define -> const 的类型自动检测效果更好。
  • 速度更快,大约 0.1 秒 vs 0.7-3.0 秒(在我的系统上)。
  • 如果没有更改,则不会覆盖输出。
  • 小巧的 36kb 文件,无需安装。

总结:对于经常修改且在构建过程中更新的更简单的头文件,请使用 cppHeader2CS。此工具不支持 char 数组、typedef、自定义类型和 include 文件。

如果您想尝试使用此工具替代 cppHeader2CS 的说明:

  1. 访问 http://clrinterop.codeplex.com/releases/view/14120[^] 并下载程序。
  2. 安装程序。
  3. 在安装文件夹中“类似 C:\Program Files (x86)\InteropSignatureToolkit”的位置,有一个名为 sigimp.exe 的文件,这个文件是需要的。在“预构建事件命令行”区域使用类似 `"C:\Program Files (x86)\InteropSignatureToolkit\sigimp.exe" /genPreProc:yes /lang:cs /out:"$(ProjectDir)MyOutput.cs" "$(ProjectDir)MyInput.h"` 的内容。

使用 T4 模板 - 这是执行构建时 C/C++ 到 C# 转换的另一种方法。它只是一个简单的 T4 文件。下面链接中的示例仅处理 #define 到常量转换,并且可以自动检测简单的整数。我包含它是因为这是我在 cppHeader2CS 之前使用的工具。链接:http://stackoverflow.com/a/5638890

历史

  • 2014/5/30 - 第一个工作程序,仅支持 #define。
  • 2014/6/20 - 添加了更多预处理支持、结构体和常量支持。
  • 2014/6/27 - 添加了对源文件命令的支持。
  • 2014/7/12 - 添加了 #define 自动检测类型支持。
  • 2014/7/21 - 添加了枚举支持。
  • 2014/7/26 - 从 C2CS 重命名为 CppHeader2CS(C2CS 可能被误解为完整的 C 到 C# 转换器)。
  • 2014/7/27 - 在 CodeProject 上首次发布。
  • 10/15/2014
    • 同文件自定义类型不受支持。 “struct A { ... }; struct B { A a;...}” 会被转换。
    • 没有后缀的小数现在默认为 double。
    • 添加了“d”后缀支持。
    • 添加了额外的 double 支持。
    • 添加了 void* 支持。
    • 位字段结构体被接受,但位部分被移除。
    • 其他修复和代码清理。
  • 5/6/2016
    • 添加了接受 Linux/Mac OS X 风格的行尾 (\n) 行结束符的支持。
    • 已添加到 GitHub
  • 5/22/2016 
    • 添加了对“if defined(...)”风格预定义宏的支持。
    • 性能:添加了更快的移除空格的方法。
    • 其他:一般性代码/注释清理(次要)。
© . All rights reserved.