PY08-03:修饰名与动态库编译


  考虑Python的性能,梳理下Cython的扩展,这个主题主要梳理下window平台下的动态库编译与使用技术之一:修饰名与动态库的关系。Linux下的动态库没有这么啰嗦,Windows下的修饰名在obj中就开始修饰,但是因为修饰名的缘故obj,lib,dll之间的修饰名配合工作一旦一个地方出错,会导致查找错误,就会产生那个鼎鼎大名的link错误:找不到外部引用符号。为了理解,我们使用def来导出函数,变量与类,而且使用VSCode来撸代码,很是酸爽。本主题涉及几个工具:cl, lib, link, dumpbin, undname等。


函数名,变量名与类型的内部表示

测试代码

头文件model.h

#ifndef YQ_MODEL_H
#define YQ_MODEL_H
int var_a = 20;

float calculate(float, float);

class Sobel{
public: 
    Sobel();
    int m_a;
    int getInfo(const char *);
private:
    void queryInfo(int);
};
#endif

实现文件model.cpp

#include "model.h"

float calculate(float p1, float p2){
    return p1 + p2;
}

Sobel::Sobel():m_a(88){

}

int Sobel::getInfo(const char *filename){
    return 77;
}

void Sobel::queryInfo(int id){

}

编译成目标文件

  • 编译脚本
CL_ARGS    = /EHsc  \
             /MD \
             /source-charset:utf-8  \
             /execution-charset:utf-8 \
             /nologo

obj: model.cpp model.h
# 编译目标文件
    @cl /c $(CL_ARGS)  model.cpp


clean:
    @del *.obj *.dll *.pdb *.ilk *.exe *.lib  *.exp 2>/Nul


  • 执行脚本后,生成obj文件
    • vcvars64.bat
    • nmake obj

bumpbin工具查看修饰名

命令

  • dumpbin /symbols model.obj

修饰名输出

C:\01works\13python\codes\export_all>dumpbin /symbols  model.obj
Microsoft (R) COFF/PE Dumper Version 14.24.28319.0        
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file model.obj

File Type: COFF OBJECT

COFF SYMBOL TABLE
000 01056E9F ABS    notype       Static       | @comp.id
001 80000190 ABS    notype       Static       | @feat.00
002 00000000 SECT1  notype       Static       | .drectve
    Section length   2F, #relocs    0, #linenums    0, checksum        0
004 00000000 SECT2  notype       Static       | .debug$S
    Section length   80, #relocs    0, #linenums    0, checksum        0
006 00000000 SECT3  notype       Static       | .data
    Section length    4, #relocs    0, #linenums    0, checksum DF7BC0C8
008 00000000 SECT3  notype       External     | ?var_a@@3HA (int var_a)
009 00000000 SECT4  notype       Static       | .text$mn
    Section length   6A, #relocs    0, #linenums    0, checksum 4BDEC2ED
00B 00000000 SECT4  notype ()    External     | ?calculate@@YAMMM@Z (float __cdecl calculate(float,float))
00C 00000020 SECT4  notype ()    External     | ??0Sobel@@QEAA@XZ (public: __cdecl Sobel::Sobel(void))
00D 00000040 SECT4  notype ()    External     | ?getInfo@Sobel@@QEAAHPEBD@Z (public: int __cdecl Sobel::getInfo(char const *))
00E 00000060 SECT4  notype ()    External     | ?queryInfo@Sobel@@AEAAXH@Z (private: void __cdecl Sobel::queryInfo(int))
00F 00000000 UNDEF  notype       External     | _fltused
010 00000000 SECT5  notype       Static       | .chks64
    Section length   28, #relocs    0, #linenums    0, checksum        0

String Table Size = 0x6D bytes


C:\01works\13python\codes\export_all>nmake clean

Microsoft (R) 程序维护实用工具 14.24.28319.0 版
版权所有 (C) Microsoft Corporation。  保留所有权利。


C:\01works\13python\codes\export_all>nmake obj

C:\01works\13python\codes\export_all>dumpbin /symbols  model.obj
Microsoft (R) COFF/PE Dumper Version 14.24.28319.0        
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file model.obj

File Type: COFF OBJECT

COFF SYMBOL TABLE
000 01056E9F ABS    notype       Static       | @comp.id  
001 80000190 ABS    notype       Static       | @feat.00  
002 00000000 SECT1  notype       Static       | .drectve
    Section length   2F, #relocs    0, #linenums    0, checksum        0
004 00000000 SECT2  notype       Static       | .debug$S
    Section length   80, #relocs    0, #linenums    0, checksum        0
006 00000000 SECT3  notype       Static       | .data
    Section length    4, #relocs    0, #linenums    0, checksum DF7BC0C8
008 00000000 SECT3  notype       External     | ?var_a@@3HA (int var_a)
009 00000000 SECT4  notype       Static       | .text$mn
    Section length   6A, #relocs    0, #linenums    0, checksum 4BDEC2ED
00B 00000000 SECT4  notype ()    External     | ?calculate@@YAMMM@Z (float __cdecl calculate(float,float))
00C 00000020 SECT4  notype ()    External     | ??0Sobel@@QEAA@XZ (public: __cdecl Sobel::Sobel(void))
00D 00000040 SECT4  notype ()    External     | ?getInfo@Sobel@@QEAAHPEBD@Z (public: int __cdecl Sobel::getInfo(char const *))
00E 00000060 SECT4  notype ()    External     | ?queryInfo@Sobel@@AEAAXH@Z (private: void __cdecl Sobel::queryInfo(int))
00F 00000000 UNDEF  notype       External     | _fltused
010 00000000 SECT5  notype       Static       | .chks64
    Section length   28, #relocs    0, #linenums    0, checksum        0

String Table Size = 0x6D bytes

  Summary

          28 .chks64
           4 .data
          80 .debug$S
          2F .drectve
          6A .text$mn

C:\01works\13python\codes\export_all>
  • ?var_a@@3HA (int var_a)前面部分就是修饰名。
    • 重载函数的修饰名是不同的(这里隐含着重载的实现原理)。

修饰名转换工具

  • 修饰名转换工具:C++ Name Undecorator (Undname.exe)
    • 用于还原修饰过的名字。
C:\01works\13python\codes\export_all>undname ??0Sobel@@QEAA@XZ
Microsoft (R) C++ Name Undecorator
Copyright (C) Microsoft Corporation. All rights reserved.

Undecoration of :- "??0Sobel@@QEAA@XZ"
is :- "public: __cdecl Sobel::Sobel(void) __ptr64"

修饰名与DLL符号调用

  • 修饰名在编译目标(.obj)文件的时候就按照规则生成修饰名,修饰名在内部使用会自动转换,一般对用户来讲是不可见的,也不需要使用。
    • 修饰名在lib中使用,用来内部处理映射关系。
    • 在dll层不存在修饰名。

导出类,函数与变量

导出变量

头文件

#ifndef YQ_MODEL_H
#define YQ_MODEL_H
int var_a;
int var_b;

float calculate(float, float);

class Sobel{
public: 
    Sobel();
    int m_a;
    int getInfo(const char *);
private:
    void queryInfo(int);
};
#endif

实现文件

#include "model.h"

float calculate(float p1, float p2){
    var_a = 99;
    var_b = 199;
    return p1 + p2;
}

Sobel::Sobel():m_a(88){

}

int Sobel::getInfo(const char *filename){
    return 77;
}

void Sobel::queryInfo(int id){

}

def文件

LIBRARY libmodel
EXPORTS
    calculate            @1
    ?var_a@@3HA          @2 DATA
    var_b                @3 DATA

编译脚本

CL_ARGS    = /EHsc  \
             /MD \
             /source-charset:utf-8  \
             /execution-charset:utf-8 \
             /nologo

obj: model.cpp model.h
# 编译目标文件
    @cl /c $(CL_ARGS)  model.cpp
# 链接动态库
    @link /MACHINE:X64 /NOLOGO /DLL /DEF:model.def model.obj

main: main.cpp
    @cl $(CL_ARGS) main.cpp /link /OUT:main.exe  
clean:
    @del *.obj *.dll *.pdb *.ilk *.exe *.lib  *.exp 2>/Nul

查看lib的导出

C:\01works\13python\codes\export_all>dumpbin /exports model.lib
Microsoft (R) COFF/PE Dumper Version 14.24.28319.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file model.lib

File Type: LIBRARY

     Exports

       ordinal    name

             1    ?calculate@@YAMMM@Z (float __cdecl calculate(float,float))
             2    ?var_a@@3HA (int var_a)
             3    ?var_b@@3HA (int var_b)

  Summary

          C6 .debug$S
          14 .idata$2
          14 .idata$3
           8 .idata$4
           8 .idata$5
           E .idata$6

C:\01works\13python\codes\export_all>

查看dll的导出

C:\01works\13python\codes\export_all>dumpbin /exports libmodel.dll
Microsoft (R) COFF/PE Dumper Version 14.24.28319.0        
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file libmodel.dll

File Type: DLL

  Section contains the following exports for libmodel.dll 

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           3 number of functions
           3 number of names

    ordinal hint RVA      name

          2    0 00003040 ?var_a@@3HA
          1    1 00001000 calculate
          3    2 00003044 var_b

  Summary

        1000 .data
        1000 .pdata
        1000 .rdata
        1000 .reloc
        1000 .text

C:\01works\13python\codes\export_all>

调用导出的变量

#include <iostream>
#include <windows.h>

#pragma comment(lib, "model.lib")
__declspec(dllimport) int var_a;
__declspec(dllimport) int var_b;

float calculate(float, float);

int main(int argc, const char *argv){
    std::cout << "---------------编译调用-------------------" << std::endl;
    std::cout << var_a << std::endl;
    std::cout << var_b << std::endl;
    std::cout << calculate(45.0, 55.0) << std::endl;   
    std::cout << var_a << std::endl;
    std::cout << var_b << std::endl;

    std::cout << "---------------动态调用-------------------" << std::endl;
    HMODULE h = LoadLibraryA("libmodel.dll");
    if(h == NULL){
        std::cout << "加载动态库文件失败!" << std::endl;
        return -1;
    }
    else{
        std::cout << "加载动态库文件成功!" << std::endl;
    }
    // 查找符号函数
    // FARPROC v = GetProcAddress(h, "var_b");    // 注意def中的定义
    FARPROC v = GetProcAddress(h, "?var_a@@3HA");
    if(v == NULL){
        std::cout << "查找变量失败!" << std::endl;
        // 释放加载的库
        FreeLibrary(h);
        return -1;
    }
    else{
        std::cout << "查找变量成功!" << std::endl;
    }
    // 调用变量
    std::cout << *(int*)v << std::endl;

    FreeLibrary(h);
    
    return 0;
}

  • 执行结果
导出变量成功
  • 总结:
    1. 编程源代码为目标文件,开始对变量做修饰处理;
    2. dll没有使用修饰名;lib内部使用修饰名;
    3. 编译调用程序源代码也是采用修饰名匹配;
    4. def中可以使用原名字,也可以使用修饰名。

导出类

  • 其中.cpp文件做了点改动
#include "model.h"

float calculate(float p1, float p2){
    var_a = 99;
    var_b = 199;
    return p1 + p2;
}

Sobel::Sobel():m_a(88){

}

int Sobel::getInfo(const char *filename){
    return m_a;
}

void Sobel::queryInfo(int id){

}

确定修饰名

  • 首先编译成目标文件

C:\01works\13python\codes\def_class>dumpbin /symbols model.obj
Microsoft (R) COFF/PE Dumper Version 14.24.28319.0        
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file model.obj

File Type: COFF OBJECT

COFF SYMBOL TABLE
000 01056E9F ABS    notype       Static       | @comp.id  
001 80000190 ABS    notype       Static       | @feat.00
002 00000000 SECT1  notype       Static       | .drectve
    Section length   2F, #relocs    0, #linenums    0, checksum        0
004 00000000 SECT2  notype       Static       | .debug$S
    Section length   80, #relocs    0, #linenums    0, checksum        0
006 00000000 SECT3  notype       Static       | .bss
    Section length    8, #relocs    0, #linenums    0, checksum        0
008 00000000 SECT3  notype       External     | ?var_a@@3HA (int var_a)
009 00000004 SECT3  notype       External     | ?var_b@@3HA (int var_b)
00A 00000000 SECT4  notype       Static       | .text$mn
    Section length   8A, #relocs    2, #linenums    0, checksum 3B5C665F
00C 00000000 SECT4  notype ()    External     | ?calculate@@YAMMM@Z (float __cdecl calculate(float,float))
00D 00000040 SECT4  notype ()    External     | ??0Sobel@@QEAA@XZ (public: __cdecl Sobel::Sobel(void))
00E 00000060 SECT4  notype ()    External     | ?getInfo@Sobel@@QEAAHPEBD@Z (public: int __cdecl Sobel::getInfo(char const *))
00F 00000080 SECT4  notype ()    External     | ?queryInfo@Sobel@@AEAAXH@Z (private: void __cdecl Sobel::queryInfo(int))
010 00000000 UNDEF  notype       External     | _fltused
011 00000000 SECT5  notype       Static       | .chks64
    Section length   28, #relocs    0, #linenums    0, checksum        0

String Table Size = 0x79 bytes

  Summary

           8 .bss
          28 .chks64
          80 .debug$S
          2F .drectve
          8A .text$mn
  • 其中我们需要导出的类的成员
00D 00000040 SECT4  notype ()    External     | ??0Sobel@@QEAA@XZ (public: __cdecl Sobel::Sobel(void))
00E 00000060 SECT4  notype ()    External     | ?getInfo@Sobel@@QEAAHPEBD@Z (public: int __cdecl Sobel::getInfo(char const *))
00F 00000080 SECT4  notype ()    External     | ?queryInfo@Sobel@@AEAAXH@Z (private: void __cdecl Sobel::queryInfo(int))

编写def文件:model.def

LIBRARY libmodel
EXPORTS
    ??0Sobel@@QEAA@XZ              @1
    ?getInfo@Sobel@@QEAAHPEBD@Z    @2
    ?queryInfo@Sobel@@AEAAXH@Z     @3

编译dll

CL_ARGS    = /EHsc  \
             /MD \
             /source-charset:utf-8  \
             /execution-charset:utf-8 \
             /nologo

obj: model.cpp model.h
# 编译目标文件
    @cl /c $(CL_ARGS)  model.cpp
# 链接动态库
    @link /MACHINE:X64 /NOLOGO /DLL /DEF:model.def model.obj

main: main.cpp
    @cl $(CL_ARGS) main.cpp /link /OUT:main.exe  
clean:
    @del *.obj *.dll *.pdb *.ilk *.exe *.lib  *.exp 2>/Nul


查看model.lib的导出符号

  • 命令:dumpbin /exports model.lib
C:\01works\13python\codes\def_class>dumpbin /exports model.lib
Microsoft (R) COFF/PE Dumper Version 14.24.28319.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file model.lib

File Type: LIBRARY

     Exports

       ordinal    name

             1    ??0Sobel@@QEAA@XZ (public: __cdecl Sobel::Sobel(void))
             2    ?getInfo@Sobel@@QEAAHPEBD@Z (public: int __cdecl Sobel::getInfo(char const *))
             3    ?queryInfo@Sobel@@AEAAXH@Z (private: void __cdecl Sobel::queryInfo(int))

  Summary

          C6 .debug$S
          14 .idata$2
          14 .idata$3
           8 .idata$4
           8 .idata$5
           E .idata$6

C:\01works\13python\codes\def_class>

查看model.dll导出的符号

  • 命令:dumpbin /exports libmodel.dll
C:\01works\13python\codes\def_class>dumpbin /exports libmodel.dll
Microsoft (R) COFF/PE Dumper Version 14.24.28319.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file libmodel.dll

File Type: DLL

  Section contains the following exports for libmodel.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           3 number of functions
           3 number of names

    ordinal hint RVA      name

          1    0 00001040 ??0Sobel@@QEAA@XZ
          2    1 00001060 ?getInfo@Sobel@@QEAAHPEBD@Z
          3    2 00001080 ?queryInfo@Sobel@@AEAAXH@Z

  Summary

        1000 .data
        1000 .pdata
        1000 .rdata
        1000 .reloc
        1000 .text

C:\01works\13python\codes\def_class>

调用导出的类

#include <iostream>
#include <windows.h>
#include "model.h"

#pragma comment(lib, "model.lib")

typedef Sobel (*builder)();

int main(int argc, const char *argv){
    std::cout << "---------------编译调用-------------------" << std::endl;
    Sobel s;   // 构造对象
    int re = s.getInfo("Hello"); // 调用成员
    std::cout << "动态库调用结果:" << re << std::endl; 
    
    std::cout << "---------------编译调用-------------------" << std::endl;
    HMODULE h = LoadLibraryA("libmodel.dll");
    if(h == NULL){
        std::cout << "加载动态库文件失败!" << std::endl;
        return -1;
    }
    else{
        std::cout << "加载动态库文件成功!" << std::endl;
    }
    // 查找符号函数
    FARPROC cls_name = GetProcAddress(h, MAKEINTRESOURCEA(1)); 
    FARPROC cls_getInfo = GetProcAddress(h, MAKEINTRESOURCEA(2));    
    if(cls_name == NULL || cls_getInfo == NULL){
        std::cout << "查找类或者成员函数失败!" << std::endl;
        // 释放加载的库
        FreeLibrary(h);
        return -1;
    }
    else{
        std::cout << "查找类构造器与成员函数成功!" << std::endl;
    }
    // 调用变量
    builder con = (builder)cls_name;
    Sobel sobel = con();
    int r = sobel.getInfo("Hello"); // 调用成员
    std::cout << "动态库手工调用结果:" << r << std::endl; 
    std::cout << "---------------------------------------" << std::endl;
    int (*cal)(const char *) =(int (*)(const char *)) cls_getInfo;     // 没有对象依赖,指向没有初始化的全局栈位置。
    std::cout << "动态库手工调用结果:" << cal("world") << std::endl;
    FreeLibrary(h);
    
    return 0;
}

  • 问题:
    • 怎样使用成员函数与对象绑定调用? 参考网上方法,使用的是汇编,使用汇编切换对象栈,但是VCx64位不支持内涵嵌入汇编。
    • 实际上面的代码也能使用使用,关键是想找到成员函数与对象栈一起工作的方法。

附录

  • 顺便说一下,def的方法已经不再推荐使用,这类梳理下只是理解程序运行的技巧而已。通用的方法还是使用
    • __declspec(dllexport)
    • __declspec(dllimport)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350