Opaque Pointers——不透明指针

本文探讨了不透明指针的概念及其在C/C++中的应用,通过实例展示如何使用不透明指针提供强大的抽象,解决库升级时的兼容性问题。
 

Programming Tools - Opaque Pointers

by Chris McKillop

One of the most powerful concepts when writing software is abstraction - hiding the details of a system behind a simpler interface. This article shows you how to use a language feature of C (and C++) to provide a very powerful form of abstraction for use in libraries: opaque pointers.

C/C++ has an interesting language feature. If you declare a typedef of a structure pointer, you don't need to provide that structure's definition. For example:

typedef struct _hidden_struct *handle;

This has declared a new type, handle, which is a pointer to a struct _hidden_struct. What's interesting about this? Mainly the fact that you can now use this handle type without ever having a definition of the structure it's a pointer to. Why is this powerful? That's what this article will show you (some people might already see why). Please note that the typedef above (and those used throught this article) aren't strictly needed to hide the structure's internal definition - you can just keep using the struct keyword as part of the name. However, the typedefs are used to make an abstraction away from the fact that there's a structure at all, instead of turning it into a "handle."

Sample problem

To really see the power of opaque pointers, we'll need a problem to solve. Let's suppose we want to make an image library that loads and saves bitmaps. We know that as time goes on this library will need to be able to do more things (new image types, basic transforms) and we want to preserve both compile time and runtime compatibility for this library. This means that if we have an older application, it should work with a new shared library and that if that older application is rebuilt against the new library and headers, it should still build without errors.

What about C++?

Before this goes much further, I need to address the waving hands of all the C++ fans out there, who might be thinking: "C++ already lets me do all of this behind a nice class interface with inheritance and other nice C++ language features". And this is pretty much true in the case of compile-time compatibility. But, because C++ doesn't let you separate the public and private definitions of a class, the way the class is declared changes its runtime behavior (class size, vtable offsets, etc.) - you can't provide runtime compatibility without a lot of tender care (and sometimes special compilers). So, this is still useful to C++ people, even if the slant is more towards those of us using C.

Okay, back to the library. When people design libraries like this, they'll often declare a structure that will get filled in by the library and an API for manipulating this structure. For example:

typedef struct

{

void *ptr;

int size;

int bytes_per_pixel;

} bitmap_t;

int bitmap_load_file( char *filename, bitmap_t *bitmap );

int bitmap_save_file( char *filename, bitmap_t *bitmap );

So, to use this library, you would declare a bitmap_t variable and invoke bitmap_load_file() with a filename and a pointer to the bitmap_t that was declared. For example:

bitmap_t bitmap;

int ret;

ret = bitmap_load_file( "sample.bmp", &bitmap );

Then the user of the library could simply access the pointer inside the structure and, along with the size and bytes_per_pixel members, go to work on the image. However, because the contents of bitmap_t are known publicly, if the library is updated with new entries in that structure, then the size of the structure will have been changed. And applications that use an updated shared library will probably crash or Other Bad Things when they call into the new library with a structure of a smaller (different) size.

Alternatively, the API could be defined to take a structure size:

int bitmap_load_file( char *filename, bitmap_t *bitmap, int size );

int bitmap_save_file( char *filename, bitmap_t *bitmap, int size );

and would be used like this:

ret = bitmap_load_file( "sample.bmp", &bitmap, sizeof( bitmap_t ) );

Which effectively tags the version of the library by using the size of the structure. However, this makes for a terrible support nightmare inside the library where the structure size has to be checked and different code paths are taken based on this structure size. This leads to a lot of code bloat.

We could also try to avoid the structure size issue by padding the structure with some "reserved" space:

typedef struct

{

void *ptr;

int size;

int bytes_per_pixel;

char reserved[64];

} bitmap_t;

But this is only a temporary fix. What if we didn't choose a value that's large enough? Then we're back to the case where a new library causes problems. If we're liberal with our reserved space, then we waste memory. (Since you're reading this on a QNX web page, I'm guessing that wasting memory doesn't sit well with you either.)

Another problem common to all of these approaches is what can occur if the layout of the structure changes. Say a new version of the library is built with a structure definition that looks like this:

typedef struct

{

int version;

void *ptr;

int size;

int bytes_per_pixel;

} bitmap_t;

Then the compiled apps will also get "confused", since what was previously the structure member ptr is now version, and so on. The position of a structure member within a structure is important. Seems pretty much impossible to meet our goals, huh? Fear not, loyal readers, the situation isn't that dire!

Hiding the structure's internals

The common thread to all the situations above was that the compiled application was aware of the size of the structure and the location in the structure of the structure members. So we need to hide the internals of the structure and provide access functions to get the important data out of the structure.

Let's try out a new public interface:

typedef struct _internal_bitmap * bitmap_t;

int bitmap_alloc( bitmap_t *bitmap );

int bitmap_free( bitmap_t *bitmap );

int bitmap_load_file( bitmap_t bitmap, char *filename );

int bitmap_save_file( bitmap_t bitmap, char *filename );

int bitmap_get_ptr( bitmap_t bitmap, void **ptr );

int bitmap_get_size( bitmap_t bitmap, int *size );

int bitmap_get_bpp( bitmap_t bitmap, int *bpp );

And now we can maintain a private interface that applications never get to see or use, only the library:

struct _internal_bitmap

{

void *ptr;

int size;

int bytes_per_pixel;

}

Did you notice the opaque pointer? Also, notice we've added "access functions" to get the interesting data from the new bitmap "handle"? It's pretty obvious now that we're passing in a bitmap_t (a structure pointer) as a handle to the library, but the alloc and free functions are a little confusing for people.

When we declare a bitmap_t, we're really just declaring a pointer to a structure, so we need to provide some memory for that pointer to point at. Here are the guts of the bitmap_alloc() function:

int bitmap_alloc( bitmap_t *bitmap )

{

struct _internal_bitmap *handle;

handle = ( struct _internal_bitmap * )malloc( sizeof( *handle ) );

if( handle == NULL )

{

return -1;

}

memset( handle, 0, sizeof( *handle ) );

*bitmap = handle;

return 0;

}

Since a bitmap_t is just a struct pointer, we allocate the proper sized struct (which we can do - this code is part of the library and it knows how big the structure is). Once we've verified that the malloc() didn't fail, we assign the newly allocated structure to the bitmap_t pointer. So when the application calls this function, it will get back the proper sized structure to pass into the rest of the library functions.

Here's an example of an "access function" that uses the allocated bitmap handle:

int bitmap_get_ptr( bitmap_t bitmap, void **ptr )

{

if( ptr == NULL )

{

return -1;

}

*ptr = bitmap->ptr;

return 0;

}

Since the library knows the definition of the _internal_bitmap structure, it can directly access its members. If you tried to access the internals of the bitmap_t handle in application code, the compiler would return an error, because it has no idea how the structure is organized or what any of its members are named.

For the last bit of code, I'll write a function that loads a bitmap, sets all the pixels to 255, and writes the bitmap back:

int turn_bitmap_white( char *filename )

{

int ret, i;

bitmap_t bitmap;

unsigned char *ptr;

int size;

ret = bitmap_alloc( &bitmap );

if( ret )

return ret;

ret = bitmap_load_file( bitmap, filename );

if( ret )

return ret;

ret = bitmap_get_ptr( bitmap, (void **)&ptr );

ret |= bitmap_get_size( bitmap, &size );

if( ret )

return ret;

for( i=0; i<size; i++ )

{

ptr[i] = 255;

}

ret = bitmap_save_file( bitmap, filename );

if( ret )

return ret;

bitmap_free( &bitmap );

return 0;

}

Problem solved

So, we've solved our problem. If we change the structure layout, we'll be okay, since the application code can't access the internals of the structure directly and must use "access functions" to get at the internal data.

If we change the size of the structure, we'll be okay, since the library itself allocates the memory for the structure and knows the proper size of the structure to allocate for the given version of the library. You can now replace the library (in shared library form) without having to rebuild any applications. And you can rebuild applications against the library without change. Now, this does assume that you haven't changed the API to your library - opaque pointers are a powerful tool, but they can't perform magic.

Lastly, the use of opaque pointers enforces good programming practice by providing a defined interface (abstraction) between the application and the library. This is usually a good method even when doing your own projects, since it lets you easily separate functionality for future projects!

 

 

http://blog.chinaunix.net/space.php?uid=13701930&do=blog&id=336480

蓝色部分为自己增加内容

不透明类型

不透明数据类型隐藏了它们内部格式或结构。在C语言中,它们就像黑盒一样。支持它们的语言不是很多。作为替代,开发者们利用typedef声明一个类型,把它叫做不透明类型,希望其他人别去把它重新转化回对应的那个标准C类型。通常开发者们在定义一套特别的接口时才会用到它们。比如说用来保存进程标识符的pid_t类型。该类型的实际长度被隐藏起来了——尽管任何人都可以偷偷撩开它的面纱,发现它就是一个int。如果所有代码都不显式的利用它的长度(显式利用长度这里指直接使用int类型的长度,比如说在编程时使用sizeof(int)而不是sizeof(pid_t)),那么改变时就不会引起什么争议,这种改变确实可能会出现:在老版本的Unix系统中,pid_t的定义是short类型。

       另外一个不透明数据类型的例子是atomic_t。在第八章“内核同步方法”中介绍过,它放置的是一个可以进行原子操作的整型值。尽管这种类型就是一个int,但利用不透明类型可以帮助确保这些数据只在特殊的有关原子操作的函数中才会被使用。不透明类型还帮助我们隐藏了类型的长度,该类型也并不总是完整的32位。

内核还用到了其它一些不透明类型,包括dev_t,gid_t和uid_t等等。处理不透明类型时的原则是:

¨         不要假设该类型的长度。

¨         不要将该类型转化回其对应的C标准类型使用。

¨         编程时要保证在该类型实际存储空间和格式发生变化时代码不受影响。

===================================================================
 
问:glib里_GMutex类型是在哪里定义的呀? 找了一圈没看到。

只看到个 typedef sturct _GMutex GMutex; // glib/gthread.h P76  glib-2.29.10
 
答:The GMutex struct is an opaque data structure.
 
Opaque data types do not reveal their internal format or structure. They are about as "black box" as you can get in C. There is not a lot of language support for them. Instead, developers declare a typedef, call it an opaque type, and hope no one typecasts it back to a standard C type. All use is generally through a special set of interfaces that the developer creates.
 
See 《Linux_Kernel_Development_Second_Edition》or
Google "Opaque data type"
 
* Sometimes OPAQUE means "this function doesn't return anything at all",
similarly to "returns void" in C and some other languages.

* Sometimes OPAQUE means "this value can be any datatype at all" (eg,
input of the count() aggregate).

* Sometimes OPAQUE means "I know exactly what this should be, but it's
an internal datatype with no SQL-level equivalent". This is usually
what's at stake with a trigger function.

* Sometimes OPAQUE means "I know exactly what this should be, but I
haven't declared it yet" (ie, a forward reference). This is needed
to declare I/O functions of user-defined types, since the system
requires the functions to already exist when the type is declared.
 
如果声明了_GMutex,那么GMutex就变成了普通的结构,如果不声明他就是一个不透明类型。

再说,mutex类型是内核对象,必须定义在内核中,不依赖于系统是很难实现的。

不透明类型是一种灵活的类型,他的大小是未知的。所以 一般只能定义GMutex *变量,不能定义GMutex对象。然后通过一套接口来操作GMutex,就是
代码:

struct _GThreadFunctions
{
  GMutex*  (*mutex_new)           (void);

  GMutex*  (*mutex_lock)           (void);
  GMutex*  (*mutex_trylock)           (void);
  GMutex*  (*mutex_unlock)           (void); 

  GMutex*  (*mutex_free)           (void); 
 /* .... */
};

 下面是win32中对mutex_new 的实现
  static GMutex * g_mutex_new_win32_cs_impl(void)
  {
        CRITICAL_SECTION * cs = g_new(CRITICAL_SECTION ,1);
        gpointer * retval = g_new(gpointer, 1);
         InitializeCriticalSection(cs);
         *retval = cs;
         return (GMutex *)retval;
  }
  // GMutex隐藏了实际具体平台相关的结构,相当于对用户统一表示,GMutex只是作为一种标示来使用,用户是不可见的。
这有点像C++的interface, 那一堆函数指针就是vtable。这张表要进行初始化,间接让他们指向操作系统提供的API。
所以就没有定义_GMutex必要,因为这份数据已经定义在系统核心中,我们只需要一个叫GMutex的名字来引用这份数据。

 

内容概要:本文提出了一种面向综合能源系统的算力-电力-热力联合优化调度策略,并提供了基于Matlab的代码实现。该策略聚焦于多能源耦合系统的协同优化,通过整合计算资源(算力)、电力系统与热力网络,实现能源生产、传输与消耗的全局优化调度。研究充分考虑了系统中电、热、算力等多种能源形式之间的相互转换与动态耦合关系,旨在提升综合能源系统的整体能效、增强运行灵活性并降低综合运行成本。文中系统性地阐述了优化模型的构建过程,包括多目标函数的设计、复杂物理与运行约束条件的数学表征,以及高效求解算法的选择与Matlab编程实现,最终通过严谨的仿真实验验证了所提策略在提升能源利用效率和系统经济性方面的有效性与优越性。; 适合人群:具备一定电力系统、能源工程、热力学或运筹优化理论基础,熟悉Matlab编程语言,从事综合能源系统、智能电网、智慧数据中心或相关领域研究的研发人员、工程技术人员及高校研究生。; 使用场景及目标:① 深入学习和理解综合能源系统中多能流(电、热、算力)协同优化的建模方法与理论基础;② 掌握利用Matlab进行复杂能源系统优化调度问题的数学建模、编程求解与仿真分析的具体技术流程;③ 为相关领域的科研课题、学位论文、工程项目或政策制定提供可复现的代码范例、技术参考与决策支持。; 阅读建议:在学习过程中,应紧密结合提供的Matlab代码来深入理解优化模型背后的数学原理、算法逻辑与工程含义,建议动手调试、运行并修改代码中的参数与模型结构,以直观感受不同场景和变量对优化结果的影响,从而深化对多能耦合系统调度机制与优化规律的认识。
商城主题管理:DIY设计,商城由你定义 如果你希望进一步个性化设计,v3.0还带来了全面进化的商城页面DIY装修能力,让有设计需求的用户获得媲美专业工具的灵活度。 超级组件,自由DIY 这次更新还增加了一个亮点组件——超级组件 传统DIY组件一般布局固定,商家想要设计个性化的组件比较困难。这次,超级组件直接给足组件DIY设计超高自由度。 四大数据源打通:用户、文章、优惠券、商品,四大系统核心数据可直接接入组件,成为承载业务数据的动态载体。 布局自由如画板:组件内部支持自由拖拽规划,图片、文本、图标等视觉元素随意组合排列,操作体验媲美PS,想怎么排就怎么排。 创意不被组件形态绑架:不再是“选一个组件然后填内容”,而是“想到什么效果就用超级组件设计”,千变万化的页面布局从设想变成现实。 以前是组件决定你能做什么,现在是你决定组件长什么样。 商城风格自定义 除了让页面DIY设计变得更加灵活,这次的商城主题管理中,还增加了商城风格自定义功能,让品牌视觉管理变得更简单: 自定义风格色系:设定品牌专属主色调,主题风格全局联动,色彩统一有质感。 风格库管理:支持提前预设多套风格方案,大促、节日、日常运营各备一套,需要时一键切换,零等待上线。 品牌大促要热烈、节日活动要应景、日常运营要清爽……不同场景,不同风格,随时切换。 我的主题,私有模版库 商城主题功能中还新增了【我的主题】管理,这里就是你的私有模板工坊。 自建主题:商家自主创作并保存的主题模板集中管理于此。不同风格、不同场景、不同活动节点的模板都可以在这里保存积累。需要更换商城主题时,一键切换,不用再重新设计。 导入/导出:模板支持文件化流转,团队协作、跨环境迁移、备份存档轻松搞定。 一键切换:已保存模板随时启用,无需重复设计,运营效率翻倍。
内容概要:本文针对弱电网环境下光储虚拟同步发电机(VSG)并网系统中存在的谐波阻抗不匹配与稳定性问题,提出一种自适应虚拟谐波阻抗抑制方法。通过建立VSG的正负序阻抗模型,深入分析其在弱电网中的谐波交互特性,设计了能够动态调节虚拟阻抗参数的自适应控制策略,以有效抑制并网电流谐波,提升系统在电网阻抗变化和不平衡工况下的稳定性和电能质量。研究基于Simulink搭建完整的光储VSG并网仿真系统,对所提方法在稳态运行、电网波动及不对称故障等多种场景下的性能进行了全面验证,结果表明该方法显著增强了系统的鲁棒性、适应性与动态响应能力。; 适合人群:具备电力电子、新能源发电或自动控制等相关领域基础知识,从事电力系统稳定性、并网控制策略研究的研究生、科研人员及工程技术人员。; 使用场景及目标:① 深入分析弱电网条件下VSG并网系统的谐波振荡与失稳机理;② 设计并验证自适应虚拟阻抗控制策略以提升系统稳定性与电能质量;③ 利用Simulink进行复杂电力电子系统的建模、仿真与控制算法开发; 阅读建议:建议结合文中涉及的谐波线性化建模与正负序阻抗分析方法,深入理解VSG的动态响应特性,并动手复现Simulink仿真模型,通过调整电网阻抗、负载条件及故障类型等参数,系统性地测试控制策略的有效性与鲁棒性,从而全面掌握自适应虚拟谐波阻抗的设计原理与工程应用价值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值