вывод без изменения значения аргумента int

Функция определена в проекте C# как:

[DllImport("Project2.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void modifyVar(ref int x);

И вызов функции:

modifyVar(ref a)

где а = 4

Собственное определение функции C++ выглядит следующим образом:

extern "C" _declspec(dllexport) void modifyVar(int* a)
{   
        int x = 1;
        a = &x;
}

Почему в проекте C# a не указывает на новую переменную со значением 1 после вызова функции? Все равно остается 4.

Однако, если я изменю функцию С++ на это, она будет работать нормально:

 extern "C" _declspec(dllexport) void modifyVar(int* a)
{   
        int x = 1;
        *a = x;
}

Но как заставить его указывать на новую ячейку памяти, а не изменять значение?

Это как-то связано с использованием IntPtr, а не ref int?


person Tyler Durden    schedule 03.01.2014    source источник
comment
Та же проблема, что и раньше, вы все еще пишете неправильный код C++.   -  person Hans Passant    schedule 03.01.2014
comment
возможный дубликат Pinvoking собственной функции с аргументами массива   -  person Hans Passant    schedule 03.01.2014
comment
Но обратите внимание, что здесь я передаю одно целое число, а не массив.   -  person Tyler Durden    schedule 03.01.2014
comment
Второй фрагмент передает int, в 1-м фрагменте та же ошибка, что и раньше. Вам нужно будет поработать над пониманием указателей, попросите помощи у коллеги.   -  person Hans Passant    schedule 04.01.2014
comment
@HansPassant Разве это не так просто, как: *a = 1; (не то чтобы это не обязательно решит проблему, которую он хочет решить, конечно) Или это всегда должно было быть определено void modifyVar(int& a) {...}   -  person    schedule 04.01.2014


Ответы (2)


Но как заставить его указывать на новую ячейку памяти, а не изменять значение?

Итак, я так понимаю вопрос, вы спрашиваете, как вернуть адрес переменной в нативном модуле. Делай это так:

static int i = 42;

extern "C" _declspec(dllexport) int* getPtr()
{   
    return &i;
}

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

И из С# назовите это так:

[DllImport("...", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr getPtr();

....

IntPtr ptr = getPtr();
int i = Marshal.ReadInt32(ptr);

Если вы предпочитаете возвращать указатель через параметр, сделайте это следующим образом:

static int i = 42;

extern "C" _declspec(dllexport) void getPtr(int** ptr)
{   
    *ptr = &i;
}

И из С#:

[DllImport("...", CallingConvention = CallingConvention.Cdecl)]
static extern void getPtr(out IntPtr ptr);

....

IntPtr ptr;
getPtr(out ptr);
int i = Marshal.ReadInt32(ptr);

Однако, сказав все это, я действительно сомневаюсь, что вы действительно хотите вернуть адрес переменной. Эта глобальная переменная не выглядит очень полезной. И альтернатива — память, выделенная кучей. Но тогда вам нужно экспортировать делокатор или выделить из общей кучи. Опять же, особого удовольствия там не будет. И боль в шее тоже для сортировки.

Гораздо более полезно выделить переменную в вызывающем коде, управляемом коде и попросить собственный код изменить значение этой переменной. Это делает управление жизненным циклом тривиальным и позволяет вам позволить маршаллеру pinvoke выполнять всю работу.

person David Heffernan    schedule 03.01.2014
comment
Да, адрес не тот, что я хочу. Изменения должны быть внесены в код C++. Еще раз спасибо. - person Tyler Durden; 04.01.2014
comment
Верно. Но я думаю, что ответил на вопрос, который вы на самом деле задали. - person David Heffernan; 04.01.2014
comment
Ваш второй пример именно то, что я хотел. Итак, теперь у этого будет измененный аргумент int в функции C#. На самом деле я имею дело с аргументами массива int. Я попробую экстраполировать с помощью массивов - person Tyler Durden; 04.01.2014
comment
У меня большие сомнения, Дэвид, почему intPtr работает как аргумент для функции pinvoke, а не ref int? - person Tyler Durden; 04.01.2014
comment
ref int имеет один уровень косвенности от значения. Но у IntPtr их два. Итак, ref int — это int*, а out IntPtr — это int**. - person David Heffernan; 04.01.2014

Ваш первый фрагмент:

void modifyVar( int* a )
{   
  int x = 1;
  a = &x;
}

принимает int* (указатель на int). При вызове он получает в стек слово, содержащее адрес, предположительно местонахождение int, в области действия вызывающего объекта. Затем вы присваиваете этому слову в стеке адрес локальной переменной в кадре стека текущей функции. Когда эта функция возвращается, стек извлекается, а кадр стека выходящей функции и работа, помещенная в стек в виде списка ее параметров, освобождаются.

Вызывающий объект не знает, что только что сделала ваша функция, поскольку вы ничего не изменили в области действия вызывающего объекта. Вы не изменили объект (int), на который указывает ваш int*.

Во втором фрагменте

void modifyVar( int* a )
{   
  int x = 1;
  *a = x;
}

То же самое происходит и при звонке. Тело функции делает нечто другое: оператор *a = x; делает следующее:

  • принимает адрес, содержащийся в int* с именем a,
  • удаляет ссылку на него и
  • сохраняет значение переменной int x в этом месте.

Теперь вы изменили хранилище вызывающего абонента, и это изменение известно вызывающему абоненту.

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

Если бы вы вернули вызывающей программе адрес одной из ваших локальных переменных, и она использовала ее, то, скорее всего, произошли бы плохие вещи, поскольку вызывающая программа затем возилась бы со стековой памятью, которая могла бы использоваться или не использоваться.

Вам необходимо получить копию Язык программирования C (он же K+R):

Обложка языка программирования C

person Nicholas Carey    schedule 03.01.2014
comment
Да, действительно серьезная ошибка с моей стороны. Сейчас нужно сделать много домашней работы - person Tyler Durden; 04.01.2014