Example #1
0
        public XlParameterInfo ReturnType;         // Macro will have ReturnType null

        // THROWS: Throws a DnaMarshalException if the method cannot be turned into an XlMethodInfo
        // TODO: Manage errors if things go wrong
        private XlMethodInfo(MethodInfo targetMethod, ModuleBuilder modBuilder)
        {
            // Default Name, Description and Category
            Name            = targetMethod.Name;
            Description     = "";
            Category        = IntegrationHelpers.DnaLibraryGetName();
            HelpTopic       = "";
            IsVolatile      = false;
            IsExceptionSafe = false;
            IsHidden        = false;
            IsMacroType     = false;
            IsThreadSafe    = false;

            ShortCut = "";
            // DOCUMENT: Default MenuName is the library name
            // but menu is only added if at least the MenuText is set.
            MenuName = IntegrationHelpers.DnaLibraryGetName();
            MenuText = null;                    // Menu is only

            SetAttributeInfo(targetMethod.GetCustomAttributes(false));

            // Return type conversion
            if (targetMethod.ReturnType == typeof(void))
            {
                IsCommand  = true;
                ReturnType = null;
                // HACK: Check that there are no other parameters
                // Does Excel allow macros with parameters?
                if (targetMethod.GetParameters().Length > 0)
                {
                    throw new DnaMarshalException("Macros with parameters are not supported.");
                }
            }
            else
            {
                IsCommand  = false;
                ReturnType = new XlParameterInfo(targetMethod.ReturnType, true, IsExceptionSafe);
            }

            // Parameters - meta-data and type conversion
            Parameters =
                Array.ConvertAll <ParameterInfo, XlParameterInfo>(
                    targetMethod.GetParameters(),
                    delegate(ParameterInfo pi) { return(new XlParameterInfo(pi)); });

            // Create the delegate type, wrap the targetMethod and create the delegate
            Type     delegateType = CreateDelegateType(modBuilder);
            Delegate xlDelegate   = CreateMethodDelegate(targetMethod, delegateType);

            // Need to add a reference to prevent garbage collection of our delegate
            // Don't need to pin, according to
            // "How to: Marshal Callbacks and Delegates Using C++ Interop"
            // Currently this delegate is never released
            // TODO: Clean up properly
            DelegateHandle  = GCHandle.Alloc(xlDelegate);
            FunctionPointer = Marshal.GetFunctionPointerForDelegate(xlDelegate);
        }
Example #2
0
        private static object[] GetRegisterParameters(XlMethodInfo mi, string exportedProcName)
        {
            string functionType;

            if (mi.ReturnType != null)
            {
                functionType = mi.ReturnType.XlType;
            }
            else
            {
                if (mi.Parameters.Length == 0)
                {
                    functionType = "";  // OK since no other types will be added
                }
                else
                {
                    // This case is also be used for native async functions
                    functionType = ">"; // Use the void / inplace indicator if needed.
                }
            }

            string argumentNames    = "";
            bool   showDescriptions = false;
            // For async functions, we need to leave off the last argument
            int numArgumentDescriptions = mi.IsExcelAsyncFunction ? mi.Parameters.Length - 1 : mi.Parameters.Length;

            string[] argumentDescriptions = new string[numArgumentDescriptions];

            for (int j = 0; j < numArgumentDescriptions; j++)
            {
                XlParameterInfo pi = mi.Parameters[j];

                functionType += pi.XlType;

                if (j > 0)
                {
                    argumentNames += ",";   // TODO: Should this be a comma, or the Excel list separator?
                }
                argumentNames          += pi.Name;
                argumentDescriptions[j] = pi.Description;

                if (pi.Description != "")
                {
                    showDescriptions = true;
                }

                // DOCUMENT: Truncate the argument description if it exceeds the Excel limit of 255 characters
                if (j < mi.Parameters.Length - 1)
                {
                    if (!string.IsNullOrEmpty(argumentDescriptions[j]) &&
                        argumentDescriptions[j].Length > 255)
                    {
                        argumentDescriptions[j] = argumentDescriptions[j].Substring(0, 255);
                        Logger.Registration.Warn("Truncated argument description of '{0}' in function '{1}'", pi.Name, mi.Name);
                    }
                }
                else
                {
                    // Last argument - need to deal with extra ". "
                    if (!string.IsNullOrEmpty(argumentDescriptions[j]))
                    {
                        if (argumentDescriptions[j].Length > 253)
                        {
                            argumentDescriptions[j] = argumentDescriptions[j].Substring(0, 253);
                            Logger.Registration.Warn("Truncated final argument description of function '{0}'", mi.Name);
                        }

                        // DOCUMENT: Here is the patch for the Excel Function Description bug.
                        // DOCUMENT: Ensure the ". " suffix exists on the last parameter.
                        if (!argumentDescriptions[j].EndsWith(". "))
                        {
                            argumentDescriptions[j] += argumentDescriptions[j].EndsWith(".") ? " " : ". ";
                        }
                    }
                }
            } // for each parameter

            // Add async handle
            if (mi.IsExcelAsyncFunction)
            {
                functionType += "X"; // mi.Parameters[mi.Parameters.Length - 1].XlType should be "X" anyway
            }
            // Native async functions cannot be cluster safe
            if (mi.IsClusterSafe && ProcessHelper.SupportsClusterSafe && !mi.IsExcelAsyncFunction)
            {
                functionType += "&";
            }

            if (mi.IsMacroType)
            {
                functionType += "#";
            }

            if (!mi.IsMacroType && mi.IsThreadSafe && XlAddIn.XlCallVersion >= 12)
            {
                functionType += "$";
            }

            if (mi.IsVolatile)
            {
                functionType += "!";
            }
            // DOCUMENT: If # is set and there is an R argument, Excel considers the function volatile anyway.
            // You can call xlfVolatile, false in beginning of function to clear.

            // DOCUMENT: Truncate Description to 253 characters (for all versions)
            string functionDescription = Truncate(mi.Description, 253, "function description", mi);

            // DOCUMENT: Here is the patch for the Excel Function Description bug.
            // DOCUMENT: Ensure the ". " suffix exists if the function takes no parameters and has a description.
            if (mi.Parameters.Length == 0 && functionDescription != "" && !functionDescription.EndsWith(". "))
            {
                functionDescription += functionDescription.EndsWith(".") ? " " : ". ";
            }

            // DOCUMENT: When there is no description, we don't add any.
            // This allows the user to work around the Excel bug where an extra parameter is displayed if
            // the function has no parameter but displays a description
            if (mi.Description != "")
            {
                showDescriptions = true;
            }

            int numRegisterParameters;
            // DOCUMENT: Maximum 20 Argument Descriptions when registering using Excel4 function.
            int maxDescriptions = (XlAddIn.XlCallVersion < 12) ? 20 : 245;

            if (showDescriptions)
            {
                numArgumentDescriptions = Math.Min(numArgumentDescriptions, maxDescriptions);
                numRegisterParameters   = 10 + numArgumentDescriptions;  // function description + arg descriptions
            }
            else
            {
                // Won't be showing any descriptions.
                numArgumentDescriptions = 0;
                numRegisterParameters   = 9;
            }

            // DOCUMENT: Additional truncations of registration info - registration fails with strings longer than 255 chars.
            argumentNames = Truncate(argumentNames, 255, "argument names", mi);
            argumentNames = argumentNames.TrimEnd(','); // Also trim trailing commas (for params case)
            string category  = Truncate(mi.Category, 255, "Category name", mi);
            string name      = Truncate(mi.Name, 255, "Name", mi);
            string helpTopic = string.Empty;

            if (mi.HelpTopic != null)
            {
                if (mi.HelpTopic.Length > 255)
                {
                    // Can't safely truncate the help link
                    Logger.Registration.Warn("Ignoring HelpTopic of function '{0}' - too long", mi.Name);
                }
                else
                {
                    // It's OK
                    helpTopic = mi.HelpTopic;
                }
            }

            object[] registerParameters = new object[numRegisterParameters];
            registerParameters[0] = XlAddIn.PathXll;
            registerParameters[1] = exportedProcName;
            registerParameters[2] = functionType;
            registerParameters[3] = name;
            registerParameters[4] = argumentNames;
            registerParameters[5] = mi.IsCommand ? 2                      /*macro*/
                                                 : (mi.IsHidden ? 0 : 1); /*function*/
            registerParameters[6] = category;
            registerParameters[7] = mi.ShortCut;                          /*shortcut_text*/
            registerParameters[8] = helpTopic;                            /*help_topic*/

            if (showDescriptions)
            {
                registerParameters[9] = functionDescription;

                for (int k = 0; k < numArgumentDescriptions; k++)
                {
                    registerParameters[10 + k] = argumentDescriptions[k];
                }
            }

            return(registerParameters);
        }
Example #3
0
        public XlParameterInfo ReturnType; // Macro will have ReturnType null (as will native async functions)

        // THROWS: Throws a DnaMarshalException if the method cannot be turned into an XlMethodInfo
        // TODO: Manage errors if things go wrong
        XlMethodInfo(ModuleBuilder modBuilder, MethodInfo targetMethod, object target, object methodAttribute, List<object> argumentAttributes)
        {
            // Default Name, Description and Category
            Name = targetMethod.Name;
            Description = "";
            Category = IntegrationHelpers.DnaLibraryGetName();
            HelpTopic = "";
            IsVolatile = false;
            IsExceptionSafe = false;
            IsHidden = false;
            IsMacroType = false;
            IsThreadSafe = false;
            IsClusterSafe = false;
            ExplicitRegistration = false;

            ShortCut = "";
            // DOCUMENT: Default MenuName is the library name
            // but menu is only added if at least the MenuText is set.
            MenuName = IntegrationHelpers.DnaLibraryGetName();
            MenuText = null; // Menu is only 

            // Set default IsCommand - overridden by having an [ExcelCommand] attribute,
            // or by being a native async function.
            // (Must be done before SetAttributeInfo)
            IsCommand = (targetMethod.ReturnType == typeof(void));

            SetAttributeInfo(methodAttribute);
            // We shortcut the rest of the registration
            if (ExplicitRegistration) return;

            FixHelpTopic();

            // Return type conversion
            // Careful here - native async functions also return void
            if (targetMethod.ReturnType == typeof(void))
            {
                ReturnType = null;
            }
            else
            {
                ReturnType = new XlParameterInfo(targetMethod.ReturnType, true, IsExceptionSafe);
            }

            ParameterInfo[] parameters = targetMethod.GetParameters();
            
            // Parameters - meta-data and type conversion
            Parameters = new XlParameterInfo[parameters.Length];
            for (int i = 0; i < parameters.Length; i++)
            {
                object argAttrib = null;
                if ( argumentAttributes != null && i < argumentAttributes.Count)
                    argAttrib = argumentAttributes[i];
                 Parameters[i] = new XlParameterInfo(parameters[i], argAttrib);
            }

            // A native async function might still be marked as a command - check and fix.
            // (these have the ExcelAsyncHandle as last parameter)
            // (This check needs the Parameters array to be set up already.)
            if (IsExcelAsyncFunction)
            {
                // It really is a function, though it might return null
                IsCommand = false;
            }

            // Create the delegate type, wrap the targetMethod and create the delegate

            // CONSIDER: Currently we need a special delegate type here so that we can hook on the marshaling attributes.
            //           Future version might do straight-forward marshaling, so we can get rid of these types (just use generic methods)
            
            // FirstArgument (if received) is not used in the delegate type created ...
            Type delegateType = CreateDelegateType(modBuilder);
            // ... but is baked into the delegate itself.
            Delegate xlDelegate = CreateMethodDelegate(delegateType, targetMethod, target);

            // Need to add a reference to prevent garbage collection of our delegate
            // Don't need to pin, according to 
            // "How to: Marshal Callbacks and Delegates Using C++ Interop"
            // Currently this delegate is never released
            // TODO: Clean up properly
            DelegateHandle = GCHandle.Alloc(xlDelegate);
            FunctionPointer = Marshal.GetFunctionPointerForDelegate(xlDelegate);
        }
Example #4
0
        private Delegate CreateMethodDelegate(MethodInfo targetMethod, Type delegateType)
        {
            // Check whether we can skip wrapper
            if (IsExceptionSafe &&
                Array.TrueForAll(Parameters,
                                 delegate(XlParameterInfo pi){ return(pi.BoxedValueType == null); }) &&
                (IsCommand || ReturnType.BoxedValueType == null))
            {
                // Create the delegate directly
                return(Delegate.CreateDelegate(delegateType, targetMethod));
            }

            // Else we create a dynamic wrapper
            Type[] paramTypes = Array.ConvertAll <XlParameterInfo, Type>(Parameters,
                                                                         delegate(XlParameterInfo pi) { return(pi.DelegateParamType); });

            DynamicMethod wrapper = new DynamicMethod(
                string.Format("Wrapped_f{0}_{1}", Index, targetMethod.Name),
                IsCommand ? typeof(void) : ReturnType.DelegateParamType,
                paramTypes, typeof(object), true);
            ILGenerator wrapIL      = wrapper.GetILGenerator();
            Label       endOfMethod = wrapIL.DefineLabel();

            LocalBuilder retobj = null;

            if (!IsCommand)
            {
                // Make a local to contain the return value
                retobj = wrapIL.DeclareLocal(ReturnType.DelegateParamType);
            }
            if (!IsExceptionSafe)
            {
                // Start the Try block
                wrapIL.BeginExceptionBlock();
            }

            // Generate the body
            // push all the arguments
            for (byte i = 0; i < paramTypes.Length; i++)
            {
                wrapIL.Emit(OpCodes.Ldarg_S, i);
                XlParameterInfo pi = Parameters[i];
                if (pi.BoxedValueType != null)
                {
                    wrapIL.Emit(OpCodes.Unbox_Any, pi.BoxedValueType);
                }
            }
            // Call the real method
            wrapIL.EmitCall(OpCodes.Call, targetMethod, null);
            if (!IsCommand && ReturnType.BoxedValueType != null)
            {
                // Box the return value (which is on the stack)
                wrapIL.Emit(OpCodes.Box, ReturnType.BoxedValueType);
            }
            if (!IsCommand)
            {
                // Store the return value into the local variable
                wrapIL.Emit(OpCodes.Stloc_S, retobj);
            }

            if (!IsExceptionSafe)
            {
                wrapIL.Emit(OpCodes.Leave_S, endOfMethod);
                wrapIL.BeginCatchBlock(typeof(object));
                wrapIL.Emit(OpCodes.Pop);
                if (!IsCommand && ReturnType.DelegateParamType == typeof(object))
                {
                    // Create a boxed Excel error value, and set the return object to it
                    wrapIL.Emit(OpCodes.Ldc_I4, IntegrationMarshalHelpers.ExcelError_ExcelErrorValue);
                    wrapIL.Emit(OpCodes.Box, IntegrationMarshalHelpers.GetExcelErrorType());
                    wrapIL.Emit(OpCodes.Stloc_S, retobj);
                }
                wrapIL.EndExceptionBlock();
            }
            wrapIL.MarkLabel(endOfMethod);
            if (!IsCommand)
            {
                // Push the return value
                wrapIL.Emit(OpCodes.Ldloc_S, retobj);
            }
            wrapIL.Emit(OpCodes.Ret);
            return(wrapper.CreateDelegate(delegateType));;
        }
Example #5
0
        Delegate CreateMethodDelegate(Type delegateType, MethodInfo targetMethod, object target)
        {
            bool isInstanceMethod = !targetMethod.IsStatic;

            // We expect static methods to have target null
            Debug.Assert(isInstanceMethod || target == null);

            // Check whether we can skip wrapper completely
            // TODO: Change this - we should always wrap in an exception handler, but for double return values
            //       we let IsExceptionSafe mean we do a 1/0 in the exception handler, which Excel will catch and handle as #NUM!
            //       For other types it shouldn't matter...
            if (IsExceptionSafe &&
                Array.TrueForAll(Parameters,
                                 delegate(XlParameterInfo pi) { return(pi.BoxedValueType == null); }) &&
                (!HasReturnType || ReturnType.BoxedValueType == null))
            {
                // Create the delegate directly
                if (isInstanceMethod)
                {
                    // Can't be DynamicMethod
                    return(Delegate.CreateDelegate(delegateType, target, targetMethod));
                }
                if (targetMethod is DynamicMethod)
                {
                    return(((DynamicMethod)targetMethod).CreateDelegate(delegateType));
                }
                return(Delegate.CreateDelegate(delegateType, targetMethod));
            }

            // DateTime input parameters are never exception safe - we need to be able to fail out of the
            // marshaler when the passed-in argument is an invalid date.
            bool emitExceptionHandler =
                !IsExceptionSafe ||
                Array.Exists(Parameters,
                             delegate(XlParameterInfo pi) { return(pi.BoxedValueType == typeof(DateTime)); });

            // Now we create a dynamic wrapper
            Type[] paramTypes = Array.ConvertAll <XlParameterInfo, Type>(Parameters,
                                                                         delegate(XlParameterInfo pi)
                                                                         { return(pi.DelegateParamType); });
            if (isInstanceMethod)
            {
                Type[] allParams = new Type[paramTypes.Length + 1];
                allParams[0] = typeof(object);
                Array.Copy(paramTypes, 0, allParams, 1, paramTypes.Length);
                paramTypes = allParams;
            }

            DynamicMethod wrapper = new DynamicMethod(
                string.Format("Wrapped_f{0}_{1}", Index, targetMethod.Name),
                HasReturnType ? ReturnType.DelegateParamType : typeof(void),
                paramTypes, typeof(object), true);
            ILGenerator wrapIL      = wrapper.GetILGenerator();
            Label       endOfMethod = wrapIL.DefineLabel();

            LocalBuilder retobj = null;

            if (HasReturnType)
            {
                // Make a local to contain the return value
                retobj = wrapIL.DeclareLocal(ReturnType.DelegateParamType);
            }
            if (emitExceptionHandler)
            {
                // Start the Try block
                wrapIL.BeginExceptionBlock();
            }

            // Generate the body - push all the arguments, including the target for instance methods
            if (isInstanceMethod)
            {
                // First is the target of the delegate
                wrapIL.Emit(OpCodes.Ldarg_S, 0);
            }
            for (byte i = 0; i < Parameters.Length; i++)
            {
                if (i < 255)
                {
                    byte argIndex = isInstanceMethod ? (byte)(i + 1) : i;
                    wrapIL.Emit(OpCodes.Ldarg_S, argIndex);
                }
                else
                {
                    short argIndex = isInstanceMethod ? (short)(i + 1) : i;
                    wrapIL.Emit(OpCodes.Ldarg, argIndex);
                }
                XlParameterInfo pi = Parameters[i];
                if (pi.BoxedValueType != null)
                {
                    wrapIL.Emit(OpCodes.Unbox_Any, pi.BoxedValueType);
                }
            }
            // Call the real method
            wrapIL.EmitCall(OpCodes.Call, targetMethod, null);
            if (HasReturnType)
            {
                if (ReturnType.BoxedValueType != null)
                {
                    // Box the return value (which is on the stack)
                    wrapIL.Emit(OpCodes.Box, ReturnType.BoxedValueType);
                }
                // Store the return value into the local variable
                wrapIL.Emit(OpCodes.Stloc_S, retobj);
            }

            if (emitExceptionHandler)
            {
                wrapIL.Emit(OpCodes.Leave_S, endOfMethod);
                wrapIL.BeginCatchBlock(typeof(object));
                if (HasReturnType && ReturnType.DelegateParamType == typeof(object))
                {
                    // Call Integration.HandleUnhandledException - Exception object is on the stack.
                    wrapIL.EmitCall(OpCodes.Call, IntegrationHelpers.UnhandledExceptionHandler, null);
                    // Stack now has return value from the ExceptionHandler - Store to local
                    wrapIL.Emit(OpCodes.Stloc_S, retobj);

                    //// Create a boxed Excel error value, and set the return object to it
                    //wrapIL.Emit(OpCodes.Ldc_I4, IntegrationMarshalHelpers.ExcelError_ExcelErrorValue);
                    //wrapIL.Emit(OpCodes.Box, IntegrationMarshalHelpers.GetExcelErrorType());
                    //wrapIL.Emit(OpCodes.Stloc_S, retobj);
                }
                else
                {
                    // Just ignore the Exception.
                    wrapIL.Emit(OpCodes.Pop);
                }
                wrapIL.EndExceptionBlock();
            }
            wrapIL.MarkLabel(endOfMethod);
            if (HasReturnType)
            {
                // Push the return value
                wrapIL.Emit(OpCodes.Ldloc_S, retobj);
            }
            wrapIL.Emit(OpCodes.Ret);
            // End of Wrapper

            if (isInstanceMethod)
            {
                return(wrapper.CreateDelegate(delegateType, target));
            }
            return(wrapper.CreateDelegate(delegateType));
        }
Example #6
0
        public XlParameterInfo ReturnType; // Macro will have ReturnType null (as will native async functions)

        // THROWS: Throws a DnaMarshalException if the method cannot be turned into an XlMethodInfo
        // TODO: Manage errors if things go wrong
        XlMethodInfo(ModuleBuilder modBuilder, MethodInfo targetMethod, object target, object methodAttribute, List <object> argumentAttributes)
        {
            // Default Name, Description and Category
            Name                 = targetMethod.Name;
            Description          = "";
            Category             = IntegrationHelpers.DnaLibraryGetName();
            HelpTopic            = "";
            IsVolatile           = false;
            IsExceptionSafe      = false;
            IsHidden             = false;
            IsMacroType          = false;
            IsThreadSafe         = false;
            IsClusterSafe        = false;
            ExplicitRegistration = false;

            ShortCut = "";
            // DOCUMENT: Default MenuName is the library name
            // but menu is only added if at least the MenuText is set.
            MenuName = IntegrationHelpers.DnaLibraryGetName();
            MenuText = null; // Menu is only

            // Set default IsCommand - overridden by having an [ExcelCommand] attribute,
            // or by being a native async function.
            // (Must be done before SetAttributeInfo)
            IsCommand = (targetMethod.ReturnType == typeof(void));

            SetAttributeInfo(methodAttribute);
            // We shortcut the rest of the registration
            if (ExplicitRegistration)
            {
                return;
            }

            FixHelpTopic();

            // Return type conversion
            // Careful here - native async functions also return void
            if (targetMethod.ReturnType == typeof(void))
            {
                ReturnType = null;
            }
            else
            {
                ReturnType = new XlParameterInfo(targetMethod.ReturnType, true, IsExceptionSafe);
            }

            ParameterInfo[] parameters = targetMethod.GetParameters();

            // Parameters - meta-data and type conversion
            Parameters = new XlParameterInfo[parameters.Length];
            for (int i = 0; i < parameters.Length; i++)
            {
                object argAttrib = null;
                if (argumentAttributes != null && i < argumentAttributes.Count)
                {
                    argAttrib = argumentAttributes[i];
                }
                Parameters[i] = new XlParameterInfo(parameters[i], argAttrib);
            }

            // A native async function might still be marked as a command - check and fix.
            // (these have the ExcelAsyncHandle as last parameter)
            // (This check needs the Parameters array to be set up already.)
            if (IsExcelAsyncFunction)
            {
                // It really is a function, though it might return null
                IsCommand = false;
            }

            // Create the delegate type, wrap the targetMethod and create the delegate

            // CONSIDER: Currently we need a special delegate type here so that we can hook on the marshaling attributes.
            //           Future version might do straight-forward marshaling, so we can get rid of these types (just use generic methods)

            // FirstArgument (if received) is not used in the delegate type created ...
            Type delegateType = CreateDelegateType(modBuilder);
            // ... but is baked into the delegate itself.
            Delegate xlDelegate = CreateMethodDelegate(delegateType, targetMethod, target);

            // Need to add a reference to prevent garbage collection of our delegate
            // Don't need to pin, according to
            // "How to: Marshal Callbacks and Delegates Using C++ Interop"
            // Currently this delegate is never released
            // TODO: Clean up properly
            DelegateHandle  = GCHandle.Alloc(xlDelegate);
            FunctionPointer = Marshal.GetFunctionPointerForDelegate(xlDelegate);
        }
Example #7
0
        // THROWS: Throws a DnaMarshalException if the method cannot be turned into an XlMethodInfo
        // TODO: Manage errors if things go wrong
        XlMethodInfo(MethodInfo targetMethod, object target, object methodAttribute, List <object> argumentAttributes)
        {
            // Default Name, Description and Category
            Name                 = targetMethod.Name;
            Description          = "";
            Category             = IntegrationHelpers.DnaLibraryGetName();
            HelpTopic            = "";
            IsVolatile           = false;
            IsExceptionSafe      = false;
            IsHidden             = false;
            IsMacroType          = false;
            IsThreadSafe         = false;
            IsClusterSafe        = false;
            ExplicitRegistration = false;

            ShortCut = "";
            // DOCUMENT: Default MenuName is the library name
            // but menu is only added if at least the MenuText is set.
            MenuName = IntegrationHelpers.DnaLibraryGetName();
            MenuText = null; // Menu is only

            // Set default IsCommand - overridden by having an [ExcelCommand] attribute,
            // or by being a native async function.
            // (Must be done before SetAttributeInfo)
            IsCommand = (targetMethod.ReturnType == typeof(void));

            SetAttributeInfo(methodAttribute);
            // We shortcut the rest of the registration
            if (ExplicitRegistration)
            {
                return;
            }

            FixHelpTopic();

            // Return type conversion
            // Careful here - native async functions also return void
            if (targetMethod.ReturnType == typeof(void))
            {
                ReturnType = null;
            }
            else
            {
                ReturnType = new XlParameterInfo(targetMethod.ReturnType, true, IsExceptionSafe);
            }

            ParameterInfo[] parameters = targetMethod.GetParameters();

            // Parameters - meta-data and type conversion
            Parameters = new XlParameterInfo[parameters.Length];
            for (int i = 0; i < parameters.Length; i++)
            {
                object argAttrib = null;
                if (argumentAttributes != null && i < argumentAttributes.Count)
                {
                    argAttrib = argumentAttributes[i];
                }
                Parameters[i] = new XlParameterInfo(parameters[i], argAttrib);
            }

            // A native async function might still be marked as a command - check and fix.
            // (these have the ExcelAsyncHandle as last parameter)
            // (This check needs the Parameters array to be set up already.)
            if (IsExcelAsyncFunction)
            {
                // It really is a function, though it might return null
                IsCommand = false;
            }
        }
Example #8
0
        private static void RegisterXlMethod(XlMethodInfo mi)
        {
            int index = registeredMethods.Count;

            SetJump(index, mi.FunctionPointer);
            String procName = String.Format("f{0}", index);

            string functionType     = mi.ReturnType == null ? "" : mi.ReturnType.XlType.ToString();
            string argumentNames    = "";
            bool   showDescriptions = false;

            string[] argumentDescriptions = new string[mi.Parameters.Length];
            string   helpTopic;

            for (int j = 0; j < mi.Parameters.Length; j++)
            {
                XlParameterInfo pi = mi.Parameters[j];

                functionType += pi.XlType;
                if (j > 0)
                {
                    argumentNames += ",";
                }
                argumentNames          += pi.Name;
                argumentDescriptions[j] = pi.Description;

                if (pi.Description != "")
                {
                    showDescriptions = true;
                }

                // DOCUMENT: Here is the patch for the Excel Function Description bug.
                // DOCUMENT: I add ". " to the last parameters.
                if (j == mi.Parameters.Length - 1)
                {
                    argumentDescriptions[j] += ". ";
                }
            } // for each parameter

            if (mi.IsMacroType)
            {
                functionType += "#";
            }
            else if (mi.IsThreadSafe && XlAddIn.xlCallVersion >= 12)
            {
                functionType += "$";
            }

            if (mi.IsVolatile)
            {
                functionType += "!";
            }
            // DOCUMENT: If # is set and there is an R argument,
            // Excel considers the function volatile
            // You can call xlfVolatile, false in beginning of function to clear.

            // DOCUMENT: Here is the patch for the Excel Function Description bug.
            // DOCUMENT: I add ". " if the function takes no parameters and has a description.
            string functionDescription = mi.Description;

            if (mi.Parameters.Length == 0 && functionDescription != "")
            {
                functionDescription += ". ";
            }

            // DOCUMENT: When there is no description, we don't add any.
            // This allows the user to work around the Excel bug where an extra parameter is displayed if
            // the function has no parameter but displays a description
            if (mi.Description != "")
            {
                showDescriptions = true;
            }

            int numArguments;
            // DOCUMENT: Maximum 20 Argument Descriptions when registering using Excel4 function.
            int maxDescriptions = (XlAddIn.xlCallVersion < 12) ? 20 : 245;
            int numArgumentDescriptions;

            if (showDescriptions)
            {
                numArgumentDescriptions = Math.Min(argumentDescriptions.Length, maxDescriptions);
                numArguments            = 10 + numArgumentDescriptions;
            }
            else
            {
                numArgumentDescriptions = 0;
                numArguments            = 9;
            }

            // Make HelpTopic without full path relative to xllPath
            if (mi.HelpTopic == null || mi.HelpTopic == "")
            {
                helpTopic = mi.HelpTopic;
            }
            else
            {
                if (Path.IsPathRooted(mi.HelpTopic))
                {
                    helpTopic = mi.HelpTopic;
                }
                else
                {
                    helpTopic = Path.Combine(Path.GetDirectoryName(pathXll), mi.HelpTopic);
                }
            }

            object[] registerParameters = new object[numArguments];
            registerParameters[0] = pathXll;
            registerParameters[1] = procName;
            registerParameters[2] = functionType;
            registerParameters[3] = mi.Name;
            registerParameters[4] = argumentNames;
            registerParameters[5] = mi.IsCommand ? 2                               /*macro*/
                                                          : (mi.IsHidden ? 0 : 1); /*function*/
            registerParameters[6] = mi.Category;
            registerParameters[7] = mi.ShortCut;                                   /*shortcut_text*/
            registerParameters[8] = helpTopic; /*help_topic*/;

            if (showDescriptions)
            {
                registerParameters[9] = functionDescription;

                for (int k = 0; k < numArgumentDescriptions; k++)
                {
                    registerParameters[10 + k] = argumentDescriptions[k];
                }
            }

            // Basically suppress problems here !?
            try
            {
                object xlCallResult;
                XlCallImpl.TryExcelImpl(XlCallImpl.xlfRegister, out xlCallResult, registerParameters);
                mi.RegisterId = (double)xlCallResult;
                registeredMethods.Add(mi);
            }
            catch (Exception e)
            {
                // TODO: What to do here?
                Debug.WriteLine(e.Message);
            }

            if (mi.IsCommand)
            {
                RegisterMenu(mi);
            }
        }
Example #9
0
        MethodInfo CreateMethodInfo(TypeBuilder wrapperType, Type delegateType, MethodInfo targetMethod, object target)
        {
            bool isInstanceMethod = !targetMethod.IsStatic;

            // We expect static methods to have target null
            Debug.Assert(isInstanceMethod || target == null);

            // Check whether we can skip wrapper completely
            // TODO: Change this - we should always wrap in an exception handler, but for double return values
            //       we let IsExceptionSafe mean we do a 1/0 in the exception handler, which Excel will catch and handle as #NUM!
            //       For other types it shouldn't matter...
            if (IsExceptionSafe &&
                Array.TrueForAll(Parameters,
                                 delegate(XlParameterInfo pi) { return(pi.BoxedValueType == null); }) &&
                (!HasReturnType || ReturnType.BoxedValueType == null))
            {
                return(targetMethod);
            }

            // DateTime and boolean input parameters are never exception safe - we need to be able to fail out of the
            // marshaler when the passed-in argument is an invalid date or bool.
            bool emitExceptionHandler =
                !IsExceptionSafe ||
                Array.Exists(Parameters,
                             delegate(XlParameterInfo pi) { return(pi.BoxedValueType == typeof(DateTime) ||
                                                                   pi.BoxedValueType == typeof(bool)); });

            if (emitExceptionHandler && IsExceptionSafe && HasReturnType && ReturnType.DelegateParamType != typeof(object))
            {
                throw new DnaMarshalException("DateTime and bool input parameters are incompatible with IsExceptionSafe attribute");
            }

            // Now we create a dynamic wrapper
            Type[] paramTypes = Array.ConvertAll <XlParameterInfo, Type>(Parameters,
                                                                         delegate(XlParameterInfo pi)
                                                                         { return(pi.DelegateParamType); });
            if (isInstanceMethod)
            {
                Type[] allParams = new Type[paramTypes.Length + 1];
                allParams[0] = typeof(object);
                Array.Copy(paramTypes, 0, allParams, 1, paramTypes.Length);
                paramTypes = allParams;
            }

            MethodBuilder wrapper = wrapperType.DefineMethod(
                string.Format("Wrapped_f{0}_{1}", Index, targetMethod.Name),
                MethodAttributes.Public | MethodAttributes.Static,
                HasReturnType ? ReturnType.DelegateParamType : typeof(void),
                paramTypes);

            // Add [HandleCorruptProcessExceptions] to wrapper method, if we're running on .NET 4.0
            if (Environment.Version.Major >= 4 && emitExceptionHandler)
            {
                Type                   hcpeType     = Type.GetType("System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptionsAttribute");
                ConstructorInfo        hcpeCtorInfo = hcpeType.GetConstructor(Type.EmptyTypes);
                CustomAttributeBuilder hcpeBuilder  = new CustomAttributeBuilder(hcpeCtorInfo, Type.EmptyTypes);
                wrapper.SetCustomAttribute(hcpeBuilder);
            }

            ILGenerator wrapIL      = wrapper.GetILGenerator();
            Label       endOfMethod = wrapIL.DefineLabel();

            LocalBuilder retobj = null;
            LocalBuilder except = null;

            if (HasReturnType)
            {
                // Make a local to contain the return value
                retobj = wrapIL.DeclareLocal(ReturnType.DelegateParamType);
            }
            if (emitExceptionHandler)
            {
                // Start the Try block
                except = wrapIL.DeclareLocal(typeof(Exception));
                wrapIL.BeginExceptionBlock();
            }

            // Generate the body - push all the arguments, including the target for instance methods
            if (isInstanceMethod)
            {
                // First is the target of the delegate
                wrapIL.Emit(OpCodes.Ldarg_S, 0);
            }
            for (byte i = 0; i < Parameters.Length; i++)
            {
                if (i < 255)
                {
                    byte argIndex = isInstanceMethod ? (byte)(i + 1) : i;
                    wrapIL.Emit(OpCodes.Ldarg_S, argIndex);
                }
                else
                {
                    short argIndex = isInstanceMethod ? (short)(i + 1) : i;
                    wrapIL.Emit(OpCodes.Ldarg, argIndex);
                }
                XlParameterInfo pi = Parameters[i];
                if (pi.BoxedValueType != null)
                {
                    wrapIL.Emit(OpCodes.Unbox_Any, pi.BoxedValueType);
                }
            }
            // Call the real method
            wrapIL.EmitCall(OpCodes.Call, targetMethod, null);
            if (HasReturnType)
            {
                if (ReturnType.BoxedValueType != null)
                {
                    // Box the return value (which is on the stack)
                    wrapIL.Emit(OpCodes.Box, ReturnType.BoxedValueType);
                }
                // Store the return value into the local variable
                wrapIL.Emit(OpCodes.Stloc_S, retobj);
            }

            if (emitExceptionHandler)
            {
                wrapIL.Emit(OpCodes.Leave_S, endOfMethod);
                wrapIL.BeginCatchBlock(typeof(object));
                if (IsExcelAsyncFunction)
                {
                    wrapIL.Emit(OpCodes.Stloc_S, except);
                    int idx = Parameters.Length - (isInstanceMethod ? 0 : 1);
                    if (idx < 256)
                    {
                        wrapIL.Emit(OpCodes.Ldarg_S, (byte)idx);
                    }
                    else
                    {
                        wrapIL.Emit(OpCodes.Ldarg, (short)idx);
                    }
                    wrapIL.Emit(OpCodes.Ldloc_S, except);
                    wrapIL.Emit(OpCodes.Callvirt, IntegrationMarshalHelpers.ExcelAsyncHandleType.GetMethod("SetException"));
                    wrapIL.Emit(OpCodes.Pop);
                }
                else
                if (!HasReturnType || ReturnType.DelegateParamType == typeof(object))
                {
                    // Call Integration.HandleUnhandledException - Exception object is on the stack.
                    wrapIL.Emit(OpCodes.Call, typeof(IntegrationHelpers).GetMethod("HandleUnhandledException"));

                    if (HasReturnType)
                    {
                        // Stack now has return value from the ExceptionHandler - Store to local if we have a return type
                        wrapIL.Emit(OpCodes.Stloc_S, retobj);

                        //// Create a boxed Excel error value, and set the return object to it
                        //wrapIL.Emit(OpCodes.Ldc_I4, IntegrationMarshalHelpers.ExcelError_ExcelErrorValue);
                        //wrapIL.Emit(OpCodes.Box, IntegrationMarshalHelpers.GetExcelErrorType());
                        //wrapIL.Emit(OpCodes.Stloc_S, retobj);
                    }
                    else
                    {
                        // Pop result from ExceptionHandler off the stack
                        wrapIL.Emit(OpCodes.Pop);
                    }
                }
                else
                {
                    // Just ignore the Exception.
                    wrapIL.Emit(OpCodes.Pop);
                }
                wrapIL.EndExceptionBlock();
            }
            wrapIL.MarkLabel(endOfMethod);
            if (HasReturnType)
            {
                // Push the return value
                wrapIL.Emit(OpCodes.Ldloc_S, retobj);
            }
            wrapIL.Emit(OpCodes.Ret);
            // End of Wrapper

            return(wrapper);
        }
Example #10
0
        private static void RegisterXlMethod(XlMethodInfo mi)
        {
            int index = registeredMethods.Count;

            SetJump(index, mi.FunctionPointer);
            String procName = String.Format("f{0}", index);

            string functionType;

            if (mi.IsCommand)
            {
                if (mi.Parameters.Length == 0)
                {
                    functionType = "";  // OK since no other types will be added
                }
                else
                {
                    functionType = ">"; // Use the void / inplace indicator if needed.
                }
            }
            else
            {
                functionType = mi.ReturnType.XlType;
            }

            string argumentNames    = "";
            bool   showDescriptions = false;

            string[] argumentDescriptions = new string[mi.Parameters.Length];
            string   helpTopic;

            for (int j = 0; j < mi.Parameters.Length; j++)
            {
                XlParameterInfo pi = mi.Parameters[j];

                functionType += pi.XlType;
                if (j > 0)
                {
                    argumentNames += ",";
                }
                argumentNames          += pi.Name;
                argumentDescriptions[j] = pi.Description;

                if (pi.Description != "")
                {
                    showDescriptions = true;
                }

                // DOCUMENT: Truncate the argument description if it exceeds the Excel limit of 255 characters
                if (j < mi.Parameters.Length - 1)
                {
                    if (!string.IsNullOrEmpty(argumentDescriptions[j]) &&
                        argumentDescriptions[j].Length > 255)
                    {
                        argumentDescriptions[j] = argumentDescriptions[j].Substring(0, 255);
                        Debug.Print("Truncated argument description of {0} in method {1} as Excel limit was exceeded",
                                    pi.Name, mi.Name);
                    }
                }
                else
                {
                    // Last argument - need to deal with extra ". "
                    if (!string.IsNullOrEmpty(argumentDescriptions[j]))
                    {
                        if (argumentDescriptions[j].Length > 253)
                        {
                            argumentDescriptions[j] = argumentDescriptions[j].Substring(0, 253);
                            Debug.Print("Truncated field description of {0} in method {1} as Excel limit was exceeded",
                                        pi.Name, mi.Name);
                        }

                        // DOCUMENT: Here is the patch for the Excel Function Description bug.
                        // DOCUMENT: I add ". " to the last parameter.
                        argumentDescriptions[j] += ". ";
                    }
                }
            } // for each parameter

            if (mi.IsClusterSafe && ProcessHelper.SupportsClusterSafe)
            {
                functionType += "&";
            }

            if (mi.IsMacroType)
            {
                functionType += "#";
            }

            if (!mi.IsMacroType && mi.IsThreadSafe && XlAddIn.XlCallVersion >= 12)
            {
                functionType += "$";
            }

            if (mi.IsVolatile)
            {
                functionType += "!";
            }
            // DOCUMENT: If # is set and there is an R argument, Excel considers the function volatile anyway.
            // You can call xlfVolatile, false in beginning of function to clear.

            // DOCUMENT: There is a bug? in Excel 2007 that limits the total argumentname string to 255 chars.
            // TODO: Check whether this is fixed in Excel 2010 yet.
            // DOCUMENT: I truncate the argument string for all versions.
            if (argumentNames.Length > 255)
            {
                argumentNames = argumentNames.Substring(0, 255);
            }

            // DOCUMENT: Here is the patch for the Excel Function Description bug.
            // DOCUMENT: I add ". " if the function takes no parameters and has a description.
            string functionDescription = mi.Description;

            if (mi.Parameters.Length == 0 && functionDescription != "")
            {
                functionDescription += ". ";
            }

            // DOCUMENT: When there is no description, we don't add any.
            // This allows the user to work around the Excel bug where an extra parameter is displayed if
            // the function has no parameter but displays a description
            if (mi.Description != "")
            {
                showDescriptions = true;
            }

            int numArguments;
            // DOCUMENT: Maximum 20 Argument Descriptions when registering using Excel4 function.
            int maxDescriptions = (XlAddIn.XlCallVersion < 12) ? 20 : 245;
            int numArgumentDescriptions;

            if (showDescriptions)
            {
                numArgumentDescriptions = Math.Min(argumentDescriptions.Length, maxDescriptions);
                numArguments            = 10 + numArgumentDescriptions;
            }
            else
            {
                numArgumentDescriptions = 0;
                numArguments            = 9;
            }

            // Make HelpTopic without full path relative to xllPath
            if (string.IsNullOrEmpty(mi.HelpTopic))
            {
                helpTopic = mi.HelpTopic;
            }
            else
            {
                // DOCUMENT: If HelpTopic is not rooted - it is expanded relative to .xll path.
                // If http url does not end with !0 it is appended.
                // I don't think https is supported, but it should not be considered an 'unrooted' path anyway.
                if (mi.HelpTopic.StartsWith("http://") || mi.HelpTopic.StartsWith("https://"))
                {
                    if (!mi.HelpTopic.EndsWith("!0"))
                    {
                        helpTopic = mi.HelpTopic + "!0";
                    }
                    else
                    {
                        helpTopic = mi.HelpTopic;
                    }
                }
                else if (Path.IsPathRooted(mi.HelpTopic))
                {
                    helpTopic = mi.HelpTopic;
                }
                else
                {
                    helpTopic = Path.Combine(Path.GetDirectoryName(pathXll), mi.HelpTopic);
                }
            }

            object[] registerParameters = new object[numArguments];
            registerParameters[0] = pathXll;
            registerParameters[1] = procName;
            registerParameters[2] = functionType;
            registerParameters[3] = mi.Name;
            registerParameters[4] = argumentNames;
            registerParameters[5] = mi.IsCommand ? 2                               /*macro*/
                                                          : (mi.IsHidden ? 0 : 1); /*function*/
            registerParameters[6] = mi.Category;
            registerParameters[7] = mi.ShortCut;                                   /*shortcut_text*/
            registerParameters[8] = helpTopic; /*help_topic*/;

            if (showDescriptions)
            {
                registerParameters[9] = functionDescription;

                for (int k = 0; k < numArgumentDescriptions; k++)
                {
                    registerParameters[10 + k] = argumentDescriptions[k];
                }
            }

            // Basically suppress problems here !?
            try
            {
                object xlCallResult;
                XlCallImpl.TryExcelImpl(XlCallImpl.xlfRegister, out xlCallResult, registerParameters);
                Debug.Print("Register - XllPath={0}, ProcName={1}, FunctionType={2}, MethodName={3} - Result={4}", registerParameters[0], registerParameters[1], registerParameters[2], registerParameters[3], xlCallResult);
                if (xlCallResult is double)
                {
                    mi.RegisterId = (double)xlCallResult;
                    registeredMethods.Add(mi);
                    if (mi.IsCommand)
                    {
                        RegisterMenu(mi);
                        RegisterShortCut(mi);
                    }
                }
                else
                {
                    // TODO: What to do here? LogDisplay??
                    Debug.Print("Registration Error! - Register call failed for method {0}", mi.Name);
                }
            }
            catch (Exception e)
            {
                // TODO: What to do here? LogDisplay??
                Debug.WriteLine("Registration Error! - " + e.Message);
            }
        }
Example #11
0
        // THROWS: Throws a DnaMarshalException if the method cannot be turned into an XlMethodInfo
        // TODO: Manage errors if things go wrong
        XlMethodInfo(MethodInfo targetMethod, object target, object methodAttribute, List<object> argumentAttributes)
        {
            // Default Name, Description and Category
            Name = targetMethod.Name;
            Description = "";
            Category = IntegrationHelpers.DnaLibraryGetName();
            HelpTopic = "";
            IsVolatile = false;
            IsExceptionSafe = false;
            IsHidden = false;
            IsMacroType = false;
            IsThreadSafe = false;
            IsClusterSafe = false;
            ExplicitRegistration = false;

            ShortCut = "";
            // DOCUMENT: Default MenuName is the library name
            // but menu is only added if at least the MenuText is set.
            MenuName = IntegrationHelpers.DnaLibraryGetName();
            MenuText = null; // Menu is only

            // Set default IsCommand - overridden by having an [ExcelCommand] attribute,
            // or by being a native async function.
            // (Must be done before SetAttributeInfo)
            IsCommand = (targetMethod.ReturnType == typeof(void));

            SetAttributeInfo(methodAttribute);
            // We shortcut the rest of the registration
            if (ExplicitRegistration) return;

            FixHelpTopic();

            // Return type conversion
            // Careful here - native async functions also return void
            if (targetMethod.ReturnType == typeof(void))
            {
                ReturnType = null;
            }
            else
            {
                ReturnType = new XlParameterInfo(targetMethod.ReturnType, true, IsExceptionSafe);
            }

            ParameterInfo[] parameters = targetMethod.GetParameters();

            // Parameters - meta-data and type conversion
            Parameters = new XlParameterInfo[parameters.Length];
            for (int i = 0; i < parameters.Length; i++)
            {
                object argAttrib = null;
                if ( argumentAttributes != null && i < argumentAttributes.Count)
                    argAttrib = argumentAttributes[i];
                 Parameters[i] = new XlParameterInfo(parameters[i], argAttrib);
            }

            // A native async function might still be marked as a command - check and fix.
            // (these have the ExcelAsyncHandle as last parameter)
            // (This check needs the Parameters array to be set up already.)
            if (IsExcelAsyncFunction)
            {
                // It really is a function, though it might return null
                IsCommand = false;
            }
        }
Example #12
0
        private static object[] GetRegisterParameters(XlMethodInfo mi, string exportedProcName)
        {
            string functionType;

            if (mi.ReturnType != null)
            {
                functionType = mi.ReturnType.XlType;
            }
            else
            {
                if (mi.Parameters.Length == 0)
                {
                    functionType = "";  // OK since no other types will be added
                }
                else
                {
                    // This case is also be used for native async functions
                    functionType = ">"; // Use the void / inplace indicator if needed.
                }
            }

            // TODO: The argument names and descriptions allow some undocumented ",..." form to support paramarray style functions.
            //       E.g. check the FuncSum function in Generic.c in the SDK.
            //       We should try some support for this...
            string argumentNames    = "";
            bool   showDescriptions = false;
            // For async functions, we need to leave off the last argument
            int numArgumentDescriptions = mi.IsExcelAsyncFunction ? mi.Parameters.Length - 1 : mi.Parameters.Length;

            string[] argumentDescriptions = new string[numArgumentDescriptions];

            for (int j = 0; j < numArgumentDescriptions; j++)
            {
                XlParameterInfo pi = mi.Parameters[j];

                functionType += pi.XlType;

                if (j > 0)
                {
                    argumentNames += ",";
                }
                argumentNames          += pi.Name;
                argumentDescriptions[j] = pi.Description;

                if (pi.Description != "")
                {
                    showDescriptions = true;
                }

                // DOCUMENT: Truncate the argument description if it exceeds the Excel limit of 255 characters
                if (j < mi.Parameters.Length - 1)
                {
                    if (!string.IsNullOrEmpty(argumentDescriptions[j]) &&
                        argumentDescriptions[j].Length > 255)
                    {
                        argumentDescriptions[j] = argumentDescriptions[j].Substring(0, 255);
                        Debug.Print("Truncated argument description of {0} in method {1} as Excel limit was exceeded",
                                    pi.Name, mi.Name);
                    }
                }
                else
                {
                    // Last argument - need to deal with extra ". "
                    if (!string.IsNullOrEmpty(argumentDescriptions[j]))
                    {
                        if (argumentDescriptions[j].Length > 253)
                        {
                            argumentDescriptions[j] = argumentDescriptions[j].Substring(0, 253);
                            Debug.Print("Truncated field description of {0} in method {1} as Excel limit was exceeded",
                                        pi.Name, mi.Name);
                        }

                        // DOCUMENT: Here is the patch for the Excel Function Description bug.
                        // DOCUMENT: I add ". " to the last parameter.
                        argumentDescriptions[j] += ". ";
                    }
                }
            } // for each parameter

            // Add async handle
            if (mi.IsExcelAsyncFunction)
            {
                functionType += "X"; // mi.Parameters[mi.Parameters.Length - 1].XlType should be "X" anyway
            }
            // Native async functions cannot be cluster safe
            if (mi.IsClusterSafe && ProcessHelper.SupportsClusterSafe && !mi.IsExcelAsyncFunction)
            {
                functionType += "&";
            }

            if (mi.IsMacroType)
            {
                functionType += "#";
            }

            if (!mi.IsMacroType && mi.IsThreadSafe && XlAddIn.XlCallVersion >= 12)
            {
                functionType += "$";
            }

            if (mi.IsVolatile)
            {
                functionType += "!";
            }
            // DOCUMENT: If # is set and there is an R argument, Excel considers the function volatile anyway.
            // You can call xlfVolatile, false in beginning of function to clear.

            string functionDescription = mi.Description;

            // DOCUMENT: Truncate Description to 253 characters (for all versions)
            functionDescription = Truncate(functionDescription, 253);

            // DOCUMENT: Here is the patch for the Excel Function Description bug.
            // DOCUMENT: I add ". " if the function takes no parameters and has a description.
            if (mi.Parameters.Length == 0 && functionDescription != "")
            {
                functionDescription += ". ";
            }

            // DOCUMENT: When there is no description, we don't add any.
            // This allows the user to work around the Excel bug where an extra parameter is displayed if
            // the function has no parameter but displays a description
            if (mi.Description != "")
            {
                showDescriptions = true;
            }

            int numRegisterParameters;
            // DOCUMENT: Maximum 20 Argument Descriptions when registering using Excel4 function.
            int maxDescriptions = (XlAddIn.XlCallVersion < 12) ? 20 : 245;

            if (showDescriptions)
            {
                numArgumentDescriptions = Math.Min(numArgumentDescriptions, maxDescriptions);
                numRegisterParameters   = 10 + numArgumentDescriptions;  // function description + arg descriptions
            }
            else
            {
                // Won't be showing any descriptions.
                numArgumentDescriptions = 0;
                numRegisterParameters   = 9;
            }

            // DOCUMENT: Additional truncations of registration info - registration fails with strings longer than 255 chars.
            argumentNames = Truncate(argumentNames, 255);
            string category  = Truncate(mi.Category, 255);
            string name      = Truncate(mi.Name, 255);
            string helpTopic = (mi.HelpTopic == null || mi.HelpTopic.Length <= 255) ? mi.HelpTopic : "";

            object[] registerParameters = new object[numRegisterParameters];
            registerParameters[0] = XlAddIn.PathXll;
            registerParameters[1] = exportedProcName;
            registerParameters[2] = functionType;
            registerParameters[3] = name;
            registerParameters[4] = argumentNames;
            registerParameters[5] = mi.IsCommand ? 2                      /*macro*/
                                                 : (mi.IsHidden ? 0 : 1); /*function*/
            registerParameters[6] = category;
            registerParameters[7] = mi.ShortCut;                          /*shortcut_text*/
            registerParameters[8] = helpTopic;                            /*help_topic*/

            if (showDescriptions)
            {
                registerParameters[9] = functionDescription;

                for (int k = 0; k < numArgumentDescriptions; k++)
                {
                    registerParameters[10 + k] = argumentDescriptions[k];
                }
            }

            return(registerParameters);
        }
Example #13
0
        public string ShortCut; // For macros only

        #endregion Fields

        #region Constructors

        // THROWS: Throws a DnaMarshalException if the method cannot be turned into an XlMethodInfo
        // TODO: Manage errors if things go wrong
        XlMethodInfo(MethodInfo targetMethod, ModuleBuilder modBuilder)
        {
            // Default Name, Description and Category
            Name = targetMethod.Name;
            Description = "";
            Category = IntegrationHelpers.DnaLibraryGetName();
            HelpTopic = "";
            IsVolatile = false;
            IsExceptionSafe = false;
            IsHidden = false;
            IsMacroType = false;
            IsThreadSafe = false;
            IsClusterSafe = false;

            ShortCut = "";
            // DOCUMENT: Default MenuName is the library name
            // but menu is only added if at least the MenuText is set.
            MenuName = IntegrationHelpers.DnaLibraryGetName();
            MenuText = null; // Menu is only

            SetAttributeInfo(targetMethod.GetCustomAttributes(false));

            // Return type conversion
            if (targetMethod.ReturnType == typeof (void))
            {
                IsCommand = true;
                ReturnType = null;
            }
            else
            {
                IsCommand = false;
                ReturnType = new XlParameterInfo(targetMethod.ReturnType, true, IsExceptionSafe);
            }

            // Parameters - meta-data and type conversion
            Parameters =
                Array.ConvertAll<ParameterInfo, XlParameterInfo>(
                    targetMethod.GetParameters(),
                    delegate(ParameterInfo pi) { return new XlParameterInfo(pi); });

            // Create the delegate type, wrap the targetMethod and create the delegate
            Type delegateType = CreateDelegateType(modBuilder);
            Delegate xlDelegate = CreateMethodDelegate(targetMethod, delegateType);

            // Need to add a reference to prevent garbage collection of our delegate
            // Don't need to pin, according to
            // "How to: Marshal Callbacks and Delegates Using C++ Interop"
            // Currently this delegate is never released
            // TODO: Clean up properly
            DelegateHandle = GCHandle.Alloc(xlDelegate);
            FunctionPointer = Marshal.GetFunctionPointerForDelegate(xlDelegate);
        }