2013-09-09 2 views
0

У меня есть C++ DLL с помощью следующего метода:Проходя управляемый элемент класса в C метод ++

//C++ dll method (external) 
GetServerInterface(ServerInterface* ppIF /*[OUT]*/) 
{ 
//The method will set ppIF 
} 

//ServerInterface is defined as: 
typedef void * ServerInterface; 

Чтобы получить доступ к DLL из C# проекта, я создал проект C++/CLI и объявил управляемый класс следующим образом:

public ref class ComWrapperManager 
{ 
// 
// 
ServerInterface _serverInterface; 
void Connect(); 
// 
// 
} 

Я использую метод Connect() для вызова GetServerInterface, как показано ниже. Первый звонок работает, второй - нет. Может кто-нибудь объяснить, почему? Мне нужно сохранить этот указатель как переменную-член в управляемом классе. Любой лучший способ сделать это?

void Connect() 
    { 
    ServerInterface localServerInterface; 
    GetServerInterface(&localServerInterface); //THIS WORKS 

    GetServerInterface(&_serverInterface); //THIS DOESNT 

    //Error 1 error C2664: 'ServerInterface ' : 
    //cannot convert parameter 1 from //'cli::interior_ptr<Type>' 
    //to 'ServerInterface *' 


    } 

ответ

6

Вы передаете указатель на элемент управляемого объекта. Такие указатели являются специальными, известными как внутренними указателями. Они отслеживаются сборщиком мусора, он изменяет значение указателя, когда управляемый объект перемещается, когда GC уплотняет кучу.

Проблема в том, что вы передаете этот указатель на неуправляемый код. GC не способен модифицировать копию значения указателя, используемого нативным кодом. Теперь бедствие ударяется, когда другой поток запускает сборку мусора, только когда выполняется собственный код и разыгрывает указатель. Объект больше не существует на исходном адресе. Очень очень плохо. И очень трудно диагностировать, так как это так маловероятно.

Компилятор может видеть, что вы допустили эту ошибку. И жалуется на C2664.

Обходным путем является передача указателя, который хранится в ячейке памяти, которая не будет перемещаться GC. Такое местоположение очень легко получить, локальная переменная квалифицируется. Он хранится в стеке, он не будет перемещен.Так что это выглядит, как это вместо:

void Connect() 
{ 
    ServerInterface temp; 
    GetServerInterface(&temp); 
    this->_serverInterface = temp; 
    // etc.. 
} 

Что вы уже открыли для себя, только не забудьте назначить члена класса.

+0

Спасибо, Ханс. Отличный ответ, как всегда. Итак, присвоение переменной стека (temp) члену класса будет оставаться фиксированным? – bobbyalex

+0

Erm, нет, дело в том, что указатель на * temp * не является внутренним указателем, поэтому его можно без проблем передать на собственный код. Назначение члену класса не вызывает проблем, поскольку оно выполняется в управляемом коде. Большое дело в том, что джиттер генерирует этот код, он создает дополнительную информацию для GC, которая позволяет обнаруживать управляемые указатели. Подобно * this * reference, хранящемуся в регистре CPU. Эта информация отсутствует для собственного кода. –

+0

Я вижу. Итак, по вашему мнению, какой лучший способ сохранить указатель, который я получаю из метода GetServerInterface? Мне нужно это позже для других звонков. – bobbyalex

1

Вот почему вы не можете сделать второй один: _serverInterface является недействительным указатель, который является частью управляемого класса. Подумайте о том, что делает сборщик мусора ... Разрешено перемещать управляемые объекты в памяти, но он хочет, поэтому адрес указателя void может меняться от момента к моменту. Поэтому недействительно использовать этот адрес.

Есть два решения этого:

  1. Как вы отметили, где вы можете передать адрес переменной стека в неуправляемом методе. В отличие от управляемых объектов, стек не перемещается, когда сборщик мусора делает свою вещь, поэтому адрес не изменяется. Затем вы можете взять данные, хранящиеся в переменной стека, и скопировать их в поле класса, и это работает отлично, потому что вы не имеете дело с его адресом.
  2. Как заметил другой ответчик, вы можете заблокировать управляемый объект в памяти. Как только он не может двигаться, вы можете без проблем указать адрес поля указателя пустоты. (Он показывает C# синтаксис, где вы ищете для синтаксиса C++/CLI. Я не компилятором, чтобы проверить, но я считаю, что синтаксис C++/CLI не то же самое.)

Из двух решений, я предпочитаю # 1, тот, который вы уже реализовали: Решение № 2 представляет блок неизменяемой памяти в середине пространства, которое сборщик мусора хочет перестроить. Учитывая выбор, я предпочитаю не сжимать сборщик мусора.

+0

Синтаксис C++ использует псевдо-шаблон 'pin_ptr'. –