Информация об изменениях

Сообщение Re[3]: C++/CLI утечка USER объектов от 27.05.2015 3:47

Изменено 27.05.2015 4:11 Mystic Artifact

Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Он может там оказаться

Всё правильно.

Но, тут не PInvoke, поэтому я твои выдержки опустил, а вставлю другие из того же поста:

For managed C++ and IJW, the story is a little different. This is important for you C++ programmers out there, so I hope you're still reading! If you use DllImport explicitly in C++, the same rules apply as with C#. But when you call unmanaged APIs directly from managed C++ code, neither GetLastError nor Marshal.GetLastWin32Error will work reliably. GetLastError won't work for the same reason that a PInvoke call to it wouldn't work in C#. Marshal.GetLastWin32Error won't work because the implicit PInvoke goop emitted by the compiler doesn't set SetLastError to true. To fix this, you can use #pragma unmanaged to keep code that relies on GetLastError functionality as unmanaged code.

Именно на это я изначально и намекал, что есть какая-то магия.


Теперь о магии.

Пример 1. Упрощенный.
  Скрытый текст
// ConsoleApplication8CppCli.cpp : main project file.

#include "stdafx.h"
#include <windows.h>

using namespace System;
using namespace System::ComponentModel;

int main(array<System::String ^> ^args)
{
    ::DestroyWindow(NULL);  // здесь типа не PInvoke
    Win32Exception e;
    Console::WriteLine(L"{0}", e.NativeErrorCode); // выводит 1400
  return 0;
}


В этом коде: не генериться ничего сверх естественного, или это трудно увидеть (в студии).
Если полазить в CLR, то в Marshal — можной найти обычный геттер, берущий last error у потока (своего).
В Win32Exception вообще специального ничего нет.

Пример 2. Отключаем магию.
  Скрытый текст
// ConsoleApplication8CppCli.cpp : main project file.

#include "stdafx.h"
#include <windows.h>

using namespace System;
using namespace System::ComponentModel;

#pragma managed (push, off)
void unmanaged()
{
    ::DestroyWindow(NULL);
}
#pragma managed (pop)

int main(array<System::String ^> ^args)
{
    unmanaged(); // магия была тут
    Win32Exception e;
    Console::WriteLine(L"{0}", e.NativeErrorCode); // выводит 0
  return 0;
}


Осталось ещё немножко разобраться, почему это GetLastWin32Error в посте — не reliable. И если он не reliable — то как оно тогда работает, и можно ли этому доверять?
Всё до безобразия просто: в JIT / vclinker встроена эвристика, и PInvoke "goop" эмитится в флагом setlasterror для kernel32 (и что там ещё сейчас вместо модно), gdi32, user32, advapi32.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Re[3]: C++/CLI утечка USER объектов
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Он может там оказаться

Всё правильно.

Но, тут не PInvoke, поэтому я твои выдержки опустил, а вставлю другие из того же поста:

For managed C++ and IJW, the story is a little different. This is important for you C++ programmers out there, so I hope you're still reading! If you use DllImport explicitly in C++, the same rules apply as with C#. But when you call unmanaged APIs directly from managed C++ code, neither GetLastError nor Marshal.GetLastWin32Error will work reliably. GetLastError won't work for the same reason that a PInvoke call to it wouldn't work in C#. Marshal.GetLastWin32Error won't work because the implicit PInvoke goop emitted by the compiler doesn't set SetLastError to true. To fix this, you can use #pragma unmanaged to keep code that relies on GetLastError functionality as unmanaged code.

Именно на это я изначально и намекал, что есть какая-то магия.


Теперь о магии.

Пример 1. Упрощенный.
  Скрытый текст
// ConsoleApplication8CppCli.cpp : main project file.

#include "stdafx.h"
#include <windows.h>

using namespace System;
using namespace System::ComponentModel;

int main(array<System::String ^> ^args)
{
    ::DestroyWindow(NULL);  // здесь типа не PInvoke
    Win32Exception e;
    Console::WriteLine(L"{0}", e.NativeErrorCode); // выводит 1400
  return 0;
}


В этом коде: не генериться ничего сверх естественного, или это трудно увидеть (в студии).
Если полазить в CLR, то в Marshal — можной найти обычный геттер, берущий last error у потока (своего).
В Win32Exception вообще специального ничего нет.

Пример 2. Отключаем магию.
  Скрытый текст
// ConsoleApplication8CppCli.cpp : main project file.

#include "stdafx.h"
#include <windows.h>

using namespace System;
using namespace System::ComponentModel;

#pragma managed (push, off)
void unmanaged()
{
    ::DestroyWindow(NULL);
}
#pragma managed (pop)

int main(array<System::String ^> ^args)
{
    unmanaged(); // магия была тут
    Win32Exception e;
    Console::WriteLine(L"{0}", e.NativeErrorCode); // выводит 0
  return 0;
}


Осталось ещё немножко разобраться, почему это GetLastWin32Error в посте — не reliable. И если он не reliable — то как оно тогда работает, и можно ли этому доверять?
Всё до безобразия просто: в JIT / vclinker встроена эвристика, и PInvoke "goop" эмитится в флагом setlasterror для kernel32 (и что там ещё сейчас вместо модно), gdi32, user32, advapi32.

UPD:
1. Т.е. на самом деле — пока мы попадаем в эти эвристики — можно смело использовать и это будет reliable. Пост в блоге немного устарел, а этот вопрос не освещен.
2. Компилятор всё равно генерирует делает "goops" (трамплины, hunks, transitions) тем или иным способ, но неявно. Это логично и ожидаемо.
3. И уже просто в тему — забавно, но DllImport имеет разные значения по умолчанию своих параметров (например ExactSpelling) (C# vs VB.NET).