双重 Thunk (C++)
双重形式转换是指当托管上下文中的函数调用调用 Visual C++ 托管函数以及程序执行调用函数的本机入口点以调用托管函数时可能会遇到的性能损失。 本主题讨论双重形式转换的位置,以及如何避免它来提高性能。
备注
默认情况下,使用 /clr 编译时,托管函数的定义会导致编译器生成托管入口点和本机入口点。 这允许从本机和托管调用站点调用托管函数。 但是,当本机入口点存在时,它可以是对函数的所有调用的入口点。 如果托管调用函数,则本机入口点将调用托管入口点。 实际上,需要两个调用才能调用函数(因此,需要双重形式转换)。 例如,始终通过本机入口点调用虚拟函数。
一种解决方法是告诉编译器不要为托管函数生成本机入口点,即仅使用 __clrcall 调用约定从托管上下文调用该函数。
同样,如果导出 (dllexport、dllimport) 托管函数,则会生成本机入口点,以及导入和调用该函数的任何函数都将通过本机入口点调用。 为了避免在这种情况下出现双重形式转换,请勿使用本机导出/导入语义;只需通过 #using
引用元数据(查看 #using 指令)。
编译器已更新,以减少不必要的双重形式转换。 例如,签名中具有托管类型的任何函数(包括返回类型)将隐式标记为 __clrcall
。
示例:双重形式转换
说明
下面的示例演示了双重形式转换。 在(没有 /clr 的情况下)编译本机时,对 main
中虚拟函数的调用将生成一个对 T
的复制构造函数的调用和一个对析构函数的调用。 使用 /clr 和 __clrcall
声明虚拟函数时,可以实现类似的行为。 但是,当刚刚使用 /clr 编译时,函数调用会生成对复制构造函数的调用,但由于本机到托管的 thunk,对复制构造函数有另一个调用。
代码
// double_thunking.cpp
// compile with: /clr
#include <stdio.h>
struct T {
T() {
puts(__FUNCSIG__);
}
T(const T&) {
puts(__FUNCSIG__);
}
~T() {
puts(__FUNCSIG__);
}
T& operator=(const T&) {
puts(__FUNCSIG__);
return *this;
}
};
struct S {
virtual void /* __clrcall */ f(T t) {};
} s;
int main() {
S* pS = &s;
T t;
printf("calling struct S\n");
pS->f(t);
printf("after calling struct S\n");
}
示例输出
__thiscall T::T(void)
calling struct S
__thiscall T::T(const struct T &)
__thiscall T::T(const struct T &)
__thiscall T::~T(void)
__thiscall T::~T(void)
after calling struct S
__thiscall T::~T(void)
示例:双重形式转换的效果
说明
上一个示例证明了双重形式转换的存在。 此示例显示其效果。 循环 for
调用虚拟函数,程序报告执行时间。 使用 /clr 编译程序时会报告最慢的时间。 在没有 /clr 的情况下编译或使用 __clrcall
声明虚拟函数时会报告最快的时间。
代码
// double_thunking_2.cpp
// compile with: /clr
#include <time.h>
#include <stdio.h>
#pragma unmanaged
struct T {
T() {}
T(const T&) {}
~T() {}
T& operator=(const T&) { return *this; }
};
struct S {
virtual void /* __clrcall */ f(T t) {};
} s;
int main() {
S* pS = &s;
T t;
clock_t start, finish;
double duration;
start = clock();
for ( int i = 0 ; i < 1000000 ; i++ )
pS->f(t);
finish = clock();
duration = (double)(finish - start) / (CLOCKS_PER_SEC);
printf( "%2.1f seconds\n", duration );
printf("after calling struct S\n");
}
示例输出
4.2 seconds
after calling struct S