« 用四叉树管理散布在平面上的对象 | 返回首页 | 三人成虎 »

Windows 和 Unix 下动态链接库的区别

最近慢慢将开发环境转向了 freebsd ,渐渐的发现了许多东西跟 Windows 下不太一样。我指的是,跟我以前想当然的理解不太一样。比如,unix 下对动态链接库 so 的处理,和 Windows 下的 DLL 就不太相同。

之前,我对 Windows 下更为了解一些。早几年做 Windows 开发,老是为动态链接,静态链接这些问题纠缠不清;慢慢的才有了比较清晰的理解,现在基本上不会为这类问题困绕了。前段时间,碰到一些朋友写 lua 的扩展库(windows 版本)时遇到的几个 bug ,就是因为链接问题导致的。公司内部的一个项目,在服务器程序上也碰到了错误链接 lua 引起的 bug ,在内部 maillist 上争论了好久。当时,作为 windows 程序员,跟同事中的 unix 程序员争论看似相同的问题时,却老说不到一起去;今天才发现,原来是相互之间对动态链接库的理解不同导致的。

今天,写下本文,或许可以让以后跨平台开发的朋友少走点弯路。

动态链接库在 unix 下,习惯以 .so 为文件名结尾(通常还以 lib 开头)。而 Windows 下是以 .DLL 为文件后缀。Windows 在处理 dll 上还有一些细节容易被人忽略,我曾经为这个写过一篇 Blog

如果需要运行时主动加载一个动态链接库,windows 下可以使用 LoadLibrary 这个 kernel API (在 kernel32.dll 中);unix 下是用 dlopen 。Windows 下找到 dll 中导出符号的地址,可以用 GetProcAddress ,而 unix 也有对应的 api ...

这些相互对应的 api ,似乎预示着对等的功能,但事实上是有区别的。

DLL 事实上和 EXE 文件一样,同属 PE 格式的执行文件。对于隐式的引用外部符号,需要把外部符号所在的位置写在 PE 头上。PE 加载器将从 PE 头上找到依赖的符号表,并加载依赖的其它 DLL 文件。

但是,unix 上并非如此,so 文件大多为 elf 执行文件格式。当它们需要的外部符号,可以不写明这些符号所在的位置。也就是说,通常 so 文件本身并不知道它依赖的那些符号在哪些 so 里面。这些符号是由调用 dlopen 的进程运行时提供的。而 unix 下的执行文件本身会暴露自己静态链接的符号,(可以是自己本身实现的,或者是从静态库 .a 文件里链入的)。dlopen 将把这些符号通报给 dlopen 加载的 .so 文件,最终完成动态链接。(事实上 dlopen 还可以指定 mode ,完成更复杂的操作)

因为这个区别,unix 下的 lua 解释器可以完全静态链接所有的 lua api ;我们为 lua 扩展的库,以 so 的形式存在被运行时加载不会有任何隐患。而 Windows 下,必须生成一个 luacore 的 DLL 文件,由 lua 解释器于扩展库共享 lua api (还包括 crt 的实现) 才不会出问题。

也因为这个区别,VC 下才会有让 windows 开发新手困惑不解的动态链接 CRT ,静态链接 CRT ,多线程库,单线程库,等等的选项。没点点 windows 开发功力的人,多少都要在上面栽几个跟头。

从动态链接库的这个设计上来看,我个人感觉,Windows 弄的真是糟糕透顶。尤其是对开发者来说是这样。至少我们在 windows 下做一个 dll 文件给大家使用还需要携带一个 .lib 文件;而 unix 下一般只需要有相应的头文件就够了。对于编写新的 .so ,找不到的符号可以就让它在那里,直到最终执行文件来把所有需要的符号联合到一起。windows 可以存在一个 dll 对另一个 dll 的隐式依赖;而 unix 下一般不需要让 so 和 so 有隐式依赖关系。这让我们全局替换类似 CRT 的东西变的困难许多;而以我自己的编程经验来看,好处却没有多少。

顺便一提的是 unix 下需要用 ldconfig 来管理动态库,这比 windows 下 copy DLL 到当前目录下就可以用的方式,无疑提高了系统的安全性。

Comments

时隔多年来留个爪

windows的这种dll机制在著名教科书《程序员的自我修养》里仔细分析过。windows搞的这么麻烦,其实就是考虑到调用dll接口时的效率。

linux的“地址无关”调用方式在调用dll内的函数时会白白多走好几个汇编指令。对于windows这种完全架设在dll之上的系统,可能会有效率问题吧。
linux也支持另一种so机制,编译so时不加 -fPIC 的,可以用内存空间换调用时间。

/*
* Memory DLL loading code
* Version 0.0.2
*
* Copyright (c) 2004-2005 by Joachim Bauch / mail@joachim-bauch.de
* http://www.joachim-bauch.de
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MemoryModule.c
*
* The Initial Developer of the Original Code is Joachim Bauch.
*
* Portions created by Joachim Bauch are Copyright (C) 2004-2005
* Joachim Bauch. All Rights Reserved.
*
*/

/*
* Memory DLL loading code
* Version 0.0.2
*
* Copyright (c) 2004-2005 by Joachim Bauch / mail@joachim-bauch.de
* http://www.joachim-bauch.de
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MemoryModule.c
*
* The Initial Developer of the Original Code is Joachim Bauch.
*
* Portions created by Joachim Bauch are Copyright (C) 2004-2005
* Joachim Bauch. All Rights Reserved.
*
*/

关于这个“unix 下需要用 ldconfig 来管理动态库”,

Linux 下是可以用 LD_LIBRARY_PATH=. 环境变量来直接用 copy 到目录的 .so

@est,

网上邻居里运行程序不是通过 RPC 方式的哦 :D 你运行别人机器上的程序,执行代码的还是你自己的 CPU 而不是远程的。而 RPC 通常指在远程运行代码,通过协议传递输入输出数据。

这的确是在非本地文件系统上运行程序。不过我前面意思表达的不清楚,不是想说这个。

在我看到的范畴内,windows 系统上执行一个 exe 或 dll 都要求有一个 image path 。远程文件也是有这个 image path 的,只不过是用 \\ 开头。

其解析的过程可以由 netbios 来完成。

我没有找到方法在 windows 下自己写一段程序,凭空加载一个 DLL 数据块 (没有 image path 的,仅仅是我的进程空间内的一个数据块),而不需要自己去解析 PE 格式。

怎么以系统的方式从非本地文件加载DLL

网上邻居的游戏,直接双击就可以运行,这个应该是通过RPC调用的exe和dll吧

那个anonymous和enterpriseing什么鸟人,在这一知半解的玩弄文字概念,说了半天我也没觉得你说的有什么实质意义

什么流不流的,同样是signal,unix signal和windows还有一些应用层的意义就不一样,根本不是一回事,有的甚至是event,争个鸟。

unix是30年前的又怎么样,老的东西不能描述现代的情况不代表落后,人家可以很方便的“进化”一下,dll和so真有这么本质的区别?什么兼容不兼容的,和dll有关系吗?是先进性的体现?
我怎么看glibc可以往前兼容呢。

讲废话的人太多,都没讲到点子上还那么多废话。废话多的人就有问题,搞了半天文字概念也没搞清楚。

搞这行讲的是悟性,不是牛b.牛b等于菜鸟,是自我感觉良好。假牛b一看就知道,还冲老大,什么现在结构就是微内核。

我看你简单就是美都没领悟

还是加上审核吧

每个DLL有自己的namespace,因此不同的DLL可以有同名的函数。所有使用so的image共享同一个namespace,so 可以指定需要连接哪些so,需要哪些函数,却无法指定函数在哪个so里。而dll生成后要依赖的函数所在其他dll就固定死了。

Linux 内合理也是这个样子,所以符号冲突的概率要比 windows 内核驱动大得多。

楼下已经写过了 :)

"理论上, windows 下编译器在生成 dll 时随带生成的 lib 文件里的信息大多数情况可以完全从 dll 文件中萃取出来。这种工具不难实现,也有这样的工具。"

在下面留言中可以找到。

这个帖子讨论的问题已经没有什么意义了。 IMHO

这个…… 好像 gcc 可以直接用 dll 不用带 lib 难道没人知道?为什么楼下一直说 dll 一定要带一个 lib ?而且根据标准的名字搅乱机制,好像是可以直接从 dll 生成出 lib 的

一段时间没来看,这里变成天涯杂谈了,技术高也没什么了不起,不懂技术也并不丢人.搞技术的就讨论一下技术,能实作就实在出来看看,你讽刺我,我讽刺你的,有什么意义?电脑一关谁也不认识谁,也也影响不了谁的生活.像云风这样能分享一下自己的所思所得,并抽时间写出来的人还是值得鼓励的.尽管有的东西写的好,有的东西有错误.别人说你好,也未必从心里觉得你好,别人说你不好,未必从心里觉得你不好.一个人有好的品德,宽容的心态,获益最多的还是他自己.

使用LoadLibrary和GetProcAddress
动态加载DLL不用Lib文件的吧,呵呵

eMule上搜索"windows source code"就會出來了。200~400M大小。nt4和Win2000的實現。代碼不完全。但還是有不少東西可看的。另外還有開源的Wine和ReactOS項目可以參考。

madlax,看看這個,eMule的限速的實現。也許對你有用。
http://www.newsmth.net/pc/pccon.php?id=1904&nid=252546&order=&tid=
http://www.newsmth.net/pc/pccon.php?id=1904&nid=253206&order=&tid=
http://www.newsmth.net/pc/pccon.php?id=1904&nid=253207&order=&tid=

"windows 的源码可以去哪里下载"

以前在电驴上好像可以搜索到一堆,不过我没下载过。

讨论点别的吧……

客户端和服务端之间,怎样测可用带宽比较合理呢?TCP的协议和UDP的协议是否应该使用不同的办法?

那我们可以对Bennie说:“ 你是电,你是光,你是唯一的奇迹,我们全都爱你,your are our <a>S</a>uper <a>B</a>ennie ”

只有小孩子掐架才知道喊家属一起搀和,糖你自己留着吃好了,别吃出虫牙就好。

你又是CIH又是花花草草最后还要扮蛊惑仔叫你声白痴不过分。

记住 “白痴”一词是你引入的。

麻烦你告诉我“三省吾身”需要什么年龄?省省自己先。

可以试着把自己知道的词贯通以下,别胡乱蹦。

我发个100帖。哈哈

现在知道喊:妈妈,是他先动手的!

我当然承认不是善意的,有加枪带棒,所以这块糖是可以给你的!当从我成了白痴这一点来看,给你又有点冤。

至于enterprisebeing,不知道到没到三省吾身的年龄?

人不用你当成牛人,也不会把你当白痴,也没什么高不高明,只是把自己明白的对方说错的纠正一下。

enterprisebeing早过了你关心的那些东西的年龄了

自己看看自己的话,基本上也是你窜出来变成口水仗。

总是以已推人你会有更多事情不明白的


我身体很好 不累 我智商很低 不机灵。

云风的方法不错,应该就是这样了.

呵呵,这里没有什么陷阱。我说的不明智的意思是:证明口水战的对手是白痴并不能表明自己的高明之处,我宁可先尊称对方是个牛人。

当然我还是可以继续看你在这抖机灵,不过你不累么?

如猎人般机警的Bennie想通过陷阱来证明某些人是把他归到白痴堆里,证据就是他得到的答案:“你真是个白痴”,Bennie可能不知道,像“你真是笨,你真是无聊”之类的话并非一定要先把对方归类的,那个“真是”并没有潜伏作用,所以即使你把地点换成十三陵你得到的答案可能还是“你真是个白痴”。

不知道这是不是引入白痴说法得Bennie最关心的。

那我们可以对Bennie说:“
你是电,你是光,你是唯一的奇迹,
我们全都爱你,your are our Super Bennie”

一直不明白为什么有些人总喜欢把对方归到白痴的堆里,要是我就不会做这么不明智的事情。

还有下面那些话,是想看看某些人是不是除了大词以外还喜欢大话,虽然我选择了风景现在还不错的八大处,而结论也很明显了:“真是个白痴!”

刚才又想了下,纯粹是自找的。自己写段程序解析 PE 头,然后自己分配内存,做重定位;最多再处理下导入导出表就全部搞定了。又没 windows 版本兼容问题。

把去年写的程序翻出来看了一下,一共也没多长。根本没必要追求合法的调用 windows api 来做这些事情。

不发言了,昨天晚上该写完的程序现在还没收尾,真是何苦。

to mike, 我有兴趣,能否转贴一下。不过不希望是自己解析 PE ,这个我自己已经做过了。另外希望在 windows 9x 和 nt 上都可以工作。因为这是我们项目的必须要求。

先骂自己一下:再挑起不同意见的话是不是不太厚道了。:D

以我浅见,做插件的人,为了更流氓,更应该研究的还是 shell 。前几年想给 explore 做个插件自己用,还是颇费下了点工夫研究的。即使是耍流氓,我见过的几个特别阴魂不散的流氓插件也是因为在 shell 上动了手脚。

不耍流氓的话,基本上也就是在 shell 层做文章,系统核心的东西能不碰还是尽量不碰的好。

小声问一下 windows 9x 的兼容问题怎么解决?(非反问)

还有, windows 的源码可以去哪里下载?或购买?

10M 字节该有百万行了吧,也不是小数目,是需要花点时间看的。读这个自然是值得的。

Ldr开头的函数全部封装在ntdll里,各位既然都这么牛逼,调用个把的函数一下想必不是什么费劲事,这片代码最后一次修改也是上个世纪的事了,还算得上是稳定,至于说安不安全,要看各位的身手了,毕竟喝水也能噎死人。

Windows的核心代码不过十来兆上下,只有娱乐版的小编才会连Shell带外围程序放到一起攒上千万行的故事说事,要是读一遍能死人,做插件的大概都死绝了,穷经皓首这事,多虑了。

至于说读代码上不了台面这事,确实,这年头会调用个把的Dll就能评价两句操作系统,读源码多累,还不如扎个式冒充个把蛊惑仔晃人快。

顺便鼓励一下Bennie,南大楼的狱警们都盼着您老呢,扛一蒲扇去八大处太远也太累,还不如抄把小刀到中南海晃一下,大家都省事。

嘿嘿,真是个白痴。

自己加载一个dll 很容易,一般情况下只要实现段区映射还原,和 base reloc 就可以了, 没有什么好讨论的。

文字对读的人有没有恶意是由写的人的感受来决定的,如果读的人不同意,那多是读的人心中的念头所至。这种逻辑真是太绝妙了。按照这个逻辑,我应该砍掉云风的左手,而云风不能喊痛也不能流血,因为我认为他不该这样,他如果非要这样,那多半是他心中有什么恶念,他的正确做法是应该把右手伸给我再砍,砍掉后还是不能喊痛与流血,而是要跳起来欢呼砍得好,砍得好,下面砍脚好吗?因为他不这么做就是心中念头不好。

可能性是一回事,做出来是另一回事。我指的是这个区别,并无意贬低两种方式的任何一种。我是偏向于实作的,7 年前我没事找事去自己用汇编写重写了一遍 jpeg 的 decoder 当初就被很多网友痛骂一通;最近一年自己从零实现 crt 就没想在网上招摇了,我知道很多人会不屑。

如果有兴趣可以试试把一个 dll 做 base64 编码存在硬盘上,写一个函数可以加载这个编码过的 dll 文件用用。我说的是如果有兴趣,没兴趣就算了。不用去提什么立项外包。有可能做出来的话,open source 一把,我相信很多人会喜欢的。

一行一行的跟踪windows运行时的机器码。本不是件值得拿的上台面说的事,我相信任何一个在 windows 下做过几年开发的人都干过。windows 下许多 dll 文件,我也用 ida pro 反编译读过一些,关键地方的注释还留在我的一块老硬盘上。我想这些是每个 windows 开发人员都值得做的事情。根本不值得一提。

我承认我的一些回帖不是出于完全的善意,但最少是中性的。从中读出恶意来,多是读的人心中的念头所至吧。

to Cloud:

--这个把"代码伪装成一个文本文件",我想绝不是把 .dll 改掉后缀为 .txt 这种伪装。我想至少也要做一次类似 base64 的编码才能看起来象文本文件一点吧。如果能做到这一点,自由加载 dll 的梦想就能实现了。这个把"代码伪装成一个文本文件",我想绝不是把 .dll 改掉后缀为 .txt 这种伪装。我想至少也要做一次类似 base64 的编码才能看起来象文本文件一点吧。如果能做到这一点,自由加载 dll 的梦想就能实现了。

enterprisebeing 解释了“在这段代码之后,系统最终都会通过LdrpCreateDllSection对DLL文件进行处理,LdrpCreateDllSection在读取DLL的时候有两个入口,一个是路径,另一个是文件句柄,但文件句柄的参数并未公开暴露出来,所以这个参数是个后门。如果你使用自行编译的代码,当然可以用这个参数”

“流式加载就是通过IO子系统进行加载”

你当然可以把dll进行base64编码,再decode回来不就的了,至于你到底想要如何“自由”的“按你希望的方式去”加载,我智商低,至今不知道你到底想怎么样。


--“enterprisebeing 毕竟是不同层次的人,一想到要穷经皓首阅读上千万行的 windows 源码才能企及这样的层次, 就感受到什么叫人生苦短。”

enterprisebeing 没有说他“穷经皓首阅读上千万行的 windows 源码”吧,至于你怎么计算的大概只有你自己知道.

跟你工作同种的 Dunhill Hwang 说他以前为了弄明白windows的运作机制,曾经一行一行的跟踪windows运行时的机器码,也没见他喊什么人生苦短,人现在过的也挺舒服的,


--可以从 dll 加载方式来诟病 Windows对物理内存的利用率

enterprisebeing没有从dll加载方式来诟病 Windows对物理内存的利用率吧? 你似乎忘记了你可以从dll上看出“美感”...

--以上对 enterprisebeing 绝无不敬之意,盖因大家面对的问题,考虑的侧重点不同,争论下去没有什么意义了

敬不敬看客们大概都心里有数,争论下去确实没有意义.

1)
VC 下才会有让 windows 开发新手困惑不解的动态链接 CRT ,静态链接 CRT ,多线程库,单线程库
=========

The reason is that different implementation of memory management in different NT CRT.

2)
DLL 事实上和 EXE 文件一样,同属 PE 格式的执行文件。对于隐式的引用外部符号,需要把外部符号所在的位置写在 PE 头上。PE 加载器将从 PE 头上找到依赖的符号表,并加载依赖的其它 DLL 文件。
=============
NT is better than Linux in this case, because M$ offer method to let user define the symbol to be exported. On Linux, there are several methods, but not very friendly. You can refer to one RH document "how to write dynamic library" for this topic.

我能理解 Bennie , 其实我在这贴后半段绝大部分掺乎都在于前面 enterprisebeing 贴的一小段话引起了非常大的兴趣:

"实际上在Windows所有的可执行代码都可以以流的方式加载到当前地址空间并运行,这是Windows提供给Debuger的机制,你尽可以把你的代码伪装成一个文本文件,这并不影响他可以被执行。"

虽然几年前做的一点研究让我肯定 dll 按我希望的方式去加载在 windows 上找不到很好的解决方案的。但是又一次看到有人提及,还是希望否定掉从前的结论的。

这个把"代码伪装成一个文本文件",我想绝不是把 .dll 改掉后缀为 .txt 这种伪装。我想至少也要做一次类似 base64 的编码才能看起来象文本文件一点吧。如果能做到这一点,自由加载 dll 的梦想就能实现了。

不过讨论到后来,直到 LoadLibraryExW 的源码被贴出来后,我放弃了。再下去不可能获得我想要的答案,留下来的除了打口水帐毫无意义。

enterprisebeing 毕竟是不同层次的人,一想到要穷经皓首阅读上千万行的 windows 源码才能企及这样的层次, 就感受到什么叫人生苦短。或许有一天我也可以完成这样的伟业,然后就可以只追究可能性,而不需要埋头码代码以求实现了;可以把 LoadLibraryExW 信手拈来,从中看出 windows 的无限可能;可以从 dll 加载方式来诟病 Windows对物理内存的利用率...

以上对 enterprisebeing 绝无不敬之意,盖因大家面对的问题,考虑的侧重点不同,争论下去没有什么意义了。

To Zenberg,enterprisebeing:
你们的出发点是好的,心态是好的,素质也是高的。但是要分对什么人,碰上Bennie、云风这样的,你们这套就不灵了,你们这套只能用在和你们处在同一层次上的人。

用LoadLibrary加载同一个dll的时候,第一次加载和第二次加载的语义是不同的。我觉得这不算一个良好的设计。

还有,显式的运行时加载的符号造成的后果就是模块初始化的代码必须要判断是否是第一次加载,加载的到底是哪一个模块,依赖于文件名。更糟糕的是,Win32 API中存在大量的需要依赖于HINSTANCE的函数。
在MFC中要解决这些问题被迫使用大量的宏来判断到底是静态库还是动态库。被迫再包装一个AfxLoadLibrary用来处理不同情况的初始化。
所以我觉得DLL这样一个赤裸裸的代码加载方式并不太好。

我实在不觉得ldconfig有多高明,unix下假如我要链接一个so,有两个选择
1. 把 so copy 系统LD_LIBRARY目录里,链接这个so
2. 链接的时候写相对路径
这样方便吗?

unix 下c的标准库一定是一个so方式链接的,windows也可以这么做。windows多点选择有什么不好的??

什么是kernel api ?不是kernel32.dll 里提供出来的函数。ntdll.dll里面提供出来的一部分函数才是真正的kernel api。

这种话都能出口 呵呵 你确实是个白痴

问题是你灭不了我,真是个讽刺!

不如这样,11月11日,各携刃长不足100cm,刃宽不足10cm自选刀具,于北京八大处决斗如何。

不好意思,云风,这段本来不想写的!

你这么说我只能认为你是那种喜欢拯救教育并认为对方是白痴的人。

我没enterprisebeing的涵养,在那里正事不说冷言风语的还自我感觉良好的人网上尽是,这样的我巴不的见一个灭一个。

美感的话是不错,但是实际上很难去描述和把握怎么样的才是美。

具体的到程序上

小到variable, function的命名,statement的布局,return的方式。

大到module之间的level和association 都可以体现出美感来

伦勃朗酷 莫奈缥 毕加索丑 但他们可能美的

美感并不是唯一的 即使是同样的目的。

怒了?何必犯嗔戒呢?砸坏花花草草就不好了!

我并无窥伺你那神异方法之意,而且在网上讨论,不要总是把对方当白痴,等待自己去拯救或者教育就好了!

To: Bennie

你瞎扯什么蛋,人从来没说要提供给你什么方案,你失望也罢上吊也好爱咋咋。

过来本来只是要指出云风文中不当之处的。也没什么坏意。

要方案也行,立个项外包下,谈谈价钱先。

最反感就你这样的 学学人云风

也许是你说得这时Windows提供给Debugger的机制误导了我。但我以为不应该认为CIH可以破坏BIOS是MS提供给陈盈豪的机制吧。

如果这就是你的方案,说实话,有些失望。这里面语境的非本地,指的是Windows认可的文件系统之上的非本地,如果虚拟文件系统也算得话,那么就没有意义了,一个串口都可以成为本地。同样无盘工作站的例子也是如此。

而用到了Native API和非文档函数的话,和自己加载数据就没有什么区别,我们系统的JIT就是如此。就如Windows后来在dll的基础上作的那些东西一样。

上升到美感的缘故是,我始终认为,缺乏美感的设计一定是一个糟糕的设计。至于糟糕的设计是不是合理的设计,就是另一个问题了。
==========================
这段话太值得顶!

我说的可用方案指,我写一个软件用这种技术,然后发布出去。按我们游戏目前的装机量,大约会在 600 万台 windows 机器上安装。windows 9x , Me, nt ,2000, xp ... 各个小版本的都有。我希望这个方案用了后不会遭到大量的客户电话打进来询问问什么软件启动不了。

提供Windows一个虚文件子系统我曾经也想过,但是没有细究下去怎么去做,有空的话倒是可以研究一下。不过我希望的软件可以不经安装在一般权限登陆的 windows 下就可以运行。我怀疑这样做不到。这也是以前没有细致研究下去的原因之一。

至于读 windows 源码?我还真没有通读过。不过你贴的那段 LoadLibraryExW 我很早倒是挖出来看过。说是挖出来,只因为我不知道哪里去弄源码,用 ida pro 反编译读读汇编罢了。

这一小段源码似乎对解决我的问题没有什么价值,不过还是感谢你能贴出来。

ps. 想必通读 windows 源码应该是一个很大的工程吧。对所有阅读了 windows 源码的人,尤其是其中那些并非 windows os 的开发者表示致敬。我想我这辈子都完成不了这件事情了。

我一直把进程管理的地址空间跟内存看成两个东西,讨论中我指的内存都是指物理内存。

在 dll 的设计上,当是为了提高物理内存的利用率的。如果有诟病,应该指的别的东西吧。

我有点明白了,大概是都没读过Windows的源码,难怪。

to: Cloud

1.虚存也是内存,只有当前被运行的代码才会放到物理内存,Windows对物理内存的利用率确实一直被诟病。

2.无盘工作站的例子十分厚道,这就是非本地加载运行的典型例子。

上面的代码是Windows在这一部分的源代码。至于说安全与否,嘿嘿,当然安全,编译他加入到自己的程序里,就相当于在Windows里运行了一份Windows。

在这段代码之后,系统最终都会通过LdrpCreateDllSection对DLL文件进行处理,LdrpCreateDllSection在读取DLL的时候有两个入口,一个是路径,另一个是文件句柄,但文件句柄的参数并未公开暴露出来,所以这个参数是个后门。

如果你使用自行编译的代码,当然可以用这个参数,但是如果你使用Windows的API,抱歉,没有保修。

路径的用法最终通过进入核心态调用IoCreateFile对文件子系统进行只读共享读取,然后进行解析,所以既然你要自己组织文件格式,那么你的本质就是在组织一个专有格式的文件子系统,稳健的方法无非就是几种:

一、提供Windows一个虚文件子系统,只要支持读取就行,解析仍由Windows处理,这是一个通用方案,例子代码很多;

二、模拟LoadLibraryEx的过程,但不进入核心态,存储由Windows负责,解析由自己负责,这也是一个通用方案,例子代码就是Windows的源程序。

三、解析并抽取出代码,自行放到准备好的地址空间,杰西和存储都由自己负责,这是个专用方案,走进程内还是进程间都随个人喜好,例子代码还是Windows的源程序,你要是喜欢可以把整个Windows都装进来。

四、解析和存储都由Windows负责,那直接使用Dll文件就挺好。

3.流式加载就是通过IO子系统进行加载,难道还真有人认为边执行边加载,有想象力。

我可没想过谁是大尾巴狼 :D 其实怎么更自由点的加载 dll 是我很长时间的困惑,这不抓到一个人或许知道,自然是很高兴的。

to atry, 我说的(并实现的)二进制复用,仅仅只在 cpu 相同的基础上做的。如果连 cpu 都不保证相同,我中意的解决方案是类似 forth 中用到的 meta compiler 的方式。从性能和实现简洁的角度讲,要好过 java 和 .net 的 VM 的形式。

云风最后肯定会发现要想实现他的想法必须要做虚拟文件系统,欺骗操作系统去加载内存中的DLL才能实现 从'流'中加载DLL。

我有一个问题请教,你以前提到过二进制复用,但是即使可以自己解析PE文件格式,代码调用和堆栈处理的方式不同操作系统也是不同的,这些也自己解决?再有CPU指令集不同怎么解决?难道还要定义一个中间格式来转换指令集?怎么听起来像是Java或.NET,那干吗不直接用Java或者.NET来开发呢。我觉得这些需求全都是Java或.NET正好解决的问题

windows 里,即使 dll 文件长达 1M ,在 LoadLibrary 的时候,这个 dll 文件也完全不用加载到内存。os 做的只是一个内存映射而已。

我想这是 windows 最初的设计目标之一,这可能跟最早设计 windows 95 时,要求 windows 95 可以跑在 4M 物理内存的机器上有点点关系。

-----------------------------

这是否属于过早优化?

真让人失望,有些后悔把enterprisebeing拖到这里了。

架势都变了。

演变成了如果enterprisebeing不回答“怎么以系统的方式从非本地文件加载DLL”就是大尾巴狼一样了。

至于么?

别的不懂,我只针对云风对的win/unix 调用dll涉及.lib问题地描述,那是不同平台c/cpp语系的限定在iso标准的编译器实现的问题。

扯到操作系统设计的优劣从哪个角度说都是不合适的。

如果说是个人喜好我是没有任何反对意见的。

另:如果云风最终的问题得到解决也是个好事,态度都柔和点也不会损失什么。

windows 里,即使 dll 文件长达 1M ,在 LoadLibrary 的时候,这个 dll 文件也完全不用加载到内存。os 做的只是一个内存映射而已。

我想这是 windows 最初的设计目标之一,这可能跟最早设计 windows 95 时,要求 windows 95 可以跑在 4M 物理内存的机器上有点点关系。

-----------------------------

这是否属于过早优化?

to enterprisebeing:

下面那大段 LoadLibraryExW 的代码似乎没什么意义,不知道你是看过了代码觉得可以解决我的疑问再贴出来的,还是只是觉得它可以解决我的疑问?

这段代码最终是用调用一个 api 叫做 BasepLoadLibraryAsDataFile 来真正加载 dll 文件中的数据的。可惜的是,这个 api 传入的参数还是文件名而已。要追就下去,这个函数最终调用了 MmMapViewOfSection 来完成实际的映射。

再看另一边,实际去处理被加载进来的 dll 数据的函数是 LdrLoadDll 。这是 ntdll 里一个未公开的接口(想兼容 win9x 怎么办?)。是否可以安全的使用我不太清楚,先不谈吧。

这个东西到底怎么用,可否赐教呢?第四个参数里传入 dll 在当前进程中的镜像地址即可?

ps. 拿 windows 无盘工作站说事有点不厚道吧?我觉得那是虚拟了一个本地文件系统再在上面工作的。

to: Bennie

Windows的无盘工作站见过吗?

有流式IO,但IO!=流。所以我说是一种。
其实这里一直讨论的是系统加载动态库的方式,即使是操作系统,如果你不看手册,不用高级语言编译器(代劳者,根据手册产生调用信息),你可以用汇编调用它么?

这些src没有任何意义,对于流式加载,我们一直问得是怎么以系统的方式从非本地文件加载DLL。你可以再教育我一把么?

to: jhj


准确的说是“只有”微软干了这活,因为UNIXer的口头禅是“简单就是美”,套句楼下一位曾经嘣过的词,“现代的操作系统”流行的是微内核,所以CORBA是应用级,只有COM才是系统级,这也就是UNIXer讽刺MS不会设计操作系统的由来。

to: sunway

流,最常用的语境就是指IO的行为模式。

to: Cloud

最简单的办法就是看源码,差一点的就是查MSDN;

这是代码:

<pre>

\windows\base\client\module.c

HMODULE
LoadLibraryExW(
LPCWSTR lpwLibFileName,
HANDLE hFile,
DWORD dwFlags
)

{
LPWSTR TrimmedDllName;
LPWSTR AllocatedPath;
NTSTATUS Status;
HMODULE hModule;
UNICODE_STRING DllName_U, AppPathDllName_U;
UNICODE_STRING AllocatedPath_U;
ULONG DllCharacteristics;
extern PLDR_DATA_TABLE_ENTRY BasepExeLdrEntry;

TrimmedDllName = NULL;
DllCharacteristics = 0;
if (dwFlags & DONT_RESOLVE_DLL_REFERENCES) {
DllCharacteristics |= IMAGE_FILE_EXECUTABLE_IMAGE;
}

RtlInitUnicodeString(&DllName_U, lpwLibFileName);

//
// Quick check to see if dll being loaded is the main exe. For some reason
// hook stuff tends to do this and this is worst path through the loader
//

if ( !(dwFlags & LOAD_LIBRARY_AS_DATAFILE) && BasepExeLdrEntry && (DllName_U.Length == BasepExeLdrEntry->FullDllName.Length) ){
if ( RtlEqualUnicodeString(&DllName_U,&BasepExeLdrEntry->FullDllName,TRUE) ) {
return (HMODULE)BasepExeLdrEntry->DllBase;
}
}

//
// check to see if there are trailing spaces in the dll name (Win95 compat)
//
if ( DllName_U.Length && DllName_U.Buffer[(DllName_U.Length-1)>>1] == (WCHAR)' ') {
TrimmedDllName = RtlAllocateHeap(RtlProcessHeap(), MAKE_TAG( TMP_TAG ), DllName_U.MaximumLength);
if ( !TrimmedDllName ) {
BaseSetLastNTError(STATUS_NO_MEMORY);
return NULL;
}
RtlCopyMemory(TrimmedDllName,DllName_U.Buffer,DllName_U.MaximumLength);
DllName_U.Buffer = TrimmedDllName;
while (DllName_U.Length && DllName_U.Buffer[(DllName_U.Length-1)>>1] == (WCHAR)' ') {
DllName_U.Buffer[(DllName_U.Length-1)>>1] = UNICODE_NULL;
DllName_U.Length -= sizeof(WCHAR);
DllName_U.MaximumLength -= sizeof(WCHAR);
}
}

//
// If DLL redirection is on for this application, we check to see if the DLL requested
// (without path qualification) exists in the app. (EXE) folder. If so, we load that.
// Else we fall back to regular search logic.
//

if (gDoDllRedirection && DllName_U.Length) {
Status = ComputeRedirectedDllName(&DllName_U, &AppPathDllName_U) ;
if(!NT_SUCCESS(Status)) {
if ( TrimmedDllName ) {
RtlFreeHeap(RtlProcessHeap(), 0, TrimmedDllName);
}

BaseSetLastNTError(Status);
return NULL;
}

if (RtlDoesFileExists_U(AppPathDllName_U.Buffer)) {
DllName_U.Buffer = AppPathDllName_U.Buffer ;
DllName_U.MaximumLength = AppPathDllName_U.MaximumLength ;
DllName_U.Length = AppPathDllName_U.Length;
}
}

//
// Determine the path that the program was created from
//

AllocatedPath = BaseComputeProcessDllPath(
dwFlags & LOAD_WITH_ALTERED_SEARCH_PATH ? DllName_U.Buffer : NULL,
GetEnvironmentStringsW()
);
if ( !AllocatedPath ) {
Status = STATUS_NO_MEMORY;
if ( TrimmedDllName ) {
RtlFreeHeap(RtlProcessHeap(), 0, TrimmedDllName);
}
goto bail;
}
RtlInitUnicodeString(&AllocatedPath_U, AllocatedPath);
try {
if (dwFlags & LOAD_LIBRARY_AS_DATAFILE) {
#ifdef WX86
// LdrGetDllHandle clears UseKnownWx86Dll, but the value is
// needed again by LdrLoadDll.
BOOLEAN Wx86KnownDll = NtCurrentTeb()->Wx86Thread.UseKnownWx86Dll;
#endif
Status = LdrGetDllHandle(
AllocatedPath_U.Buffer,
NULL,
&DllName_U,
(PVOID *)&hModule
);
if (NT_SUCCESS( Status )) {
#ifdef WX86
NtCurrentTeb()->Wx86Thread.UseKnownWx86Dll = Wx86KnownDll;
#endif
goto alreadyLoaded;
}

Status = BasepLoadLibraryAsDataFile( AllocatedPath_U.Buffer,
&DllName_U,
(PVOID *)&hModule
);
}
else {
alreadyLoaded:
Status = LdrLoadDll(
AllocatedPath_U.Buffer,
&DllCharacteristics,
&DllName_U,
(PVOID *)&hModule
);
}
if ( TrimmedDllName ) {
RtlFreeHeap(RtlProcessHeap(), 0, TrimmedDllName);
TrimmedDllName = NULL;
}
RtlFreeHeap(RtlProcessHeap(), 0, AllocatedPath);
}
except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
if ( TrimmedDllName ) {
RtlFreeHeap(RtlProcessHeap(), 0, TrimmedDllName);
}
RtlFreeHeap(RtlProcessHeap(), 0, AllocatedPath);
}
bail:
if (gDoDllRedirection) {
// We would have bailed had we not been able to allocate this buffer in re-direction case.
RtlFreeHeap(RtlProcessHeap(), 0, AppPathDllName_U.Buffer);
}

if (!NT_SUCCESS(Status) ) {
BaseSetLastNTError(Status);
return NULL;
}
else {
return hModule;
}
}

</pre>

LoadLibrary所有版本最终都汇集到LoadLibraryExW函数。
这是注释:

/*++

Routine Description:

This function loads the library module contained in the specified
file and retrieves a handle to the loaded module.

It is important to note that module handles are NOT global, in that
a LoadLibrary call by one application does not produce a handle that
another application can use, say in calling GetProcAddress. The
other application would need to do its own call to LoadLibrary for
the module before calling GetProcAddress. Module handles will have
the same 32-bit value across all processes, but a module handle is
only valid in the context of a process after the module has been
loaded into that process, either as a result of an explicit call to
LoadLibrary or as a result of an implicit call caused by a loadtime
dynamic link to an entry point in the module.

The library file name does not need to specify an extension. If one
is not specified, then the default library file extension, .DLL, is
used (note that this is different than Win16. Under Win16 specifying
no extension would not cause ".DLL" to be appended to the name. To get
Win16 behavior, if the module name has no extension, the caller must
supply a trailing ".").

The library file name does not need to specify a directory path. If
one is specified, then the specified file must exist. If a path is
not specified, this function will look for the library file in using
the Windows search path:

- The current process image file directory

- The current directory

- The Windows system directory

- The Windows directory

- The directories listed in the PATH environment variable

The first directory searched is the directory that contains the
image file that was used to create the current process (see
CreateProcess). This allows private dynamic link library files
associated with an application to be found without having to add the
application's installed directory to the PATH environment variable.

The image file loader optimizes the search by remembering for each
loaded library module that unqualified module name that was searched
for when a module was loaded into the current process the first
time. This unqualified name has nothing to do with the module name
that is stored within the library module itself, as specified by the
NAME keyword in the .DEF file. This is a change from the Windows
3.1 behavior, where the search was optimized by comparing to the
name within the library module itself, which could lead to confusing
result if the internal name differed from the external file name.

Once a fully qualified path name to a library module file is
obtained, a search is made to see if that library module file has
been loaded into the current process. The search is case
insensitive and includes the full path name of each library module
file. If a match is found for the library module file, then it has
already been loaded into the current process, so this function just
increments the reference count for the module and returns the module
handle for that library.

Otherwise, this is the first time the specified module has been
loaded for the current process, so the library module's DLL Instance
Initialization entry point will be called. See the Task Management
section for a description of the DLL Instance Initialization entry
point.

Fine Point: If DLL re-direction is enabled for the app./process requesting
this load, if we find a DLL in the app. folder (with same base name),
we load that file (ignoring any path qualifications passed in).

Arguments:

lpwLibFileName - Points to a string that names the library file. The
string must be a null-terminated unicode string.

hFile - optional file handle, that if specified, while be used to
create the mapping object for the module.

dwFlags - flags that specify optional behavior. Valid flags are:

DONT_RESOLVE_DLL_REFERENCES - loads the library but does not
attempt to resolve any of its DLL references nor does it
attempt to call its initialization procedure.

Return Value:

The return value identifies the loaded module if the function is
successful. A return value of NULL indicates an error and extended
error status is available using the GetLastError function.

--*/

windows 里,即使 dll 文件长达 1M ,在 LoadLibrary 的时候,这个 dll 文件也完全不用加载到内存。os 做的只是一个内存映射而已。

我想这是 windows 最初的设计目标之一,这可能跟最早设计 windows 95 时,要求 windows 95 可以跑在 4M 物理内存的机器上有点点关系。

但是,反而我有 1M 的代码数据,想把它当作一个 dll 跟进程内已加载的那些动态模块共存,似乎就很难办到了。这让我怀疑,在 windows 的设计中,dll 只能用内存映射的方式加载到进程中。

如果我这个观点是错误的,那么请知道如何做的人告诉我怎么做。我一定虚心受教。

我感觉流的意义太空泛了,所以我没能理解云风的意思。
在不同的语言里,内存也是流,文件也是文件流,TCP流协议。。。流的定义太广,用来描述DLL这个命题不太合适。

to enterprisebeing:
至少Windows操作系统的开发商已经把一些额外的描述信息集成进动态联结库了。并且这个技术也如此广泛的深入了它自己编写的操作系统里与它的应用软件里。
COM它就是一种在简单的函数地址接口也就是DLL方式之上形成的一种更好接口方式。我可以拿到一个COM组件就把里面的类的数据、方法等等的一切给全部还原出来,并且很方便的去使用它。就连已经成为Windows操作系统一个不可分割的部分的硬件加速函数库DirectX,也是全盘基于它的。

其实DLL本身来说,就是为了给编写它的人带来方便,同时有一定的保密性的。如果你真的不想别人使用你的DLL,那么很简单,不要起函数名,所有函数都用索引,不公开调用接口,别人就几乎不可能再使用这个DLL了。因为函数的意义未知、函数的输入和输出未知,这还怎么用?
另外,清楚了DLL的底层原理,以及导入表的加载过程的话,自己写个特殊格式的.MYLIB动态联结库也是非常简单方便的。
还可以用另外的一些方式在DLL基础上加一层API协议来实现高级别的保密和易用性。比如COM这样基于类的接口就是把DLL当作一个底层接口,在这个底层只需要很少两三个函数
作为通信接口,再在此基础上构架复杂而庞大的类库。当然,如果着重安全性,可以不使用通用的接口方式,全部自己写一套就行了。

"但是想针对windows来树立一个对立面比较,一下还找不到合适的词."
记得Understanding.the.linux kernel.3rd里有个词叫Unix-like,也许好些,呵呵.这让我想起当年有个经常提的词:Doom-like.
另:enterprisebeing,无趣的人.

to:Bennie


流不是标准称呼?你管这东西叫什么?板凳?


操作系统还管提供一个额外的分离信息来描述?


睡糊涂了吧?那时操作系统干的活吗?找本书翻翻,现知道操作系统是干什么的

流只是IO模式的一种,而不是标准称呼。

操作系统一定是要以某种形式提供调用接口,而接口不仅仅是一个地址,还包括输入和输出信息。仅仅有地址是不能强保证调用的正确,所以需要一个额外的分离信息来描述,那么就隐含着双方不一致的可能。就象用汇编编程连弹压栈都需要自己负责而容易产生各种低级错误一样。所以现在的系统则倾向于提供有更好自描述信息的接口。

to: Bennie
流是IO行为模式的标准称呼,你叫什么?
操作系统不是用来保证你代码正确性的,那是你的活,这个世界上除了做操作系统的,还有作IDE的,做QA的。

关于流式,也是我一开始就想问的问题。如果说更改被调试进程的内存就是流式加载,那只能说是你自己的流,而不是操作系统的。我真要怀疑你之前说的那些大词了。
对于调用由程序员来保证,那么要操作系统还有什么用,不如直接用图灵机的概念,一切皆地址好了。而Dll作为MS最初的设想,他确实是这么个东西,仅仅提供一个地址,这样可以保证速度,但是到了现在,调用的安全和可靠性已经比速度更重要了。

Windows 自身不提供API从内存而不是从外存上加载dll,要这么做只能自己写个PE加载器。

我说说我想自己加载 DLL 的理由:

我们希望各种数据可以被大包放在一个大文件包里,这里包括 dll 文件。而把程序的一部分拆分成 dll 而不是一个独立的 exe ,并不一定是为了共享。更多的时候是为了更新方便。我指的是网络游戏这个特定需求。

而散列在本地文件目录下的诸多 dll 又给做外挂和逆向工程的人带来的许多方便。虽然把文件打包不根本解决问题,但是跟软件加密一样,提高分析门槛也是有必要的。

另外,我们现在的实际希望从 p2p 网络发布最新的 client 版本,而代码也希望是远程获取的,并不希望在本地文件系统中留下痕迹。

理论上,自己加载任何代码都是做的到的,但似乎 windows 下利用 os 本身的 api 来加载一个代码块似乎是做不到。windows 后来提供了一些分析 PE 用的接口,困难并没有降低。

我分析过暴雪做的 starcraft 的程序和数据包文件。发现他们的数据包内打包进了一些 dll 文件,但实际上这些 dll 还是存在于本地文件系统中。也就是说,暴雪在制作 starcraft 时的初衷很有可能是将这些 dll 也作为数据打包的,但因为技术原因没能实现。

虽然我现在自己写了 pe 的解析程序。但是我还是希望了解更好的方案。

在考虑这个加载 dll 的问题时,我把难以做到这点的理由归咎到 windows 最初可能的设计目的:快速的加载和共享代码。而这一点又与 windows 的整个系统都构建在一个又一个的 dll 之上是分不开的。

ps. unix 十年看起来都没怎么变,恰恰是我欣赏之处。把这点归到个人审美观上去吧,不用争辩 :)

真不知道你们在讨论什么……
有几个人真正从汇编级别研究过DLL是如何调用的??
DLL的导出表里的那些东西是什么意思?前面讨论的人真正清楚吗?Windows编程一知半解就来讨论的人还真不少。

每个DLL都有导出表,导出表表明了这个DLL有多少个函数可以被别人调用。不同函数都有一个index来区别,也可以再提供一个函数名来区别(也可以不提供!)。如果拿到一个DLL,那么要调用它的函数只有通过指定一个函数Index或者函数名来调用,GetProcAddress就是在LoadLibrary后,把Index或者函数名转换为实际函数入口的。
这里有个问题了:你可以知道这个DLL有几个函数,如果DLL导出表有函数名,还能猜出每个函数是干什么的。但是,你知道每个函数的该用什么参数来调用,返回值是什么吗?从DLL本身来说无法知道的!
如果一个DLL是为了写给别人使用,那么它就会附带一个说明文件,告诉你每个函数的参数和返回值的类型和意义(类似MSDN查Win32SDK的函数那样)。如果DLL提供者为了别人能更方便的使用,那么还会提供一个LIB文件,这样可以在VC里更方便的使用了。或者提供一个.H文件,可以让别的C编译器也能用。也可以同样提供.PAS文件好让Delphi也能很方便的使用。这些文件都不是DLL的必须组成部分,只是DLL提供者在想让别人能使用的情况下提供的辅助资料或者SDK档案。

Windows 下如何利用 OS 已经提供的功能,方便的以流的方式加载到当前地址空间并运行。???
这个问题我一直不明白云风提这个问题的目的?难道要自己去实现一个LoadLibrary...?这么做有什么必要?或者还有其他意思?
DELPHI下是可以动态加载他的包文件,其实也就是DLL,但是必须需要全部动态编译才行包含VCL.bpl,也就是你说的把内存管理器单独做一个DLL。也只有这样才能实现DLL之间的对象管理机制。

萝卜白菜各有所爱,审美观这个东西本来就是没有谁有资格给订个统一标准的。

to:Cloud
另,说你对Windows的机制不清楚看来确实有些错误,不过你把Dll上升到美感高度,我还是只能说,太远了

to:Cloud
A为宿主程序,将B代码段载入自身地址空间并运行,这是程序自身的事情,与OS无关,随便用什么方法,你当然可以执行本地代码,同样也可以执行来自网络的代码,这个没有什么区别。

A为宿主程序,并将代码注入另一地址空间的程序B,CreateProcess创建调试进程,然后即可以对B地址空间的任何代码进行任意更改和执行,这就是Windows一直存在的Debuger的接口。

比较复杂的情况,诸如远程调试,无非就是这些技术堆到一起。

to:Bennie 保证压栈的正确是程序员的活吧,你会把一个连文档都没有的东西塞到程序里?

to sunway, 你说的例子并不因为 "进程和DLL之间至少堆管理器是共享"

做到这一点是基于 DLL 加载的这样一个规则: 当同一进程加载多次同一个 DLL ,在当前地址空间只有一份拷贝。也就是说,当第2次加载 DLL 时,加载器会返回前一次的地址。

堆管理器被共享并不是因为加载器直接从当前地址空间把堆管理器的接口塞入后来的 DLL ,而仅仅是因为堆管理器本身是做成了另一个 dll 。也就是我们平时看到的动态库版本的 CRT 。如果一个不小心,哪个 DLL 静态连接了 crt ,隐患就此埋下。

理论上, windows 下编译器在生成 dll 时随带生成的 lib 文件里的信息大多数情况可以完全从 dll 文件中萃取出来。这种工具不难实现,也有这样的工具。但是为什么这个 lib 存在,简单的例子就是,
如果没有 msvcrt.lib 你基本就不可能顺利的开发 DLL 。windows 要求你的 dll 在生成时必须明确知道这些 crt 函数在哪,而不是把那些符号留在那里(这就是那个 lib 文件存在的价值)。而绝大多数情况下,我们自己开发 DLL, 必须用到 crt 库。这个 crt 还必须统一。许多 dll 的版本造成的问题都来源于这里。

to enterprisebeing, 我无意于和你做口水仗,虽然你随后的回帖让我觉得很多观点我已经不如你的第一次回帖让我赞同。你不用指责这些差异是因为我对 windows 的理解错误造成的。如果你这样想,就随你好了,这对大家不造成任何损失。

不过你有一个问题没肯赐教:"Windows 下如何利用 OS 已经提供的功能,方便的以流的方式加载到当前地址空间并运行。"

我问这个问题,仅仅只是希望知道,没有别的意思。

的确,没做过黑客和病毒,只是在汇编层面调调程序、优化下代码,对底层的认识绝对不能和那些专业人士相比的。这点我深有体会,所以看看热闹不参与啦:D

还有牛人的总结发言,我也非常困惑,看一些洋牛(和洋钉,洋火同义)的操作系统体系结构的书,说得就是那些东西,而且会对照Unix和Win来讲解,当了Local牛,就告诉我们,不要比对,是不是觉得有牛们知道差别就足够了。

5.DLL不需要附加任何文件即可被二次使用,只要你能保证压入堆栈的参数是正确的,jmp过去还是call过去都随便。

这条我非常有意见,你怎么能保证压栈的正确?靠上帝么?DLL是一个自描述非常差的东西,当然你可以说COM也是DLL,那我说一切都是bit呢。

有些技术"牛人",让人感觉是"神"
.
而风云, 给我的感觉是个真实的人,这是偶最尊敬风云的地方.

打住吧,已经快成口水账了。

基本上,无论是Windows转UNIXs还是UNIXs转Windows,其实就两条:

1.两者的设计思路完全不同,这就导致体系结构完全不同,不要对比,就当全新的东西看;

2.存在的都是合理的。

1.DLL提供的是共享代码段,处理都是在进程内完成的,不存在跨进程的问题。

2.进程内的代码段跳转是程序的事,不是操作系统的事。

3.跨进程的地址空间侵入,是对硬件的地址空间管理模式的考验,不是对操作系统的考验。

4.操作系统提供的是跨进程的地址空间侵入入口,这个入口在设计之初都是给Debuger使用的,但是比Debuger使用更频繁的是病毒,以及形形色色的内存修改器。

5.DLL不需要附加任何文件即可被二次使用,只要你能保证压入堆栈的参数是正确的,jmp过去还是call过去都随便。

6.DLL的Linked是runtime语境,和Linker没有任何关系,你非要给个说法,我也只能说美国人文辞修养太贫乏,说来说去就这么几个词。

7.从一个DLL看出美不美,这境界太高,有点跟不上。

8.OpenBSD号称开发人员都是数学大家级别,可惜白送都送不过Linux,Solaris号称自己根红苗正、技艺超群,不过谈到用户体验大概只能说恍如隔世,至于说更高端的主机操作系统之类,大概唯一能吸引人眼球的就是他们的开发规模,不过这些新闻一向都被归入娱乐八卦类。

9.软件就是用来解决问题的,谁解决的好、谁解决的快、谁解决的体贴人心谁就是好软件。

10.没有任何一种美可以高深的被普罗大众所无法理解,Windows拥有最大的开发人员群体和最大的最终用户群体,这一切都是靠开放市场的软件赢取的,不是靠专有硬件赢取的。

11.你十年前看了一本UNIX的书,十年后发现一切如初,可是这十年来PC服务器的计算性能已经直逼小型机,这就是UNIXs没落的原因。

12.Microsoft已经和Novell结盟,两者开始共享专利,而后者的mono不仅开始大摇大摆的接受Microsoft的鼎立扶持,而且连XNA都要照扒到Linux平台,用不了多久,在Linux上就可以玩到原汁原味的.NET游戏,相反motolola声称要开源自己的Java ME,并且和Sun毫无关系,这意味着Java的控制权之争已经拉开序幕,一旦Sun失去了Java的控制权,这个公司也就烟消云散了。

VC也是只需一个dll及一个.h头文件声明就可以了,主要是得到函数名及参数信息

还有作者未免对unix过于崇拜了,unix下库的版本问题也是很烦人的。

关于DLL和EXE共享地址信息这个问题一些编译器是做了一些工作的,MFC扩展DLL和DELPHI的BPL(其实还是DLL)好象和主EXE是共享了EXE的一些地址信息,因为他们的之间的接口函数可以传递CString,string这类对象,说明进程和DLL之间至少堆管理器是共享的,至于其他还有什么共享的数据我没仔细分析过。

我想这里的人应用dll应该都不是什么问题。

我想说的无非是.lib的问题完全是c/cpp在实作compiler时候必须符合iso的副作用,由此上升到dll/so,Win/Unix的对比并由此得出OS优劣的判定就不是很合适了。

容易误导初学者。

呵呵,讨论蛮激烈嘛。
继续继续。
很喜欢看enterprisebeing的文字。

to Zenberg, "至少我们在 windows 下做一个 dll 文件给大家使用还需要携带一个 .lib 文件" 这句话我说的的确欠妥,虽然我并不认为是因为我的理解错误导致的。

我想说的"给大家使用",是指再次开发的程序员;而不是指运行时的环境。这样解释也同样有欠妥当,因为理论上这个 .lib 文件中所包含的信息在 dll 中同样存在,写个小程序从 dll 生成这个 lib 文件也是完全可行的。

一般情况下,DLL 文件直接从本地文件系统上通过内存文件映射的机制映射到当前进程的地址空间中。有一个例外是地址已经被占用,那样的话,还需要修改代码段做重定位的操作。这个系列的过程一般发生在 PE loader 过程中。loader 会在 PE 的文件头中找到有依赖关系的 DLL 文件,如此加载 DLL 文件。在 DLL 加载完成后,就可以依靠导出表中的地址,去完成导入表的各个接口。整个过程并不太复杂。如果理解有偏差的话,任何一个中间步骤就讲不通了。

windows 下用 LoadLibrary 调用的话,是不会把任何当前进程中的地址信息填入加载的 DLL 的导入表的;而 dlopen 则不然,我认为这就是一个重要的区别。

上升到美感的缘故是,我始终认为,缺乏美感的设计一定是一个糟糕的设计。至于糟糕的设计是不是合理的设计,就是另一个问题了。

那个Anonymous真的是搞技术的?以老卖老?知道中国技术为什么落后了。。。

LoadLibrary...

写一段代码加载 DLL 文件并不是难事,我现在就是实现了一套分析 PE 文件的代码。我相信很多人都做过。加壳软件大体上也是这么做的吧,记不太清了,很早以前看过一点 upx 的源码做的论断。

而我希望知道的是,怎么利用 windows 的 os 本身做这件事情。

呵 在我看来你确实是在咬文嚼字...

如果上升到美感的角度去说的话基本没法说了。

从你的“至少我们在 windows 下做一个 dll 文件给大家使用还需要携带一个 .lib 文件;而 unix 下一般只需要有相应的头文件就够了。”这个说法上我认为你并不清楚 dll 到底是怎么工作。

我们现在用的也是你说的流的方式加载数据,用流的方式加载DLL运行没有作过,个人感觉如果参考那些加壳软件的实现(包括一些压缩EXE、DLL的ASPACK、UPX等软件也算),去实现流加载DLL应该也是可行的。

一定是可行的,我现在也是以流方式加载开发时编译好的模块。不过我想知道的是,windows 下怎样利用 os 本身提供的功能,以流方式加载 dll 。

sunway 你又是如何做到的呢?

用流加载数据并运行的方式是绝对可行的。因为我使用过这样的技术并且在产品中使用。

to enterprisebeing,

我说"暴露了代码段地址"并没有想咬文嚼字,如果想这样做的话,DLL 的全称是 Dynamic Linked Library ,强调的是 Linked 而不是 Loaded 。

在 dll 到底是怎么工作这个问题上,我相信我的理解跟你的并无分歧,没有必要再做文字上的争执。

但是就 dll 这种设计的优劣而言,即使我认为这不是一个好设计,也从未为设计本身表示过不理解。为了向后兼容是一个很重要的理由,也可以博得大众的理解。而且换种方式重新设计的话,也未必可以做的更好。但这并不影响我个人对之的批评。我想我的指责来至于对 windows 的理解和多年 windows 平台开发的经验;而且我想我对 windows 机制的了解应该比你猜想的多那么一点。

整个 windows 架构在一个又一个的 "设计机制不是系统级的,不需要统一管理" 的 DLL上,这本身就缺乏美感。

另, PE 的设计其中一点就是方便加载,尤其方便直接做文件映射。这样也更容易让进程间共享代码,我想这也是 windows 这样设计 PE 的设计目的之一。至于 PE 的发展,微软尽可以无限次的对之扩展,而永远叫它为 PE 格式。我这里提到的 PE 格式也有它的语境。

本质上,任何被编译的二进制代码,都只是一组数据而已。当然总有方法来加载运行。我们现在正在进行的项目中,Windows 版本下,那些模块就是我自己写代码解析翻译 PE 格式来加载并和其它模块做运行时链接的。这也并不困难。

这里很想请教一下,Windows 下如何利用 OS 已经提供的功能,方便的以流的方式加载到当前地址空间并运行。以前一度找过资料,发现可行的方法都不可用。既然兄台了解,请务必赐教。

to:Anonymous:不需要你再做什么解释了,如果说有什么无法理解,那就是不明白有人跑去主人家宣布主人思维有问题,还可以那么理直气壮。人啊真需要相互理解

看来某一个Anonymous受过某种刺激,流行一点可以说是有XX情结。
对于一个开放的blog,云风这里的评论一般比主体更有趣。所以RSS里面不能提供这些真是个遗憾。

to 笔端:
看来你没有仔细看前面的人的留言,或者看了但看不懂。这里我解释一下,我在“另”里面谈的问题,是和技术问题无关的另外一个问题了。主要是根据我对云风的了解总结一下他的思维方式,用来回应enterprisebeing的那句话“我尊重你在游戏领域的成就”,所以我特地用了一个“另”字。这样的事不解释都有人不懂,怎叫人与人之间不相轻呢?

把自己的想法公布出来是需要勇气的,因为要面对很多人的指着。但也不至于把一个技术问题而提升到“成就”的高度,同为技术人员又何必彼此相轻呢?

遥想97年,我们一帮人在whnet和nudt的BBS上热烈交流vax,aix,solaris,hpux时,也是对linux(那时还是slackware),freebsd推崇备至,视windows如粪土,后来那帮人都明白了,当年我们之所以有那些幼稚的想法和言论,盖因思维不全面,考虑的可能性太少,而自身素质又发展不全面使然也。看enterprisebeing均衡的思维,很有点像过来人啊,呵呵。

另:我不认为云风这种思维方式的人能够在包括游戏领域在内任何领域取得成就,所谓的“成就”,只是由于信息不充分而给一般人造成的错觉而已。就像上面的主题,你不提供这些信息,很多人都不知道他错了,更不知道他错在哪里。

忘了报下名,楼下的话也是我说的。

我尊重你在游戏领域的成就,但是很遗憾你对UNIXs和Windows的比较是错误的,事实上,如果我没猜错的话,你对Windows的机制并不清楚,而对UNIXs的了解也处于走马观花的阶段,当然这也没有什么,只要你不做黑客不作病毒,毕竟人各专一行,仅此而已。

BSDs、SVRs、Linux还有一堆大大小小的专用系统,他们只是在面对Windows的时候统称UNIXs,实际上他们之间的差别也天差地远。

如果只抓住暴露代码段地址这一点,从技术上说,没有什么二进制文件不能被暴露代码段地址,关键是语境,Dll暴露的代码段是有意义的,他就是为了通过暴露代码段地址提供二进制代码复用而存在,这就是Windows设计的机制。

ELF的全称是Executable and Linkable Format,他的机制就是Linkable,ELF文件的Link指的就是这个Linkable,所以它需要符号表。

EXE文件被赋予独立的地址空间,而Dll被加载到当前进程的地址空间,对Dll函数的调用,如果忽略技术细节的话,和程序内部的jump指令或者call指令没有什么区别,事实上很多汇编程序出于灵活控制的目的,经常用jump指令替代call指令。

所以Dll的整个机制完全局限于runtime语境,他有的只是机器码的地址空间,他的所谓导出函数符号本质上就是一个代码入口偏移地址助记符而已。

DLL的设计机制不是系统级的,所以不需要统一管理,他的设计初衷是Side-by-Side方式,每个程序可以使用自己的不同版本的DLL,尽管后来的Dll hell搞得Dll声名狼藉,但实际上Windows的向后兼容很大一部分建立在Dll的这种机制上。

相对而言,Solaris曾经因为打了个操作系统的补丁导致Java停车,不得不手忙脚乱的又给Java打了个专用补丁。这是比较夸张的,但同样的事情在UNIX世界也层出不穷,当初Borland因为具体的技术细节问题最终演变成为和Linux社区隔空对骂,本质上都是一类问题。

至于你提到的“也不可以从非本地文件系统上加载他们”,这是错误的,实际上在Windows所有的可执行代码都可以以流的方式加载到当前地址空间并运行,这是Windows提供给Debuger的机制,你尽可以把你的代码伪装成一个文本文件,这并不影响他可以被执行。

Dll的目标就是独立和复用,他的机制比较简单,自解释的并不完全,仅能够把代码入口地址暴露出来而已,尤其是在安全性上一片空白,这是他的弱势,但是从整体来看,他对Windows的兼容性提供的支持功不可没。

在Dll之后ActiveX以及COM提供了越来越完善的自解释能力和可管理性。

目前 .NET 的目标文件提供了类似的Executable and Linkable的能力,无论是Exe还是Dll都可以作为类库进行Link,而且不需要任何其他外部文件描述,因为他是完全自解释的。

但是他依然是PE格式,所以Linkable与否和文件格式毫无关系,只和机制有关,而机制完全受限于设计目的。

此外,不要以为UNIXs有多先进,十年前这样说可以,现在UNIXs已经老了。

单从"暴露了代码段地址" 这点看,倒不能说是 windows 下 exe 和 dll 的区别。exe 同样可以 export 内部函数的地址。

要说 "Dll 在unix下根本没有对应的东西" 我也不反对,正如 so 在 windows 下没有对应的东西一样。因为有 dll 这个东西,才有了 windows 这个样子;dll 就是被 windows 设计出来的,它的问题甚至它的存在就是 windows 的设计问题。

我想对于可执行代码的加载,windows 就有它的独特之处,所以我们在运行程序时不可以删除 exe 和 dll 本身,也不可以从非本地文件系统上加载他们。

顺便再说一下,unix下的动态连接库和静态连接库都是linker语境下的东西,windows下的Dll是彻头彻尾的独立运行文件,他和exe的区别在于暴露了代码段地址,也就是代码段地址而已,和linker的符号表没有任何关系。
Dll在unix下根本没有对应的东西。

enterprisebeing 说:
-------------------------
敢说才会赢, 王朔真有生活底蕴

在Windows上的Dll其实是一个严格意义上的独立二进制可运行模块,他接近于自解释,根本没有什么外部应用符号之类的东西,unix上的所谓连接库对应的其实是lib以及obj这个层面的东西,根本没有DLL这个层面的东西

Dll当初就是用于不同二进制代码之间的复用,他的发展方向是自解释的二进制可运行代码,就是后来的COM机制,unix上根本没有相对应的东西

so这种东西更偏进于lib文件,或者向java的jar文件,它是开发语境下的动态连接库,DLL的动态连接库是彻头彻尾的运行时的概念,根部不存在什么符号表之类的东西

我说:

Delphi下的链编是编译器负责的, FPC也是,不过FPC2.2版才会提供windows下的动态链编
所有这些根.h和.pas没有关系
和OS的设计更没有关系.

to sunway, 我觉得 delphi 生成的 exe 和 vc 生成的 exe 倒不太可能是 PE 格式的不同。之所以不需要 lib 文件是因为 pas 文件中已经有足够的信息了吧。

而 C 的 .h 文件中却得不到 dll 文件名的信息。

根据云风的思维特点,建议他改个名字比较恰当,比如叫云一知半解,或者叫云半桶子水淌得慌。然后要写什么书可以在书名上来一大破折号:一汪半桶水的晃荡

我发现用DELPHI开发的程序和VC开发的程序有一个显著的差别,那就是用EXESCOPE这类工具分析EXE的时候,查看EXE的Import部分的DLL的时候,同一个DLL,比如Kernel.dll, DELPHI的可能可以看到多个对这个DLL的使用,而VC只能看到一个DLL。就我知道一点DELPHI的PE格式和MS的PE格式有区别。或者云风说的问题和这个有点关系。

to sunway, 的确是 VC 的问题,但是我觉得 VC 是用一个正确的方法解决 os 设计的问题。不光是 vc, 别的 windows 下 C 编译器也是用同样的方法处理这个问题。

至于 delphi 如何在工作我没有研究。

to qyb, 那是我搞错了,最近才接触 unix 下的开发。今天想想也对,既然可以支持直接暴露未实现的符号,当然也可以指定这些符号的位置,做到这一点并不困难。

用 unix 这次词说事好象是不大好,unix 也有诸多分支和版本和很长的历史。但是想针对 windows 来树立一个对立面比较,一下还找不到合适的词。

这个问题,我想本质上是 elf 和 pe 的设计问题。或许我熟知的 pe 和不熟悉的扩展目前已经有解决方案可以解决类似的问题。但是因为 windows 自身的原因难以从根本上解放程序员的负担。而从 unix 的发展来看,升级操作系统却容易的多。至少不如 vista 这么难产。

南郭先生后继有人啊!

"而 unix 下只存在执行文件对 so 的隐式依赖"

这句话表述有问题吧,so 也会隐式依赖其他动态库的,比如 libc.so;你用 ldd 可以看到的。

或者你用 strings 察看 so 的文本内容,也会有指明它链接的文件名,以及运行中搜索的路径——这个路径可以在编译 so 的时候用 -Wl,-R 参数来指定的。

还有,在 AIX 的某些版本里(至少是 4.3 版本),某些动态库也需要一个符号表文件 .exp ,让 cc 在编译过程中使用才能正确链接。虽然我很久没有接触 AIX 了,但可以确认确实曾经碰到过这样的事情。所以用 "Unix" 来说事不是太严谨。

是VC的问题吧,DELPHI只需要一个PAS文件和DLL就够了,不需要什么LIB文件。

Post a comment

非这个主题相关的留言请到:留言本