Sdílet prostřednictvím


Dvojitý převod adres na jinou bitovou šířku (C++)

Dvojitý převod odkazuje na ztrátu výkonu, kterou můžete zaznamenat, když volání funkce ve spravovaném kontextu volá spravovanou funkci Visual C++ a kde provádění programu volá nativní vstupní bod funkce, aby volala spravovanou funkci. Toto téma popisuje, kde dochází k dvojitému převodu a jak se můžete vyhnout jeho zlepšení výkonu.

Poznámky

Při kompilaci pomocí /clr ve výchozím nastavení způsobí definice spravované funkce kompilátoru vygenerovat spravovaný vstupní bod a nativní vstupní bod. To umožňuje volání spravované funkce z nativních a spravovaných webů volání. Pokud však existuje nativní vstupní bod, může to být vstupní bod pro všechna volání funkce. Pokud je funkce volání spravovaná, nativní vstupní bod pak zavolá spravovaný vstupní bod. V důsledku toho jsou k vyvolání funkce potřeba dvě volání (tedy dvojitého převodu). Virtuální funkce se například vždy volají prostřednictvím nativního vstupního bodu.

Jedním z řešení je informovat kompilátor, aby negeneroval nativní vstupní bod pro spravovanou funkci, že funkce bude volána pouze ze spravovaného kontextu pomocí __clrcall konvence volání.

Podobně pokud exportujete (dllexport, dllimport) spravovanou funkci, vygeneruje se nativní vstupní bod a jakákoli funkce, která importuje a volá tuto funkci, bude volat prostřednictvím nativního vstupního bodu. Abyste se v této situaci vyhnuli dvojitému převodu, nepoužívejte nativní sémantiku exportu/importu; jednoduše odkazovat na metadata prostřednictvím #using (viz směrnice #using).

Kompilátor byl aktualizován, aby se snížil nepotřebný dvojitý převod. Například jakákoli funkce se spravovaným typem v podpisu (včetně návratového typu) se implicitně označí jako __clrcall.

Příklad: Dvojitý převod

Popis

Následující ukázka ukazuje dvojité převody. Při kompilaci nativní (bez /clr) volání virtuální funkce vygeneruje main jedno volání Tkonstruktoru kopírování a jedno volání destruktoru. Podobné chování se dosahuje, když je virtuální funkce deklarována pomocí /clr a __clrcall. Při kompilaci pomocí /clr však volání funkce generuje volání konstruktoru kopírování, ale existuje další volání konstruktoru kopírování z důvodu nativní-to-spravované thunk.

Kód

// 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");
}

Ukázkový výstup

__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)

Příklad: Účinek dvojitého převodu

Popis

Předchozí vzorek ukázal existenci dvojitého převodu. Tato ukázka ukazuje její účinek. Smyčka for volá virtuální funkci a program hlásí čas spuštění. Nejpomalejší doba je hlášena při kompilaci programu pomocí /clr. Nejrychlejší časy jsou hlášeny při kompilaci bez /clr nebo pokud je virtuální funkce deklarována pomocí __clrcall.

Kód

// 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");
}

Ukázkový výstup

4.2 seconds
after calling struct S

Viz také

Smíšená (nativní a spravovaná) sestavení