Search code examples
c#.netc++-cli

How to pass an int from C# to C++ by reference?


Is it possible to pass a int from C# to C++ by reference?

I have a simple method in C++ that just increment a number and print messages.

void ProcessOrder(int& num)
{
    int start = num;
    int stop = num + 10;

    for (int i = start; i < stop; i++)
    {
        cout << "[Legacy] counting " << i << endl;
    }

    cout << endl;

    // Note the above was original posted code and I made a stupid mistake and was never
    // even changing `num` value passed by reference. The correct function to test
    // should be as below (discard all of the above)
    num =  num + 5; // just modify the number to any value
}

Now I want to call this from C++/CLI and then that from C#.

void StoreWrapper::ProcessOrder(int^ num)
{
    LegacyStore* legacyStore = new LegacyStore();
    legacyStore->ProcessOrder(num); // error here
}

But this returns compiler error:

error C2664: 'void LegacyStore::ProcessOrder(int &)': cannot convert argument 1 from 'System::Int32 ^' to 'int &'

I made sure all three projects are x86 but the error still persists. To be price, the CLR module is Win32 and C# Console application is x86.

The issues are here:

If I declare void StoreWrapper::ProcessOrder(int& num) then this method compiles but now I believe its pure C++? I can't call this from C# side, get the following compiler err:

error CS1503: Argument 1: cannot convert from 'int' to 'int*'

If I declare it `void StoreWrapper::ProcessOrder(int^ num)' now the method doesn't even compile.

I have tried using System::Int32 variable type but same result.

Is there a way I pass int to C++ from .NET world by reference? Is it possible if C++ side is 32 bit and .NET side is 64 bit?

Update

The C# code is here, basically I want the number to get updated in the code below.

  int number = 0;
  Console.WriteLine("Original number = {0}", number);
  store.ProcessOrder(ref number);
  Console.WriteLine("After ProcessOrder number = {0}", number);

The answer by dxiv unfortunately doesn't update the number above.


Solution

  • Attempting to use num directly like ProcessOrder(num); fails with the error quoted in the original post "cannot convert argument 1 from System::Int32 ^ to int &" because of the different levels of indirection between the passed (pointer) variable and the expected (reference) argument.

    Attempting to fix that with ProcessOrder(*num); corrects the indirection and gets the types to match, but fails to compile with the error "an object from the gc heap (an unboxed value type) cannot be converted to a native reference". This points to the real issue, which is that an unmanaged/unboxed reference cannot point into the gc managed heap, which is subject to compacting and reshuffles.

    The easiest solution is to use a tracking reference int% instead, as advised in the answer to the question linked as a duplicate.

    void StoreWrapper::ProcessOrder(int% num)
    {
        LegacyStore* legacyStore = new LegacyStore();
    
        legacyStore->ProcessOrder(*&num);  // shorthand for:
                                           //
                                           // int &n = *&num;
                                           // legacyStore->ProcessOrder(n);
                                           // num = n;
    }
    
    // to be called from C# as:
    //
    // int number = 0;
    // store.ProcessOrder(ref number);
    
    


    [ EDIT ]   It is also possible to use num by address, though that requires explicit pinning.

    void StoreWrapper::ProcessOrder([System::Runtime::InteropServices::Out] int %num)
    {
        LegacyStore* legacyStore = new LegacyStore();
    
        pin_ptr<int> p = &num;
        legacyStore->ProcessOrder(*p);
    }
    
    // to be called from C# as:
    //
    // int number = 0;
    // store.ProcessOrder(out number);
    


    [ EDIT #2 ]   Edited code and added comments to cover returning the value to the C# caller.