首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C#应用程序调用C++方法,错误: PInvoke:无法返回变体

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

Stack Overflow用户
提问于 2013-06-16 20:21:06
回答 1查看 3.7K关注 0票数 4

我正在试图找出如何将复杂对象从C++ dll返回到调用的C#应用程序。我有一个简单的方法,就是返回一个运行良好的int。有人能告诉我我做错了什么吗?

C#应用程序:

代码语言:javascript
复制
class Program
{
    static void Main(string[] args)
    {
        // Error on this line: "PInvoke: Cannot return variants"
        var token = LexerInterop.next_token();
    }
}

C# LexerInterop代码:

代码语言:javascript
复制
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

代码语言:javascript
复制
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++ low.h

代码语言:javascript
复制
#include "Token.h"

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

C++ Token.h

代码语言:javascript
复制
#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;
    };
}

我确信这与令牌返回类型有关,但我找不到任何合适的信息。

如有任何协助或指示,敬请谅解!

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2013-06-16 20:43:23

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

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

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

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

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

最后,一个相当复杂的选项:您可以从您的token函数中将void*的地址返回为void*,然后创建许多导出的普通C函数,以便基于输入指针读取各种属性。当您完成C++对象时,您还需要一个函数来释放它。类似于:

代码语言:javascript
复制
__declspec(dllexport) TokenType WINAPI GetTokenType ( Kaleidoscope::Token* pToken )
{
    return ( pToken->Type );
}

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

代码语言:javascript
复制
[DllImport ( "MyDll.dll" )]
public static extern int GetTokenType ( UIntPtr pObj );

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

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

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

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

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

这个解决方案很简单,但是得到的DLL现在需要.NET运行时。

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/17137333

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档