【问题标题】:How to pass an int from C# to C++ by reference?如何通过引用将 int 从 C# 传递到 C++?
【发布时间】:2020-06-19 22:01:43
【问题描述】:

是否可以通过引用将 int 从 C# 传递到 C++?

我在 C++ 中有一个简单的方法,它只增加一个数字并打印消息。

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
}

现在我想从 C++/CLI 调用它,然后从 C# 调用它。

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

但这会返回编译器错误:

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

我确保所有三个项目都是 x86,但错误仍然存​​在。价格方面,CLR 模块是 Win32,C# 控制台应用程序是 x86。

问题在这里:

如果我声明void StoreWrapper::ProcessOrder(int&amp; num),那么这个方法可以编译,但现在我相信它是纯C++?我不能从 C# 端调用它,得到以下编译器错误:

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

如果我将它声明为 `void StoreWrapper::ProcessOrder(int^ num)' 现在该方法甚至无法编译。

我尝试过使用System::Int32 变量类型但结果相同。

有没有办法通过引用将 int 从 .NET 世界传递给 C++?如果 C++ 端是 32 位,.NET 端是 64 位,是否可以?

更新

C#代码就到这里了,基本上我想在下面的代码中更新数字。

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

不幸的是,dxiv 的答案没有更新上面的数字。

【问题讨论】:

  • 您需要使用int%。与 int& 非常相似,但垃圾收集器可以跟踪它。 int^ 强制将 int 装箱,这是 C# 中不可用的功能(看起来像元数据中的 System.ValueType)。
  • @HansPassant 这绝对不是标记问题的重复。这个问题介于 .NET 和 C++ 边界之间,而不仅仅是在 C++/CLI 环境中通过引用传递,而这只是用 % 符号完成。
  • @zar “更新”下的最后一行说它仍然是“不更新”。还是这样吗?
  • @dxiv 我已经复制/粘贴了您的代码,但我将重新运行我可能遗漏的代码。明天更新。

标签: c# .net c++-cli


【解决方案1】:

尝试像ProcessOrder(num); 一样直接使用num 失败,原帖中引用的错误“cannot convert argument 1 from System::Int32 ^ to int &amp;”因为不同级别的传递的(指针)变量和预期的(引用)参数之间的间接关系。

尝试使用ProcessOrder(*num); 修复该问题,可以更正间接并获得匹配的类型,但无法编译并出现错误“gc 堆中的对象(未装箱的值类型)无法转换为本机参考”。这指出了真正的问题,即非托管/未装箱的引用不能指向 gc 托管堆,该堆会受到压缩和重新洗牌。

最简单的解决方案是使用tracking reference int% 代替,正如answer 中建议的那样,链接为重复的问题。

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 ] 也可以通过地址使用num,但这需要显式固定。
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 ] 编辑代码并添加 cmets 以涵盖将​​值返回给 C# 调用者。

【讨论】:

  • 我正在寻找第二种方式,所以调用变量值被更新,但上面的代码没有构建。如果我执行pin_ptr&lt;int&gt; p = &amp;num;,我会构建,但这不会更新调用变量。
  • @zar 1) 如果您希望第一个包装器将值返回给调用者,您需要将参数更改为int^ %num。这在作为重复链接的问题中进行了解释。
  • @zar 2) 对于第二个包装器,您使用的是什么版本的编译器?以上编译并与我的 VS 2019 v16.5.4 一起正常工作。您提出的更改 pin_ptr&lt;int&gt; p = &amp;num; 确实 not 编译,带有“error C2440: 'initializing': cannot convert from 'System::Int32 ^*' to 'cli::pin_ptr '".
  • 抱歉迟到了。刚刚再试一次,它正在工作!我之前没有看到它改变的原因是因为如果你注意到,我的遗留 Process() 并没有真正改变参考值,所以很明显它永远不会改变。我解决了这个问题,它确实改变了。好一个!谢谢!
  • @zar 当它引起我的注意时,我会让他们满意,因为它没有被忽视;-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-12
  • 1970-01-01
  • 2021-09-15
  • 2011-01-17
  • 1970-01-01
相关资源
最近更新 更多