应当讲我现在是很不喜欢C++这个语言。
不仅仅语言复杂,这其实倒也无所谓,但问题是:C++是“一次编写,到处编链(生成),各种编链不过”,这就是C++的真正特点。这两天弄那个Teigha的CAD文件处理库,很装x闭源的库(所以C++也更应提倡开源,开源你去解决编译问题,闭源你就去解决可能永远解决不了的链接问题去吧)加一些很装x的例子程序写了一大坨,每种平台和配置都做一个包,总大小好几个G,拿下来去升级整合原先的调用程序,折腾了两天最后运行还通不过。这种情况只要遇到调用第三方库就总会遇到,而且每次遇到就算情况再相似都会无谓耗费大量时间。所以可以讲C++是一种开发效率非常低下的语言。无尽的咒骂。。。
最终除了很多冗繁的各类编译链接设置,这个问题最终是关于Unmanaged C++库(需要Windows CRT库)和C#应用程序side-by-side链接问题。
我的场景是(也是一个比较经典的):C#应用调用C++/CLI的类似Wrapper的库,而后者包含一些普通C++的动态连接库。而每次运行总会提示一些错误主要是“MSVCR90.DLL”无法找到,这个错误可以忽略,但之后的运行行为则不确定,一个旧版本的Teigha库可以正常运行(但也有一些报错),另一些版本则无法正常输出结果。有趣的是如果用一个C++/CLI做的managed的等价应用程序去调用,则不会有任何错误。一个简单的栈检查发现(也在意料之中),C++/CLI的应用的调用入口不存在managed和unmanaged的转移(C++在Windows(也许是几乎所有操作系统)下的确是名副其实的一等公民),这很有可能是造成这种执行行为差异的原因。
我尝试做了一个纯C/C++的DLL,然后其中做一些类似于那个Wrapper库做的简单的初始化工作(会导致那个MSVCR90.DLL无法找到错误)并暴露纯C接口(这是P/Invoke的规定),然后由C#去调用之,发现错误仍旧出现,说明这和C#对Native的互联操作方式无关。
而且解决这个问题的办法一般总是为C# App配置一个manifest。
有一些比较好的解答如:http://stackoverflow.com/questions/8039586/pinvoke-fails-because-of-dlls-dependent-on-other-sxs-dlls;只是这里要对文件版本等参数根据系统中存在的进行调整(要到Windows\winsxs目录下去搜索MSVCR90.DLL,然后根据文件夹名称的提示改写,名称可以从文件夹名字中看出,注意public key token是不变的(也在文件夹名中可看出)。另外这篇文章演示了如何根据项目配置读取特定的manifest(因为在debug下应当使用MSVCR90D.DLL)。
在根据步骤配置完manifest之后,我发现我仍然得到这些错误,非常恼怒,我尝试对C++ Wrapper工程也引入manifest,也无效。偶然我将配置改为Release,问题解决,运行一切正常,包括在那个DLL遗失错误之后的更严重运行错误也消失了(我估计对纯.NET接入的程序只能在Release模式不依赖那个debug版本CRT的情形下执行)。
然后我最后还得测试一下,如果去掉这个manifest会怎样,结果是运行失常,由此最终确定C# App需要这个新增的manifest。
另外在Teigha本身提供的.NET版本的库和实例工程中看,它的app并没有定制manifest,我估计原因是那个工程是在VS2008中,于是默认manifest或生成自动加入了这个VS2008 C++的运行时库路径。但它的功德是它只包含了Release版本,所以提示了我回到Release模式下去看看状况。
还是放松一下,让C#来平复一下被C++搞毛的心情,弄了一个用来分账单的小程序。多么优雅,写写这种代码被C++折去的寿命也能修补几年回来。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 5 namespace BillSplitter /* 20120130 */ 6 { 7 class Program 8 { 9 struct UsageSegment : IComparable<UsageSegment> 10 { 11 public DateTime Start; // inclusive 12 public DateTime End; // inclusive 13 public decimal Units; // number of persons 14 15 public decimal TotalUnits 16 { 17 get { return ((decimal)(End - Start).TotalDays + 1) * Units; } 18 } 19 20 public int CompareTo(UsageSegment other) 21 { 22 if (End < other.Start) return -1; 23 return Start > other.End ? 1 : 0; 24 } 25 } 26 27 class PersonalUsage 28 { 29 public DateTime MinDate { get; private set; } 30 public DateTime MaxDate { get; private set; } 31 32 public decimal TotalUnits 33 { 34 get 35 { 36 if (_dirty) 37 { 38 _totalUnits = 0; 39 foreach (var seg in _segments) 40 { 41 _totalUnits += seg.TotalUnits; 42 } 43 } 44 return _totalUnits; 45 } 46 } 47 48 private readonly List<UsageSegment> _segments = new List<UsageSegment>(); 49 50 public PersonalUsage() 51 { 52 MinDate = DateTime.MaxValue; 53 MaxDate = DateTime.MinValue; 54 } 55 56 public string[] GetDescription() 57 { 58 var res = new string[_segments.Count]; 59 var i = 0; 60 foreach (var seg in _segments) 61 { 62 res[i++] += string.Format("{0} Users: {1} to {2}", 63 seg.Units, seg.Start.ToShortDateString(), seg.End.ToShortDateString()); 64 } 65 return res; 66 } 67 68 public PersonalUsage Add(DateTime start, DateTime end, decimal units = 1) 69 { 70 if (start > end) 71 { 72 throw new Exception("End date is not allowed to be earlier than start"); 73 } 74 var newSeg = new UsageSegment { Start = start, End = end, Units = units }; 75 var index = _segments.BinarySearch(newSeg); 76 if (index >= 0) 77 { 78 // overlapping 79 throw new Exception("Current implementation doesn't allow overlapping"); 80 } 81 82 index = -index - 1; 83 _segments.Insert(index, newSeg); 84 85 if (start < MinDate) MinDate = start; 86 if (end > MaxDate) MaxDate = end; 87 88 _dirty = true; 89 90 return this; 91 } 92 93 private bool _dirty; 94 private decimal _totalUnits; 95 } 96 97 class PersonDictionary : Dictionary<string, PersonalUsage>, IDictionary<string, PersonalUsage> 98 { 99 public new PersonalUsage this[string key] 100 { 101 get 102 { 103 if (!ContainsKey(key)) 104 { 105 base[key] = new PersonalUsage(); 106 } 107 return base[key]; 108 } 109 set { base[key] = value; } 110 } 111 } 112 113 abstract class AbstractBalanceSheet 114 { 115 public abstract ICollection<string> PersonNames { get; } 116 public abstract decimal AmountsDueRounded(string name); 117 118 public static AbstractBalanceSheet operator +(AbstractBalanceSheet a, AbstractBalanceSheet b) 119 { 120 var result = new BalanceSheet(); 121 foreach (var p in a.PersonNames) 122 { 123 decimal value; 124 if (b.PersonNames.Contains(p)) 125 { 126 value = a.AmountsDueRounded(p) + b.AmountsDueRounded(p); 127 } 128 else 129 { 130 value = a.AmountsDueRounded(p); 131 } 132 result.SetAmountsDue(p, value); 133 } 134 135 foreach (var p in b.PersonNames) 136 { 137 if (a.PersonNames.Contains(p)) 138 { 139 continue; 140 } 141 var value = b.AmountsDueRounded(p); 142 result.SetAmountsDue(p, value); 143 } 144 return result; 145 } 146 } 147 148 private class BalanceSheet : AbstractBalanceSheet 149 { 150 private readonly IDictionary<string, decimal> PersonsUsage = new Dictionary<string, decimal>(); 151 152 public override ICollection<string> PersonNames 153 { 154 get { return PersonsUsage.Keys; } 155 } 156 157 public override decimal AmountsDueRounded(string name) 158 { 159 return PersonsUsage[name]; 160 } 161 162 public void SetAmountsDue(string name, decimal value) 163 { 164 PersonsUsage[name] = value; 165 } 166 } 167 168 class UsageSplitter : AbstractBalanceSheet 169 { 170 public IDictionary<string, PersonalUsage> Persons { get; private set; } 171 public decimal TotalAmountDue { private get; set; } 172 public decimal SummedUnits { private get; set; } 173 174 public override ICollection<string> PersonNames { get { return Persons.Keys; } } 175 176 public UsageSplitter() 177 { 178 Persons = new PersonDictionary(); 179 } 180 181 public void Recalculate() 182 { 183 SummedUnits = Persons.Sum(x => x.Value.TotalUnits); 184 } 185 186 public override decimal AmountsDueRounded(string name) 187 { 188 return AmountsDueRounded(Persons[name]); 189 } 190 191 public decimal AmountsDueAccurate(PersonalUsage p) 192 { 193 var result = TotalAmountDue * p.TotalUnits / SummedUnits; 194 return result; 195 } 196 197 public decimal AmountsDueRounded(PersonalUsage p) 198 { 199 var result = Math.Round(TotalAmountDue * p.TotalUnits * 100 / SummedUnits) / 100; 200 return result; 201 } 202 203 public void MakeSummary() 204 { 205 Recalculate(); 206 decimal summedPayment = 0; 207 var minDate = DateTime.MaxValue; 208 var maxDate = DateTime.MinValue; 209 Console.WriteLine(" Name | Description | Usage | Payment "); 210 foreach (var p in Persons) 211 { 212 var val = p.Value; 213 var desc = val.GetDescription(); 214 var payment = AmountsDueRounded(val); 215 summedPayment += payment; 216 Console.WriteLine("{0,-20}|{1,-40}|{2,7}|{3,9:0.00}", p.Key, desc[0], val.TotalUnits, payment); 217 for (var i = 1; i < desc.Length; i++) 218 { 219 Console.WriteLine(" |{0,-40}| |", desc[i]); 220 } 221 if (val.MinDate < minDate) minDate = val.MinDate; 222 if (val.MaxDate > maxDate) maxDate = val.MaxDate; 223 } 224 Console.WriteLine(" [Total] |{0,-40}|{1,7}|{2,9}", 225 string.Format("Usage from {0} to {1}", minDate.ToShortDateString(), maxDate.ToShortDateString()), 226 SummedUnits, summedPayment); 227 } 228 } 229 230 static UsageSplitter ElectricitySummary() 231 { 232 var startDate = new DateTime(2012, 10, 26); 233 var endDate = new DateTime(2013, 1, 21); 234 var split = new UsageSplitter { TotalAmountDue = 431m }; 235 var normal = new PersonalUsage(); 236 normal.Add(startDate, endDate); 237 238 split.Persons["Li"].Add(startDate, new DateTime(2012, 12, 9)); 239 split.Persons["Hou"].Add(startDate, new DateTime(2012, 12, 9)); 240 split.Persons["Sama"].Add(startDate, new DateTime(2012, 12, 5), 2); 241 split.Persons["Thomas"].Add(new DateTime(2013, 1, 7), endDate); 242 split.Persons["TonysNephew"].Add(new DateTime(2013, 1, 7), new DateTime(2013, 1, 13)); 243 244 split.Persons["Linc"].Add(startDate, new DateTime(2013, 1, 4), 2) 245 .Add(new DateTime(2013, 1, 5), new DateTime(2013, 1, 11)) 246 .Add(new DateTime(2013, 1, 12), endDate, 2); 247 248 split.Persons["Fulei"].Add(startDate, new DateTime(2013, 1, 9)); 249 split.Persons["Jiale"].Add(startDate, new DateTime(2012, 12, 5)); 250 split.Persons["Max"].Add(startDate, endDate); 251 split.Persons["Timmy"].Add(new DateTime(2013, 1, 7), endDate); 252 253 split.MakeSummary(); 254 return split; 255 } 256 257 static UsageSplitter GasSummary() 258 { 259 var startDate = new DateTime(2012, 10, 26); 260 var endDate = new DateTime(2013, 1, 25); 261 var split = new UsageSplitter { TotalAmountDue = 263.75m }; 262 var normal = new PersonalUsage(); 263 normal.Add(startDate, endDate); 264 265 split.Persons["Li"].Add(startDate, new DateTime(2012,12,9)); 266 split.Persons["Hou"].Add(startDate, new DateTime(2012, 12, 9)); 267 split.Persons["Sama"].Add(startDate, new DateTime(2012, 12, 5), 2); 268 split.Persons["Thomas"].Add(new DateTime(2013,1,7), endDate); 269 split.Persons["TonysNephew"].Add(new DateTime(2013, 1, 7), new DateTime(2013, 1, 13)); 270 271 split.Persons["Linc"].Add(startDate, new DateTime(2013, 1, 4), 2) 272 .Add(new DateTime(2013, 1, 5), new DateTime(2013, 1, 11)) 273 .Add(new DateTime(2013, 1, 12), endDate, 2); 274 275 split.Persons["Fulei"].Add(startDate, new DateTime(2013, 1, 9)); 276 split.Persons["Jiale"].Add(startDate, new DateTime(2012, 12, 5)); 277 split.Persons["Max"].Add(startDate, endDate); 278 split.Persons["Timmy"].Add(new DateTime(2013, 1, 7), endDate); 279 280 split.MakeSummary(); 281 return split; 282 } 283 284 static void Main() 285 { 286 Console.WriteLine("== Electricity =="); 287 var se = ElectricitySummary(); 288 Console.WriteLine("== Gas =="); 289 var sg = GasSummary(); 290 291 Console.WriteLine("== Summed =="); 292 var t = se + sg; 293 foreach (var p in t.PersonNames) 294 { 295 Console.WriteLine("{0}: {1}DB", p, t.AmountsDueRounded(p)); 296 } 297 } 298 } 299 }
现在有人说,C/C++节能环保,放在数据中心还是手持等大小设备上能减少功耗和热能输出。我了个去,生产力下来了,可维护性下来了,人的智慧和精力被拖垮了,一星半点的“环保”有什么用?C++就用在它所局限的地方那个就可以了,比如.NET的框架现在需要用它实现那没办法。但等到以后软硬件架构足够成熟,就应该将.NET对资源和运行时类型的管理都整合到硬件和操作系统架构中,并可以用这种更精简更符合人类思维的方式来同时进行软件甚至硬件的设计,这样真正做到思维、软件、硬件的相互推动,而不是局限于现在的老式的纯冯诺依曼架构。
我反正认为人的生命和创造力高于一切。C++如果再火起来是人类的悲哀,看来也不太可能。