VBA的调试输出只有简单的Debug.Print,在接触过C语言之后,很喜欢printf那种形式的打印输出,无奈一直觉得C语言太难了,没能深入去学习。
后来接触了go语言后,觉得这种语言学起来还算简单,也很像C语言。所以在学习一段时间后,就想着能不能用go语言封装dll给VBA使用,前面讲到的那些关于指针、数据类型的东西,主要就是为了做dll:
- 指针Pointer
- Object对象的函数参数传递
- 数据类型String
- 数据类型Array
- 数据类型Variant
在了解了VBA的数据类型后,我们才能把VBA的数据传递到dll中,并正确的解析数据,这里简单介绍一下封装go语言的Sprintf函数。
效果:
1、函数声明
代码语言:javascript复制func Sprintf(format string, a ...interface{}) string
这个是go语言的Sprintf函数,可以按c语言的Sprintf函数来理解,第1个参数好处理,关键是后面的可变参数,因为它是允许任意类型和任意数量的:
- 任意类型,VBA里正好有Variant类型
- 任意数量,VBA也正好有ParamArray
所以,VBA里的函数声明为:
代码语言:javascript复制Public Sub Printf(format As Variant, ParamArray args() As Variant)
End Sub
format是string,为了方便统一处理,都按Variant类型来传递。
封装Sprintf的目的就是为了能得到一个格式化的字符串,VBA的String和go语言里的string是不一样的,所以dll传出来的string需要转换,API声明:
代码语言:javascript复制Public Declare Function gosprintf Lib "godllForVBA32.dll" (ByVal pFormat As Long, ByVal pVBAVariant As Long, ByVal nCount As Long) As MyString
Type MyString
pUCS2 As Long
Len As Long
End Type
为了方便处理,我是在go语言里把String转换为了VBA里的编码,这样在VBA里不需要再次转码了。
2、go实现:
go语言里的函数:
代码语言:javascript复制func Sprintf(pformat, pParamArray, nCount int32) (ptr unsafe.Pointer, lenth int)
参数pformat, pParamArray接收VBA传入的指针,nCount直接接收数值,函数返回VBA的String及长度。
实现过程:
- 根据传递进来的VBA的Variant指针,结合数据类型Variant讲到的情况进行分别解析为go语言的数据类型
- 然后调用go语言的Sprintf函数获取需要的String
- 结合cgo,把String传递出去(go是一个有垃圾回收的语言,所以go的对象指针包括String先用C的malloc申请内存ptr,再memcpy过去,让函数返回ptr,最后free释放内存)
- 编写C语言的函数,必须用__stdcall修饰,因为VBA调用API的参数传递方式是__stdcall
- 最后go编译器结合gcc编译器编译dll
3、编译
- 编译.a文件
go.exe build -v -x -buildmode=c-archive -o cgo.a
生成.a和.h 2个文件
- 编写.c文件,因为VBA调用API的参数传递方式是__stdcall
struct Sprintf_return __stdcall gosprintf(GoInt p0, GoInt p1, GoInt p2) {
return Sprintf(p0, p1, p2);
}
- 编写.def文件,目的是让dll导出函数不要带@符号:
EXPORTS
gosprintf
cfree
- 最后编译dll
gcc.exe cstdcall.c cgo.def cgo.a -shared -lwinmm -lWs2_32 -o go.dll -Wl,--enable-stdcall-fixup,--out-implib,go.lib
go语言的具体实现因为和VBA代码相关性不大,就不展开讲。