Управляемое преобразование в класс собственных значений: безопасно ли приводить указатель? - PullRequest
0 голосов
/ 29 мая 2019

У меня есть проект C #, который использует классы C ++ из библиотеки. Классы C # на самом деле являются обертками для классов C ++, они предоставляют функциональность C ++ клиентскому коду C #. Во многих местах классы значений C ++ преобразуются в оболочки C # и обратно. Во время обзора кода я нашел два способа преобразования классов: через reinterpret_cast (см. Оператор *) и через pin_ptr (см. MultiplyBy); Как видите, у нативного и управляемого классов есть три «двойных» поля, поэтому кто-то использовал reinterpret_cast;

Во многих местах классы копируются из C # в C ++ с использованием memcpy: memcpy (& NativePointInstance, & ManagedPointIntance, sizeof (double) * 3);

Я слышал от одного разработчика, что reinterpret_cast может быть безопасным в некоторых случаях, когда мы работаем с классами значений C #.

Вопрос в следующем: Когда безопасно использовать reinterpret_cast для классов значений C #, а когда нет? Каков наиболее правильный способ преобразования указателей в этом случае - как в операторе * или как в MultiplyBy, или другой альтернативе?

Может кто-нибудь подробно объяснить, что происходит в MultiplyBy (), как работает этот трюк?

Насколько я понял, проблема может быть вызвана тем, что оптимизатор может изменить порядок полей, GC может реорганизовать кучу, а выравнивание полей может отличаться для управляемого и собственного кода.

// this is C++ native class
class NativePoint
{
public:
  double x;
  double y;
  double z;
  NativePoint(double x, double y, double z)
  {
    this->x = x;
    this->y = y;
    this->z = z;
  }
  NativePoint operator * (int value)
  {
    return NativePoint(x * value, y * value, z * value);
  }
};

// this class managed C++ class
[StructLayout(LayoutKind::Sequential)]
public value class ManagedPoint
{
internal:
  double x;
  double y;
  double z;
  ManagedPoint(const NativePoint& p)
  {
    x = p.x;
    y = p.y;
    z = p.z;
  }
public:
  static ManagedPoint operator * (ManagedPoint a, double value)
  {
    return ManagedPoint((*reinterpret_cast<NativePoint*>(&(a))) * value);
  }
  ManagedPoint MultiplyBy(double value)
  {
    pin_ptr<ManagedPoint> pThisTmp = &*this;
    NativePoint* pThis = reinterpret_cast<NativePoint*>(&*pThisTmp);
    return ManagedPoint(*pThis * value);
  }
};

// this should be called from C# code, or another .NET app
int main(array<System::String ^> ^args)
{
  NativePoint p_native = NativePoint(1, 1, 1);
  ManagedPoint p = ManagedPoint(p_native);
  Console::WriteLine("p is {" + p.x + ", " + p.y + ", " + p.z + "}");
  ManagedPoint p1 = p * 5;
  Console::WriteLine("p1 is {" + p1.x + ", " + p1.y + ", " + p1.z + "}");
  ManagedPoint p2 = p.MultiplyBy(5);
  Console::WriteLine("p2 is {" + p2.x + ", " + p2.y + ", " + p2.z + "}");
  Console::ReadLine();
  return 0;
}

1 Ответ

0 голосов
/ 03 июня 2019

Ну, в итоге я использовал обычные конструкторы нативных классов.Он выглядит для меня абсолютно безопасным и самым быстрым из оставшихся вариантов.Идея из комментариев с Marshal :: PtrToStructure () была хорошей, но для моего тестового примера давала более медленное выполнение, чем то же самое с использованием конструкторов.Приведение указателей является самым быстрым решением, но после очень страшного примера из комментариев я больше не рискну его использовать (за исключением случаев, когда нам действительно нужно оптимизировать его, тогда LayoutKind :: Explicit должен сделать это).

Вот код, который я использовал для тестирования:

// this is C++ native class
class NativePoint
{
public:
  double x;
  double y;
  double z;
  NativePoint()
  {

  }
  NativePoint(double x, double y, double z)
  {
    this->x = x;
    this->y = y;
    this->z = z;
  }
  NativePoint operator * (int value)
  {
    return NativePoint(x * value, y * value, z * value);
  }
};

// this class managed C++ class
[StructLayout(LayoutKind::Sequential)]
public value class ManagedPoint
{
internal:
  double x;
  double y;
  double z;
  ManagedPoint(const NativePoint& p)
  {
    x = p.x;
    y = p.y;
    z = p.z;
  }
  ManagedPoint(double x, double y, double z)
  {
    this->x = x;
    this->y = y;
    this->z = z;
  }
public:
  static ManagedPoint operator * (ManagedPoint a, double value)
  {
    return ManagedPoint((*reinterpret_cast<NativePoint*>(&(a))) * value);
  }
  ManagedPoint MultiplyBy(double value)
  {
    pin_ptr<ManagedPoint> pThisTmp = &*this;
    NativePoint* pThis = reinterpret_cast<NativePoint*>(&*pThisTmp);
    return ManagedPoint(*pThis * value);
  }
};

// this class managed C++ class
[StructLayout(LayoutKind::Sequential)]
public value class ManagedPoint2
{
internal:
  double x;
  double y;
  double z;
  ManagedPoint2(const NativePoint& p)
  {
    x = p.x;
    y = p.y;
    z = p.z;
  }
  ManagedPoint2(double x, double y, double z)
  {
    this->x = x;
    this->y = y;
    this->z = z;
  }
public:
  static ManagedPoint2 operator * (ManagedPoint2 a, double value)
  {
    return ManagedPoint2((NativePoint(a.x, a.y, a.z)) * value);
  }
  ManagedPoint2 MultiplyBy(double value)
  {
    return ManagedPoint2((NativePoint(this->x, this->y, this->z)) * value);
  }
};

// this class managed C++ class
[StructLayout(LayoutKind::Sequential)]
public value class ManagedPoint3
{
internal:
  double x;
  double y;
  double z;
  ManagedPoint3(const NativePoint& p)
  {
    x = p.x;
    y = p.y;
    z = p.z;
  }
  ManagedPoint3(double x, double y, double z)
  {
    this->x = x;
    this->y = y;
    this->z = z;
  }
public:
  static ManagedPoint3 operator * (ManagedPoint3 a, double value)
  {
    NativePoint p;
    Marshal::StructureToPtr(a, IntPtr(&p), false);
    return ManagedPoint3(p * value);
  }
  ManagedPoint3 MultiplyBy(double value)
  {
    NativePoint p;
    Marshal::StructureToPtr(*this, IntPtr(&p), false);
    return ManagedPoint3(p * value);
  }
};

// this class managed C++ class
[StructLayout(LayoutKind::Sequential)]
public value class ManagedPoint4
{
internal:
  double x;
  double y;
  double z;
  ManagedPoint4(const NativePoint& p)
  {
    x = p.x;
    y = p.y;
    z = p.z;
  }
  ManagedPoint4(double x, double y, double z)
  {
    this->x = x;
    this->y = y;
    this->z = z;
  }
public:
  static ManagedPoint4 operator * (ManagedPoint4 a, double value)
  {
    return ManagedPoint4(ManagedPoint4::ToNative(a) * value);
  }
  ManagedPoint4 MultiplyBy(double value)
  {
    return ManagedPoint4(ManagedPoint4::ToNative(*this) * value);
  }
  static NativePoint ToNative(const ManagedPoint4& pp)
  {
    NativePoint p;
    Marshal::StructureToPtr(pp, IntPtr(&p), false);
    return p;
  }
};

// this should be called from C# code, or another .NET app
int main(array<System::String ^> ^args)
{
  Stopwatch time;
  time.Start();
  for (int i = 0; i < 10000000; i++)
  {
    ManagedPoint a = ManagedPoint(1, 2, 3) * 4;
  }
  time.Stop();
  Console::WriteLine("time: " + time.ElapsedMilliseconds);

  Stopwatch time2;
  time2.Start();
  for (int i = 0; i < 10000000; i++)
  {
    ManagedPoint2 a2 = ManagedPoint2(1, 2, 3) * 4;
  }
  time2.Stop();
  Console::WriteLine("time2: " + time2.ElapsedMilliseconds);

  Stopwatch time3;
  time3.Start();
  for (int i = 0; i < 10000000; i++)
  {
    ManagedPoint3 a3 = ManagedPoint3(1, 2, 3) * 4;
  }
  time3.Stop();
  Console::WriteLine("time3: " + time3.ElapsedMilliseconds);

  Stopwatch time4;
  time4.Start();
  for (int i = 0; i < 10000000; i++)
  {
    ManagedPoint4 a3 = ManagedPoint4(1, 2, 3) * 4;
  }
  time4.Stop();
  Console::WriteLine("time4: " + time4.ElapsedMilliseconds);

  Console::ReadLine();
  Console::WriteLine("======================================================");
  Console::WriteLine();

  return 0;
}

И это вывод:

time: 374
time2: 382
time3: 857
time4: 961

time: 395
time2: 413
time3: 900
time4: 968

time: 376
time2: 378
time3: 840
time4: 909
...