C#应用程序调用C++;方法,错误:PInvoke:无法返回变量 我试图找出如何将复杂对象从C++ DLL返回到调用C语言应用程序。我有一个简单的方法,返回一个运行良好的int。谁能告诉我我做错了什么

C#应用程序调用C++;方法,错误:PInvoke:无法返回变量 我试图找出如何将复杂对象从C++ DLL返回到调用C语言应用程序。我有一个简单的方法,返回一个运行良好的int。谁能告诉我我做错了什么,c#,c++,interop,pinvoke,C#,C++,Interop,Pinvoke,C#应用: class Program { static void Main(string[] args) { // Error on this line: "PInvoke: Cannot return variants" var token = LexerInterop.next_token(); } } public class LexerInterop { [DllImport("Lexer.dll")] publ

C#应用:

class Program
{
    static void Main(string[] args)
    {
        // Error on this line: "PInvoke: Cannot return variants"
        var token = LexerInterop.next_token();
    }
}
public class LexerInterop
{
    [DllImport("Lexer.dll")]
    public static extern object next_token();

    [DllImport("Lexer.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int get_int(int i);
}
C#LexerInterop代码:

class Program
{
    static void Main(string[] args)
    {
        // Error on this line: "PInvoke: Cannot return variants"
        var token = LexerInterop.next_token();
    }
}
public class LexerInterop
{
    [DllImport("Lexer.dll")]
    public static extern object next_token();

    [DllImport("Lexer.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int get_int(int i);
}
C++Lexer.cpp

extern "C" __declspec(dllexport) Kaleidoscope::Token get_token()
{
    int lastChar = ' ';
    Kaleidoscope::Token token = Kaleidoscope::Token();

    while(isspace(lastChar))
    {
        lastChar = getchar();
    }

    ... Remainder of method body removed ...

    return token;
}

extern "C" __declspec(dllexport) int get_int(int i)
{
    return i * 2;
}
C++Lexer.h

#include "Token.h"

namespace Kaleidoscope
{
    class Lexer
    {
    public:
        int get_int();
        Token get_token();
    };
}
#include <string>

namespace Kaleidoscope
{
    enum TokenType
    {
        tok_eof = -1,
        tok_def = -2,
        tok_extern = -3,
        tok_identifier = -4,
        tok_number = -5
    };

    class Token
    {
    public:
        TokenType Type;
        std::string IdentifierString;
        double NumberValue;
    };
}
C++令牌.h

#include "Token.h"

namespace Kaleidoscope
{
    class Lexer
    {
    public:
        int get_int();
        Token get_token();
    };
}
#include <string>

namespace Kaleidoscope
{
    enum TokenType
    {
        tok_eof = -1,
        tok_def = -2,
        tok_extern = -3,
        tok_identifier = -4,
        tok_number = -5
    };

    class Token
    {
    public:
        TokenType Type;
        std::string IdentifierString;
        double NumberValue;
    };
}
#包括
名称空间万花筒
{
枚举标记类型
{
tok_eof=-1,
tok_def=-2,
tok_extern=-3,
tok_标识符=-4,
tok_数=-5
};
类令牌
{
公众:
标记类型;
std::字符串标识符字符串;
双数值;
};
}
我确信这与令牌返回类型有关,但我找不到任何适当的信息,即缺少什么


任何帮助或指导都将不胜感激

< P>不能将C++对象返回到非C++调用方。(如果尝试用不同的C++编译器编译一个C++对象,它甚至不工作)。原因是C++对象的实际内存布局不规范,不同编译器可能会有不同的处理。 NET运行时不知道如何处理您的对象,也不知道组成
令牌
对象的各种类型。类似地,<>代码> STD::String 对.NET没有意义,因为它是C++类型,依赖于非托管内存分配,并且具有不同的内部字符串格式和不同于C的语义。

您可以尝试将C++对象转换为COM对象,然后可以将COM接口返回到C语言调用方。这将需要一些工作,因为COM类型又与C++类型不兼容(因为上面的原因类似)。

您还可以尝试将C++对象序列化为字节数组,然后只返回缓冲区/长度,将首先将对象反序列化为.NET表示形式。您必须复制您试图以这种方式处理的类

另一种可能是,您从函数调用中返回所有数据作为独立的<>代码> Outs< /Cux>参数,即,您不试图返回<代码>令牌< /C> >,但将其每个属性作为独立的<代码> Outs> /Cux>参数从C++返回。您仍然需要将某些数据类型转换为.NET可以识别的数据类型。(

std::string
无法返回,您必须将其复制到字节数组或将其分配为
BSTR
等。)

最后,一个相当有牵连的选项:您可以从C++函数中返回 ToeG/<代码>的地址作为<代码> Value*/Cuff>,然后创建一些导出的C函数,以根据输入指针读取各种属性。当你完成C++对象时,你也需要一个函数来释放它。比如:

__declspec(dllexport) TokenType WINAPI GetTokenType ( Kaleidoscope::Token* pToken )
{
    return ( pToken->Type );
}
然后用C#声明这些函数,如下所示:

uintpttr
实例将从
void*
函数返回的
next\u令牌初始化。然后调用每个导出的属性读取器函数来获取
令牌的各个属性

我可能会选择自定义序列化程序,因为这是最少的工作量(在我看来),而且我认为它是最干净的解决方案(在可读性和可维护性方面)

<>注意:使用COM对象可能会减少工作量,但是必须远离任何C++类型(至少在公共接口中)。 编辑: 我忘了在前面提到一个比较明显的选项——使用混合代码和C++/CLI。这样,您可以在同一时间内同时拥有非托管代码和托管代码,并且可以执行C++代码中的所有转换。如果您在C++/CLI中定义了大多数类,则只需将相关(托管)实例传递给C#应用程序,而无需进行额外转换

(当然,如果使用前者,您仍然需要在
std:string
System.string
之间进行转换,但至少可以在同一个项目中使用所有转换逻辑。)


此解决方案很简单,但生成的DLL现在需要.NET运行时。

非常好。谢谢你的快速回复。您是否知道在线上的任何一个例子,它概述了C++对象的序列化?不管怎样,谢谢。@nDev不是我脑子里想出来的-我认为它不能自动化,但我可能错了。我只需要自己编写(你需要一个C++中的序列化程序和C语言中的一个反序列化器),它实际上只是在写一个字节代码数组,然后以相同的顺序读取它们。我意识到我忘了提到使用混合代码DLL作为选项之一,所以我更新了答案。@DavidHeffernan是的,你是对的,这就是为什么我用小写字母“M”编写“managed”。不管怎样,我从来没有在它和C++/CLI之间做过太大的区别。:)