windows -COM技术+DLL

Windows程序设计 专栏收录该内容
12 篇文章 1 订阅

Office中的COM对象简介

开发者可以使用多种程序语言通过COM技术操作文档对象而无需了解文件格式,大大丰富了应用程序功能。
COM技术的局限性表现在较以来Windows操作系统,不支持其他类型操作系统平台。
COM程序开发遵照下面的5个步骤:

  1. 安装office软件产品
  2. 创建程序项目
  3. 项目添加引用Office产品的COM库
  4. 源代码中添加相应的命名空间
  5. 使用类方法操作Office对象

动态链接库

Windows操作系统提供给开发者的接口Applied Programming Interdface简称API,是程序使用操作系统功能和服务的途径,它不仅帮助用户程序执行Windows任务,还是Windows系统运行的重要组成部分。API聚集在动态链接库(DLL)文件中,程序通过申明外部函数引用DLL中的函数。

分别编译与链接

大多数高级语言都支持分别编译(compiling),程序员可以显式地把程序划分为独立的模块或文件,然后由编译器(compiler)对每个独立部分分别进行编译。在编译之后,由**链接器(Linker)把这些独立编译单元链接(Linking)**到一起。
链接方式有两种:静态链接、动态链接

链接方式

  • 静态链接方式:
    在程序开发中,将各种目标模块(.OBJ)文件、运行时库(.LIB)文件,以及已编译的资源(.RES)文件链接在一起,以便创建Windows的.EXE文件
  • 动态链接方式:
    在程序运行时,Windows把一个模块中的函数调用链接到库模块中的实际函数上的过程。
    静态链接库(简称LIB)与动态链接库(简称DLL)都是共享代码的方式。

如果使用静态链接库(也称静态库),则无论你愿不愿意,.LIB文件中的指令都会被直接包含到最终生成的.EXE文件中。
但是若使用.DLL文件,该.DLL文件中的代码不必被包含在最终的.EXE文件中,.EXE文件执行时可以“动态”地载入和卸载这个与.EXE文件独立的.DLL文件。

动态链接方式

链接一个DLL有两种方式:
1、载入时动态链接(Load-Time Dynamic Linking)
2、运行时动态链接(Run-Time Dynamic Linking)

使用载入时动态链接,调用模块可以像调用本模块中的函数一样直接使用导出函数名调用DLL中的函数。这需要在链接时将函数所在DLL的导入库链接到可执行文件中,导入库向系统提供了载入DLL时所需的信息及用于定位DLL函数的地址符号。(相当于注册,当作API函数来使用,其实API函数就存放在系统DLL当中)。

使用运行时动态链接,运行时可以通过LoadLibrary或LoadLibraryEx函数载入DLL。DLL载入后,模块可以通过调用GetProcAddress获取DLL函数的入口地址,然后就可以通过返回的函数指针调用DLL中的函数了。如此即可避免导入库文件了。

静态链接与动态链接二者优点及不足

静态链接库的优点:
(1) 代码装载速度快执行速度略比动态链接库快;
(2) 只需保证在开发者的计算机中有正确的.LIB文件,在以二进制形式发布程序时不需考虑在用户的计算机上.LIB文件是否存在及版本问题,可避免DLL地狱等问题。
动态链接库的优点
(1) 更加节省内存并减少页面交换;
(2) DLL文件与EXE文件独立,只要输出接口不变(即名称、参数、返回值类型和调用约定不变),更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性;
(3) 不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数。

不足之处
(1) 使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费;
(2) 使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息。而使用运行时动态链接,系统不会终止,但由于DLL中的导出函数不可用,程序会加载失败
(3) 使用动态链接库可能造成DLL地狱

动态链接库的详细分析

独特的动态链接库(Dynamic Link Library,DLL)

DLL是Windows系统中非常有效的运行机制,通用功能的函数和数据聚集在DLL文件中。DLL减少程序文件的大小和对内存空间的需求,又使公共函数在多个应用程序之间共享。DLL文件是独立编译和测试的,多种编程语言和编译器都支持生成和使用DLL。不同编程语言生成的DLL函数还可互相调用
Windows 系统中非常重要的动态链接文件有KERNEL32.DLL、USER32.dll和GDI32.dll,其中包含了大量的系统核心函数称为API。驱动程序文件如KEYBOARD.DRV、SYSTEM.DRV、MOUSE.DRV和打印机驱动程序也都是动态链接库,还有以.FON、.SYS和许多以.EXE为拓展名的系统文件都可以是DLL。

动态链接库原理

动态链接库(DLL)意思为Dynamic Link Library,这是Windows系统平台上提供的一种较有效的编程和运行机制,用户可以将独立的程序模块创建为较小的DLL(Dynamic Linkable Library)文件,并可对它们单独编译和测试,DLL就是一个包含可由多个程序同时使用的代码和数据的库
DLL模块可以同时被多个应用程序使用,DLL实现了代码封装性,它的编制与具体的编程语言及编译器无关不同编程语言生成的DLL函数可以互相调用
减少了EXE文件的大小和对内存空间的需求,是一种软件复用技术。

动态链接库运行机制/windows的虚地址映射

DLL文件中的内容由全局数据、服务函数和资源组成,程序运行时使用LoadLibrary或者LoadLibraryEx函数来加载DLL模块,WIndows实现地址映射工作,例如一个DLL文件被加载后在物理内存中只占一个固定区域,有多个进程使用同一个DLL文件,Windows将这个DLL的内存地址空间通过地址映射后提供给各个进程,进程代码地址与DLL映射后地址构成的是进程的虚地址空间,进程在自己的虚地址空间中好像是自己独立在使用这个DLL文件,使用DLL中函数的与自身的函数没有区别。DLL有自己的数据段,没有自己的堆栈,使用与调用它的应用程序相同的堆栈模式,它在运行时需要分配的内存是属于调用它的进程的,不同程序即使调用相同函数所分配的内存互相也不会影响,DLL函数中的代码所创建的任何对象(包括变量)都归调用它的线程或进程所有

Windows中主要的dll

Windows API主要以dll的形式封装并提供底层功能调用
各种驱动程序文件如KEYBOARD.DRV、SYSTEM.DRV和MOUSE.DRV和音视频及打印机驱动程序也都是动态链接库,还有以.FON、.SYS和许多以.EXE为扩展名的系统文件都可以是DLL
在这里插入图片描述

dll中函数参数与返回值

函数设计考虑三个问题:输入参数,输出和函数功能,参数个数可以是0个或多个,还可在函数形参定义时采用params关键字定义可变数目参数。
获取函数运算结果有return方式和参数引用方式(ref、out)
C#规定函数参数的使用有三种:a.传值 b. ref 和 c.out
ref要求函数调用前已被赋初值,out则不需要

dll的引用计数

在Windows环境中,每个进程都复制了自己的读/写全局变量,如果想要与其他进程共享内存,可以使用内存映射文件,或者声明一个共享数据段。一个DLL在内存中只有一个实例,系统为每个DLL维护一个线程级的引用计数,一旦一个线程载入了该DLL,引用计数将会加1.而程序终止引用计数变为0(仅指运行时动态链接库),系统会释放DLL占用的虚地址空间。不指定DLL文件完整路径时候,Windows将遵循一定的规则。

DLL文件的定位

包含EXE文件的目录
进程的当前工作目录
Windows系统目录
Windows目录
Path环境变量中的一系列目录

动态链接库相关工具

DLL地狱问题

DLL 地狱(DLL Hell)是指因为系统文件被覆盖而让整个系统像是掉进了地狱。
简单地讲,DLL地狱是指当多个应用程序试图共享一个公用组件时,如某个DLL或某个组件对象模型(COM)类,所引发的一系列问题。
重新编译生成的DLL可能造成意想不到的后果,用户程序使用新版本的DLL库不能正常工作称为DLL低于(DLL Hell)问题,特别是DLL形式的COM组件在升级后,依赖它的软件会无法使用。
在.Net 平台中采用自我描述与版本管理功能,实现 Side by Side 技术,应用程序安装成功就不必担心 DLL 的更新问题,它允许一个 DLL 的多个编译版本在同一台机器上运行,每一个应用程序可使用指定的 DLL 编译版本,不再发生 DLL Hell 问题。

托管与非托管

托管代码与非托管代码是微软针对运行中的windows程序与公共语言运行库的关系进行的一种划分

托管代码和非托管代码

  1. 在运行时公共语言运行库(CLR)控制下执行的代码称为托管代码。,通过高级语言(C#,VB,F#等)相应的编译器将其编译为中间语言(IL),CLR将其编译成机器代码,然后执行。
    托管代码应用程序可以获得公共语言运行库服务,例如自动垃圾回收、运行库类型检查和安全支持等。这些服务帮助提供独立于平台和语言的、统一的托管代码应用程序行为。
    托管代码可以通过GC垃圾回收机制进行内存的管理和释放。
  2. 在运行时以外运行的代码成为非托管代码。如:COM组件、ActiveX接口和Win32 API函数都是非托管代码的示例。非托管代码需要手动释放资源
    非托管代码与公共语言运行库环境无关。编写这些程序代码使用专用语言编译工具如C++与VB,生非托管代码与公共语言运行库环境无关。编写这些程序代码使用专用语言编译工具如C++与VB,生成的是机器可以直接执行的二进制代码,在这些程序中,用户必须自己提供内存的申请和释放,要保证指针引用的正确性,进行类型检查等功能,稍有不慎即容易发生地址越界,内存泄露等错误,而且机器也难由这些错误中恢复回来

最简单的说呢,受托管的代码不能直接写内存,是安全的,而非托管代码是非安全代码,可以使用指针操作内存。

托管与非托管区别

托管代码中不推荐使用指针,而非托管代码可以使用指针来直接读取内存。
托管DLL和非托管DLL的区别。狭义解释讲,托管DLL就在Dotnet环境生成的DLL文件。非托管DLL不是在Dotnet环境生成的DLL文件。托管DLL文件,可以在Dotnet环境通过 “添加引用” 的方式,直接把托管DLL文件添加到项目中。然后通过 Using DLL命名空间,来调用相应的DLL对象 。非托管DLL文件,在Dotnet环境应用时,通过 DllImport 调用。C# 调用非托管DLL文件。DLL文件是用C语言编写的。

托管代码动态链接库

dll文件与调用程序是各自编译的

提取DLL中的类方法和属性

在没有dll文件的源代码的情况下,使用C#生成的类库,可应用反射机制获得dll文件中的类方法和属性

反射

通过 System.Reflection 命名空间中的类以及 System.Type,可以获取有关已加载的程序集和在其中定义的类型(如类、接口和值类型)的信息。也可以使用反射在运行时创建类型实例调用和访问这些实例
使用 Assembly 定义和加载程序集,加载在程序集清单中列出的模块,以及从此程序集中查找类型并创建该类型的实例。
使用 Module 发现以下信息:包含模块的程序集以及模块中的类等。
您还可以获取在模块上定义的所有全局方法或其他特定的非全局方法。
使用 ConstructorInfo 发现以下信息:构造函数的名称、参数、访问修饰符(如 public 或 private)和实现详细信息(如 abstract 或 virtual)等。
使用 Type 的 GetConstructors 或 GetConstructor 方法来调用特定的构造函数。

使用 MethodInfo 发现以下信息:方法的名称、返回类型、参数、访问修饰符(如 public 或 private)和实现详细信息(如 abstract 或 virtual)等。使用 Type 的 GetMethods 或 GetMethod 方法来调用特定的方法。
使用 FieldInfo 发现以下信息:字段的名称、访问修饰符和实现详细信息(如 static)等;并获取或设置字段值。
使用 EventInfo 发现以下信息:事件的名称、事件处理程序\数据类型、自定义属性、声明类型和反射类型等;并添加或移除事件处理程序。
使用 PropertyInfo 发现以下信息:属性的名称、数据类型、声明类型、反射类型和只读或可写状态等;并获取或设置属性值。
使用 ParameterInfo 发现以下信息:参数的名称、数据类型、参数是输入参数还是输出参数,以及参数在方法签名中的位置等。

非托管代码参数的封送与调用

CLR类似java虚拟机提供运行时环境,它负责资源管理(内存分配和垃圾收集),在应用程序和底层操作系统之间完成必要的分离。CLR进数据指针值传入非托管代码中行数据参数封送有两种方式:多数情况下数据值执行复制,特定情况下将数据指针值传入非托管代码中

非托管的动态链接库

API声明为外部:

  1. extern修饰符声明调用外部API
  2. 使用DllImport 指定库文件和方法的参数格式
  3. 使用static关键词声明方法为静态

[ DllImport( “xxxxx.dll", EntryPoint=“yyy" )]
public static extern int MessageBox(int h, string m, string c, int type);

DllImport属性

[ DllImport( “kernel32.dll”,EntryPoint=“GetVersionEx” )]
“DllImport”属性用来从不可控代码中调用一个方法,它指定了DLL的相对/绝对地址;
EntryPoint指示要调用的 DLL 入口点的名称或序号—DLL中的函数指针
CharSet控制调用函数的名称版本
CallingConvention指示向非托管实现传递方法参数

class DllTest
    {
        [DllImport(@"../../../Release/dll_cpp.dll", EntryPoint ="testAdd", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = false, CallingConvention = CallingConvention.StdCall)]
        public static extern int testAdd(int a, int b);

        [DllImport(@"../../../Release/dll_cpp.dll", EntryPoint = "testMulti", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = false, CallingConvention = CallingConvention.StdCall)]
        public static extern int testMulti(int a, int b);
    }

DllImport函数wrapper

调用非托管的动态链接库需要使用 Interop 服务
extern 修饰符用于声明在外部实现的方法,与 DllImport 属性一起使用,且将方法声明为 static
[DllImport(“User32.dll”)]
public static extern int MessageBox(int h, string m, string c, int type);
函数返回值及函数参数类型与创建动态链接库中的函数参数保持一致

  • 数值型直接用对应的就可(DWORD -> int , WORD -> Int16)
  • API中字符串指针类型-> .net中string
  • API中句柄 (dWord) --> .net中IntPtr
  • API中结构struct --> .net中结构或者类。注意这种情况下,要先用StructLayout特性限定声明结构或类,虽然比较复杂,在用到时查示例就好,不需死记

COM和DLL区别

COM英文为Component Object Model(组件对象模型),是微软生产软件组件的标准。
它是构造二进制兼容软件组件的规范,不管组件应用何种语言编写只要遵循com规范就可以相互直接通信。提出com规范主要是为了满足:
1.程序的快速开发,可以将一个大型的工程分成若干个com组件同时开发。
2.可以动态的插入或卸载 com组件。
3.可以 隐藏或封装com 组件内部的实现细节。
COM组件可以由不同的语言进行编写,但COM组件之间的通信是通过组件的接口来实现的,COM组件接口的实现是统一的,它采用的是虚拟函数表(VTBL)形式。虚拟函数表中包含了组件函数的一组指针,我们可以通过这组指针来获取我们想要通信的组件函数的内存地址。dll(动态链接库)是包含函数和数据的模块的集合。它可以导出数据也可以导出函数以供其它的dll调用。dll的加载可以通过静态链接和动态链接两种方式。

1.静态链接时将所要链接的dll模块以二进制的形式编译进其他模块。
2.动态链接指调用模块在运行时加载DLL,使用LoadLibrary函数或LoadLibraryEx函数将dll加载到进程的地址空间,并调用GetProcAddress函数以获取导出的 DLL函数的地址。

DLL与COM的关系:COM是一种规范,按照是COM规范实现的DLL可以被视为COM组件,

例如我们用mfc建立的Active X控件工程其中的接口封装是靠idl描述的所以可以视为com组件。而且从上面关于com和dll的说明可以看出com组件的接口是一组具有特定规范的函数,所以com组件可被视为dll但dll不一定是com组件

COM和DLL 最大的区别 就是: dll是以函数集合的方式来调用的是编程语言相关的,像VC必须加上extern “C”…而COM是以interface的方式提供给用户使用的是一种二进制的调用规范,是与编程语言无关的,它使用idl接口定义语言来描述自己使用类继承来实现自己的功能和方法.DLL只有DLL一种形式,里面可任意定义函数无限制,只能运行在本机上。而COM有DLL和EXE两种存在形势: COM所在的DLL中必须导出四个函数:dllgetobjectclass,dllregisterserver, dllunregisterserver,dllunloadnow这四个函数各有作用,有些是提供给COM管理器用的,通过CLSID和IID来使用,有些是提供给注册机用的.COM结合MTS,就是COM+, 是DCOM的高级版本,提供了更为强大和安全的分布式COM服务,DCOM运行在不同的机器上 用proxy和stub来实现远程接口的本地映射 二者从执行速度来说 二者相差无几 但是启动速度DLL要比COM快!

com组件可以由不同的语言进行编写,但com组件之间的通信是通过组件的接口来实现的,com组件接口的实现是统一的,它采用的是虚拟函数表VTBL形式。虚拟函数表中包含了组件函数的一组指针,我们可以通过这组指针来获取我们想要通信的组件函数的内存地址。dll(动态链接库)是包含函数和数据的模块的集合。它可以导出数据也可以导出函数以供其它的dll调用。

  • 0
    点赞
  • 0
    评论
  • 4
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:游动-白 设计师:我叫白小胖 返回首页

打赏作者

想想虔诚怎么做

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值