繁体   English   中英

如何将字符串从 C++ native 传递给 C# Unity?

[英]How pass string from C++ native to C# Unity?

我在 SO 上找到了这样的答案

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

还有我的实现

C# 团结

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

我得到了一个错误

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)

我究竟做错了什么?

result超出范围。 然后,从c_str()返回的指针不再有效。 快速解决方法是使用static字符串:

static std::string result = ...

但更强大的解决方案涉及临时字符缓冲区的分配/解除分配。 或者直接返回m_prof_txt 不确定那个变量是什么。

自从我完成任何本机/托管互操作以来已经有一段时间了,但我可以帮助你。

首先,在您的本机代码中执行以下操作:

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

这可能会做到这一点。 未经测试,但我很确定这会起作用,除非您有其他问题,我们稍后会解决。 您只需要确保指定您希望互操作层如何对事物进行编组。 我将为您提供一些关于这些东西的官方文档,解释所有细节以及如何使用它......

您会发现 System.Runtime.InteropServices.Marshal class 在托管 <-> 本机代码互操作情况下非常有用: System.Runtime.InteropServices.Marshal class例如,如果 C# 中的公共 static extern 方法将 IntPtr 维护为返回类型(这是可能的)你可以使用 Marshal class 中的方法将该指针转换为有效的 .NET/Mono 字符串 object。你可以用各种各样的东西来做,一旦你掌握了它,你就可以甚至开始为您自己的类和结构创建自定义编组规则,并来回使用它们。 您还可以做一些很酷的事情,例如将 C/C++ function 指针转换为委托并在 C# 代码中传递它们,我曾经写过一些 C# DLL 可以在运行时加载本机 DLL,从中获取 function 指针。 将它们转换为委托,然后在所有地方使用它们,就像它们被集成到 C# 项目中一样。 Interop 可能有点棘手但非常有趣。

另外,阅读 MarshalAsAttribute: C# 中的 MarshalAsAttribute

您可以在此处查看 UnmanagedType 枚举的完整列表: UnmanagedType enum 文档

另一个提示是,在可能的情况下,当我在 C# 和 C++ 代码之间来回传递字符串时,我会尝试使用 std::wstring。 C# 使用 16 位 Unicode 字符,而 std::string 使用标准 ASCII 和 UTF-8 字符,因此它们本身不会相互匹配或 map 并且始终需要显式编组。 我认为互操作层实际上可以在不被告知的情况下转换 wchar_t* 和 wstrings,但我对此不是 100% 确定。 我只是阅读它或尝试一下,看看会发生什么。


对于互操作,我能想到的另一个重要问题是你(至少你曾经)必须明确地select C# 程序集的目标平台也匹配你试图与之互操作的本机模块,并且它们都必须兼容与目标设备的 CPU 架构。 “任何 CPU”目标平台根本行不通。 因此,如果 C++ 代码是为 x86 编译的,则必须为 x86 构建 .NET/Mono,如果一个是 x64,另一个必须是 x64。 其他 CPU 架构(ARM、IA-64 等)也是如此。 造成这种情况的主要原因之一是因为这些体系结构可能具有不同的本机指针(地址)宽度和 memory 模型,可能使用相反的字节序(例如,Big Endian 与 Little)并且它们使用数据的方式可能存在许多根本差异和 memory。因此,当某些二进制数据尝试将 go 从托管数据传输到非托管数据 realm(反之亦然)时,互操作层甚至不知道如何实际处理它或如何编组它。

在这种情况下,看起来您正在尝试在 Android 设备上运行它,并且您的目标是armeabi-v7a架构。 因此,第一步是确保您实际上是在为该平台编译 C++ 代码。 我之前没有为 Android 设备编写任何 C++ 代码(仅 C#/Xamarin),但可能还需要一些额外的依赖项或构建设置。 您需要查看文档才能确定。 在任何情况下,您的 C++ 代码必须以与您的目标设备/CPU 兼容的方式编译,否则它根本无法工作。 并且 C# 程序集很可能仍需要针对相同的体系结构进行显式编译。

注意:我已经很多年没有在项目中这样做过,所以这些信息目前可能已经过时,也可能已经过时了。 .NET 框架现在可以自动为您执行此操作。 但是,我怀疑这仍然是一个重要问题,您需要检查目标平台以在这两个程序集上构建,并确保它们针对相同的 CPU 架构编译并具有匹配的指令集。 在任何情况下,让它们与目标 CPU 体系结构和彼此匹配都不会有什么坏处!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM