Повторное использование инструкции определения из файла .h в коде C#
-
01-07-2019 - |
Вопрос
У меня есть проект C++ (VS2005), который включает заголовочный файл с номером версии в директиве #define.Теперь мне нужно включить точно такое же число в проект-близнец C#.Как лучше всего это сделать?
Я подумываю включить этот файл в качестве ресурса, а затем проанализировать его во время выполнения с помощью регулярного выражения, чтобы восстановить номер версии, но, возможно, есть лучший способ, как вы думаете?
Я не могу переместить версию за пределы файла .h, от этого также зависит система сборки, и проект C # должен быть адаптирован.
Решение
Вы можете добиться желаемого всего за несколько шагов:
- Создайте задачу MSBuild – http://msdn.microsoft.com/en-us/library/t9883dzc.aspx
- Обновите файл проекта, включив в него вызов задачи, созданной до сборки.
Задача получает параметр с указанием местоположения упомянутого вами заголовка .h-файла.Затем он извлекает версию и помещает ее в ранее созданный вами файл-заполнитель C#.Или вы можете подумать об использовании AssemblyInfo.cs, который обычно содержит версии, если вас это устраивает.
Если вам нужна дополнительная информация, пожалуйста, оставляйте комментарии.
Другие советы
Я бы рассмотрел возможность использования файла .tt для обработки .h и преобразования его в файл .cs.Это очень просто, и исходные файлы станут частью вашего решения C# (то есть они будут обновляться по мере изменения файла .h), их можно будет щелкнуть, чтобы открыть в редакторе и т. д.
Если у вас есть только 1 #define, это может быть немного излишним, но если у вас есть файл, полный их (например, файл mfc resources.h), то это решение становится большой победой.
например:создайте файл DefineConverter.tt и добавьте его в свой проект, измените отмеченную строку, чтобы она ссылалась на ваш файл .h, и вы получите в своем проекте новый класс, полный статических константных записей.(обратите внимание, что входной файл относится к файлу вашего проекта, установите hostspecific=false, если вам нужны абсолютные пути).
<#@ template language="C#v3.5" hostspecific="True" debug="True" #>
<#@ output extension="cs" #>
<#@ assembly name="System.Core.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#
string input_file = this.Host.ResolvePath("resource.h"); <---- change this
StreamReader defines = new StreamReader(input_file);
#>
//------------------------------------------------------------------------------
// This code was generated by template for T4
// Generated at <#=DateTime.Now#>
//------------------------------------------------------------------------------
namespace Constants
{
public class <#=System.IO.Path.GetFileNameWithoutExtension(input_file)#>
{
<#
// constants definitions
while (defines.Peek() >= 0)
{
string def = defines.ReadLine();
string[] parts;
if (def.Length > 3 && def.StartsWith("#define"))
{
parts = def.Split(null as char[], StringSplitOptions.RemoveEmptyEntries);
try {
Int32 numval = Convert.ToInt32(parts[2]);
#>
public static const int <#=parts[1]#> = <#=parts[2]#>;
<#
}
catch (FormatException e) {
#>
public static const string <#=parts[1]#> = "<#=parts[2]#>";
<#
}
}
} #>
}
}
MSDN сообщает нам:
Директива #Define не может быть использована для объявления постоянных значений, как это обычно делается в C и C ++.Константы в C# лучше всего определяются как статические члены класса или структуры.Если у вас есть несколько таких постоянных, рассмотрите возможность создания отдельного класса «константы», чтобы удержать их.
Вы можете создать библиотеку, используя управляемый C++, которая включает в себя класс-оболочку ваших констант.Затем вы можете ссылаться на этот класс из проекта C#.Только не забудьте использовать только для чтения <тип> вместо константа <тип> для объявления ваших констант :)
Вы всегда можете использовать событие перед сборкой для запуска препроцессора C для файла .cs и событие после сборки, чтобы отменить этап предварительной сборки.Препроцессор — это всего лишь система замены текста, поэтому это возможно:
// version header file
#define Version "1.01"
// C# code
#include "version.h"
// somewhere in a class
string version = Version;
и препроцессор сгенерирует:
// C# code
// somewhere in a class
string version = "1.01";
Вы можете написать простую утилиту C++/C, включающую этот файл .h, и динамически создавать файл, который можно использовать в C#.
Эту утилиту можно запустить как часть проекта C# на этапе предварительной сборки.
Таким образом, вы всегда синхронизируетесь с исходным файлом.
Я написал скрипт Python, который преобразует #define FOO «bar» во что-то, что можно использовать в C#, и использую его на этапе предварительной сборки в моем проекте C#.Оно работает.
# translate the #defines in messages.h file into consts in MessagesDotH.cs
import re
import os
import stat
def convert_h_to_cs(fin, fout):
for line in fin:
m = re.match(r"^#define (.*) \"(.*)\"", line)
if m != None:
if m.group() != None:
fout.write( "public const string " \
+ m.group(1) \
+ " = \"" \
+ m.group(2) \
+ "\";\n" )
if re.match(r"^//", line) != None:
fout.write(line)
fin = open ('..\common_cpp\messages.h')
fout = open ('..\user_setup\MessagesDotH.cs.tmp','w')
fout.write( 'using System;\n' )
fout.write( 'namespace xrisk { class MessagesDotH {\n' )
convert_h_to_cs(fin, fout)
fout.write( '}}' )
fout.close()
s1 = open('..\user_setup\MessagesDotH.cs.tmp').read()
s2 = open('..\user_setup\MessagesDotH.cs').read()
if s1 != s2:
os.chmod('..\user_setup\MessagesDotH.cs', stat.S_IWRITE)
print 'deleting old MessagesDotH.cs'
os.remove('..\user_setup\MessagesDotH.cs')
print 'remaming tmp to MessagesDotH.cs'
os.rename('..\user_setup\MessagesDotH.cs.tmp','..\user_setup\MessagesDotH.cs')
else:
print 'no differences. using same MessagesDotH.cs'
Опираясь на решение gbjbaanb, я создал файл .tt, который находит все файлы .h в определенном каталоге и объединяет их в файл .cs с несколькими классами.
Различия
- Я добавил поддержку парных игр
- Переключен с try-catch на TryParse
- Читает несколько файлов .h
- Использует «только для чтения» вместо «const»
- Обрезает #define строки, которые заканчиваются на ;
- Пространство имен задается на основе местоположения .tt в проекте.
<#@ template language="C#" hostspecific="True" debug="True" #>
<#@ output extension="cs" #>
<#@ assembly name="System.Core.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#
string hPath = Host.ResolveAssemblyReference("$(ProjectDir)") + "ProgramData\\DeltaTau\\";
string[] hFiles = System.IO.Directory.GetFiles(hPath, "*.h", System.IO.SearchOption.AllDirectories);
var namespaceName = System.Runtime.Remoting.Messaging.CallContext.LogicalGetData("NamespaceHint");
#>
//------------------------------------------------------------------------------
// This code was generated by template for T4
// Generated at <#=DateTime.Now#>
//------------------------------------------------------------------------------
namespace <#=namespaceName#>
{
<#foreach (string input_file in hFiles)
{
StreamReader defines = new StreamReader(input_file);
#>
public class <#=System.IO.Path.GetFileNameWithoutExtension(input_file)#>
{
<# // constants definitions
while (defines.Peek() >= 0)
{
string def = defines.ReadLine();
string[] parts;
if (def.Length > 3 && def.StartsWith("#define"))
{
def = def.TrimEnd(';');
parts = def.Split(null as char[], StringSplitOptions.RemoveEmptyEntries);
Int32 intVal;
double dblVal;
if (Int32.TryParse(parts[2], out intVal))
{
#>
public static readonly int <#=parts[1]#> = <#=parts[2]#>;
<#
}
else if (Double.TryParse(parts[2], out dblVal))
{
#>
public static readonly double <#=parts[1]#> = <#=parts[2]#>;
<#
}
else
{
#>
public static readonly string <#=parts[1]#> = "<#=parts[2]#>";
<#
}
}
} #>
}
<#}#>
}