Friday, May 22, 2009

C# quit excel appliction after using COM object

Following my last post of how to read and write excel files in C#, which can be found here: http://www.idothink.com/2009/03/c-read-write-excel-spreedsheet.html

I discovered some serious bug. The Excel process didn’t quit sometimes and leaves in the tast manager. Some one suggest that kill the process, but it is a dangerous move.

After some research, I have come up with the following solution.

Firstly, I create the following method to release the comobject.

 

        private void NAR(object o)
{
try
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(o);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
o = null;
}
}



Secondly, I put the release call in the finally block, and try to release everything. The reason is that



“When Visual Studio .NET calls a COM object from managed code, it automatically creates a Runtime Callable Wrapper (RCW). The RCW marshals calls between the .NET application and the COM object. The RCW keeps a reference count on the COM object. Therefore, if all references have not been released on the RCW, the COM object does not quit.”



            finally
{

if(objBooks != null)
objBooks.Close();
if(objApp != null)
objApp.Quit();
NAR(objSheet);
NAR(objSheets);
NAR(objBooks);
NAR(objBook);
NAR(objApp);
}



After doing all these, the problem still wasn’t solved when I automate the whole process and try to have lots of Excel object open and close, few of them are still not release. I then use the very dangerous GC call.



 



                GC.Collect();
GC.WaitForPendingFinalizers();




Have Fun. Oh, BTW, the application is now significant slower :)

Saturday, May 16, 2009

C# File Compression using GZipStream

An very good Zip Utility example which can be used to compress file.

using System;
using System.IO;
using System.IO.Compression;
using System.Windows.Forms;

namespace CompressionSample
{
class ZipUtil
{
public void CompressFile ( string sourceFile, string destinationFile )
{
// make sure the source file is there
if ( File.Exists ( sourceFile ) == false )
throw new FileNotFoundException ( );

// Create the streams and byte arrays needed
byte[] buffer = null;
FileStream sourceStream = null;
FileStream destinationStream = null;
GZipStream compressedStream = null;

try
{
// Read the bytes from the source file into a byte array
sourceStream = new FileStream ( sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read );

// Read the source stream values into the buffer
buffer = new byte[sourceStream.Length];
int checkCounter = sourceStream.Read ( buffer, 0, buffer.Length );

if ( checkCounter != buffer.Length )
{
throw new ApplicationException ( );
}

// Open the FileStream to write to
destinationStream = new FileStream ( destinationFile, FileMode.OpenOrCreate, FileAccess.Write );

// Create a compression stream pointing to the destiantion stream
compressedStream = new GZipStream ( destinationStream, CompressionMode.Compress, true );

// Now write the compressed data to the destination file
compressedStream.Write ( buffer, 0, buffer.Length );
}
catch ( ApplicationException ex )
{
MessageBox.Show ( ex.Message, "An Error occurred during compression", MessageBoxButtons.OK, MessageBoxIcon.Error );
}
finally
{
// Make sure we allways close all streams
if ( sourceStream != null )
sourceStream.Close ( );

if ( compressedStream != null )
compressedStream.Close ( );

if ( destinationStream != null )
destinationStream.Close ( );
}
}

public void DecompressFile ( string sourceFile, string destinationFile )
{
// make sure the source file is there
if ( File.Exists ( sourceFile ) == false )
throw new FileNotFoundException ( );

// Create the streams and byte arrays needed
FileStream sourceStream = null;
FileStream destinationStream = null;
GZipStream decompressedStream = null;
byte[] quartetBuffer = null;

try
{
// Read in the compressed source stream
sourceStream = new FileStream ( sourceFile, FileMode.Open );

// Create a compression stream pointing to the destination stream
decompressedStream = new GZipStream ( sourceStream, CompressionMode.Decompress, true );

// Read the footer to determine the length of the destination file
quartetBuffer = new byte[4];
int position = (int)sourceStream.Length - 4;
sourceStream.Position = position;
sourceStream.Read ( quartetBuffer, 0, 4 );
sourceStream.Position = 0;
int checkLength = BitConverter.ToInt32 ( quartetBuffer, 0 );

byte[] buffer = new byte[checkLength + 100];

int offset = 0;
int total = 0;

// Read the compressed data into the buffer
while ( true )
{
int bytesRead = decompressedStream.Read ( buffer, offset, 100 );

if ( bytesRead == 0 )
break;

offset += bytesRead;
total += bytesRead;
}

// Now write everything to the destination file
destinationStream = new FileStream ( destinationFile, FileMode.Create );
destinationStream.Write ( buffer, 0, total );

// and flush everyhting to clean out the buffer
destinationStream.Flush ( );
}
catch ( ApplicationException ex )
{
MessageBox.Show ( ex.Message, "An Error occured during compression", MessageBoxButtons.OK, MessageBoxIcon.Error );
}
finally
{
// Make sure we allways close all streams
if ( sourceStream != null )
sourceStream.Close ( );

if ( decompressedStream != null )
decompressedStream.Close ( );

if ( destinationStream != null )
destinationStream.Close ( );
}

}
}
}

Tuesday, May 12, 2009

【转载】抽象的代价

转载自德明泰科技博客:http://blog.sina.com.cn/s/blog_5faf4b830100dtdy.html

随着设计模式的发展,抽象类被越来越广泛的使用。很多设计模式都要求用户接口全部使用抽象类,这样可以随意修改派生类,而维持接口不变。但是我们为此付出了什么代价呢?让我们来看一个简单的例子。

我们先定义两个类,AbstractClass与ConcreteClass,他们实现了完全一致的功能。不同的是,AbstractClass是通过派生AbstractBaseClass实现的。

class AbstractBaseClass

{

public:

virtual int func() const = 0;

};

class AbstractClass :public AbstractBaseClass

{

public:

int func() const {return 1;}

};

class ConcreteClass

{

public:

int func() const {return 1;}

};

好的,现在假设我们有一个接口需要传入一个上述class。如果我们没有使用抽象的话,我们可以直接传入一个ConcreteClass,即:

void CallConcrete(const ConcreteClass &c)

{

c.func();

}

但是在很多设计模式中,推荐像下面这样做,传入一个AbstractBaseClass类型,这样我们可以任意改动其具体实现而不影响接口。

void CallAbstract(const AbstractBaseClass& c)

{

c.func();

}

无疑,后一种方式有着好得多的可扩展性、可维护性。但是在性能上,我们付出了怎样的代价呢?让我们具体调用他们试一下。

int main()

{

AbstractBaseClass& ac = AbstractClass();

ConcreteClass cc;

CallAbstract(ac);

CallConcrete(cc);

return 0;

}

我们来看一下编译器生成的代码。

AbstractBaseClass& ac = AbstractClass();

0041157E lea ecx,[ebp-14h]

00411581 call AbstractClass::AbstractClass (411136h)

00411586 lea eax,[ebp-14h]

00411589 mov dword ptr [ac],eax

ConcreteClass cc;

CallAbstract(ac);

0041158C mov eax,dword ptr [ac]

0041158F push eax

00411590 call CallAbstract (41102Dh)

00411595 add esp,4

CallConcrete(cc);

00411598 lea eax,[cc]

0041159B push eax

0041159C call CallConcrete (41109Bh)

004115A1 add esp,4

return 0;

004115A4 xor eax,eax

从这里我们可以看得很清楚,ac由于是一个抽象类型,显然编译器是无法知道他具体需要占用多少空间的,只能在运行时在自由空间上面进行分配。而cc则是编译时完全可确定的,不需要在运行时进行分配。

两个方法调用在这里看是一样的,让我们深入函数的内部看一下,执行过程究竟有何不同。函数首尾的例行操作我这里省略了,只看关键部分。

void CallConcrete(const ConcreteClass &c)

{

c.func();

0041149E mov ecx,dword ptr [c]

004114A1 call ConcreteClass::func (4110F5h)

}

void CallAbstract(const AbstractBaseClass& c)

{

c.func();

0041143E mov eax,dword ptr [c]

00411441 mov edx,dword ptr [eax]

00411443 mov esi,esp

00411445 mov ecx,dword ptr [c]

00411448 mov eax,dword ptr [edx]

0041144A call eax

}

可以看到,在具体类的调用中,一切都很明确,编译器知道需要调用哪个函数(例子中4110F5h)。而在对抽象类的调用中,编译器就不知道究竟应该调用哪个函数了。怎么办?在代码里可以看得,c的地址被传过去了。这里究竟有什么呢?我们实际的去看一眼,这里设一个断点,可以看到c的地址。

 5faf4b83t68529a218e1e&690

c的地址位于0x415640。0x415640有什么呢?打开内存监视看一眼(不要用反汇编了,这个反汇编会搞成错误的语句)。我们看到这一行数据。

0x00415640 0e 11 41 00 00 00 00 00 70 64 41 00 6d 11 41 00 00 00 00 00 88 64 41 00 e1 10 41 00 00 00

第一条数据是0x41110E。这是什么,去看看。

0041110E E9 1D 05 00 00 jmp AbstractClass::func (411630h)

哈哈,终于找到正主AbstractClass::func了。这下明白了,这0x415640不就是传说中的虚函数表嘛!嗯,至少在VS2005编译器(我试验用的)里,一个对象最开始放的就是虚函数表。

说的有点儿远了,总结一下。当CallAbstract拿到一个AbstractBaseClass的对象c的时候,它并不知道这个c是什么类型的。调用c.func()自然也不知道该调用哪个函数。但编译器有这样一种机制,它把一个对象中实际要调用的函数地址列表(也就是虚函数表)放在了对象的一开始。于是CallAbstract方法拿到一个AbstractBaseClass的对象c之后,只需要去它的一开始去找到这个虚函数表,就能够调用正确的函数。也就是说具体调用哪个函数,CallAbstract是不知道的,但是进来的对象自己知道,这就够了。

好了,我们简单的使用了抽象类接口之后,编译器在背后帮我们做了这许多事情。比起直接使用具体类,明显复杂了不少。这就是抽象的代价了。

【转载】C++中引用的本质

转载自德明泰科技博客:http://blog.sina.com.cn/s/blog_5faf4b830100dwbd.html

一般的教材上讲到引用时,都是说“引用是对象的一个别名”。我认为这种定义是不清晰的,不利于初学者理解引用。至少我自己曾经被这个定义困扰了一段时间。到底什么是“别名”?

实际上,引用的实质是位于xxxxxx地址上的一个xxxx类型的对象。比如教科书上常用的例子:

int a = 5; //不妨假设编译器将a分配到0x400000

int &b = a;

这里面b的准确意义就是,放在0x400000地址上的一个int类型对象。这里面包括了两重含义,首先b是一个int类型对象,因此他的使用完全与int类型对象一样。另外这个int类型对象的地址是0x400000,因此从底层来看,它具有指针的一些特性,无论你怎样传递,他都代表放在0x400000的那个int。

在c++中,引用全部是const类型,定义之后不可更改。实际上“引用”对目标代码来说是不存在的,因为对于编译器来说,使用上例中的b就是使用0x400000地址的那个int。引用一经定义,就不会指向别的地址,也不会指向别的类型,因此编译器不会专门开辟空间存储这个引用,而是将发送引用的地方替换为真正的地址,接收引用的地方则替换为接受指针。

在java中,数据对象也都是引用类型,但是这里的引用与C++有很大不同,他们不是const类型,可以指向一个空值,也可以随时更改其指向的内存地址。这实际上与C++中的指针概念完全对应。java中的引用实际上对应C++中的指针而非引用,只不过是省去了C++中指针的取地址(&)与取值(*)操作。

Monday, May 11, 2009

【招聘】软件开发兼职人员

      共同抵御经济寒冬——北京德明泰科技有限公司招聘兼职启事
      现在,全球经济萧条,超过40%的大企业已经削减了IT开销预算,多家大型IT企业拟裁员或者叫停招工计划,IT工作者们面临着一个寒冬的考验。
      北京德明泰科技有限公司(http://www.domintec.com)承邀您的加盟,期待同您携手并肩,共同渡过这个寒冷的冬季。我们是一家专业从事软件外包的高新技术企业。软件工厂,是我们的发展方向。公司的主要经营项目包括企业日常运营管理系统,专业应用软件以及产品配套支持软件。
      公司致力于与国内企业,特别是中小企业在IT领域开展业务。完整的方案支持,专业的开发团队,卓越的产品质量,稳定的售后服务,严格的保密制度,在这些经营理念的基础上,我们已经与某些行业的尖端客户有了稳定的业务往来,并开始逐步拓展海外市场。
      虽然我们同样面临着经济危机的冲击,但由于工作需要,现向广大未来的IT精英们提供大量的兼职机会。先进的工作经验和不菲的报酬,也许可以帮助您顺利过冬。
职位:C/C++高级程序员
1、计算机专业,有工作经验者优先
2、精通c/c++,熟悉Windows编程,有MFC开发经验者优先
3、熟练使用微软VS集成开发环境
4、精通数据库,熟练使用mysql、sqlserver、oracle等数据库中的一种或以上
5、有面向对象分析设计的开发经验
6、有较强的自学能力和沟通能力,能刻苦钻研技术,具有一定的创新思维,富有团队协作精神
7、有良好的编码风格和文档编写习惯
8、具备一定的英语读写能力者优先
职位:JAVA高级程序员
1、计算机专业,有工作经验者优先
2、精通java编程,熟练使用JBuilder、Eclipse等开发工具,有.net开发经验者优先
3、精通数据库,熟练使用mysql、sqlserver、oracle等数据库中的一种或以上
4、有面向对象分析设计的开发经验
5、有较强的自学能力和沟通能力,能刻苦钻研技术,具有一定的创新思维,富有团队协作精神
6、有良好的编码风格和文档编写习惯
7、具备一定的英语读写能力者优先
职位:测试工程师
1、计算机专业,有工作经验者优先
2、了解测试原理、测试方法和测试过程,对软件测试有浓厚的兴趣
3、熟悉如何设计测试用例及自动测试,良好的问题分析和解决能力
4、能熟练地掌握一门或多门以下的技术:C/C++/Java;Linux/Unix Shell编程;Perl/Python/PHP
5、有较强的自学能力和沟通能力,富有团队协作精神
6、有良好的编码风格和文档编写习惯
有意者请把简历和相关材料发送至hr@domintec.com,我们真诚期待与您的合作。

Wednesday, May 6, 2009

cygwin shell syntax error near unexpected token

This is a common error and the reason is your are using Windows to create the file. Thus the line break is \r\n rather than \n.

All you need to do is change the format using a system command:

dos2unix filename_here