gpt4 book ai didi

c++ - 如何在 COM 类型库中创建模块定义的函数

转载 作者:可可西里 更新时间:2023-11-01 17:53:18 27 4
gpt4 key购买 nike

VBA 使用的 VBE7.dll 类型库具有以下 MIDL,用于 Conversion模块:

[
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 来定义 HRESULT module 中的返回函数,然后查看 VBA 如何处理该函数。

我不相信这可以在 C# 或 VB.NET 中完成,当我尝试在 VB6 的标准模块中定义一个函数时,该模块在 dll 中不可见。

这可以使用 C++ 吗?我需要创建什么样的项目?有什么特别需要我做的吗?我可能需要手动编辑 MIDL 吗?

注意:我没有特别将这个问题标记为 VBA ,因为我正在尝试从 C# 解释 TLB。为了测试 VBA 主机如何解释 TLB,我想用支持它的任何语言创建一个合适的 TLB。我可以使用 Visual Studio 6、2003、2013 和 2015。

最佳答案

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 智能感知将像我们期望的那样显示方法,就像在 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 调用并执行 Goto Definition (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] 形式。剩下的都是糖和 wrapper 。
大多数 COM 接口(interface)方法在二进制级别上使用 [out, retval] 设计,因为编译器不支持用于函数返回的通用兼容二进制格式。

VBE7 定义的是 dispinterface ,也是某种形式的语法糖,用于在 COM 原始/二进制 IUnknown 接口(interface)之上定义接口(interface)。
剩下的唯一谜团是为什么 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++?

关于c++ - 如何在 COM 类型库中创建模块定义的函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44510639/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com