#pragma pack(4)
struct C_user_type
short iVal;
double dVal;
BSTR bstr; // VBA String type is a byte string
#pragma pack() // restore default
在某些情况下,VBA 比 Excel 支持的值范围更大。 VBA 双精度符合 IEEE 标准,支持工作表中当前已四舍五入为零的次正规数。 VBA Date 类型可使用负序列化日期表示早至 0100 年 1 月 1 日的日期。 Excel 仅支持大于或等于零的序列化日期。 VBA Currency 类型(成比例的 64 位整数)可以实现 8 字节双精度上不支持的精度,这也与工作表不匹配。
Excel 仅可将以下类型的变体传递至 VBA 用户定义的函数。
VBA 数据类型
C/C++ 变体类型位标志
可以使用 VarType 检查 VBA 中的传入变体类型,通过引用调用时返回区域值类型的函数除外。 若要确定变体区域引用对象,可以使用 IsObject 函数。
可以从区域创建 VBA 中包含变体数组的变体,方法是将其值属性指定为变量。 源区域中使用当时的区域设置中的标准货币格式的所有单元格均将转换为货币类型的数组元素。 采用日期格式的所有单元格均将转换为日期类型的数组元素。 包含字符串的单元格将转换为宽字符 BSTR 变体。 包含错误的单元格将转换为 VT_ERROR 类型的变体。 包含布尔True 或 False 的单元格将转换为 VT_BOOL 类型的变体。
变体将 True 存储为 -1,将 False 存储为 0。 未采用日期或货币金额格式的数字将转化为 VT_R8 类型的变体。
变体和字符串参数
Excel 从内部使用宽字符 Unicode 字符串。 如果 VBA 用户定义的函数被声明为采用字符串参数,则 Excel 将以特定于区域设置的方式将提供的字符串转换为字节字符串。 如果想要函数传递为 Unicode 字符串,则 VBA 用户定义的函数应接受变体而不是字符串参数。 DLL 函数随后可接受 VBA 中的变体 BSTR 宽字符字符串。
若要将 Unicode 字符串从 DLL 返回至 VBA,应相应地修改变体字符串参数。 为此,必须将 DLL 函数声明为将指针指向变体和 C/C++ 代码,并将 VBA 代码中的参数声明为 ByRef varg As Variant
。 应擦除原先的字符串内存,并且使用 OLE Bstr 字符串函数创建的新字符串只能在 DLL 中运行。
若要将字节字符串从 DLL 返回至 VBA,应相应地修改字节字符串 BSTR 参数。 为此,必须将 DLL 函数声明为将指针指向 BSTR 指针和 C/C++ 代码,并将 VBA 代码中的参数声明为“ByRef varg As String”。
应仅使用 OLE BSTR 字符串函数处理以这些方式从 VBA 传递的字符串,以免出现与内存相关的问题。 例如,必须先调用 SysFreeString 以释放内存,然后再覆盖传入的字符串,并调用 SysAllocStringByteLen 或 SysAllocStringLen 为新字符串分配空间。
可以结合使用 CVerr 函数和下表中所示的参数,以在 VBA 中创建如变体一样的 Excel 工作表错误。 此外,还可以使用 VT_ERROR 类型的变体以及以下ulVal字段值,将工作表错误返回至 VBA。
变体 ulVal 值
CVerr参数
请注意,提供的变体 ulVal 值等于 CVerr 参数值加上 x800A0000 十六进制值。
直接从工作表调用 DLL 函数
例如,如果没有将 VBA 或 XLM 用作接口,或者没有让 Excel 提前知道气焊、其参数及其返回类型,则无法从工作表返回 Win32 DLL 函数。 此操作过程称为注册。
可在工作表中访问 DLL 函数的方式如下所示:
按上面所述在 VBA 中声明函数,然后通过 VBA 用户定义的函数访问它。
使用 XLM 宏工作表上的 CALL 调用 DLL 函数,然后通过 XLM 用户定义的函数访问它。
使用 XLM 或 VBA 命令调用 XLM REGISTER 函数,这将会提供 Excel 识别函数(在函数被输入到工作表单元格时)所需的信息。
将 DLL 变为 XLL 并在 XLL 激活时使用 C API xlfRegister 函数注册函数。
第四种方法为独立方法:用于注册函数和函数代码的代码包含于同一代码项目中。 更改加载项并不涉及更改 XLM 工作表或 VBA 代码模块。 若要以管理良好的方式完成此操作,同时保持 C API 的功能,则必须使用加载项管理器将 DLL 变为 XLL 并加载生成的加载项。 这使 Excel 能够在加载或激活加载项时调用 DLL 公开的函数,这样你就可以注册 XLL 包含的所有函数,并执行任何其他 DLL 初始化。
直接通过 Excel 调用 DLL
在没有接口(如 VBA)或没有提前注册命令的情况下,无法直接通过 Excel 对话框和菜单访问 Win32 DLL 命令。
可用于访问 DLL 命令的方式如下所示:
按上面所述在 VBA 中声明函数,然后通过 VBA 宏访问它。
使用 XLM 宏工作表上的 CALL 调用 DLL,然后通过 XLM 宏访问它。
使用 XLM 或 VBA 命令调用 XLM REGISTER 函数,这将会提供 Excel 识别命令(在命令被输入到期望获得宏命令名称的对话框时)所需的信息。
将 DLL 变为 XLL 并使用 C API xlfRegister 函数注册命令。
如前面的 DLL 函数上下文所述,第四种方法最独立,使注册代码与命令代码接近。 若要执行此操作,必须使用加载项管理器将 DLL 变为 XLL 并加载生成的加载项。 如果使用此方式注册命令,则还可以将命令附加到某个用户界面元素,如自定义菜单,或者设置事件陷进,以在指定击键或其他事件时调用此命令。
Excel 假定使用 Excel 注册的所有 XLL 命令采用以下形式。
int WINAPI my_xll_cmd(void)
// Function code...
return 1;
Excel 将忽略返回值,除非是通过 XLM 宏工作表调用该值,在此情况下,返回值将转换为 TRUE 或 FALSE。 因此,如果命令执行成功,则应返回 1,如果失败或者被用户取消,则返回 0。
DLL 内存和多个 DLL 实例
应用程序加载 DLL 时,此 DLL 的可执行代码将加载到全局堆中,以便运行该代码,并且将会在全局堆中为其数据结构分配空间。 Windows 使用内存映射使这些内存区域看上去像位于应用程序进程中一样,这样一来应用程序就可访问它们。
如果另一个应用程序随后加载 DLL,则 Windows 不会生成另一个 DLL 可执行代码副本,因为该内存为只读。 Windows 会将此 DLL 可执行代码内存映射至两个应用程序的进程。 但是,它会为 DLL 数据结构专用副本分配另一个空间,并且只会将此副本映射至第二个进程。 这可确保两个应用程序的 DLL 数据不会相互干扰。
这意味着,DLL 开发人员无需在意静态和全局变量及数据结构被多个应用程序或相同应用程序的多个实例访问。 每个应用程序的每个实例均有自己的 DLL 数据副本。
DLL 开发人员必须考虑应用程序的相同实例从不同线程多次调用 DLL,因为这会导致该实例的数据被争用。 有关更多信息,请参阅 Excel 中的内存管理。
开发 DLL
从 DLL 或 XLL 调用 Excel