在制作GacUI讀pdb生成代碼的過(guò)程中,感受到了C++語(yǔ)言設(shè)計(jì)和dll的需求之間的鴻溝。對(duì)于一個(gè)充分利用了C++各種功能的類庫(kù)來(lái)說(shuō),制作成dll具有非常大的困難,特別是在函數(shù)返回POD(Plain Old Data)的引用,和輸入輸出帶有泛型的類上面。所以現(xiàn)在還是決定以源代碼的方式來(lái)發(fā)布GacUI。但是pdb生成代碼并沒有白做,因?yàn)榉瓷溥€是存在的。但是因?yàn)镚acUI一共有48000行代碼,80多個(gè)源代碼文件,直接發(fā)布使用起來(lái)總是不方便。所以我寫了個(gè)小工具,根據(jù)xml的配置來(lái)將源代碼合并成少數(shù)幾個(gè)比較大的代碼文件。這樣使用的時(shí)候,只需要直接把幾個(gè)cpp拖進(jìn)工程里面,就可以使用了。而且根據(jù)
之前發(fā)布的一個(gè)投票,似乎大家也最喜歡這種方法。因此這次的決定,僅僅刪掉了作為backup plan的dll方法。
這里我給出小工具的代碼和配置文件。這個(gè)配置文件是基于GacUI做出來(lái)的,不過(guò)大家可以修改它,以便用于自己的工程上面:
<?xml version="1.0" encoding="utf-8" ?>
<codegen>
<projects>
<project path="..\..\..\GacUISrc\GacUISrc.vcxproj" />
</projects>
<categories>
<category name="vlpp" pattern="\Library\"/>
<category name="gacui" pattern="\GacUILibrary\">
<except filename="GacUI_WinMain.cpp" />
<except filename="GuiTypeDescriptorImpHelper.cpp" />
<except filename="GuiTypeDescriptorImpProvider_codegen.cpp" />
</category>
</categories>
<output path="..\..\..\GacUILibraryExternal\">
<codepair category="vlpp" filename="Vlpp" />
<codepair category="gacui" filename="GacUI" />
<header source="..\..\..\GacUILibrary\GacUI.h" filename="GacUIIncludes" />
</output>
</codegen>
在這里面,project包含了用于開發(fā)這個(gè)工程的所有VC++2010的工程文件的地址,然后使用category對(duì)他們進(jìn)行分類(pattern是文件全名的某個(gè)部分),最后對(duì)每一個(gè)部分生成一對(duì)cpp和h。在最后生成代碼對(duì)的時(shí)候,如果源代碼從一開始就存在依賴關(guān)系的話,那么在代碼對(duì)的h文件里面,會(huì)包含依賴的代碼對(duì)的h。在這里,vlpp是獨(dú)立的,而gacui依賴了vlpp,所以gacui.h將會(huì)#include"vlpp.h",而cpp只include自己的h文件。output里面除了codepair以外還有header,header是不參與codepair計(jì)算的,純粹為了生成“可以省事直接include”的頭文件。在這個(gè)例子里面,GacUIIncludes.h將會(huì)包含完整的GacUI.h和一部分的Vlpp.h,而且是可以通過(guò)編譯的。
驅(qū)動(dòng)器 E 中的卷沒有標(biāo)簽。
卷的序列號(hào)是 9614-79B9
E:\Codeplex\vlpp\Workspace\Tools\Release\SideProjects\GacUISrc\GacUILibraryExternal 的目錄
2012/02/29 21:42 <DIR> .
2012/02/29 21:42 <DIR> ..
2012/02/29 21:42 0 dir.txt
2012/02/29 21:18 677,987 GacUI.cpp
2012/02/29 21:18 304,231 GacUI.h
2012/02/29 21:18 481,551 GacUIIncludes.h
2012/02/29 21:18 69,348 Vlpp.cpp
2012/02/29 21:18 310,126 Vlpp.h
6 個(gè)文件 1,843,243 字節(jié)
2 個(gè)目錄 166,357,680,128 可用字節(jié)
在生成的時(shí)候,生成器將會(huì)閱讀代碼本身,然后獲取#include "path",然后對(duì)他們的關(guān)系進(jìn)行處理。這個(gè)工具的代碼如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Linq;
using System.Text.RegularExpressions;
namespace Codegen
{
class Program
{
static string[] GetCppFiles(string projectFile)
{
string np = @"http://schemas.microsoft.com/developer/msbuild/2003";
XDocument document = XDocument.Load(projectFile);
return document
.Root
.Elements(XName.Get("ItemGroup", np))
.SelectMany(e => e.Elements(XName.Get("ClCompile", np)))
.Select(e => Path.GetFullPath(Path.GetDirectoryName(projectFile) + "\\" + e.Attribute("Include").Value))
.ToArray();
}
static Dictionary<string, string[]> CategorizeCodeFiles(XDocument config, string[] files)
{
Dictionary<string, string[]> categorizedFiles = new Dictionary<string, string[]>();
foreach (var e in config.Root.Element("categories").Elements("category"))
{
string name = e.Attribute("name").Value;
string pattern = e.Attribute("pattern").Value.ToUpper();
string[] exceptions = e.Elements("except").Select(x => x.Attribute("filename").Value.ToUpper()).ToArray();
categorizedFiles.Add(
name,
files
.Where(f => f.ToUpper().Contains(pattern))
.Where(f => !exceptions.Contains(Path.GetFileName(f).ToUpper()))
.ToArray()
);
}
foreach (var a in categorizedFiles.Keys)
{
foreach (var b in categorizedFiles.Keys)
{
if (a != b)
{
if (categorizedFiles[a].Intersect(categorizedFiles[b]).Count() != 0)
{
throw new ArgumentException();
}
}
}
}
return categorizedFiles;
}
static Dictionary<string, string[]> ScannedFiles = new Dictionary<string, string[]>();
static Regex IncludeRegex = new Regex(@"^\s*\#include\s*""(?<path>[^""]+)""\s*$");
static Regex IncludeSystemRegex = new Regex(@"^\s*\#include\s*\<(?<path>[^""]+)\>\s*$");
static string[] GetIncludedFiles(string codeFile)
{
codeFile = Path.GetFullPath(codeFile).ToUpper();
string[] result = null;
if (!ScannedFiles.TryGetValue(codeFile, out result))
{
List<string> directIncludeFiles = new List<string>();
foreach (var line in File.ReadAllLines(codeFile))
{
Match match = IncludeRegex.Match(line);
if (match.Success)
{
string path = match.Groups["path"].Value;
path = Path.GetFullPath(Path.GetDirectoryName(codeFile) + @"\" + path).ToUpper();
if (!directIncludeFiles.Contains(path))
{
directIncludeFiles.Add(path);
}
}
}
for (int i = directIncludeFiles.Count - 1; i >= 0; i--)
{
directIncludeFiles.InsertRange(i, GetIncludedFiles(directIncludeFiles[i]));
}
result = directIncludeFiles.Distinct().ToArray();
ScannedFiles.Add(codeFile, result);
}
return result;
}
static string[] SortDependecies(Dictionary<string, string[]> dependeicies)
{
var dep = dependeicies.ToDictionary(p => p.Key, p => new HashSet<string>(p.Value));
List<string> sorted = new List<string>();
while (dep.Count > 0)
{
bool found = false;
foreach (var p in dep)
{
if (p.Value.Count == 0)
{
found = true;
sorted.Add(p.Key);
foreach (var q in dep.Values)
{
q.Remove(p.Key);
}
dep.Remove(p.Key);
break;
}
}
if (!found)
{
throw new ArgumentException();
}
}
return sorted.ToArray();
}
static void Combine(string[] files, string outputFilename, HashSet<string> systemIncludes, params string[] externalIncludes)
{
try
{
using (StreamWriter writer = new StreamWriter(new FileStream(outputFilename, FileMode.Create), Encoding.Default))
{
writer.WriteLine("/***********************************************************************");
writer.WriteLine("THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY");
writer.WriteLine("DEVELOPER: 陳梓瀚(vczh)");
writer.WriteLine("***********************************************************************/");
foreach (var inc in externalIncludes)
{
writer.WriteLine("#include \"{0}\"", inc);
}
foreach (var file in files)
{
writer.WriteLine("");
writer.WriteLine("/***********************************************************************");
writer.WriteLine(file);
writer.WriteLine("***********************************************************************/");
foreach (var line in File.ReadAllLines(file, Encoding.Default))
{
Match match = null;
match = IncludeSystemRegex.Match(line);
if (match.Success)
{
if (systemIncludes.Add(match.Groups["path"].Value.ToUpper()))
{
writer.WriteLine(line);
}
}
else
{
match = IncludeRegex.Match(line);
if (!match.Success)
{
writer.WriteLine(line);
}
}
}
}
}
Console.WriteLine("Succeeded to write: {0}", outputFilename);
}
catch (Exception)
{
Console.WriteLine("Failed to write: {0}", outputFilename);
}
}
static void Combine(string inputFilename, string outputFilename, params string[] externalIncludes)
{
HashSet<string> systemIncludes = new HashSet<string>();
string[] files = GetIncludedFiles(inputFilename).Concat(new string[] { inputFilename }).Distinct().ToArray();
Combine(files, outputFilename, systemIncludes, externalIncludes);
}
static void Main(string[] args)
{
// load configuration
XDocument config = XDocument.Load("CodegenConfig.xml");
string folder = Path.GetDirectoryName(typeof(Program).Assembly.Location) + "\\";
// collect project files
string[] projectFiles = config.Root
.Element("projects")
.Elements("project")
.Select(e => Path.GetFullPath(folder + e.Attribute("path").Value))
.ToArray();
// collect code files
string[] unprocessedCppFiles = projectFiles.SelectMany(GetCppFiles).Distinct().ToArray();
string[] unprocessedHeaderFiles = unprocessedCppFiles.SelectMany(GetIncludedFiles).Distinct().ToArray();
// categorize code files
var categorizedCppFiles = CategorizeCodeFiles(config, unprocessedCppFiles);
var categorizedHeaderFiles = CategorizeCodeFiles(config, unprocessedHeaderFiles);
var outputFolder = Path.GetFullPath(folder + config.Root.Element("output").Attribute("path").Value);
var categorizedOutput = config.Root
.Element("output")
.Elements("codepair")
.ToDictionary(
e => e.Attribute("category").Value,
e => Path.GetFullPath(outputFolder + e.Attribute("filename").Value
));
// calculate category dependencies
var categoryDependencies = categorizedCppFiles
.Keys
.Select(k =>
{
var headerFiles = categorizedCppFiles[k]
.SelectMany(GetIncludedFiles)
.Distinct()
.ToArray();
var keys = categorizedHeaderFiles
.Where(p => p.Value.Any(h => headerFiles.Contains(h)))
.Select(p => p.Key)
.Except(new string[] { k })
.ToArray();
return Tuple.Create(k, keys);
})
.ToDictionary(t => t.Item1, t => t.Item2);
// sort categories by dependencies
var categoryOrder = SortDependecies(categoryDependencies);
Dictionary<string, HashSet<string>> categorizedSystemIncludes = new Dictionary<string, HashSet<string>>();
// generate code pair header files
foreach (var c in categoryOrder)
{
string output = categorizedOutput[c] + ".h";
List<string> includes = new List<string>();
foreach (var dep in categoryDependencies[c])
{
includes.AddRange(categorizedSystemIncludes[dep]);
}
HashSet<string> systemIncludes = new HashSet<string>(includes.Distinct());
categorizedSystemIncludes.Add(c, systemIncludes);
Combine(
categorizedHeaderFiles[c],
output,
systemIncludes,
categoryDependencies[c]
.Select(d => Path.GetFileName(categorizedOutput[d] + ".h"))
.ToArray()
);
}
// generate code pair cpp files
foreach (var c in categoryOrder)
{
string output = categorizedOutput[c];
string outputHeader = Path.GetFileName(output + ".h");
string outputCpp = output + ".cpp";
HashSet<string> systemIncludes = categorizedSystemIncludes[c];
Combine(
categorizedCppFiles[c],
outputCpp,
systemIncludes,
outputHeader
);
}
// generate header files
var headerOutput = config.Root
.Element("output")
.Elements("header")
.ToDictionary(
e => Path.GetFullPath(folder + e.Attribute("source").Value),
e => Path.GetFullPath(outputFolder + e.Attribute("filename").Value)
);
foreach (var o in headerOutput)
{
Combine(o.Key, o.Value + ".h");
}
}
}
}
代碼已經(jīng)checkin在了
Vczh Library++3.0(Tools\Release\SideProjects\GacUISrc\GacUISrc.sln)下面,里面也包含了生成后的代碼。
posted on 2012-02-29 05:34
陳梓瀚(vczh) 閱讀(4012)
評(píng)論(9) 編輯 收藏 引用 所屬分類:
GacUI