如何使用g++编译调用dll的c++代码

本文将有以下4个部分来讲如何使用g++编译调用dll的c++代码。

1.如何调用dll

2.动态链接和静态链接的区别

3.g++的编译参数以及如何编译调用dll的c++代码

4.总结

1.如何调用dll

    动态链接库(Dynamic Link Library),简称DLL。DLL 是一个包含可由多个程序同时使用的代码和数据的库。它允许程序共享执行特殊任务所必需的代码和其他资源,一般来说,DLL是一种磁盘文件,以.dll、.DRV、.FON、.SYS和许多以.EXE为扩展名的系统文件都可以是DLL。它由全局数据、服务函数和资源组成,在运行时被系统加载到调用进程的虚拟空间中,成为调用进程的一部分。

DLL的调用可以分为两种:一种是隐式调用(需要.lib和.dll),一种是显示调用(需要.dll)。

隐式加载就是在程序编译的时候就将dll编译到可执行文件中。实现隐式链接很容易,只要将导入函数关键字_declspec(dllimport)函数名等写到应用程序相应的头文件中就可以了。下面将通过一个例子来讲解隐式链接调用Dlltest.dll库中的Min函数。

首先新建一个项目为TestDll,在Dlltest.h、Dlltest.cpp文件中分别输入如下代码:

Dlltest.h的代码如下:

//隐式加载Dlltest
#pragma comment(lib,"Dlltest.lib")

//声明外部函数
extern "C"_declspec(dllimport) int Max(int a,int b);
extern "C"_declspec(dllimport) int Min(int a,int b);

Dlltest.cpp的代码如下:

#include<stdio.h>
#include"Dlltest.h"
int  main()
{
   int a;
   a=min(1,2);
   printf("比较的结果为%d\n",a);
   return 0;
}

 在生成Dlltest.exe文件之前,要先将Dlltest.dll和Dlltest.lib拷贝到debug同目录(工程根目录)下,也可以拷贝到windows的System目录下。如果DLL使用的是def文件,要删除Dlltest.h文件中关键字extern "C"。Dlltest.h文件中的关键字pragma commit是要Visual C++的编译器在link时,链接到Dlltest.lib文件,当然,开发人员也可以不使用#pragma comment(lib,"Dlltest.lib")语句,而直接在工程的Setting->Link页的Object/Moduls栏填入Dlltest.lib既可。 

显式加载是指在程序运行过程中,需要用到dll里的函数时,再动态加载dll到内存中,这种加载方式因为是在程序运行后再加载的,dll的维护更容易,使得程序如果需要更新,很多时候直接更新dll,而不用重新安装程序。使用这种方式使应用程序在执行过程中随时可以加载DLL文件,也可以随时卸载DLL文件。

下面讲一个通过显式链接调用DLL中的Max函数的例子。

#include <stdio.h>
#include <windows.h>
int main()
{
typedef int(*pMax)(int a,int b);
typedef int(*pMin)(int a,int b);
HINSTANCE hDLL;
int A;
PMax Max;
//加载动态链接库MyDll.dll文件
HDLL=LoadLibrary("Dlltest.dll");

Max=(pMax)GetProcAddress(hDLL,"Max");
A=Max(5,8);
Printf("比较的结果为%d\n",A);

//卸载MyDll.dll文件;
FreeLibrary(hDLL);
}

  在上例中使用类型定义关键字typedef,定义指向和DLL中相同的函数原型指针,然后通过LoadLibray()将DLL加载到当前的应用程序中并返回当前DLL文件的句柄,然后通过GetProcAddress()函数获取导入到应用程序中的函数指针,函数调用完毕后,使用FreeLibrary()卸载DLL文件。在编译程序之前,首先要将DLL文件拷贝到工程所在的目录或Windows系统目录下。

学习参考链接:

DLLs in Visual C++:

https://docs.microsoft.com/en-us/previous-versions/1ez7dh12%28v%3dvs.140%29

Walkthrough: Creating and Using a Dynamic Link Library (C++):

https://docs.microsoft.com/en-us/previous-versions/ms235636%28v%3dvs.140%29

Using Run-Time Dynamic Linking:

https://docs.microsoft.com/zh-cn/windows/desktop/Dlls/using-run-time-dynamic-linking

2.动态链接和静态链接的区别

静态链接方法:#pragma comment(lib, "test.lib") ,静态链接的时候,载入代码就会把程序会用到的动态代码或动态代码的地址确定下来
静态库的链接可以使用静态链接,动态链接库也可以使用这种方法链接导入库

动态链接方法:LoadLibrary()、GetProcessAddress()FreeLibrary(),使用这种方式的程序并不在一开始就完成动态链接,而是直到真正调用动态库代码时,载入程序才计算(被调用的那部分)动态代码的逻辑地址,然后等到某个时候,程序又需要调用另外某块动态代码时,载入程序又去计算这部分代码的逻辑地址,所以,这种方式使程序初始化时间较短,但运行期间的性能比不上静态链接的程序。

静态库链接时搜索路径顺序:

1. ld会去找g++命令中的参数-L
2. 再找g++的环境变量LIBRARY_PATH
3. 再找内定目录 /lib /usr/lib /usr/local/lib 这是当初compile g++时写在程序内的

动态链接时、执行时搜索路径顺序:

1. 编译目标代码时指定的动态库搜索路径
2. 环境变量LD_LIBRARY_PATH指定的动态库搜索路径
3. 配置文件/etc/ld.so.conf中指定的动态库搜索路径
4. 默认的动态库搜索路径/lib
5. 默认的动态库搜索路径/usr/lib

有关环境变量:
LIBRARY_PATH环境变量:指定程序静态链接库文件搜索路径
LD_LIBRARY_PATH环境变量:指定程序动态链接库文件搜索路径

静态链接和动态链接的优缺点: 

静态链接库的优点 

     (1) 代码装载速度快,执行速度略比动态链接库快; 

     (2) 只需保证在开发者的计算机中有正确的.LIB文件,在以二进制形式发布程序时不需考虑在用户的计算机上.LIB文件是否存在及版本问题。

动态链接库的优点 

     (1) 更加节省内存并减少页面交换;

     (2) DLL文件与EXE文件独立,只要输出接口不变(即名称、参数、返回值类型和调用约定不变),更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性;

     (3) 不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数;

     (4)适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试。

 不足之处

     (1) 使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费;

     (2) 使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息。而使用运行时动态链接,系统不会终止,但由于DLL中的导出函数不可用,程序会加载失败;速度比静态链接慢。当某个模块更新后,如果新模块与旧的模块不兼容,那么那些需要该模块才能运行的软件,统统不能正常运行

3.g++的编译参数

G++手册:https://linux.die.net/man/1/g++

编译分为3步,首先对源文件进行预处理,这个过程主要是处理一些#号定义的命令或语句(如宏、#include、预编译指令#ifdef等),生成*.i文件;然后进行编译,这个过程主要是进行词法分析、语法分析和语义分析等,生成*.s的汇编文件;最后进行汇编,这个过程比较简单,就是将对应的汇编指令翻译成机器指令,生成可重定位的二进制目标文件。

使用g++编译链接示例:

g++ -E main.c -o main.i  #预编译,生成main.i文件
g++ -S main.i            #编译,生成main.S文件
g++ -c main.S            #汇编,生成main.o文件
g++ main.o -o main       #链接,生成可执行文件  

g++ 命令的基本用法如下:

g++ [options] [filenames]

    选项指定编译器怎样进行编译。

g++常用参数:

1)-E参数

  -E 选项指示编译器仅对输入文件进行预处理。当这个选项被使用时, 预处理器的输出被送到标准输出而不是储存在文件里.

2)-S参数

  -S 编译选项告诉 g++ 在为 C 代码产生了汇编语言文件后停止编译。 g++ 产生的汇编语言文件的缺省扩展名是 .s 。

3)-c参数

  -c 选项告诉 g++ 仅把源代码编译为目标代码。缺省时 g++ 建立的目标代码文件有一个 .o 的扩展名。 

4)-o参数 

    -o 编译选项来为将产生的可执行文件用指定的文件名。

5)-O参数

  -O 选项告诉 g++ 对源代码进行基本优化。这些优化在大多数情况下都会使程序执行的更快。 -O2 选项告诉g++ 产生尽可能小和尽可能快的代码。 如-O2,-O3,-On(n 常为0--3);

-O1  主要进行跳转和延迟退栈两种优化;

-O2 除了完成-O1的优化之外,还进行一些额外的调整工作,如指令调整等。

-O3 则包括循环展开和其他一些与处理特性相关的优化工作。

选项将使编译的速度比使用 -O 时慢, 但通常产生的代码执行速度会更快。

6)调试选项-g和-pg

  g++ 支持数种调试和剖析选项,常用到的是 -g 和 -pg 。

  -g 选项告诉 g++ 产生能被 GNU 调试器使用的调试信息以便调试你的程序。g++ 提供了一个很多其他 C 编译器里没有的特性, 在 g++ 里你能使-g 和 -O (产生优化代码)联用。

 -pg 选项告诉 g++ 在编译好的程序里加入额外的代码。运行程序时, 产生 gprof 用的剖析信息以显示你的程序的耗时情况。

7) -l参数和-L参数

  -l参数就是用来指定程序要链接的库,-l参数紧接着就是库名,那么库名跟真正的库文件名有什么关系呢?

就拿数学库来说,他的库名是m,他的库文件名是libm.so,很容易看出,把库文件名的头lib和尾.so去掉就是库名了。

-L参数跟着的是库文件所在的目录名。再比如我们把libtest.so放在/aaa/bbb/ccc目录下,那链接参数就是-L/aaa/bbb/ccc -ltest

8 -include-I参数

      -include用来包含头文件,但一般情况下包含头文件都在源码里用#i nclude xxxxxx实现,-include参数很少用。

       -I参数是用来指定头文件目录,/usr/include目录一般是不用指定的,g++知道去那里找,但 是如果头文件不在/usr/icnclude里我们就要用-I参数指定了,比如头文件放在/myinclude目录里,那编译命令行就要加上-I/myinclude参数了,如果不加你会得到一个"xxxx.h: No such file or directory"的错误。-I参数可以用相对路径,比如头文件在当前目录,可以用-I.来指定。

9-Wall-w  -v参数

     -Wall 打印出gcc提供的警告信息

     -w     关闭所有警告信息

     -v      列出所有编译步骤

我们可以使用visual studio,还有Dev C++来编译调用dll的c++程序,方便快捷。

VS的编译器名称为MSVC。MSVC官方文档为:

https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-tasks-specific-to-visual-cpp?view=vs-2017

msvc的编译器cl.exe 

msvc的链接器link.exe 

Dev-C++的编译器名称为Mingw(包含了g++和gcc)

那我们如何使用g++编译调用dll的c++程序呢?

我要调用的dll名称为winusb_dll_spr_x64.dll(64位),使用的是动态链接。调用dll的c++代码如下:

#include <Windows.h>
#include<iostream>
#include<stdlib.h>

using namespace std;

int main(int argc, char* argv[])

{
        //open_handle open_usb;	
	HINSTANCE hDllInst = LoadLibrary("winusb_dll_spr_x64.dll");
	//HINSTANCE hDllInst = LoadLibrary("winusb_dll_x86.dll");
	cout<<"hDllInst:"<<hDllInst<<endl; 
	

	if(NULL!=GetProcAddress(hDllInst, "open")()){
		cout<<"GetProcAddress success";
	}else{
		cout<<"fail"<<endl; 
	
	} 
	
    return 0;
}

 在Dev c++的运行结果为:

使用g++编译,按住win+R,打开cmd(你要确保已经配置g++的环境变量),输入如下指令:

g++ -E test2.cpp -o test.i
g++ -S test.i
g++ -c test.S
g++ test.o -o test.exe
test.exe

在cmd下运行显示hDllInst0 ,test.exe已停止工作,运行截图如下所示:

 

我将代码改为调用winusb_dll_x86.dll32位的)

HINSTANCE hDllInst = LoadLibrary("winusb_dll_x86.dll");
cout<<"hDllInst:"<<hDllInst<<endl;

重新使用g++编译。

g++ -c test2.cpp

g++ test2.o -o test2.exe

test2.exe

Cmd下运行test2.exe输出为hDllInst:0x6a510000(调用dll成功。)

4.总结

我在dev c++和g++下编译了32位版本和64位版本的可执行程序,当编译的可执行程序为64位版本的,而调用的dll为64位版本时,运行不会出错。当编译的可执行程序为32位版本的,而调用的dll为64位时,运行可执行程序会出现loadlibrary的返回值为0,可执行程序停止工作。

猜你喜欢

转载自www.cnblogs.com/huyourongmonkey/p/10054551.html