ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 效率神器,一键搞定繁琐工作
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
让更多数据处理,一键完成 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
查看: 21469|回复: 8

[原创] VBA调用C++的dll简易指南

[复制链接]

TA的精华主题

TA的得分主题

发表于 2014-7-31 11:19 | 显示全部楼层 |阅读模式
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
本帖已被收录到知识树中,索引项:封装
本帖最后由 Patrick.Sorcery 于 2014-7-31 11:21 编辑

新人报到~呈上这个东东

据说很多用VBA的就和本人一样属于业余程序员,写代码的目的也就是为了辅助工作。很多东东在专业高手看来可能都是小儿科,但对于咱这种水准来讲就是问题了。而专业高手们又总是不太爱讲话……

OK~下面进入正题。比如偶最近要做一个小玩意,里面有些不想告人的内容。如果全用VBA写的话就算加密码也会被破,但用C++的话,对有本事反编译出来的大虾来说这玩意也没啥偷看滴价值了>o<高手可能会推荐COM,不过那种东东可能太高大上了一点,看着就让业余人士一个头两个大,相比之下对于简单问题,传统的C++dll完全就够用~

这个技术本身没啥难度,但似乎是涉及此道的人太少,对于里面的一些问题和陷阱网上很难找到比较全面的资料,因此在这里把遇到的问题总结一下~




首先,要在Excel中调用dll的函数……

C++(注意是C++不是C)端要这样写:Visual Studio版
  1. extern "C" __declspec(dllexport) double __stdcall
  2. {
  3.     return *a + b;
  4. }
复制代码


GCC(MinGW)版
  1. extern "C" double Add(double *a, double b) __attribute__((stdcall));

  2. double Add(double *a, double b)
  3. {
  4. return *a + b;
  5. }
复制代码


VBA端:
  1. Declare PtrSafe Function Add Lib "DLLTest.dll" (ByRef x As Double, ByVal y As Double) As Double
复制代码


注意以上下划线都是两个。可以看出,C++端的函数声明多了3个内容:
1 extern "C":表明函数名按照C的习惯编译。因为C++比C多了个重载,库中的函数名可能会被编译器添上各种修饰尾巴,用这个可以禁掉那些尾巴
2 declspec(dllexport):表明这个函数被摆上了货架子可以被外部的程序调用。为啥只有VS有这个?因为GCC会自动把所有extern的都摆上去……
3 stdcall:大概就是参数清栈规则一类滴东东。默认的cdecl是调用者清栈,实际上VBA可不会去给C++擦屁股,所以加上stdcall让被调者自己清栈

VBA端的写法很简单,查下Declare的说明就知道了。注意那个PtrSafe,是Excel 2007没有、2010开始32位可加、64位必须加的。关于这个时代32位和64位共存带来的种种麻烦,后面会单独讨论。




会写函数形式后,下一个问题就是怎么传参数,怎么传字符串,怎么传数组……其实这里的对应原则很简单:ByVal传值,ByRef传指针。变量类型的对应关系如下:
VBA C++字节数
Longint4
doubledouble8
stringchar* (定长) 1×长度
这三个是比较保险的,其它类型的就不太好讲了……值得注意的是,VBA的string在传给C++后会变成定长的char*,也就是说长度信息会一起传过去。建议一接到后立刻将其用C++自己的string存起来:
  1. #include <string>
复制代码
至于如何传数组或复杂的数据结构……建议只要东东一多就上结构体吧,像这样:
C++端:
  1. struct ABC{
  2. int a;
  3. double b;
  4. int c[12];
  5. };
复制代码

VBA端:
  1. Type ABC
  2. a as Long
  3. b as Double
  4. c(0 to 11) as Long
  5. End Type
复制代码

调用函数时直接用ByRef/指针把结构体地址传过去~注意两边要保持一致哦。另外如果在32位系统上试验这个结构体会出现错误,下面会详细分析~


还有dll文件放置的地方也是个问题……如果要放在同一目录下,建议最好在一开始就把当前目录拽过来:
  1. Sub Workbook_Open()ChDrive (ThisWorkbook.Path)
复制代码





32位和64位带来的困扰

目前这个时期,32位和64位共存带来了许多困扰,如果要发布东东的话就得两边都照顾到。在VBA和C++联用的环境下,这里有好几个小陷阱……

首先要明确几个问题:
1 说起【32位】和【64位】,指的可能是处理器、系统或是程序,这里是单指Office的位数,因为64位Windows也可能装了一个32位的Office。
2 32位和64位到底有多大区别?有很多传言,说是int变64位啦之类的。实际上int是4字节、double是8字节这两个长度已经相当深入人心了,随便乱动的话会引发动乱的。比较确定变化的是指针的位数,而其它的就……比如指针的真身,C++的Long和unsigned Long就有可能跟着升到了8字节64位……
3 同一个结构体,就算只由char、int和double组成,在32位和64位中的储存方式也可能不一样,这是引发混乱的根源之一……

在VBA调用C++dll的场合关于位数,有3个问题要注意:

1 位数对应

dll的位数必须和Office的位数一致。dll的位数取决于编译器,和自己写的代码没啥关系,如果用VS的话就要在解决方案的配置属性里设置下平台(Win32还是x64),GCC的话MinGW到64位就不灵了,得去下个MinGW w64,这个东东有32位和64位的版本。至于判断Office的位数有个笨办法,去Program Files和Program Files (x86)下的Office目录里看下哪边东西多……

如果位数不对,VBA运行时就会提示找不到那个库文件,不管把库放在哪里都一样

2 函数名尾巴

目前64位的编译器编出来的函数名没发现啥问题,32位的编译器编出的dll函数名后面还是经常会带着一个尾巴,似乎是@+参数总位数的样子。对付这一点最简单的办法就是,先去下个Depends搞清楚编出来的函数到底叫什么,然后在VBA的声明里分开写:
  1. #If Win64 Then
  2. Declare PtrSafe Function Add Lib "DLLTest.dll" (ByRef x As Double, ByVal y As Double) As Double
  3. #Else
  4. Declare Function Add Lib "DLLTest.dll" Alias "Add@12" (ByRef x As Double, ByVal y As Double) As Doubler
  5. #End If
复制代码
注意,VBA的预编译宏Win64返回的刚好是Office是不是64位,而非Windows是否64位。

3 讨厌的结构体
结构体在内存中的存储方式可不是一个挨一个的,有一个叫做【对齐】的很复杂的问题,这里就不具体分析了,感兴趣可以问度受 。在64位平台上,如果只用int、double和string的话一般不会有事,但32位平台上……据大致分析,MinGW w64不论64位版还是32位版对齐值都是8,64位VBA的对齐值也是8,但32位VBA的对齐值是4。回顾下上面说有问题的那个结构体:
  1. Type ABC
  2. a as Long
  3. b as Double
  4. c(0 to 11) as Long
  5. End Type
复制代码
在64位平台下,每个变量都占着8个字节的屋子,a只有4个字节的话后面4个字节就是空的。32位平台的MinGW w64也是这样干的,不知道VS是不是这样干,但VBA中对齐值变成了4,a只能住刚好标配的4字节屋子,紧跟着b就挤进来了,于是在32位VBA和C++(MinGW w64)中两种结构体里b的位置就差了4字节,传数据时当然就乱了套。

解决这个问题的办法,一是写结构体时注意变量顺序,一定要让4字节的变量两两凑成对,但这样很麻烦,尤其是遇到数组时经常算不清。这里建议从C++那边想办法,用预编译宏:
  1. #ifndef _WIN64
  2. #pragma pack(4)
  3. #endif
复制代码

让编译器在32位系统中强制规定对齐值为4。

呼呼~终于写完了,希望能帮上和偶一样不幸有此需求滴盆友们~

评分

1

查看全部评分

TA的精华主题

TA的得分主题

发表于 2014-7-31 11:23 | 显示全部楼层
C++和VB的DLL有何不同呢?

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-7-31 12:29 | 显示全部楼层
Zamyi 发表于 2014-7-31 11:23
C++和VB的DLL有何不同呢?

VB的DLL没用过……不过Windows的DLL是有标准的,二者用起来应该差不多的样子~

TA的精华主题

TA的得分主题

发表于 2014-7-31 20:50 | 显示全部楼层
本帖最后由 liucqa 于 2014-7-31 20:53 编辑

http://www.cnblogs.com/yedaoq/archive/2010/11/17/1879815.html
创建用于非Visual C++工具的DLL

若要使用与其他编译器供应商的工具链接的M i c r o s o f t的工具创建一个可执行模块,必须告诉M i c r o s o f t的编译器输出没有经过改变的函数名。可以用两种方法来进行这项操作。第一种方法是为编程项目建立一个. d e f文件,并在该. d e f文件中加上类似下面的E X P O RT S节:

EXPORTS
   MyFunc
当M i c r o s o f t的链接程序分析这个. d e f文件时,它发现_ M y F u n c @ 8和M y F u n c均被输出。由于这两个函数名是互相匹配的(除了截断的尾部外),因此链接程序使用M y F u n c的. d e f文件名来输出该函数,而根本不使用_ M y F u n c @ 8的名字来输出函数。

现在你可能认为,如果使用M i c r o s o f t的工具创建一个可执行模块,并且设法将它链接到包含未截断名字的D L L,那么链接程序的运行将会失败,因为它将试图链接到称为_ M y F u n c @ 8的函数。当然,你会高兴地了解到M i c r o s o f t的链接程序进行了正确的操作,将可执行模块链接到名字为M y F u n c的函数。

如果想避免使用. d e f文件,可以使用第二种方法输出未截断的函数版本。在D L L的源代码模块中,可以添加下面这行代码:

#pragma comment(linker, "/export:MyFunc=_MyFunc@8")
这行代码使得编译器发出一个链接程序指令,告诉链接程序,一个名叫M y F u n c的函数将被输出,其进入点与称为_ M y F u n c @ 8的函数的进入点相同。第二种方法没有第一种方法容易,因为你必须自己截断函数名,以便创建该代码行。另外,当使用第二种方法时, D L L实际上输出用于标识单个函数的两个符号,即M y F u n c和_ M y F u n c @ 8,而第一种方法只输出符号M y F u n c。第二种方法并没有给你带来更多的好处,它只是使你可以避免使用. d e f的文件而已。


http://363809446.blog.163.com/blog/static/11497344920091010105817224/?fromdm&fromSearch&isFromSearchEngine=yes





TA的精华主题

TA的得分主题

发表于 2014-12-3 12:39 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
为避免麻烦以及程序的不同平台的兼容性,最好结构体规划时做到8字节对齐;
另外即使在64位下,也不是每种变量都是8位;
----------------------------------------------
C一侧做开发时,最好养成这种习惯,无法对齐,就增加一个空的没用变量

TA的精华主题

TA的得分主题

发表于 2014-12-3 12:42 | 显示全部楼层
做DLL时要充分考虑64位和32位的不同需求,
数据结构要能兼容,
同时还要考虑DLL侧 Ansi  和 Unicode 编码的不同;
-----------------------
这个在64位 涉及 字符串的时候超级蛋疼 ,尤其是你还需要用 回调函数的时候............

TA的精华主题

TA的得分主题

发表于 2014-12-15 10:14 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
谢谢分享,很受用

TA的精华主题

TA的得分主题

发表于 2016-6-19 11:35 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2020-10-22 11:30 | 显示全部楼层
您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-11-23 16:24 , Processed in 0.041253 second(s), 10 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表