C
C++
XML
C#
JSON

C#リフレクションでC#クラス構造を解析してC構造体を生成する


長い前置き

(サクッと見たい人はささっとサンプルへGO!)

 とあるプロジェクトで(これはまぁ追々書きたいと思う)グローバルフックを行うアプリを作成しています。exe、dll共にwin32API直叩きのオールドスクールなアプリでメンテを10年ほど細々と気分で続けているけどさすがに古めかしてくて更新することにしました。

 リプレースするにあたっていろいろ調査を行ったうえで、exeはC#+WPF、フックdllはC++というのが現実的だろうということに。

 さて、この既存のアプリはdllとexe間でプロセス間通信を行ってて、ファイルマッピングを使って設定データのやり取りを行い、dllからのイベントはウィンドウメッセージを用いていましたが、リプレースに伴いこのあたりも再検討したところやっぱり同様の手法がお手軽だろうという結論に。

 設定ファイルはXML or JSONのフォーマットと決めてて、またフック側は可能ならメモリを動的生成したくないのでSTLも使用禁止としたかったので、dll側(C++)でJSON or XMLパーサのライブラリをリンクするのは諦めました。

 となるとC#でパースしたデータをC++のフックで読み込み可能なバイナリデータ、すなわち構造体型に押し込んでファイルマッピングに詰め込めばいいんじゃね?という結論に至って、じゃぁその構造体作るかとなりました。

 しかし、C#で設定ファイルを保持するデータクラスを作成した上にC++用に構造体を作成せねばならなくて、設定をイジルときに二重管理メンドクセーなーおいーと思ったので、以前お仕事でリフレクションとCodeDOMを使ってコンパイラをメンテしたなぁもうよく覚えてないけどそれでなんとか出来んじゃね?と思って表題が頭に浮かんだわけです。でもって、調べてもみなさんサクッとやってらっしゃるのか特殊すぎるのか知らないけどサンプルが無かったのでんじゃぁサンプル置きますかと一念発起してqiitaのアカウント取りました。

 はい、長いですね。次行きましょう。


サンプル

C#のコンソールアプリで確認したレベルのものです。解説は下に。

    /// <summary>

/// テストクラス:Personの中にいる子クラス
/// </summary>
public class Family
{
public string name;
}

/// <summary>
/// テストクラス:変換対象クラス
/// </summary>
public class Person
{
public int age;
public string name;
public int BrotherNumber { get; set; }
public Family family;
public string[] favItems;
public BroSis bros;
}

/// <summary>
/// テスト列挙子
/// </summary>
public enum BroSis
{
None,
OlderSister,
YoungerSister,
OlderBrother,
YounderBrother
}

class Program
{
/// <summary>
/// Main
/// </summary>
static void Main(string[] args)
{
// Personクラスのpublicメンバーを構造体形式でコンソール出力する
var human = new Person();
ConvertCSharpClasses2CStructure(human.GetType());
}

/// <summary>
/// C#のプリミティブ型をCの基本型の名称に変換する辞書
/// </summary>
private static readonly Dictionary<string, string> convertDeclaration = new Dictionary<string, string>()
{
{ "Int32", "__int32" },
{ "UInt32", "unsigned __int32" },
{ "Boolean", "bool" },
{ "String", "wchar_t" },
};

/// <summary>
/// メンバを保持するクラス
/// </summary>
private class Members
{
public string Key { get; set; }
public string Value { get; set; }
}

/// <summary>
/// C#クラスのpublicメンバーを構造体形式に変換する
/// サンプルなのでコンソール出力してる
/// </summary>
/// <param name="classes">The classes.</param>
private static void ConvertCSharpClasses2CStructure(Type classes)
{
var memberNames = new List<Members>();
var enumNames = new Dictionary<string, string[]>();
var nestMembers = new List<Type>();

// publicのプロパティとフィールドを検索
var memberInfos = classes.GetMembers(BindingFlags.Public | BindingFlags.Instance);
foreach (var memberInfo in memberInfos)
{
switch (memberInfo.MemberType)
{
case MemberTypes.Field:
{
var prop = classes.GetField(memberInfo.Name);

// 配列なら要素数の位置を移動(要検討)
if (prop.FieldType.IsArray)
{
memberNames.Add(new Members() { Key = prop.FieldType.GetElementType().Name, Value = prop.Name + "[128]" });
}
else
{
memberNames.Add(new Members() { Key = prop.FieldType.Name, Value = prop.Name });
}

// プリミティブとSystemネームスペース以外
if (!"System".Equals(prop.FieldType.Namespace) && prop.FieldType.IsPrimitive == false)
{
// enumは一階層のため列挙
if (prop.FieldType.IsEnum)
{
enumNames[prop.FieldType.Name] = prop.FieldType.GetEnumNames();
}
// 独自クラスがある場合はさらに掘り進める
else if (prop.FieldType.IsClass)
{
nestMembers.Add(prop.FieldType);
}
}
}
break;
case MemberTypes.Property:
{
var prop = classes.GetProperty(memberInfo.Name);
memberNames.Add(new Members() { Key = prop.PropertyType.Name, Value = prop.Name});
// enumは一階層のため列挙
if (prop.PropertyType.IsEnum)
{
enumNames[prop.PropertyType.Name] = prop.PropertyType.GetEnumNames();
}
}
break;
default:
// ignore;
break;
}
}

// メンバーに独自クラスがある場合は掘り進める ※1
foreach (var item in nestMembers)
{
ConvertCSharpClasses2CStructure(item);
}

// enum
foreach (var item in enumNames)
{
Console.WriteLine("enum {0}", item.Key);
Console.WriteLine("{");
foreach (var name in item.Value)
{
Console.WriteLine(" {0},", name);
}
Console.WriteLine("};\r\n");
}

// コンソール出力をファイル出力(.h)で出すとしぁゎせ。ファイル出力する場合はstreamを引数にするとか工夫しよう。
// ※1より後に出力があるのは、掘り進めた深い階層の構造体を先に定義したいがため
Console.WriteLine("typedef struct\r\n{");
foreach (var item in memberNames)
{
string replaceValue;
if (!convertDeclaration.TryGetValue(item.Key, out replaceValue))
{
replaceValue = item.Key;
}

if (string.Compare(item.Key, "string", true) == 0)
{
Console.WriteLine(" {0} {1}[256];", replaceValue, item.Value);
}
else
{
Console.WriteLine(" {0} {1};", replaceValue, item.Value);
}
}

Console.Write("} ");
Console.WriteLine("{0};\r\n", classes.Name);
}
}


  • C#のクラスのpublicのフィールドとプロパティを読み込んでそれを構造体形式の文字列で出力しています。

  • 構造体に構造体がネストされているような階層構造にも対応しつつenum定義も出力対象としています。

  • サンプルはListなどのGenericな物には対応していないので、工夫して対応してください。

  • enumも数字が個別に指定されているようなものは対応していないので、同じく工夫の必要あり。

  • 文字列系と配列は固定長に変換していますので、おなj(ry


サンプル動作結果

typedef struct

{
wchar_t name[256];
} Family;

enum BroSis
{
None,
OlderSister,
YoungerSister,
OlderBrother,
YounderBrother,
};

typedef struct
{
__int32 BrotherNumber;
__int32 age;
wchar_t name[256];
Family family;
wchar_t favItems[128][256];
BroSis bros;
} Person;

続行するには何かキーを押してください . . .

私のやってるプロジェクト的にはこんだけ出れば満足なので残りは御自分でお試しください:boy_tone1: