C#应用程序调用C++;方法,错误:PInvoke:无法返回变量 我试图找出如何将复杂对象从C++ DLL返回到调用C语言应用程序。我有一个简单的方法,返回一个运行良好的int。谁能告诉我我做错了什么
C#应用: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
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++函数中返回__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之间做过太大的区别。:)