C# コードで .h ファイルのdefine ステートメントを再利用する
-
01-07-2019 - |
質問
#define ディレクティブにバージョン番号を持つヘッダー ファイルが含まれる C++ プロジェクト (VS2005) があります。次に、ツイン C# プロジェクトにまったく同じ番号を含める必要があります。最善の方法は何でしょうか?
このファイルをリソースとして含めて、実行時に正規表現で解析してバージョン番号を回復することを考えていますが、もっと良い方法があるかもしれませんが、どう思いますか?
バージョンを .h ファイルの外に移動することはできません。ビルド システムもそれに依存しており、C# プロジェクトは適応する必要があります。
解決
わずか数ステップで目的を達成できます。
- MSBuild タスクを作成する - http://msdn.microsoft.com/en-us/library/t9883dzc.aspx
- プロジェクト ファイルを更新して、ビルド前に作成されたタスクへの呼び出しを含めます。
タスクは、参照したヘッダー .h ファイルの場所を含むパラメーターを受け取ります。次に、バージョンを抽出し、そのバージョンを、以前に作成した C# プレースホルダー ファイルに置きます。または、それでも問題ない場合は、通常はバージョンを保持する AssemblyInfo.cs を使用することを考えることもできます。
追加情報が必要な場合は、お気軽にコメントしてください。
他のヒント
.tt ファイルを使用して .h を処理し、それを .cs ファイルに変換することを検討します。これは非常に簡単で、ソース ファイルは C# ソリューションの一部となり (つまり、.h ファイルが変更されると更新されます)、クリックしてエディターなどで開くことができます。
#define が 1 つしかない場合、それは少しやりすぎかもしれませんが、それらがいっぱいのファイル (たとえば、mfc resource.h ファイルなど) がある場合、この解決策は大きな勝利になります。
例えば:ファイル DefineConverter.tt を作成してプロジェクトに追加し、マークされた行を .h ファイルを参照するように変更すると、プロジェクト内に静的 const エントリでいっぱいの新しいクラスが作成されます。(入力ファイルはプロジェクト ファイルに相対的なものであることに注意してください。絶対パスが必要な場合は 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# プロジェクトからこのクラスを参照できるようになります。忘れずに使用してください 読み取り専用 < タイプ > の代わりに const <型> 定数宣言の場合:)
いつでも、ビルド前イベントを使用して .cs ファイルに対して C プリプロセッサを実行し、ビルド後イベントを使用してビルド前のステップを元に戻すことができます。プリプロセッサは単なるテキスト置換システムであるため、次のようなことが可能です。
// 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";
この .h ファイルを含む単純な C++/C ユーティリティを作成し、C# で使用できるファイルを動的に作成できます。
このユーティリティは、ビルド前の段階として C# プロジェクトの一部として実行できます。
こうすることで、常に元のファイルと同期することができます。
#define FOO "bar" を C# で使用できるものに変換する Python スクリプトを作成し、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 のソリューションに基づいて、特定のディレクトリ内のすべての .h ファイルを検索し、それらを複数のクラスを含む .cs ファイルにロールする .tt ファイルを作成しました。
違い
- ダブルスのサポートを追加しました
- Try-Catch から TryParse に切り替えました
- 複数の .h ファイルを読み取ります
- 「const」の代わりに「readonly」を使用します
- 次で終わる #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]#>";
<#
}
}
} #>
}
<#}#>
}