雜物聚集地

c++與c#間的溝通與資料傳遞

Wed, 07 Feb 2018 02:55:29 GMT

目前的專案server端與client端間的共用邏輯是用C++寫的, 而Unity的主要使用程式碼又是c#, 所以有需要從c#呼叫c++端的方法, 以及從c++取資料, 這裡主要整理一下專案用的方法以及遇到的一些問題。

<!--more-->
  1. Windows是編成dll, android是編成.so, ios主要是編成.a

  2. 溝通方式有分兩種:

  3. 簡單的呼叫範例
    c++用的巨集

    #ifdef _MSC_VER #define PLUGIN_DLL_API __declspec(dllexport) #else #define PLUGIN_DLL_API #endif

    c++端的方法

    extern "C" { PLUGIN_DLL_API void Test_Func(int StartIdx, int Count, int* OutArray) { ... } };

    c#的lib名稱宣告

    #if (UNITY_IOS && !UNITY_EDITOR_OSX) || UNITY_XBOX360 // On iOS and Xbox 360 plugins are statically linked into // the executable, so we have to use **Internal as the // library name. public const string PlatformDllName = "__Internal"; #else public const string PlatformDllName = "Name"; // 不用加lib. #endif

    c#端方法宣告

    [DllImport(SysPluginBase.PlatformDllName)] private static extern void Test_Func(int StartIdx, int Count, System.IntPtr OutArray);

    要注意的有兩點:

    • 如果c++要傳指標給c#, c#需要用System.IntPtr來處理。
    • 兩邊的名稱必須一樣, 不然會有EntryPointNotFoundException
  4. 簡單的結構範例
    c++端

    class Data { public: int m_Int; // int char m_Str[32]; // 字串 int m_IntAry[10]; // int陣列 };

    c#端

    [StructLayout(LayoutKind.Sequential, Pack = 4)] [Serializable] public class Data { public int m_Int; // int [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string m_Str; // 字串 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] public int[] m_IntAry; // int陣列 };

    LayoutKind.Sequential主要是告知這個結構的記憶體是照順序排下來, 也可以宣告成 Explicit 來自己指定, 不過用 Sequential 是比較簡單的作法, 只要注意順序不要有錯誤就行了
    最基本的作法, 是傳該結構的指標到c#(IntPtr), 再使用Marshal.PtrToStructure來轉換成c#用的結構

    extern "C" { PLUGIN_DLL_API Data* Test_Func(){ ... } };

    在取IntPtr時需要檢查是否為null, 可以檢查該值是否為IntPtr.Zero

    [DllImport(SysPluginBase.PlatformDllName)] private static extern System.IntPtr Test_Func(); public Data GetData() { System.IntPtr DataPtr = Test_Func(); if (DataPtr == System.IntPtr.Zero) return null; Data Dta = (Data)Marshal.PtrToStructure(DataPtr, typeof(Data)); return Dta; }
  5. 除了最基本的Marshal.PtrToStructure方法以外, 還有幾種其他的方法可以取得結構的值, 由於實在太多程式碼用gist來存

  6. iOS要使用CallBack C++Plugin, MonoPInvokeCallback 是一定要加的
    https://developer.xamarin.com/guides/ios/advanced_topics/limitations/#Reverse_Callbacks
    原文節錄如下:

    When using the ahead-of-time compiler required by the iPhone there are two important limitations at this point:
    You must flag all of your callback methods with the MonoPInvokeCallbackAttribute
    The methods have to be static methods, there is no support for instance methods.

    除了要在方法上方加上[AOT.MonoPInvokeCallback(typeof(對應該方法的delegate))]外, 該方法還需要是static方法.
    PC端跟android不需要

  7. 在android上如果結構使用pack = 1 , 在加上該struct有float的話, c#在使用Marshal.PtrToStructure就會造成crash, 只有android會有這個問題, 該問題找不到確切造成的原因。

  8. 結構中不要使用bool, 因為無法保證他的位元長度, 最好的方法還是用byte之類的代替。

ref: https://www.mono-project.com/docs/advanced/pinvoke/