C#应用程序调用C++方法,错误:PInvoke:无法返回变体

Nat*_*222 4 c# c++ pinvoke interop

我试图找出如何将复杂对象从C++ DLL返回到调用C#应用程序.我有一个简单的方法,返回一个工作正常的int.谁能告诉我我做错了什么?

C#应用:

class Program
{
    static void Main(string[] args)
    {
        // Error on this line: "PInvoke: Cannot return variants"
        var token = LexerInterop.next_token();
    }
}
Run Code Online (Sandbox Code Playgroud)

C#LexerInterop代码:

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);
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

C++ Lexer.h

#include "Token.h"

namespace Kaleidoscope
{
    class Lexer
    {
    public:
        int get_int();
        Token get_token();
    };
}
Run Code Online (Sandbox Code Playgroud)

C++ Token.h

#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;
    };
}
Run Code Online (Sandbox Code Playgroud)

我确定这与令牌返回类型有关,但我找不到任何不错的信息.

任何帮助或方向表示赞赏!

xxb*_*bcc 6

您不能将C++对象返回给非C++调用者.(当您尝试将C++对象导出到调用者时,如果使用不同的C++编译器进行编译,它甚至不起作用.)原因是C++对象的实际内存中布局没有标准化,不同的编译器可能会这样做它不同.

.NET运行时不知道如何处理您的对象,它不知道构成token对象的各种类型.同样,std::string对.NET没有任何意义,因为它是一种C++类型,依赖于非托管内存分配和不同的内部字符串格式以及与C#不同的语义.

您可以尝试将C++对象转换为COM对象,然后将COM接口返回给C#调用者.这将需要一些工作,因为COM类型再次与C++类型不兼容(出于上述类似的原因).

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

另一种可能性是,您将所有数据作为out函数调用的独立参数返回- 也就是说,您不会尝试返回,token而是将其每个属性作为outC++中的单独参数返回.您仍然需要将一些数据类型转换为.NET可识别的内容.(std::string无法返回,您必须将其复制到字节数组或将其分配为BSTR等等)

最后,一个相当复杂的选项:您可以从C++函数返回token作为a 的地址,void*然后创建一些导出的普通C函数,以根据输入指针读取各种属性.当你完成它时,你还需要一个释放C++对象的函数.就像是:

__declspec(dllexport) TokenType WINAPI GetTokenType ( Kaleidoscope::Token* pToken )
{
    return ( pToken->Type );
}
Run Code Online (Sandbox Code Playgroud)

然后,您将在C#中声明这些函数,如下所示:

[DllImport ( "MyDll.dll" )]
public static extern int GetTokenType ( UIntPtr pObj );
Run Code Online (Sandbox Code Playgroud)

UIntPtr实例将从void*您的next_token函数返回的初始化.然后,您将调用每个导出的属性读取器函数以获取其各个属性token.

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

注意:使用COM对象可能会减少工作量,但您必须远离任何C++类型(至少在公共接口中).

编辑: 我之前忘了提一个有点明显的选择 - 使用混合代码和C++/CLI.这样,您可以在同一DLL中同时拥有非托管代码和托管代码,并且可以使用C++代码执行所有转换.如果您在C++/CLI中定义了大多数类,则只需将相关(托管)实例传递给C#应用程序而无需额外转换.

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

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