博文
DLL地狱及其解决方案(2008-01-22 22:57:00)
摘要:原作者:Ivan S Zapreev
译者:陆其明
概要
本文将要介绍DLL的向后兼容性问题,也就是著名的“DLL Hell”问题。首先我会列出自己的研究结果,其中包括其它一些研究者的成果。在本文的最后,我还将给出“DLL Hell”问题的一个解决方案。
介绍
我曾经接受过一个任务,去解决一个DLL版本更新的问题————某个公司给用户提供了一套SDK,这个SDK是由一系列DLL组成的;DLL中导出了很多类,用户使用这些类(直接使用或派生新的子类)来继续他们的C++程序开发。用户在使用这些DLL时没有得到很详细的使用说明(比如使用这些DLL中导出的类有什么限制等)。当这些DLL更新为新的版本之后,他们发现他们开发的基于这些DLL的应用程序会经常崩溃(他们的应用程序从SDK的导出类派生了新的子类)。为了解决这个问题,用户必须重新编译他们的应用程序,重新连接新版本的SDK DLL。
我将对这个问题给出我的研究结果,同时还有我从其它地方搜集过来的相关信息。最后,我将来解决这个“DLL Hell”问题。
研究结果
就我个人的理解,这个问题是由SDK DLL中导出的基类改动之后引起的。我查看了一些文章后发现,DLL的向后兼容性问题其实早有人提出。但作为一个实在的研究者,我决定自己做一些试验。结果,我发现如下的问题:
1. 在DLL的导出类中增加一个新的虚函数将导致如下问题:
(1)如果这个类以前就有一个虚函数B,此时在它之前增加一个新的虚函数A。这样,我们改变了类的虚函数表。于是,表中的第一个函数指向了函数A(而不是原来的B)。此时,客户程序(假设没有在拿到新版本的DLL之后重新编译、连接)调用函数B就会产生异常。因为此时调用函数B实际上转向了调用函数A,而如果函数A和函数B的参数类型、返回值类型迥异的话问题就出来了!
(2)如果这个类原本没有虚函数(它的父类也没有虚函数),那么给这个类增加一个新的虚函数(或者在它的父类增加一个虚函数)将导致新增加一个类成员,这个成员是一个指针类型的,指向虚函数表。于是,这个类的尺寸将会被改变(因为增加了一个成员变量)。这种情况下,客户程序如果创建了这个类的实例,并且需要直接或间接修改类成员的值的时候就会有问题了。因为虚函数表的指针是作为类的第一个成员加入的,也就是说......
函数返回指向自己的指针(2008-01-20 21:37:00)
摘要:#include <iostream>
using namespace std;
class FuncReturnItself;
typedef FuncReturnItself (*pFunc)(void);
class FuncReturnItself{
public:
FuncReturnItself( pFunc _p ){
_pFunc = _p;
}
operator pFunc(){
return _pFunc;
}
private:
pFunc _pFunc;
};
FuncReturnItself demoFunc(){
cout << "deomFunc" << endl;
return demoFunc;
}
int _tmain(int argc, _TCHAR* argv[])
{
pFunc p = demoFunc();
p();
system("pause");
return 0;
}......
C++编译器如何实现异常处理(2008-01-11 14:47:00)
摘要:C++编译器如何实现异常处理
作者:Vishal Kochhar 查看原文 翻译:局部变量
注:本文在网上已经有几个译本,但都不完整,所以我决定自己把它翻译过来。虽然力求信、雅、达,但鉴于这是我的第一次翻译经历,不足之处敬请谅解并指出。
与传统语言相比,C++的一项革命性创新就是它支持异常处理。传统的错误处理方式经常满足不了要求,而异常处理则是一个极好的替代解决方案。它将正常代码和错误处理代码清晰的划分开来,程序变得非常干净并且容易维护。本文讨论了编译器如何实现异常处理。我将假定你已经熟悉异常处理的语法和机制。本文还提供了一个用于VC++的异常处理库,要用库中的处理程序替换掉VC++提供的那个,你只需要调用下面这个函数: install_my_handler();之后,程序中的所有异常,从它们被抛出到堆栈展开(stack unwinding),再到调用catch块,最后到程序恢复正常运行,都将由我的异常处理库来管理。
与其它C++特性一样,C++标准并没有规定编译器应该如何来实现异常处理。这意味着每一个编译器的提供商都可以用它们认为恰当的方式来实现它。下面我会描述一下VC++是怎么做的,但即使你使用其它的编译器或操作系统①,本文也应该会是一篇很好的学习材料。VC++的实现方式是以windows系统的结构化异常处理(SEH)②为基础的。
结构化异常处理—概述
在本文的讨论中,我认为异常或者是被明确的抛出的,或者是由于除零溢出、空指针访问等引起的。当它发生时会产生一个中断,接下来控制权就会传递到操作系统的手中。操作系统将调用异常处理程序,检查从异常发生位置开始的函数调用序列,进行堆栈展开和控制权转移。Windows定义了结构“EXCEPTION_REGISTRATION”,使我们能够向操作系统注册自己的异常处理程序。struct EXCEPTION_REGISTRATION
{
EXCEPTION_REGISTRATION* prev;
DWORD handler;
};
&......
一个编译问题(2008-01-11 12:05:00)
摘要:感觉这篇文章很不错,就转贴过来了。
一个很巧妙的错误,欺骗编译器。
作者: Panic 2006年7月27日
很久没碰到什么有趣的问题了,今天看到一个,特写随笔一篇以作留念:P
问题的来源:
一种让另外的类对本类成员只读,本类对本类成员可读写的方法。 [所有相关帖子]
例子
//a.h
class a
{
public:
void test();
#ifndef A_CPP
const
#endif
int b;
};
//a.cpp
#define A_CPP
#include "a.h"
#undef A_CPP
void a::test
{
b = 12; //可以写
}
//b.cpp
#include "a.h"
void b()
{
a _a;
_a.b = 12;//编译错误 不可写
}
书童 xulingfv 发表于 2006-7-27 10:46:09
周星星的代码:
// a.hpp
struct foo
{
foo();
#ifdef SOMETHING
int a;
#endif
int b;
};
// a.cpp
#define SOMETHING
#include "a.hpp"
#undef SOMETHING
foo::foo()
{
a = 1;
b = 2;
}
// main.cpp
#include "a.hpp"
#include <iostream>
int main()
{
foo test; ......
星星写的俄罗斯方块(2008-01-10 17:39:00)
摘要:#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
class Console
{
public:
Console()
{
hStdOutput = INVALID_HANDLE_VALUE;
hStdError = INVALID_HANDLE_VALUE;
}
bool Open( void )
{
hStdOutput = GetStdHandle( STD_OUTPUT_HANDLE );
hStdError = GetStdHandle( STD_ERROR_HANDLE );
return INVALID_HANDLE_VALUE!=hStdOutput && INVALID_HANDLE_VALUE!=hStdError;
}
inline bool SetTitle( char* title ) // 设置标题
{
return TRUE==SetConsoleTitle(ti......
函数指针与函数名(2008-01-10 14:40:00)
摘要:迷惑很久的问题了,今天正好又遇到这个问题,就抽了个时间研究总结了一下把它记录下来。
和同学讨论一个typedef的问题,实验过程中偶然发现VS2005的行为:
int fun(){
cout << "fun()" <<endl;
return 43;
}
int (*callback())(){//对
return fun; //returns function pointer
}
int (callback())(){//不对,报错:function returns function
return fun; //returns function
}
C99中的一个例子:
typedef int F(void); // type F is "function with no parameters
// returning int"
F f, g; ......
M$ Unicode参考(2008-01-09 16:08:00)
摘要:
Initial Steps for Unicode-enabling Microsoft C/C++ Source
Define _UNICODE, undefine _MBCS if defined.
Convert literal strings to use L or _T
Convert string functions to use Wide or TCHAR versions.
Clarify string lengths in API as byte or character counts. For character-based display or printing (as opposed to GUI which is pixel-based) use column counts, not byte or character.
Replace character pointer arithmetic with GetNext style, as characters may consist of more than one Unicode code unit.
Watch buffer size and buffer overflows- changing encodings may require either larger buffers or limiting string lengths. If character size changes from 1 byte to as many as 4 bytes, and string length was formerly 20 characters and 20 bytes, either expand the string buffer(s) from 20 to 80 bytes or limit the string to 5 characters (and therefore 20 bytes). Note maximum buffer expansion may be constrained (for example to 65 KB). Reducing string length to a fixed number of characters m......
const修饰的谁?(2007-10-30 11:28:00)
摘要:问题源于今天我于csdn上看到的一个帖子:
const引用的疑惑
例子1:
#include <iostream >
#include <typeinfo >
using namespace std;
typedef char* PCHAR ;
int main(int argc, char *argv[])
{
cout < <typeid(const PCHAR).name() < <endl;
cout < <typeid(const char*).name() < <endl;
return 0;
}
输出结果:
char *
char const *应该两者都是char const *类型啊? 为什么不一样?
例子2:
#include <iostream >
#include <typeinfo >
using namespace std;
template <class T >
void TestFunc2(const T &pConstRef)
{
cout < < pConstRef < <endl;
pConstRef[0] = 'S '; cout < < pConstRef < < endl;
}
关于类的大小问题(2007-10-19 11:20:00)
摘要: 一直以来在各个论坛上都不时的见过一些关于类大小的讨论,尤其是当涉及到虚继承时,类的大小就变得更加扑朔迷离,每看完一个帖子都觉得自己有所收获,但当下次遇到类似的帖子时却怎么也想不起自己以前对此问题的记忆了,于是乎,干脆勤快些一劳永逸地把他们记录下来。纯属个人理解,难免有错,我会定期更新这篇文章,修改其中的错误之处。本文仅供参考!也希望诸位高手多多纠正其中的错误。谢谢!
关于类的大小问题,有些是和编译器有关的。很多技术取决于编译器的具体实现。所以编译器不同,得到的结果有时会有所出入。下面所有的测试不加声明的话都是基于VS2005。
为使问题更加清晰,下面的讨论将不考虑对齐问题。
#pragma pack(1)
class a {
char i;
int ii;
};
class b: virtual public a{
char c;
};
class c: virtual public a{};
class d: public b, public c{};
#pragma pack()
int main(int argc, char *argv[])
{
cout << "a size is " << sizeof(a) << endl;
cout << "b size is " << sizeof(b) << endl;
cout << "b size is " << sizeof(c) << endl;
cout << "d ......
C++中的名字查找(2007-10-18 09:43:00)
摘要:本文引用自:Herb Sutter 中文博客
原文地址:http://tb.blog.csdn.net/TrackBack.aspx?PostId=1677385
Vijay Visana最近发邮件问我两个问题。内容如下(为了更适合阅读,我做了简单修改。译者在此基础上又做了修改):
我在C++的多继承上遇到了很大麻烦。
如图1,A、B1和B2为纯抽象类;C从B1、B2多继承,且实现了全部父类的抽象方法。
现在:
C* p = new C;
p->Method_of_A(); //从B1、B2都能得到被调用方法,为什么编译器不报“二义性”(ambiguity)错误呢?
而按图2结构实现继承关系后:
B4* p = new C;
p->Method_of_A();
编译器(VC++)认为有二义性。经调试我发现编译过程中使用了“adjustor thunk”(译者注:具体请参看http://blog.sina.com.cn/u/491874bb010004xq或Stan Lippman的《Inside the C++ Object Model》)。希望您能解答这两个问题,以帮助我更好理解C++(更确切的说是VC++)中的MI(多继承)机制。
好,我们深入研究下这个问题。
上述编译器行为的差异,与继承关系的复杂度、vtable以及adjustor thunk并无直接关系,它其实就是一个名字查找(name lookup)过程(以本例而言,就是查找方法“Method_of_A”)。
在C++中,函数编译时检查过程如下:
第一步,执行名字查找(name lookup):在调用类中查找,并生成候选列表;若候选列表为空,再扩大查找范围(如名字空间内,或父类);如此循环。如果最终无结果,那么抱歉,就会提示你“名字未能找到”;否则,编译器跳到第二步。
第二步,执行重载辨别(overload resolution):如果第一步得到的候选者个数大于一,编译器将以传递给函数的参数及其类型为依据,尝试找到最佳答案。如果无法据此确定最优者,就会报告“存在二义性调用”。
第三步,可见性检查(accessibility checking):编译器检查是否可真正执行调用(比如,......