最终还是决定直接以源代码方式发布GacUI了
在制作GacUI读pdb生成代码的过程中,感受到了C++语言设计和dll的需求之间的鸿沟。对于一个充分利用了C++各种功能的类库来说,制作成dll具有非常大的困难,特别是在函数返回POD(Plain Old Data)的引用,和输入输出带有泛型的类上面。所以现在还是决定以源代码的方式来发布GacUI。但是pdb生成代码并没有白做,因为反射还是存在的。但是因为GacUI一共有48000行代码,80多个源代码文件,直接发布使用起来总是不方便。所以我写了个小工具,根据xml的配置来将源代码合并成少数几个比较大的代码文件。这样使用的时候,只需要直接把几个cpp拖进工程里面,就可以使用了。而且根据 之前发布的一个投票,似乎大家也最喜欢这种方法。因此这次的决定,仅仅删掉了作为backup plan的dll方法。
这里我给出小工具的代码和配置文件。这个配置文件是基于GacUI做出来的,不过大家可以修改它,以便用于自己的工程上面:
在这里面,project包含了用于开发这个工程的所有VC++2010的工程文件的地址,然后使用category对他们进行分类(pattern是文件全名的某个部分),最后对每一个部分生成一对cpp和h。在最后生成代码对的时候,如果源代码从一开始就存在依赖关系的话,那么在代码对的h文件里面,会包含依赖的代码对的h。在这里,vlpp是独立的,而gacui依赖了vlpp,所以gacui.h将会#include"vlpp.h",而cpp只include自己的h文件。output里面除了codepair以外还有header,header是不参与codepair计算的,纯粹为了生成“可以省事直接include”的头文件。在这个例子里面,GacUIIncludes.h将会包含完整的GacUI.h和一部分的Vlpp.h,而且是可以通过编译的。
在生成的时候,生成器将会阅读代码本身,然后获取#include "path",然后对他们的关系进行处理。这个工具的代码如下:
代码已经checkin在了 Vczh Library++3.0(Tools\Release\SideProjects\GacUISrc\GacUISrc.sln)下面,里面也包含了生成后的代码。
这里我给出小工具的代码和配置文件。这个配置文件是基于GacUI做出来的,不过大家可以修改它,以便用于自己的工程上面:
<?
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 >
< 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包含了用于开发这个工程的所有VC++2010的工程文件的地址,然后使用category对他们进行分类(pattern是文件全名的某个部分),最后对每一个部分生成一对cpp和h。在最后生成代码对的时候,如果源代码从一开始就存在依赖关系的话,那么在代码对的h文件里面,会包含依赖的代码对的h。在这里,vlpp是独立的,而gacui依赖了vlpp,所以gacui.h将会#include"vlpp.h",而cpp只include自己的h文件。output里面除了codepair以外还有header,header是不参与codepair计算的,纯粹为了生成“可以省事直接include”的头文件。在这个例子里面,GacUIIncludes.h将会包含完整的GacUI.h和一部分的Vlpp.h,而且是可以通过编译的。
驱动器 E 中的卷没有标签。
卷的序列号是 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 个文件 1 , 843 , 243 字节
2 个目录 166 , 357 , 680 , 128 可用字节
卷的序列号是 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 个文件 1 , 843 , 243 字节
2 个目录 166 , 357 , 680 , 128 可用字节
在生成的时候,生成器将会阅读代码本身,然后获取#include "path",然后对他们的关系进行处理。这个工具的代码如下:
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 " );
}
}
}
}
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 " );
}
}
}
}
代码已经checkin在了 Vczh Library++3.0(Tools\Release\SideProjects\GacUISrc\GacUISrc.sln)下面,里面也包含了生成后的代码。