楼主:
LPH66 (-6.2598534e+18f)
2014-08-14 15:51:21※ 引述《sv39933993 (^^)》之铭言:
: typedef void *DEV_HANDLE; //line 1
: DEV_HANDLE WINAPI Device_xOpen(const int nDevIndex); //line 2
: typedef DEV_HANDLE (WINAPI *LP_DevOpen)(const int nDevIndex,
: const char *pDevName); // line 3
: DEV_HANDLE m_hLink; // line 4
: LP_DevOpen m_Open; // line 5
: m_Open = (LP_DevOpen)::GetProcAddress(hDLL, "Device_xOpen"); // line 6
: m_hLink = m_Open(0, ""); // line 7
: if (m_hLink){ ......} // line 8
: 疑问:
: 1. DEV_HANDEL为指标型式,所以 line 2 的意思是函式Device_xOpen的回传值
: 是一个位址吗?
: 2. 如果"函式Device_xOpen的回传值是一个位址",
: 那为何line 7, line 8 看起来 m_hLink 是一个值?
: 3. Device_xOpen函式的输入值只有一个nDevIndex,
: 经过了line 3, line 6可以多出一个pDevName的原因是?
: pDevName在原本的Device_xOpen没有,它代表的是什么呢?
4.5.两题在推文差不多回答完了
这里就只是明确的表示我要呼叫 global 的那个 GetProcAddress 而已
(之所以要明确表示是因为不加的话不一定会呼叫到那一个
这跟编译器怎么找到你的函式有关, 这边表过不提)
1.2.两题可以仔细看我推的那篇 #1I6t3pwd 的最后一段
DEV_HANDLE 就是那篇文里谈的所谓的 HANDLE 值
它很有机会是一个内部指标, 所以形式上它是一个 void * 这种万用指标
但外面在用时只要当做一个不透明的值就好 (可以想成跟 C 的 FILE* 很像就行了)
这种形态通常会有一个特殊值表示“这值不代表任何东西”
#8 就是在测试是不是这种特殊值, 如果不是才做事
第三题要连着 Windows 的 DLL 相关 API 一起来谈
(其实主要就是 #6 的 GetProcAddress 这个 API)
我们的目的其实是要找出一个 DLL 里的函数去呼叫它
如果 DLL 本身有一个对应的 .lib 的话
那个 .lib 里面会有一小段程式码帮你找出 DLL 里的函式在哪里
(这有个专有名词叫 stub, 不过 stub 的意义稍微广了点, 这里也表过不提)
这段 stub 实际上就是使用 GetProcAddress 去找出函式
http://msdn.microsoft.com/en-us/library/windows/desktop/ms683212(v=vs.85).aspx
GetProcAddress 使用两个参数, 第一个是 DLL 的 HANDLE, 第二个是函式名字串
而它的回传值上面的说明写着 FARPROC
深入研究这个名字会发现它是这么被定义的:
typedef int (FAR WINAPI *FARPROC)();
它是一个不检查参数,回传 int,具有 WINAPI (__stdcall) 的 function pointer
(FAR 这东西是历史遗迹, 现在可以不用理它)
总之, 它回传一个 function pointer, 指向所找到的函式
所以我们可以用这个 function pointer 来呼叫到那个函式
(其实 stub 里做的还稍微多了一点
它取一次之后会把结果存起来, 下次再呼叫时就不用再取一次;
这里还有一个最佳化技巧在里面不过因为又更进阶了所以表过不提)
但是因为 GetProcAddress 是用来取得各种函式的
所以不可能知道目标函式实际上需要何种参数
因此只好折衷用一个相对万用的函式指标型态 int(*)() 回传
于是要使用 GetProcAddress 时必须转型成你呼叫的那个函式的形态才能呼叫
#3 就是将这个函式形态定一个名字叫做 LP_DevOpen
在 #6 里 GetProcAddress 回来之后就转成这种函式之后再在 #7 呼叫
这里就回到了你最初的问题: 为什么经过那一段之后多了一个参数
原因很简单: 不是 DLL 作者写错了就是 DLL 使用者写错了
意思就是要嘛 #2 有问题, 要嘛 #3/#6/#7 有问题
这两个函式形态必须要一样呼叫才不会有事
(这种不一样编译器抓不出来
因为 DLL 是动态连结的东西, 在静态连结时编译器不知道那个 DLL 的函式长怎样
使用附带的 .lib 在这里有个好处就是那里的 stub 是静态的, 可以给 DLL 使用者比对
而且因为它是在编 DLL 时附带一起产生的因此不会有一致性问题)
或许 #3/#6/#7 使用的 DLL 用的是更新版的函数多带一个字串参数
这样这里就又有一个常见的问题叫做 ABI 相容性
ABI 全名 Application Binary Interface 它是指二进制层级上互相呼叫的接口
一个 DLL 的函数接口更新代表对这个 DLL 的 ABI 有变动
这对于程式码的使用者来说是最糟的状况
跟同样也在这个状况里出现的原始码层级 API 变动还糟
ABI 变动代表你不能直接换掉 DLL, 要连 DLL 使用者的程式一起更新才行
使用者必须要去看过所有使用这个 DLL 的地方并更改使得它符合新的 ABI
一不小心忘了改就有可能有惨剧发生
所以你最好去找这个 DLL 的相关资料看看是不是哪里有问题
(会造成 ABI 变动不只有函数接口更新会造成, 还有很多其他原因
这也是 DLL 作者需要小心的地方)
楼主:
LPH66 (-6.2598534e+18f)
2014-08-15 03:22:00英文维基百科 Application binary interface 条目当中提到了一些 ABI 所包含的东西, 这些东西都是需要注意的近年一个常见的 ABI 基本规范是 Itanium C++ ABIgcc/g++ 跟的就是这套 ABI 规范走, 所以满值得参考的不过这个主要是在做编译器才比较容易碰到这层细节一般的 AP 层主要还是以原始码层级的 API 相容为主只要编译时使用的 ABI 相关选项一致那大致上不会有什么问题