C # Application Calling C ++ Method, error: PInvoke: cannot return options

I am trying to figure out how to return a complex object from a C ++ library to a calling C # application. I have a simple method that returns an int that works fine. Can someone tell me what I am doing wrong?

C # application:

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

C # LexerInterop Code:

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

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

I am sure that this has something to do with the type of token return, but I can not find decent information about what is missing.

Any help or direction is appreciated!

+4
source share
1 answer

You cannot return C ++ objects for non-C ++ subscribers. (This does not even work when you try to export a C ++ object to the calling object if it is compiled with another C ++ compiler.) The reason is that the actual layout for the C ++ object is not standardized and can be executed by different compilers, it differently.

The .NET runtime has no idea how to deal with your object, and it does not know about the different types that make up your token . Likewise, std::string does not make sense for .NET, since it is a C ++ type that relies on unmanaged memory allocation and with a different internal string format and different semantics than C #.

You can try turning your C ++ objects into COM objects, and then you can return the COM interfaces to your C # caller. This will require some work, as COM types are again incompatible with C ++ types (for the same reasons as above).

You can also try serializing your C ++ object into an array of bytes, and then just return the buffer / length in C #, which first deserializes the object in the .NET view. You will have to duplicate the classes you are trying to handle in this way.

Another possibility is that you return all data as independent out parameters from your function calls, that is, you are not trying to return token , but return each of its properties as a separate out parameter from C ++. You still have to convert some of your data types into something that .NET recognizes. ( std::string cannot be returned, you will have to copy it to an array of bytes or select it as BSTR , etc.).

Finally, a pretty attractive option: you can return the token address as void* from your C ++ function, and then create some exported simple C functions to read various properties based on the input pointer. You will also need a function to free a C ++ object when you are done with it. Sort of:

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

Then you declare these functions in C # as follows:

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

The UIntPtr instance will be initialized from void* returned by your next_token function. Then you call each of the exported property reader functions to get individual token properties.

I would probably go with a special serializer, because this is the least amount of work (in my opinion), and I consider this to be the cleanest solution (in terms of readability and maintainability).

Note: using COM objects may be less, but you will have to move away from any type of C ++ (at least in the open interface).

Edit: I forgot to mention the somewhat obvious option earlier - using mixed code and C ++ / CLI. This way you can have unmanaged code and managed code in the same DLL at the same time, and you can perform all the transformations in C ++ code. If you define most of your classes in C ++ / CLI, you can simply pass the appropriate (managed) instances to your C # application without further conversion.

(Of course, you still have to convert between std:string and System.String if you use the first, but at least you can use all your conversion logic in the same project.)

This solution is simple, but the resulting DLL now requires the .NET runtime.

+5
source

Source: https://habr.com/ru/post/1486507/


All Articles