0

I found such answer on SO

https://stackoverflow.com/a/42987032/5709159

And there is my implementation

C# Unity

    [DllImport(m_pluginName, CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr /* char const * */ get_profiling_info(IntPtr native_stream_ptr);

string foo(){
            string prof_info;

            IntPtr pU = get_profiling_info(stream);
            if (pU == IntPtr.Zero)
            {
                prof_info = null;
            }
            else
            {
                prof_info = Marshal.PtrToStringAnsi(pU);
            }
    return prof_info;
}

C++

    DllExport char const * get_profiling_info(void * native_stream_ptr)
    {
        TV_DecoderStream * stream_decoder = reinterpret_cast<TV_DecoderStream *>(native_stream_ptr);
        assert(stream_decoder != nullptr);
        std::string result = stream_decoder->m_prof_txt;
        return result.c_str();
    }

and there is an error I get

2020-10-15 17:27:42.488 4105-4134/com.com.unityandroidplayer E/AndroidRuntime: FATAL EXCEPTION: UnityMain
    Process: com.com.unityandroidplayer, PID: 4105
    java.lang.Error: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
    Version '2020.1.8f1 (22e8c0b0c3ec)', Build type 'Development', Scripting Backend 'mono', CPU 'armeabi-v7a'
    Build fingerprint: 'google/taimen/taimen:11/RP1A.201005.004/6782484:user/release-keys'
    Revision: 'rev_10'
    ABI: 'arm'
    Timestamp: 2020-10-15 17:27:41+0300
    pid: 4105, tid: 4134, name: UnityMain  >>> com.com.unityandroidplayer <<<
    uid: 10036
    signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0xbc344000
        r0  9ab64640  r1  bc343ff5  r2  003c11e2  r3  00000034
        r4  be389120  r5  be39cf68  r6  be3b2e10  r7  bc33f9c1
        r8  00000000  r9  0000046c  r10 00000001  r11 bc33f8a0
        ip  a0000000  sp  bc33f880  lr  bc6b8cec  pc  eaecb1d8
    
    backtrace:
          #00 pc 000351d8  /apex/com.android.runtime/lib/bionic/libc.so (__memcpy_base_aligned_a9+144) (BuildId: 92e55abcc7bb795778c1353de856043e)
          #01 pc 001019bf  [anon:stack_and_tls:4134]
    
    managed backtrace:
          #00 (wrapper managed-to-native) object:__icall_wrapper_mono_string_from_bstr_icall (intptr)
          #01 (wrapper managed-to-native) comCAPI:get_profiling_info (intptr)
          #02 comPlayer:OnGUI () <D:\TV_repo\com_unity\TestControlller2\Assets\Scripts\comPlayer.cs:322>
          #03 (wrapper runtime-invoke) object:runtime_invoke_void__this__ (object,intptr,intptr,intptr)
    
        at libc.__memcpy_base_aligned_a9(__memcpy_base_aligned_a9:144)
        at [anon:stack_and_tls:4134].0x1019bf(Native Method)
        at System.Object.__icall_wrapper_mono_string_from_bstr_icall (intptr)(Native Method)
        at comCAPI.get_profiling_info (intptr)(Native Method)
        at comPlayer.OnGUI ()(D:\TV_repo\com_unity\TestControlller2\Assets\Scripts\comPlayer.cs:322)
        at System.Object.runtime_invoke_void__this__ (object,intptr,intptr,intptr)(Native Method)

What am I doing wrong?

Sirop4ik
  • 4,543
  • 2
  • 54
  • 121
  • Have you tried [this](https://answers.unity.com/questions/142150/passing-strings-to-and-from-a-c-dll.html) or [this](https://forum.unity.com/threads/c-plugin-getting-a-string-back-from-a-function.123594/)? What is `stream` in `IntPtr pU = get_profiling_info(stream);`? – derHugo Oct 16 '20 at 06:59

2 Answers2

1

result goes out of scope. Then, the pointer returned from c_str() is no longer valid. A quick fix is to use a static string:

static std::string result = ...

But a more robust solution involves allocation/deallocation of a temporary char buffer. Or perhaps returning m_prof_txt directly? Not sure what that variable is though.

l33t
  • 18,692
  • 16
  • 103
  • 180
1

It's been a while since I've done any native/managed interop, but I may be able to help you out.

To start with, in your native code, do this:

#define DLLEXPORT __declspec(dllexport)

extern "C" {
     DLLEXPORT const char* get_profiling_info( void* native_stream_ptr )
    {
        TV_DecoderStream * stream_decoder = reinterpret_cast<TV_DecoderStream*>(native_stream_ptr);
        assert(stream_decoder != nullptr);
        std::string result = stream_decoder->m_prof_txt;
        return result.c_str();
    }
}
[DllImport(m_pluginName, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string get_profiling_info( IntPtr native_stream_ptr );

That will probably do it! Untested but I'm pretty sure that will work unless you have some other problems which we'll get to in a moment. You just have to make sure you're specifying how you want things to be marshaled by the interop layer. I'll provide you with some official documentation on these things that explains all the details and how to use it ...

You'll find the System.Runtime.InteropServices.Marshal class very useful in managed <-> native code interop situations: System.Runtime.InteropServices.Marshal class For example, if your public static extern method in C# maintained IntPtr as the return type (which is possible to do) you could use a method in the Marshal class to convert that pointer into a valid .NET/Mono string object. You can do that with all kinds of stuff, and once you get the hang of it you can even start creating custom marshaling rules for your own classes and structs and using them back and forth. You can also do cool things like convert C/C++ function pointers into delegates and pass them around in C# code! I used to have a little C# DLL I wrote that could load native DLLs at runtime, get function pointers from them, convert them to delegates and then use them all over the place like they were integrated into the C# project. Interop can be kind of tricky but very interesting.

Also, read up on the MarshalAsAttribute: MarshalAsAttribute in C#

You can see a whole list of the UnmanagedType enum here: UnmanagedType enum documentation

Another tip is that, where possible, I'd try to use std::wstring when I'm passing strings back and forth between C# and C++ code. C# is using 16-bit Unicode characters, and std::string uses standard ASCII and UTF-8 characters so they don't natively match or map to one another and always require explicit marshaling. I think the interop layer can actually convert wchar_t* and wstrings over without being told, but I'm not 100% certain about that. I'd just read about it or try it and see what happens.


Another important issue I can think of for interop is that you (at least you used to) have to explicitly select a target platform for the C# assembly that also matches the native module you're trying to interoperate with, and they both must be compatible with the target device's CPU architecture. The "Any CPU" target platform simply wouldn't work. So if the C++ code was compiled for x86, you had to make the .NET/Mono build for x86, and if one was x64 the other had to be x64. Same thing with other CPU architectures (ARM, IA-64, etc). One of the major reasons for this is because these architectures may have different native pointer (address) widths and memory models, may use opposing endianness (e.g., Big Endian vs Little) and can have a number of fundamental differences in the way they use data and memory. So when some binary data tries to go from the managed to the unmanaged realm (or vice-versa) the interop layer didn't even know what to actually do with it or how to marshal it.

In this case, it looks like you're trying to run this on an Android device, and you're targeting the armeabi-v7a architecture. So the first step is making sure you're actually compiling your C++ code for this platform. I haven't written any C++ code for an Android device before (only C#/Xamarin), but there could also be some additional dependencies or build settings required. You'd need to check the documentation to find out for sure. In any case, your C++ code must be compiled in such a way that it's compatible with the device/CPU you're targeting or it simply will not work. And the C# assembly will most likely still need to be compiled explicitly for that same architecture.

NOTE: I haven't done this in a project in years, so that information may or may be out of date at this time. It's possible the .NET Framework can now do this automatically for you. However, I suspect this is still an important issue and you need to check the target platforms for builds on both of these assemblies and make sure they're compiled for the same CPU architecture and have matching instruction sets. In any case, it can't hurt to have them matching the target CPU architecture and each other!

  • NOTE: Also, I did not check your C++ function for correctness. I'm not even sure what that is you're trying to do, but just looks like you're trying to fetch a string from some sort of device data in memory. As someone else pointed out, we can't be sure about the scope of your variable `result`. You should attach your debugger and investigate what's going on if you're still having problems. You may need to use `std::string* result = new std::string(stream_decoder->m_prof_txt);` to allocate it on the heap before you return `result->cstr();` to your C# code. If it lived on the stack it's dead. – Aaron Carter Feb 14 '22 at 12:21