简体   繁体   English

为什么 C++ 从 C# 获取错误的参数值,因为 function 返回自定义结构

[英]Why is C++ getting wrong parameter values from C# for a function returning a custom struct

Suppose I have the following C# Console application calling a C++ library.假设我有以下 C# 控制台应用程序调用 C++ 库。

internal static class Program
{
    private struct NumberContainer
    {
        internal int Number1 { get; }
        internal uint Number2 { get; }

        internal NumberContainer(int number1, uint number2)
        {
            Number1 = number1;
            Number2 = number2;
        }

        public override string ToString()
            => $"{Number1}, {Number2}";
    }

    [DllImport("CPlusPlusTargetLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
    private static extern int Add(int number1, uint number2);

    [DllImport("CPlusPlusTargetLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
    private static extern NumberContainer Compose(int signedInput, uint unsignedInput);

    private static void Main(string[] args)
    {
        var unsignedInput = args.Any() && uint.TryParse(args.First(), out uint unsignedValue) ? unsignedValue : 0;
        var signedInput = args.Count() > 1 && int.TryParse(args.ElementAt(1), out int signedValue) ? signedValue : 0;

        Console.WriteLine("Adding {0} and {1}...", signedInput, unsignedInput);
        Console.WriteLine("Result = {0}", Add(signedInput, unsignedInput));

        Console.WriteLine("Composing {0} and {1}...", signedInput, unsignedInput);
        Console.WriteLine("Composed {0}", Compose(signedInput, unsignedInput));
    }
}

And the following header file in CPlusPlusTargetLibrary.dll :以及 CPlusPlusTargetLibrary.dll 中的以下CPlusPlusTargetLibrary.dll文件:

#pragma once

struct NumberContainer
{
    const signed long Number1;
    const unsigned long Number2;

    NumberContainer(const signed long number1, const unsigned long number2)
        : Number1(number1),
        Number2(number2)
    {
    }
};

extern "C" __declspec(dllexport) signed long __cdecl Add
(
    signed long number1,
    unsigned long number2
);

extern "C" __declspec(dllexport) NumberContainer __cdecl Compose
(
    signed long signedInput,
    unsigned long unsignedInput
);

And the following corresponding CPP file for that header:以及该 header 的以下相应 CPP 文件:

#include "pch.h"
#include "TargetFunctions.h"

extern "C"
{    
    __declspec(dllexport) signed long __cdecl Add(signed long number1, unsigned long number2)
    {
        return number1 + number2;
    }

    __declspec(dllexport) NumberContainer __cdecl Compose(signed long number1, unsigned long number2)
    {
        return NumberContainer
        (
            number1,
            number2
        );
    }
}

Given 5 as the unsignedInput in C# and -8 as the signedInput , why is it that Add correctly receives -8 and 5 when the breakpoint on the return statement in its C++ implementation is hit, but Compose receives 5 and 710724189 ?给定5作为 C# 中的unsignedInput-8作为signedInput ,为什么Add在其 C++ 实现中的return语句上的断点被命中时正确接收-85 ,但Compose接收5710724189

Also, as a side, the immutable struct NumberContainer fails to initialize throwing a write access violation.此外,作为一个方面,不可变结构NumberContainer无法初始化引发write access violation. exception once Number1(number1) is hit - albeit this is after the faulty values have been received so this probably isn't relevant to the 1st problem I'm trying to resolve.一旦Number1(number1)被命中就会出现异常——尽管这是在收到错误值之后,所以这可能与我试图解决的第一个问题无关。

If I flip the call order, Compose then receives 5 and 1817521252 but I cannot proceed any further to see what Add receives due to the aforementioned exception.如果我翻转调用顺序, Compose会收到51817521252 ,但由于上述异常,我无法继续查看Add收到的内容。

From debugging, I have found that placing a breakpoint on the opening curly brace of either implementation shows incorrect parameter values;通过调试,我发现在任一实现的左大括号上放置断点都会显示不正确的参数值; after this line, the parameter values are both correct for Add but for Compose the 1st parameter is consistently correct if it were the 2nd parameter whilst the 2nd parameter appears to be some random value in memory.在此行之后,参数值对于Add都是正确的,但对于Compose ,如果它是第二个参数,则第一个参数始终正确,而第二个参数似乎是 memory 中的某个随机值。 I'm thinking something's offset the arguments so that the first one is being discarded somewhere between the C# and C++ but what is doing that, I am not sure (I've also tried using __stdcall with CallingConvention.StdCall and CallingConvention.Winapi both to the same end result). I'm thinking something's offset the arguments so that the first one is being discarded somewhere between the C# and C++ but what is doing that, I am not sure (I've also tried using __stdcall with CallingConvention.StdCall and CallingConvention.Winapi both to相同的最终结果)。

With the help of this article I found the answer: https://www.codeproject.com/Articles/66243/Marshaling-with-C-Chapter-3-Marshaling-Compound-Ty在这篇文章的帮助下,我找到了答案: https://www.codeproject.com/Articles/66243/Marshaling-with-C-Chapter-3-Marshaling-Compound-Ty

All I had to do was mark my C# NumberContainer struct with the StructLayoutAttribute like this, and the parameter values started coming in correctly!我所要做的就是用这样的StructLayoutAttribute标记我的 C# NumberContainer结构,然后参数值开始正确输入!

[StructLayout(LayoutKind.Sequential, Size = 64)]
private struct NumberContainer
{
    internal int Number1 { get; }
    internal uint Number2 { get; }

    internal NumberContainer(int number1, uint number2)
    {
        Number1 = number1;
        Number2 = number2;
    }

    public override string ToString()
        => $"{Number1}, {Number2}";
}

I believe what was happening before was the struct was being assumed to be 32-bit by C++ and thus the first parameter of -8 (a 32-bit signed integer) was making up the 2nd half of the default 64-bit NumberContainer struct on the C++ side.我相信之前发生的事情是结构被 C++ 假定为 32 位,因此-8的第一个参数(一个 32 位有符号整数)构成了默认 64 位NumberContainer结构的第二半C++ 侧。 However, having said this: if I change Size to 32 or 128 and do a rebuild, it weirdly continues to pass the correct parameter values in!但是,话虽如此:如果我将Size更改为32128并进行重建,它会奇怪地继续传递正确的参数值! If Size is omitted from the attribute though, then the parameter values come in incorrectly as before.但是,如果属性中省略了Size ,则参数值会像以前一样错误地输入。

I seem to have resolved the original problem and the immutable struct successfully instantiates and returns from the C++ code, but the aforementioned mystery about the Size parameter is still unsolved.我似乎已经解决了最初的问题,并且不可变结构成功实例化并从 C++ 代码返回,但上述关于Size参数的谜团仍未解开。 If someone has an answer to this then I'll happily accept it over my own.如果有人对此有答案,那么我会很乐意接受它而不是我自己的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM