VBA使用的VBE7.dll类型库具有Conversion
模块的以下MIDL:
[
dllname("VBE7.DLL"),
uuid(36785f40-2bcc-1069-82d6-00dd010edfaa),
helpcontext(0x000f6ebe)
]
module Conversion {
[helpcontext(0x000f6ea2)]
BSTR _stdcall _B_str_Hex([in] VARIANT* Number);
[helpcontext(0x000f652a)]
VARIANT _stdcall _B_var_Hex([in] VARIANT* Number);
[helpcontext(0x000f6ea4)]
BSTR _stdcall _B_str_Oct([in] VARIANT* Number);
[helpcontext(0x000f6557)]
VARIANT _stdcall _B_var_Oct([in] VARIANT* Number);
[hidden, helpcontext(0x000f6859)]
long _stdcall MacID([in] BSTR Constant);
[helpcontext(0x000f6ea9)]
BSTR _stdcall _B_str_Str([in] VARIANT* Number);
[helpcontext(0x000f658a)]
VARIANT _stdcall _B_var_Str([in] VARIANT* Number);
[helpcontext(0x000f659f)]
double _stdcall Val([in] BSTR String);
[helpcontext(0x000f64c8)]
BSTR _stdcall CStr([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
BYTE _stdcall CByte([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
VARIANT_BOOL _stdcall CBool([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
CY _stdcall CCur([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
DATE _stdcall CDate([in] VARIANT* Expression);
[helpcontext(0x000f6e7a)]
VARIANT _stdcall CVDate([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
short _stdcall CInt([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
long _stdcall CLng([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
int64 _stdcall CLngLng([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
LONG_PTR#i _stdcall CLngPtr([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
float _stdcall CSng([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
double _stdcall CDbl([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
VARIANT _stdcall CVar([in] VARIANT* Expression);
[helpcontext(0x000f64b5)]
VARIANT _stdcall CVErr([in] VARIANT* Expression);
[helpcontext(0x000f6c6d)]
BSTR _stdcall _B_str_Error([in, optional] VARIANT* ErrorNumber);
[helpcontext(0x000f6c6d)]
VARIANT _stdcall _B_var_Error([in, optional] VARIANT* ErrorNumber);
[helpcontext(0x000f649b)]
VARIANT _stdcall Fix([in] VARIANT* Number);
[helpcontext(0x000f6533)]
VARIANT _stdcall Int([in] VARIANT* Number);
[helpcontext(0x000f64c8)]
HRESULT _stdcall CDec(
[in] VARIANT* Expression,
[out, retval] VARIANT* pvar
);
};
我特别感兴趣的是VBA如何解释HRESULT
返回CDec
函数(上面MIDL中的最后一个函数),这样在 VBA中, CDec
函数的签名为
Function CDec(Expression)
似乎就像VBA正在影响HRESULT
返回的TLB定义,所以为了测试理论,我想创建自己的TLB来定义{{1} }返回HRESULT
内的函数,然后查看VBA如何处理该函数。
我不相信这可以在C#或VB.NET中完成,当我尝试在VB6中的标准模块中定义函数时,该模块在dll中不可见。
这可能使用C ++吗?我需要创建什么样的项目?我需要做些什么特别的事吗?我是否需要手动编辑MIDL?
注意:我特意没有将此问题标记为module
,因为我试图从C#解释TLB。为了测试VBA主机如何解释TLB,我想用任何支持它的语言创建一个合适的TLB。我可以使用Visual Studio 6,2003,2013和2015。
答案 0 :(得分:6)
CDec声明中重要的是[out] and [retval] attributes的组合。 理解它的工具(如VB / VBA)将能够以简化的方式编译对此方法的调用,掩盖错误处理,所以
HRESULT _stdcall CDec(
[in] VARIANT* Expression,
[out, retval] VARIANT* pvar
);
是等同于到
VARIANT _stdcall CDec([in] VARIANT* Expression);
等效这里并不意味着它在二进制形式中是等价的,它只是意味着理解语法的工具可以在他们看到的时候使用(并在最终的二进制目标中编译)第一个表达式第二。 它还意味着如果出现错误(HRESULT失败),那么工具应该以它认为合适的任何方式引发错误(VB / VBA将执行此操作)。
那只是“syntactic sugar”。
您可以使用MIDL编写它,也可以使用.NET编写:只需使用Visual Studio创建一个标准类库,并添加此示例c#类:
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class Class1
{
public object Test(object obj)
{
return obj;
}
}
编译并运行regasm工具进行注册,使用如下命令:
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\regasm "C:\mypath\ClassLibrary1\bin\Debug\classlibrary1.dll" /tlb /codebase
这会将该类注册为COM对象,并创建一个C:\mypath\ClassLibrary1\bin\Debug\classlibrary1.tlb
类型的库文件。
现在,启动Excel(您可以使用任何与COM自动化兼容的客户端),并添加对ClassLibrary1的引用(开发人员模式,VBA编辑器,工具/参考)。
如果你没有看到它,你可能会运行不同的位。可以使用COM进行32-64通信,但是现在,只需确保客户端的运行方式与ClassLibrary1.dll
的编译方式相同。
获得引用后,添加一些VB代码,如下所示。
Sub Button1_Click()
Dim c1 As New Class1
output = c1.Test("hello from VB")
End Sub
正如您将体验到的那样,VB intellisense将像C#一样显示我们期望的方法,并且它可以正常工作。
现在,让我们尝试从C ++中使用它:创建一个控制台项目(再次确保位数兼容),并将此代码添加到它:
#include "stdafx.h" // needs Windows.h
#import "c:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\mscorlib.tlb" // adapt to your context
#import "C:\\mypath\\ClassLibrary1\\bin\\Debug\\classlibrary1.tlb"
using namespace ClassLibrary1;
int main()
{
CoInitialize(NULL);
_Class1Ptr c1(__uuidof(Class1));
_variant_t output = c1->Test(L"hello from C++");
wprintf(L"output: %s\n", V_BSTR(&output));
CoUninitialize();
return 0;
}
这也可以正常工作,代码看起来很接近VB的代码。注意我使用了Visual Studio magic #import directive这非常酷,因为它掩盖了COM Automation管道的许多细节(就像VB / VBA一样),包括bstr和变体智能类。
让我们点击Test
来电并进行转到定义(F12),这就是我们所看到的:
inline _variant_t _Class1::Test ( const _variant_t & obj ) {
VARIANT _result;
VariantInit(&_result);
HRESULT _hr = raw_Test(obj, &_result);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return _variant_t(_result, false);
}
哈哈!这基本上是VB / VBA所做的内容。我们可以看到异常处理是如何完成的。同样,如果您在_Class1Ptr
上执行F12,这就是您将看到的(简化):
_Class1 : IDispatch
{
// Wrapper methods for error-handling
...
_variant_t Test (
const _variant_t & obj );
...
// Raw methods provided by interface
...
virtual HRESULT __stdcall raw_Test (
/*[in]*/ VARIANT obj,
/*[out,retval]*/ VARIANT * pRetVal ) = 0;
};
我们在这里。如您所见,C#以二进制形式生成的Test
方法符合预期的[out, retval]
形式。其余的都是糖和包装纸。
大多数COM接口方法在二进制级别使用[out,retval]设计,因为编译器不支持函数返回的通用兼容二进制格式。
VBE7定义的是dispinterface,也是某种形式的语法糖,用于在COM原始/二进制IUnknown接口之上定义接口。
唯一的谜团是为什么CDec
的定义与VBE7中的其他方法不同。我没有答案。
现在,特别是关于IDL中的module
关键字,IDL只是一个抽象定义(函数,常量,类等)工具,可选择输出伪像(.H,.C,.TLB等)。 )针对特定语言(C / C ++等)或针对特定客户。
VB / VBA支持TLB的常量和方法。它将常量解释为它们是什么,并将模块中的函数作为DLL从模块的dll名称导出。
因此,如果您在磁盘上的某个位置创建此my.idl
文件:
[
uuid(00001234-0001-0000-0000-012345678901)
]
library MyLib
{
[
uuid(00001234-0002-0000-0000-012345678901),
dllname("kernel32.dll")
]
module MyModule
{
const int MyConst = 1234;
// note this is the real GetCurrentThreadId from kernel32.dll
[entry("GetCurrentThreadId")]
int GetCurrentThreadId();
}
}
您可以像这样编译TLB:
midl c:\mypath\my.idl /out c:\mypath
它将创建一个my.tlb
文件,您可以在VB / VBA中引用该文件。现在,从VB / VBA开始,您将有一个名为GetCurrentThreadId
的新函数(intellisense将对其进行处理)。它的工作原理是因为Windows的kernel32.dll确实导出了GetCurrentThreadId函数。
您只能从C / C ++项目(以及其他语言/工具,如Delphi)创建DLL Exports,但不能从VB / VBA创建,而不能从.NET创建。
事实上,在.NET中创建导出有一些技巧,但它并不是真正的标准:Is is possible to export functions from a C# DLL like in VS C++?