/* LinphoneWrapper.cs Copyright (C) 2017 Belledonne Communications SARL This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ using System; using System.Runtime.InteropServices; using System.Collections.Generic; #if __IOS__ using ObjCRuntime; #endif namespace Linphone { #region Wrapper specifics /// /// Only contains the LIB_NAME value that represents the library in which all DllImport are made /// public class LinphoneWrapper { #if __IOS__ public const string LIB_NAME = "linphone.framework/linphone"; #else public const string LIB_NAME = "linphone"; // With this, it automatically finds liblinphone.so #endif #if ANDROID [DllImport(LinphoneWrapper.LIB_NAME)] static extern void setAndroidLogHandler(); #endif #if __IOS__ [DllImport(LinphoneWrapper.LIB_NAME)] static extern void linphone_iphone_enable_logs(); #endif /// /// Registers the native log handler in Linphone. /// public static void setNativeLogHandler() { #if ANDROID setAndroidLogHandler(); #elif __IOS__ linphone_iphone_enable_logs(); #endif } } /// /// All methods that returns a LinphoneStatus with a value != 0 as an error code in C are translated in C# by throwing a LinphoneException /// [Serializable()] public class LinphoneException : System.Exception { public LinphoneException() : base() { } public LinphoneException(string message) : base(message) { } public LinphoneException(string message, System.Exception inner) : base(message, inner) { } protected LinphoneException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } [StructLayout(LayoutKind.Sequential)] /// /// Parent class for a Linphone public objects /// public class LinphoneObject { internal IntPtr nativePtr; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void OnLinphoneObjectDataDestroyed(IntPtr data); [DllImport(LinphoneWrapper.LIB_NAME)] static extern int belle_sip_object_data_set(IntPtr ptr, string name, IntPtr data, OnLinphoneObjectDataDestroyed cb); [DllImport(LinphoneWrapper.LIB_NAME)] static extern IntPtr belle_sip_object_data_get(IntPtr ptr, string name); [DllImport(LinphoneWrapper.LIB_NAME)] static extern IntPtr belle_sip_object_ref(IntPtr ptr); [DllImport(LinphoneWrapper.LIB_NAME)] static extern void belle_sip_object_unref(IntPtr ptr); [DllImport(LinphoneWrapper.LIB_NAME)] static extern IntPtr bctbx_list_next(IntPtr ptr); [DllImport(LinphoneWrapper.LIB_NAME)] static extern IntPtr bctbx_list_get_data(IntPtr ptr); [DllImport(LinphoneWrapper.LIB_NAME)] static extern IntPtr bctbx_list_append(IntPtr elem, string data); [DllImport(LinphoneWrapper.LIB_NAME)] static extern IntPtr bctbx_list_append(IntPtr elem, IntPtr data); #if __IOS__ [MonoPInvokeCallback(typeof(OnLinphoneObjectDataDestroyed))] #endif private static void onDataDestroyed(IntPtr data) { if (data != IntPtr.Zero) { //Console.WriteLine("Freeing C# handle"); GCHandle handle = GCHandle.FromIntPtr(data); handle.Free(); } } ~LinphoneObject() { //Console.WriteLine("Destroying " + this.ToString()); if (nativePtr != IntPtr.Zero) { //Console.WriteLine("Unreffing " + this.ToString()); belle_sip_object_unref(nativePtr); } } internal static T fromNativePtr(IntPtr ptr, bool takeRef=true) where T : LinphoneObject, new() { if (ptr == IntPtr.Zero) return null; IntPtr objPtr = belle_sip_object_data_get(ptr, "cs_obj"); if (objPtr != IntPtr.Zero) { T obj = null; GCHandle handle = GCHandle.FromIntPtr(objPtr); if (handle.IsAllocated) { obj = (T)handle.Target; } if (obj == null) { //Console.WriteLine("Handle target is null " + handle.Target); objPtr = IntPtr.Zero; } else { //Console.WriteLine("Using existing " + obj.ToString()); return obj; } } if (objPtr == IntPtr.Zero) { T obj = new T(); //Console.WriteLine("Creating " + obj.ToString()); if (takeRef) { ptr = belle_sip_object_ref(ptr); //Console.WriteLine("Reffing " + obj.ToString()); } obj.nativePtr = ptr; GCHandle handle = GCHandle.Alloc(obj, GCHandleType.WeakTrackResurrection); objPtr = GCHandle.ToIntPtr(handle); belle_sip_object_data_set(ptr, "cs_obj", objPtr, onDataDestroyed); return obj; } return null; } internal static IEnumerable MarshalStringArray(IntPtr arrayPtr) { if (arrayPtr != IntPtr.Zero) { IntPtr ptr = Marshal.ReadIntPtr(arrayPtr); while (ptr != IntPtr.Zero) { string key = Marshal.PtrToStringAnsi(ptr); yield return key; arrayPtr = new IntPtr(arrayPtr.ToInt64() + IntPtr.Size); ptr = Marshal.ReadIntPtr(arrayPtr); } } } internal static IEnumerable MarshalBctbxList(IntPtr listPtr) where T : LinphoneObject, new() { if (listPtr != IntPtr.Zero) { IntPtr ptr = listPtr; while (ptr != IntPtr.Zero) { IntPtr dataPtr = bctbx_list_get_data(ptr); if (dataPtr == IntPtr.Zero) { break; } T obj = fromNativePtr(dataPtr); yield return obj; ptr = bctbx_list_next(ptr); } } } internal static IntPtr StringArrayToBctbxList(IEnumerable stringlist) { IntPtr bctbx_list = IntPtr.Zero; foreach (string s in stringlist) { bctbx_list = bctbx_list_append(bctbx_list, s); } return bctbx_list; } internal static IntPtr ObjectArrayToBctbxList(IEnumerable objlist) where T : LinphoneObject, new() { IntPtr bctbx_list = IntPtr.Zero; foreach (T ptr in objlist) { bctbx_list = bctbx_list_append(bctbx_list, ptr.nativePtr); } return bctbx_list; } } #if ANDROID /// /// Methods that are only found in Android version of Linphone libraries and related to JNI /// public class LinphoneAndroid { [DllImport(LinphoneWrapper.LIB_NAME)] static extern void ms_set_jvm_from_env(IntPtr jnienv); [DllImport(LinphoneWrapper.LIB_NAME)] static extern void setMediastreamerAndroidContext(IntPtr jnienv, IntPtr context); /// /// Sets the JVM and JNI pointers in Linphone, required to be able to make JAVA upcalls. /// Calling this method is mandatory and must be done as soon as possible ! /// public static void setAndroidContext(IntPtr jnienv, IntPtr context) { ms_set_jvm_from_env(jnienv); setMediastreamerAndroidContext(jnienv, context); } } #endif #endregion #region Enums {{#enums}} {{#enum}} {{#doc}} {{#lines}} /// {{{line}}} {{/lines}} {{/doc}} public enum {{enumName}} { {{#values}} {{#doc}} {{#lines}} /// {{{line}}} {{/lines}} {{/doc}} {{name}} = {{value}}, {{/values}} } {{/enum}} {{/enums}} #endregion #region Listeners {{#interfaces}} {{#interface}} [StructLayout(LayoutKind.Sequential)] public class {{interfaceName}} : LinphoneObject { {{#methods}} [DllImport(LinphoneWrapper.LIB_NAME)] {{#cb_setter}} static extern void {{name}}(IntPtr thiz, {{name_private}} cb); {{/cb_setter}} {{#delegate}} [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void {{name_private}}({{params_private}}); public delegate void {{name_public}}({{params_public}}); private {{name_public}} {{var_public}}; #if __IOS__ [MonoPInvokeCallback(typeof({{name_private}}))] #endif private static void {{cb_name}}({{params_private}}) { {{interfaceClassName}} thiz = fromNativePtr<{{interfaceClassName}}>({{first_param}}); {{#isSimpleListener}}{{interfaceName}} listener = thiz.Listener;{{/isSimpleListener}} {{#isMultiListener}}{{interfaceName}} listener = thiz.CurrentCallbacks;{{/isMultiListener}} listener.{{var_public}}?.Invoke({{{params}}}); } public {{name_public}} {{name}} { get { return {{var_public}}; } set { {{var_public}} = value; {{c_name_setter}}(nativePtr, {{cb_name}}); } } {{/delegate}} {{/methods}} } {{/interface}} {{/interfaces}} #endregion #region Classes {{#classes}} {{#_class}} {{#doc}} {{#lines}} /// {{{line}}} {{/lines}} {{/doc}} [StructLayout(LayoutKind.Sequential)] public class {{className}} : LinphoneObject { {{#isLinphoneFactory}} [DllImport(LinphoneWrapper.LIB_NAME)] static extern IntPtr linphone_factory_create_core_cbs(IntPtr factory); public CoreListener CreateCoreListener() { IntPtr coreCbsPtr = linphone_factory_create_core_cbs(nativePtr); return fromNativePtr(coreCbsPtr, false); } {{/isLinphoneFactory}} {{#dllImports}} [DllImport(LinphoneWrapper.LIB_NAME)] {{{prototype}}} {{#has_second_prototype}} [DllImport(LinphoneWrapper.LIB_NAME)] {{second_prototype}} {{/has_second_prototype}} {{#has_property}} {{#doc}} {{#lines}} /// {{{line}}} {{/lines}} {{/doc}} {{property_static}}public {{{property_return}}} {{property_name}} { {{#has_getter}} get { {{#is_string}} IntPtr stringPtr = {{getter_c_name}}({{getter_nativePtr}}); return Marshal.PtrToStringAnsi(stringPtr); {{/is_string}} {{#is_bool}} return {{getter_c_name}}({{getter_nativePtr}}) == 0; {{/is_bool}} {{#is_class}} IntPtr ptr = {{getter_c_name}}({{getter_nativePtr}}); return fromNativePtr<{{return}}>(ptr, {{takeRef}}); {{/is_class}} {{#is_enum}} return {{getter_c_name}}({{getter_nativePtr}}); {{/is_enum}} {{#is_generic}} return {{getter_c_name}}({{getter_nativePtr}}); {{/is_generic}} {{#is_string_list}} return MarshalStringArray({{getter_c_name}}({{getter_nativePtr}})); {{/is_string_list}} {{#is_class_list}} return MarshalBctbxList<{{{list_type}}}>({{getter_c_name}}({{getter_nativePtr}})); {{/is_class_list}} } {{/has_getter}} {{#has_setter}} set { {{#is_string}} {{#exception}}int exception_result = {{/exception}}{{setter_c_name}}({{setter_nativePtr}}value); {{#exception}}if (exception_result != 0) throw new LinphoneException("{{property_name}} setter returned value " + exception_result);{{/exception}} {{/is_string}} {{#is_bool}} {{#exception}}int exception_result = {{/exception}}{{setter_c_name}}({{setter_nativePtr}}value ? 1 : 0); {{#exception}}if (exception_result != 0) throw new LinphoneException("{{property_name}} setter returned value " + exception_result);{{/exception}} {{/is_bool}} {{#is_class}} {{#exception}}int exception_result = {{/exception}}{{setter_c_name}}({{setter_nativePtr}}value.nativePtr); {{#exception}}if (exception_result != 0) throw new LinphoneException("{{property_name}} setter returned value " + exception_result);{{/exception}} {{/is_class}} {{#is_enum}} {{#exception}}int exception_result = {{/exception}}{{setter_c_name}}({{setter_nativePtr}}(int)value); {{#exception}}if (exception_result != 0) throw new LinphoneException("{{property_name}} setter returned value " + exception_result);{{/exception}} {{/is_enum}} {{#is_generic}} {{#exception}}int exception_result = {{/exception}}{{setter_c_name}}({{setter_nativePtr}}value); {{#exception}}if (exception_result != 0) throw new LinphoneException("{{property_name}} setter returned value " + exception_result);{{/exception}} {{/is_generic}} {{#is_string_list}} {{#exception}}int exception_result = {{/exception}}{{setter_c_name}}({{setter_nativePtr}}StringArrayToBctbxList(value)); {{#exception}}if (exception_result != 0) throw new LinphoneException("{{property_name}} setter returned value " + exception_result);{{/exception}} {{/is_string_list}} {{#is_class_list}} {{#exception}}int exception_result = {{/exception}}{{setter_c_name}}({{setter_nativePtr}}ObjectArrayToBctbxList<{{{list_type}}}>(value)); {{#exception}}if (exception_result != 0) throw new LinphoneException("{{property_name}} setter returned value " + exception_result);{{/exception}} {{/is_class_list}} } {{/has_setter}} } {{/has_property}} {{#has_impl}} {{#impl}} {{#doc}} {{#lines}} /// {{{line}}} {{/lines}} {{/doc}} public {{static}}{{override}}{{{type}}} {{name}}({{{args}}}) { {{#is_string}} IntPtr stringPtr = {{c_name}}({{nativePtr}}{{c_args}}); return Marshal.PtrToStringAnsi(stringPtr); {{/is_string}} {{#is_bool}} {{return}}{{c_name}}({{nativePtr}}{{c_args}}) == 0 ? false : true; {{/is_bool}} {{#is_class}} IntPtr ptr = {{c_name}}({{nativePtr}}{{c_args}}); return fromNativePtr<{{type}}>(ptr, {{takeRef}}); {{/is_class}} {{#is_enum}} {{#exception}}int exception_result = {{/exception}}{{return}}{{c_name}}({{nativePtr}}{{c_args}}); {{#exception}}if (exception_result != 0) throw new LinphoneException("{{name}} returned value " + exception_result);{{/exception}} {{/is_enum}} {{#is_generic}} {{#exception}}int exception_result = {{/exception}}{{return}}{{c_name}}({{nativePtr}}{{c_args}}); {{#exception}}if (exception_result != 0) throw new LinphoneException("{{name}} returned value " + exception_result);{{/exception}} {{/is_generic}} {{#is_string_list}} return MarshalStringArray({{c_name}}({{nativePtr}}{{c_args}})); {{/is_string_list}} {{#is_class_list}} return MarshalBctbxList<{{{list_type}}}>({{c_name}}({{nativePtr}}{{c_args}})); {{/is_class_list}} } {{/impl}} {{/has_impl}} {{/dllImports}} } {{/_class}} {{/classes}} #endregion }