4

我试图弄清楚如何将复杂对象从 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();
    }
}

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);
}

C++ 词法分析器.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++ 词法分析器.h

#include "Token.h"

namespace Kaleidoscope
{
    class Lexer
    {
    public:
        int get_int();
        Token get_token();
    };
}

C++ 令牌.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;
    };
}

我确定这与 Token 返回类型有关,但我找不到任何像样的信息丢失了什么。

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

4

1 回答 1

6

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

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

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

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

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

最后,一个相当复杂的选项:您可以从您的 C++ 函数中返回tokena的地址,void*然后创建许多导出的普通 C 函数,以根据输入指针读取各种属性。完成后,您还需要一个函数来释放 C++ 对象。就像是:

__declspec(dllexport) TokenType WINAPI GetTokenType ( Kaleidoscope::Token* pToken )
{
    return ( pToken->Type );
}

然后,您可以像这样在 C# 中声明这些函数:

[DllImport ( "MyDll.dll" )]
public static extern int GetTokenType ( UIntPtr pObj );

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

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

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

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

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

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

于 2013-06-16T20:43:23.343 回答