不同系统(C#/Python/IronPython/Jython/C++)之间的技术整合(续一)

上一次讨论http://sunxiunan.com/?p=1854 发出去以后提问者回复了一些问题,然后也提出一些新问题,觉得这种总体设计还是挺有趣的,值得发出来一起看看。

    首先说明下为什么会有这样的想法吧—-公司里面有很多不同部门, 部门之间有不同的产品, 对于不同的产品和项目都有着自己的测试方案, 有手工的也有自动化的, 自动化的里面又有使用各种语言的(包括Tcl, Python, Lua等), 即使使用同一种语言, 也有使用各种框架的, 这样就导致了不同产品间很难沟通(最典型的案例就是需要互相借人的时候, 发现要重新学习一套测试方式, 周期太长), 同时也不便于管理. 于是呢, 领导们就突发奇想, 是不是可以统一一种语言和框架呢?为了能满足领导们以及测试部门等各方面的诉求,就有了以上的想法…..

    再来说说我的想法吧. 目前领导要统一已经是一个不可避免的问题了, 因此我非常希望可以引导到Python上面来(其实有些部门是使用Python的), 这里大概和我的个人喜好, 同时我也相信Python可以胜任这种情况. 于是为了证明这个, 所以我就有了以上的尝试. 所有的一切都是为了尽量少改动现有的代码而实现新的统一的目标,想调用Java的原因是有大量的类库目前可以使用,希望调用C/C++的原因是因为其他部门非常有可能需要与硬件相关的C/C++的扩展来实现框架,要尽量满足大家的需求大家才会支持你 。。。。。。

    至于框架方面,说实话,现在Python的测试框架好像还没有特别成熟的,相对来说见得多的是Robot Framework,不过其实我自己的构想是重新创建的一个可以将各个部门完全分离同时又可以分别集群的架构,不过这些都是后话了。

至于这位仁兄说的这条:

3)IronPython无法调用C扩展,但是很容易调用C#,可以考虑将其作为C#与CPython的包装层,仅此而已,不要用的太多。
4)Jython也是一样,仅用于Java与CPython的包装。

鉴于我才疏学浅,不大理解具体怎么做,因为我的理解是Jython和IronPython都是重新实现的解释器,如何去包装CPython呢?(类似Pyhon For .net的做法?如果是这样的话,这个难度还是相当大的。。。。)

希望能进一步解释。

============================

我的回答如下:

包装层(wrapper)是一个抽象概念,有句名言说:编程问题大多数可以通过引入新的抽象来解决(当然,同时也产生新的问题)。

我们以IronPython和CPython以及现有的C# DotNet程序、C++程序为例来做个探讨好了。在上一次讨论提问者描述以后,我的感觉就是他对性能要求不高,交互紧密性要求不高(也就是未必有IronPython必须不得不直接调用C编写的Python扩展要求),只要把异构系统整合起来能够互相通讯就好。

整合异构系统,需要分清主次,我这里的设计方案,是以CPython为主,其它编程语言的应用程序或者以DLL形式存在或者被CPython程序调用激活,或者是驻留系统中等待CPython主程序的消息。如果你的方案不是这样,建议你重新考虑一下,没有主次之分的系统设计是有问题的。

另外看这位提问者的描述,他对于系统之间传递的内容,以及交互的紧密程度,要求不是很高,甚至感觉上就是只要Python能调用一下这些系统就行了。

这也是设计者需要考虑的,真正需要整合起来做什么,把方案做得细致一些,多考虑一些。

 

类似这种不同语言的系统进行通讯,基本上有这样几个办法:

1,基于消息Message Queue通讯

定义通用格式消息或者使用通用现成格式(比如ProtocolBuffer、Thrift、Json、XML、MsgPack等等),使用跨开发平台通讯方式比如zeromq、http web service进行通讯支持。这是扩展性最好、实现也不麻烦的解决方案,在Python-cn中Zoom.Quiet也提出同一种方案。

http://en.wikipedia.org/wiki/%C3%98MQ

http://nichol.as/zeromq-an-introduction

关于数据序列化比较,可以参考这里

http://en.wikipedia.org/wiki/Comparison_of_data_serialization_formats

另外在Windows下,如果是本机,也可以通过Windows自定义消息在不同系统之间传递。

Python、IronPython也好,C#也好,或者是C++、Lua、Java,它们都很容易建立Web Server,这种方案可以优先考虑。而且scalablity比较好,容易扩展。

 

2,基于COM

这个对开发者要求比较高,另外只能用于IronPython和Python以及C++之间,不能用于Jython和Java。但是如果不需要考虑Java系产品,可以考虑COM,其实COM技术即使在DotNet发展到4.0的现在,依然有强大的生命力。而且开发其实也不难。

 

3,基于数据库或文件系统传递

这种方案也是比较通用的,如果你不需要序列化对象,只关心结果(比如数字、字符串、时间等等),这个方案也可以参考。优点是相比第一种,更容易学习使用。可以使用SQLite这样轻量的数据库。

 

4,基于Python序列化传递对象

这是针对Python编程语言特定的办法。同时也回答了这个问题:怎么叫用IronPython作CPython和C#之间的包装器?

如果了解Python,应该知道Python有个序列化方法pickle,可以把对象保存到文件中,以后载入。

幸运的是,经过我的实验,IronPython2.7支持pickle,与CPython之间通过pickle文件传递对象,那是非常容易。

也就是说,C#程序运行完毕,或者通过IronPython运行完毕,可以将结果保存到pickle文件中,CPython载入以后继续运行,装得跟直接一样。

通过我的描述可以看出,IronPython和CPython还是没关联,你是你我是我。这的确没办法,因为它们各自都是完整的系统,设计初衷也没考虑到直接交互,但是IronPython发展前景不错,值得跟进。相比Python.Net而言,我还是建议使用CPython和IronPython。

iron_cpython

 

最后要说的是,这种大一统的方案,如果没有强力领导介入和长期支持,基本上不能成功。但这不是技术方案需要考量的。

不同系统(C#/Python/IronPython/Jython/C++)之间的技术整合

在python-cn讨论组看到某人咨询架构设计,心痒回复了一下,有点价值。放在这里大家共享一下。

Python的解释器可谓相当的丰富了,CPython,Jython,IronPython,他们分别可以以非常简单的方式与C,Java以及.NET调用, 但是它们也有严重的缺点—-他们都是单独的解释器,因此想把他们共同使用就比较麻烦了.

    最近工作上遇到这样的场景, 现有的代码中大部分代码是使用C#写的,考虑到性能以及工作量的问题, 这部分代码必须可以用上; 由于别人提供的工具中, 有大量Jython的代码, 这部分代码也不可能重写; 后续代码可能涉及到部分硬件相关代码,可能需要使用C/C++进行扩展编写; 最后后续的代码希望使用Python, 以保证开发的效率以及灵活性.  这样就遇到难题了, 如何保证这些不同语言编写的代码在同一个工程中使用呢?

    我也做了一些尝试,但是结果都不是非常理想:

        CPython: 1,使用C/C++标准扩展的方式重新将C#编写的.NET组件包装之后, 编译可以通过, 但是实际调用的时候会显示找不到目标, 使用ctypes调用用C/C++包装之后的.NET组件,报Windows Error错误, 网上随便搜索了下, CPython想使用.NET组件是个非常复杂的问题; (其实在这里我并不是非常理解C#编译成为DLL之后调用过程是怎么样的, 因为理论上C#也是一个解释型的语言, 他的执行过程是不是需要什么依赖呢? 在这里我做的实验是非常简单:使用C#实现一个类,里面有一个static method, 在C/C++的文件中调用C#中实现的static method编译为PYD文件给Python调用—-也许是我的调用逻辑有问题)

        2,同时CPython想使用Java代码比较繁琐. 

        Jython: 不支持ctypes, 使用C/C++代码相对繁琐, 与.NET组件的调用也比较繁琐, 同时Python的支持版本很低, 貌似开发进度比较不给力啊…..

        IronPython: C/C++标准扩展无法在IronPython中调用, 调用Java也相对比较繁琐.

    另外我还考虑过通过COM使用CPython调用.NET组件, 应该可行但是不想使用….

    也想过通过把不同的语言实现的部分分离在不同的进程中通讯也是可行的, 但是带来的性能问题以及稳定性难以评估.

由于时间比较仓促,需要尽快作一个技术上的选型, 所以没有更多的时间来做更多的实验, 所以在这里询问下, 不知道这里有没有以前遇到过类似问题,或者对这些有所了解的朋友, 还请不吝赐教!谢谢!

==================================

我的回答如下:

 

这个架构设计蛮有意思的,首先想问几个问题确定一下细节:

1)Python选型是否是官方意见?已经决定好的?还是仅仅是你个人的?
2)不同语言模块交换的工作是一次性的(就是用完就抛的)?还是说需要长期使用的?
3)这里需要重用的是数据?还是业务逻辑?还是仅仅就是模块?
4)你对整体设计是否有决定权?你对这些系统(Jython、CPython、C++、C#)是否了解?
5)当时做出这些选型是否考虑过整合?

说实话,从你的问题来看,你对整个系统了解程度还是不够的,这是设计的大忌。

建议你按照这些步骤来做:

1)确定真正的问题列表。把不同模块画出来,那些需要交换数据,那些需要重用逻辑。把不同系统之间的交互(已有的和将来要有的)列成表格,加上估计的技术难度以及估计的工作时间。
先把思路搞清晰,要把系统的真正问题了解透,这点最为重要。
再重复强调一次,编程是为了解决问题,重中之重就是确定你解决的是真正问题而不是枝节。

2)按照问题优先级,来进行技术分析。比如C#与Python的交互,需要Python调用C#的逻辑。最佳方案是C#暴露为service形式,可以通过web service(WCF)很容易实现,数据格式可以为xml或者json,都是语言无关的。
也可以C#作为COM组件形式暴露,优点是可重用性更好,C++也可以直接使用。

3)IronPython无法调用C扩展,但是很容易调用C#,可以考虑将其作为C#与CPython的包装层,仅此而已,不要用的太多。

4)Jython也是一样,仅用于Java与CPython的包装。

5)CPython与硬件可以通过C编写扩展来实现,不是很难。我最近在翻译的一本书就是有类似整合方法。

这里的设计方案是以CPython作为中心,其它方案向CPython靠拢。当然我觉得以C++为中心更好,但你未必能接受。

天使

星期天回到父母家,晚上吃饭的时候不知怎么父亲想起萌萌一个事,说她聪明”不好糊弄了“,说捡来的故事不相信了。

萌萌这时候接了一句”我是从妈妈肚子里生出来的“样子颇为认真,让我感觉非常有意思,于是想逗逗她。

”你不是妈妈肚子里生出来的,是从天上来的天使”。

萌萌开始很怀疑,“我就是妈妈肚子里生出来的”,于是我把故事说的比较像回事“你是从天上飞下来的,我和你妈妈把你的翅膀都保存起来了,放在银行里“。

萌萌开始围绕这个问题不停地问“那我安上翅膀会不会飞?能不能把翅膀让我看看?我不信我就是妈妈肚子里生出来的“诸如此类,小脸兴奋地通红。

说着说着,突然冒出一句”爸爸是从白菜叶子里出来的,爸爸是菜青虫!“把我们笑到不行。

宝贝,你就是我们的天使啊!

2011-04-24_16-00-05_157

C#研究系列-List<>与ArrayList的几个研究心得及问题(下)

代码在这里:

https://gist.github.com/921385

与(上)一样在一开始定义了两个继承List与ArrayList的空类。
// public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable

class CFromList : List<int> { }

// public class ArrayList : IList, ICollection, IEnumerable, ICloneable

class CFromArrayList : ArrayList{ }

注意在87行,我注释了这行代码

// list1[index++] = a;

意思很简单,就是要修改iterate当前位置的值。
其实我当初写下这个代码也就是随手为之,因为这种行为在C++中不算啥(只是对值进行修改,并不会修改list本身内存排列)。
可是debug的时候,蹦出异常InvalidOperationException了。太奇怪了。
好在我设置了step into CLR code,所以能跟踪到List内部的一些实现。从73到85行就是我找到的一些内容。
在List.cs代码中,前面list1[index++]=a;的赋值语句会进入这个函数public T this[int index] set{},在这个属性函数中
_items[index] = value; _version++;
所以_version会增加1。在MoveNext函数中,会检查这个_version
private bool MoveNextRare() {

if (version != list._version) {

ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);

}

index = list._size + 1;

current = default(T);

return false;

}

很显然,赋值以后,version和list._version不一样了。如果我们用try catch包围这些代码,那么就会捕获这个异常。

另外一个问题来了,为何ArrayList没有异常呢?在我debug的时候,如果有list2[index++]=a;这样的修改,代码就挂在这块了,也没有异常弹出。

加上try catch以后就没有问题了,这个就更奇怪了。

从MSDN可以看到,List在foreach这种形式的循环下是不可以修改内容。必须说,这个规定太蛋疼了。如果想修改也很容易,不用这种iterator形式就好了。

Re 浅谈C和C++中的const关键字

This article contains many errors, I just list them simply.
http://www.cnblogs.com/dolphin0520/archive/2011/04/18/2020248.html

BTW About const concept in C/C++, I have 2 articles.
《c专家编程》阅读笔记-关于const指针 http://sunxiunan.com/?p=1161
技术笔记-关于c/c++中的const http://sunxiunan.com/?p=870

————————
“但是程序中使用过多的const,可能在对代码的阅读时增加一定的难度”
Can’t understand. This idea is so strange.
If possible, you should use const as possible. It is a good contract.

————————
const int n; 这种声明方式是错误的
This definition method is wrong. (should NOT declaration!)
extern char* const p; // this is declaration, p as extern variable

————————
指针常量:即指针本身的值是不可改变的,而指针指向的变量的值是可以改变的;
I never heard the concept “指针常量”, it should be “the pointer points to constant” 指针指向常量!!

————————
“C语言和C++中的const有很大区别。在C语言中用const修饰的变量仍然是一个变量;而在C++中用const修饰过后,就变成常量了。
const int a=3; int *pa=&a; *pa=4; printf(“%d\n”,*pa); printf(“%d\n”,a);
这种情况在C++中是不允许的,原因在于a用const修饰后,已经成为常量了,因此是不允许被修改的,无论是显示的更改a的值或是通过其它方法修改它的值都是不允许的。”

!!!! WTF !!
C++ has a keyword const_cast !!

Try following code:
const int a=3;
int *pa= const_cast < int * > (&a); *pa=4;
printf(“%d\n”,*pa);
printf(“%d\n”,a);

Will print out “4 4” as result.

C#研究系列-List<>与ArrayList的几个研究心得及问题(上)

代码放在gist.github.com上了,看不到的请留言。

第一个心得,是我看某本书提到,IList用起来要比ArrayList快。

这里面用到了我上一篇博客提到的高精度计时器 http://sunxiunan.com/?p=1829

我在开始定义了两个类。
// public class List : IList, ICollection, IEnumerable, IList, ICollection, IEnumerable
class CFromList : List{}

// public class ArrayList : IList, ICollection, IEnumerable, ICloneable
class CFromArrayList : ArrayList{}
List和ArrayList的定义在注释中给出,可以看出来其实都差不多。ArrayList只是多了ICloneable,还少了几个泛型接口继承。

在后面代码中都用Add方法向list中添加int类型数据,然后通过foreach形式枚举数据,注意!枚举部分的代码是有问题的,我们在(下)中会提到。

这里还要推荐一个非常棒的工具ILSpy,是sharpdevelop开发的,强烈建议dotnet程序员都下载使用。

我把ILSpy disassemble出来的C#代码和IL代码分别列在后面。注意对于ArrayList的foreach语句,C#形式的代码与源代码有些差别(79到96行),编译器加入一个IEnumerator enumerator2 = cFromArrayList.GetEnumerator();本地变量,另外使用int num5 = (int)enumerator2.Current;这样访问iterator。而且还加入了IDisposable的finally部分。

再继续看IL代码部分,对于List形式,IL代码没有box装箱指令,而ArrayList在145行有个box指令,这是性能差别之一。
但是奇怪的是,在枚举部分,ILSpy生成的(以及ILDasm)IL代码,对于ArrayList和List而言,基本上差别不大,一样也有对MoveNext和Current以及IDisposable接口的调用。只不过ArrayList多出unbox和box的指令。

运行结果如我们所料,List要比ArrayList快不少。

但是我们在枚举部分的代码是有问题的,我明天在(下)中会介绍。

C#高精度定时器,可用于profiling

如果想准确profiling函数或者代码性能,一个高精度定时器是必备的。
参考这几个资料吧,
http://en.wikipedia.org/wiki/Time_Stamp_Counter

注意rdtsc在多核下的问题
http://msdn.microsoft.com/en-us/library/ee417693%28v=vs.85%29.aspx

最好最方便的还是这个函数QueryPerformanceCounter

jeffz_cn在他的blog中提到另外一种方法,但是不支持windowsXP,而且好像精度上也没有我下面写的这个高。

要注意的是软件定时器都不是非常准的,只是比较准罢了。另外定时器不要加的太多,需要的几个位置加一下就可。

实际上,我以前在C++用到的,也是一样的win32函数:


class HighResolutionTimer
{
private bool isPerfCounterSupported = false;
private Int64 frequency = 0;
// Windows CE native library with QueryPerformanceCounter().
[DllImport("kernel32.dll", SetLastError = true)]
private static extern int QueryPerformanceCounter(ref Int64 count);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern int QueryPerformanceFrequency(ref Int64 frequency);
public HighResolutionTimer()
{
int returnVal = QueryPerformanceFrequency(ref frequency);
if (returnVal != 0 && frequency != 1000)
{
// The performance counter is supported.
isPerfCounterSupported = true;
}
else
{
// The performance counter is not supported. Use
// Environment.TickCount instead.
frequency = 1000;
}
Start();
}

public Int64 Frequency
{
get
{
return frequency;
}
}

public Int64 Value
{
get
{
Int64 tickCount = 0;

if (isPerfCounterSupported)
{
// Get the value here if the counter is supported.
QueryPerformanceCounter(ref tickCount);
return tickCount;
}
else
{
// Otherwise, use Environment.TickCount.
return (Int64)Environment.TickCount;
}
}
}

private Int64 _startValue;
public void Start()
{
_startValue = Value;
}

public Int64 Stop()
{
Int64 stopValue = Value;
return stopValue - _startValue;
}
}