之前写的LIBPNG库学习小结介绍了怎么样自定义LIBPNG库的write、read、flush函数,而不使用LIBPNG库提供的默认函数。
上一篇讲述的都是在单线程的情况下,今天将程序升级,放在多线程下面跑,发现了几个问题:
首先说明一下:上一篇中是用struct保存的数据结构,而这次需要将数据封装在类中,因此程序有点小变动。以下是类的部分定义:
private: png_infop m_pInfo; png_structp m_pPng; char *m_pImage;
其中m_pImage就是上一篇tData中的data。
上一篇中tData是全局定义的,在多线程的情况下会发生争抢资源的情况,可查看上一篇中的代码,在void PNGAPI png_own_write_data(png_structp png_ptr, png_bytep data, png_size_t length)函数中需要一个变量来存储每次写入的位置,不能每次都从第0个位置开始写吧?会争抢的资源就是这个记录位置的变量。这该怎么解决呢?
1-首先想到的办法就是将所有数据成员和函数成员作为类的私有成员访问即可解决问题,可是编译的时候却提示png_own_write_data()和png_own_flush()函数出错,具体错误信息就不写了,此路不通。
2.-加锁可以解决这个问题,但是加锁效率很低,怎么办呢?有没更好的办法?
想到的解决办法:
1-在png_struct中找一个闲置的字段来存储这个变量,可是我看完了png_struct的定义,也不确定那个变量是没有用的,虽然有很多字段的值从始至终都是0,但是我还是不敢贸然使用,因为不知道什么时候这个值就被使用了。
2-还是在png_struct中找一个闲置的字段,不过这次不是使用某个int或long型的变量了,而是使用函数指针,没错,就是 函数指针,因为任何指针在系统中都是一个内存地址(4字节),可以当做一个int类型来使用。
下面就详细的介绍一下这种方法:
1-首先锁定的是output_flush_fn,在上一篇中也说了,默认情况下
PNG_WRITE_FLUSH_AFTER_IEND_SUPPORTED
这个宏是没有被定义的,因此output_flush_fn()这个函数就一直不会执行,所以output_flush_fn这个函数指针是一直没有使用的,但是若使用自己编译的LIBPNG库,而恰好定义了上面的那个宏,那么这样程序就会发生意想不到的错误。因此此方法可以用,但是要慎重。
2-根据read和write的互斥性,我们可以交错的使用write_data_fn和read_data_fn这两个函数指针来保存这个偏移地址。因为我们一般不会在读的时候进行写,也不会在写的时候进行读操作(要是有这种情况,你还是老老实实的使用output_flush_fn吧,这种情况不适合你),所以我们在写的时候使用read_data_fn来保存写偏移地址,在读的时候使用write_data_fn来保存读偏移地址,具体请见代码:
void PNGAPI pngOwnReadData( png_structp pPng, png_bytep data, png_size_t length ) { png_bytep src = (png_bytep)pPng->io_ptr; //强制类型转换,获得偏移地址 int offSet = (int)pPng->write_data_fn; memcpy( data, src + offSet, length ); offSet += length; //将偏移地址强转成png_rw_ptr类型,该类型是png库定义的函数指针类型 pPng->write_data_fn = (png_rw_ptr)offSet; }
而在读取的主函数里面必须有这样的代码:
void readPNG(…… ) { png_voidp io_ptr = (void *)image; //将write_data_fn初始化为0 m_pPng->write_data_fn = (png_rw_ptr)0; png_set_read_fn( m_pPng, io_ptr, pngOwnReadData ); png_read_png( m_pPng, m_pInfo, PNG_TRANSFORM_EXPAND, NULL ); }
同样的写函数也具有同样的结构:
void PNGAPI pngOwnWriteData(png_structp pPng, png_bytep data, png_size_t length) { …… png_bytep src = (png_bytep)pPng->io_ptr; int offSet = ( int )pPng->read_data_fn; memcpy( src + offSet, data, length ); offSet += length; pPng->read_data_fn = (png_rw_ptr)offSet; …… }
char* writePNG(…… ) { …… png_voidp io_ptr = (void *)m_pImage; m_pPng->read_data_fn = (png_rw_ptr)0; png_set_IHDR( m_pPng, m_pInfo, width, height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT ); png_set_rows( m_pPng, m_pInfo, rgb ); png_set_write_fn( m_pPng, io_ptr, pngOwnWriteData, NULL ); png_write_png( m_pPng, m_pInfo, PNG_TRANSFORM_EXPAND, NULL ); //在对象销毁前获取长度信息 length = (int)m_pPng->read_data_fn; png_destroy_write_struct( &m_pPng, &m_pInfo ); …… }
好,到此为止我们就实现了即保证了多线程的安全性,又保证了程序的效率,搞定,收工……