Skip to content

Commit eced4d3

Browse files
authored
Merge pull request #740 from Excel-DNA/ExcelHandle
Implemented explicit opt-in indicating that object handles should be used for a parameter or types
2 parents 06cb978 + 49dd818 commit eced4d3

16 files changed

+550
-18
lines changed

Source/ExcelDna.Integration/AssemblyLoader.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ public static void ProcessAssemblies(
4949
comClassTypes.Count;
5050
Logger.Initialization.Verbose("Processing assembly {0}. ExplicitExports {1}, ExplicitRegistration {2}, ComServer {3}, IsDynamic {4}",
5151
assembly.Assembly.FullName, assembly.ExplicitExports, assembly.ExplicitRegistration, assembly.ComServer, assembly.IsDynamic);
52+
53+
int assemblyAttributes = ObjectHandles.ObjectHandleRegistration.ProcessAssemblyAttributes(assembly.Assembly.GetCustomAttributes());
54+
5255
// Patch contributed by y_i on CodePlex:
5356
// http://stackoverflow.com/questions/11915389/assembly-gettypes-throwing-an-exception
5457
Type[] types;
@@ -103,7 +106,9 @@ public static void ProcessAssemblies(
103106
excelFunctionExecutionHandlerSelectors.Count +
104107
addIns.Count +
105108
rtdServerTypes.Count +
106-
comClassTypes.Count == initialObjectsCount)
109+
comClassTypes.Count +
110+
assemblyAttributes
111+
== initialObjectsCount)
107112
{
108113
Logger.Initialization.Error("No objects loaded from {0}", assembly.Assembly.FullName);
109114
}
@@ -196,6 +201,10 @@ static bool IsMethodSupported(MethodInfo mi, bool explicitExports)
196201
{
197202
isSupported = false;
198203
}
204+
else if (ObjectHandles.ObjectHandleRegistration.IsMethodSupported(new ExtendedRegistration.ExcelFunction(mi)))
205+
{
206+
isSupported = false;
207+
}
199208
else if (!IsPrimitiveParameterType(mi.ReturnType))
200209
{
201210
isSupported = false;

Source/ExcelDna.Integration/ExcelAsyncUtil.cs

+5
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ static void RunMacro(object macroName)
143143
XlCall.Excel(XlCall.xlcRun, macroName);
144144
}
145145

146+
internal static object ObserveObject<T>(string callerFunctionName, object callerParameters, Func<IObservable<T>> observableSource)
147+
{
148+
return Observe(callerFunctionName, callerParameters, () => new ExcelObjectObservable<T>(observableSource()));
149+
}
150+
146151
internal static object RunTaskObject<TResult>(string callerFunctionName, object callerParameters, Func<Task<TResult>> taskSource)
147152
{
148153
return Observe(callerFunctionName, callerParameters, delegate

Source/ExcelDna.Integration/ExcelAttributes.cs

+24
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,30 @@ public ExcelArgumentAttribute(string description)
6565
}
6666
}
6767

68+
/// <summary>
69+
/// For the arguments of object handles.
70+
/// </summary>
71+
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
72+
[MeansImplicitUse]
73+
public class ExcelHandleAttribute : Attribute
74+
{
75+
}
76+
77+
/// <summary>
78+
/// To indicate that a type will be marshalled as object handles.
79+
/// </summary>
80+
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
81+
[MeansImplicitUse]
82+
public class ExcelHandleExternalAttribute : Attribute
83+
{
84+
public ExcelHandleExternalAttribute(Type type)
85+
{
86+
Type = type;
87+
}
88+
89+
public Type Type { get; }
90+
}
91+
6892
/// <summary>
6993
/// For macro commands.
7094
/// </summary>

Source/ExcelDna.Integration/ExcelRtdObserver.cs

+63
Original file line numberDiff line numberDiff line change
@@ -1191,4 +1191,67 @@ public void OnCompleted()
11911191
}
11921192
}
11931193
}
1194+
1195+
internal class ExcelObjectObservable<T> : IExcelObservable
1196+
{
1197+
readonly IObservable<T> _observable;
1198+
1199+
public ExcelObjectObservable(IObservable<T> observable)
1200+
{
1201+
_observable = observable;
1202+
}
1203+
1204+
public IDisposable Subscribe(IExcelObserver excelObserver)
1205+
{
1206+
var observer = new AnonymousObserver<T>(value => excelObserver.OnNext(value), excelObserver.OnError, excelObserver.OnCompleted);
1207+
return _observable.Subscribe(observer);
1208+
}
1209+
1210+
// An IObserver that forwards the inputs to given methods.
1211+
class AnonymousObserver<OT> : IObserver<OT>
1212+
{
1213+
readonly Action<string> _onNext;
1214+
readonly Action<Exception> _onError;
1215+
readonly Action _onCompleted;
1216+
1217+
public AnonymousObserver(Action<string> onNext, Action<Exception> onError, Action onCompleted)
1218+
{
1219+
if (onNext == null)
1220+
{
1221+
throw new ArgumentNullException("onNext");
1222+
}
1223+
if (onError == null)
1224+
{
1225+
throw new ArgumentNullException("onError");
1226+
}
1227+
if (onCompleted == null)
1228+
{
1229+
throw new ArgumentNullException("onCompleted");
1230+
}
1231+
_onNext = onNext;
1232+
_onError = onError;
1233+
_onCompleted = onCompleted;
1234+
}
1235+
1236+
public void OnNext(OT value)
1237+
{
1238+
_onNext(CreateHandle(value));
1239+
}
1240+
1241+
public void OnError(Exception error)
1242+
{
1243+
_onError(error);
1244+
}
1245+
1246+
public void OnCompleted()
1247+
{
1248+
_onCompleted();
1249+
}
1250+
1251+
private static string CreateHandle(object o)
1252+
{
1253+
return ObjectHandles.ObjectHandler.GetHandle(o.GetType().ToString(), o);
1254+
}
1255+
}
1256+
}
11941257
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace ExcelDna.Integration
5+
{
6+
internal class ExcelTypeDescriptor
7+
{
8+
private static Dictionary<Type, List<object>> typeAttributes = new Dictionary<Type, List<object>>();
9+
10+
public static void AddCustomAttributes(Type t, IEnumerable<object> attributes)
11+
{
12+
if (!typeAttributes.ContainsKey(t))
13+
typeAttributes.Add(t, new List<object>());
14+
15+
typeAttributes[t].AddRange(attributes);
16+
}
17+
18+
public static List<object> GetCustomAttributes(Type t)
19+
{
20+
List<object> result = new List<object>();
21+
result.AddRange(t.GetCustomAttributes(true));
22+
23+
if (typeAttributes.ContainsKey(t))
24+
result.AddRange(typeAttributes[t]);
25+
26+
return result;
27+
}
28+
}
29+
}

Source/ExcelDna.Integration/ExtendedRegistration/AsyncRegistration.cs

+35-10
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,22 @@ public static IEnumerable<ExcelFunction> ProcessAsyncRegistrations(this IEnumera
3838
if (ReturnsObservable(reg.FunctionLambda))
3939
{
4040
ParameterConversionRegistration.ApplyParameterConversions(reg, ObjectHandles.ObjectHandleRegistration.GetParameterConversionConfiguration());
41-
reg.FunctionLambda = WrapMethodObservable(reg.FunctionLambda);
41+
reg.FunctionLambda = WrapMethodObservable(reg.FunctionLambda, reg.Return.CustomAttributes);
4242
}
4343
else if (ReturnsTask(reg.FunctionLambda) || reg.FunctionAttribute is ExcelAsyncFunctionAttribute)
4444
{
4545
ParameterConversionRegistration.ApplyParameterConversions(reg, ObjectHandles.ObjectHandleRegistration.GetParameterConversionConfiguration());
4646
if (HasCancellationToken(reg.FunctionLambda))
4747
{
4848
reg.FunctionLambda = useNativeAsync ? WrapMethodNativeAsyncTaskWithCancellation(reg.FunctionLambda)
49-
: WrapMethodRunTaskWithCancellation(reg.FunctionLambda);
49+
: WrapMethodRunTaskWithCancellation(reg.FunctionLambda, reg.Return.CustomAttributes);
5050
// Also need to strip out the info for the last argument which is the CancellationToken
5151
reg.ParameterRegistrations.RemoveAt(reg.ParameterRegistrations.Count - 1);
5252
}
5353
else
5454
{
5555
reg.FunctionLambda = useNativeAsync ? WrapMethodNativeAsyncTask(reg.FunctionLambda)
56-
: WrapMethodRunTask(reg.FunctionLambda);
56+
: WrapMethodRunTask(reg.FunctionLambda, reg.Return.CustomAttributes);
5757
}
5858
}
5959
// else do nothing to this registration
@@ -84,7 +84,7 @@ static bool HasCancellationToken(LambdaExpression functionLambda)
8484
return pis.Any() && pis.Last().Type == typeof(CancellationToken);
8585
}
8686

87-
static LambdaExpression WrapMethodRunTask(LambdaExpression functionLambda)
87+
static LambdaExpression WrapMethodRunTask(LambdaExpression functionLambda, List<object> returnCustomAttributes)
8888
{
8989
/* Either, from a lambda expression wrapping a method that looks like this:
9090
*
@@ -116,7 +116,15 @@ static LambdaExpression WrapMethodRunTask(LambdaExpression functionLambda)
116116
*/
117117

118118
bool returnsTask = ReturnsTask(functionLambda);
119-
bool userType = ObjectHandles.TaskObjectHandler.IsUserType(returnsTask ? functionLambda.ReturnType.GetGenericArguments()[0] : functionLambda.ReturnType);
119+
Type returnType = returnsTask ? functionLambda.ReturnType.GetGenericArguments()[0] : functionLambda.ReturnType;
120+
bool userType = ObjectHandles.TaskObjectHandler.IsUserType(returnType);
121+
if (userType)
122+
{
123+
if (!ObjectHandles.ObjectHandleRegistration.HasExcelHandle(returnCustomAttributes))
124+
throw new Exception($"Unsupported task return type {returnType}.");
125+
126+
ObjectHandles.ObjectHandleRegistration.ClearExcelHandle(returnCustomAttributes);
127+
}
120128

121129
// Either RunTask or RunAsTask, depending on whether the method returns Task<string> or string
122130
string runMethodName = returnsTask ? "RunTask" : "RunAsTask";
@@ -155,7 +163,7 @@ static LambdaExpression WrapMethodRunTask(LambdaExpression functionLambda)
155163
return Expression.Lambda(callTaskRun, functionLambda.Name, newParams);
156164
}
157165

158-
static LambdaExpression WrapMethodRunTaskWithCancellation(LambdaExpression functionLambda)
166+
static LambdaExpression WrapMethodRunTaskWithCancellation(LambdaExpression functionLambda, List<object> returnCustomAttributes)
159167
{
160168
/* Either, from a lambda expression that looks like this:
161169
*
@@ -187,7 +195,15 @@ static LambdaExpression WrapMethodRunTaskWithCancellation(LambdaExpression funct
187195
*/
188196

189197
bool returnsTask = ReturnsTask(functionLambda);
190-
bool userType = ObjectHandles.TaskObjectHandler.IsUserType(returnsTask ? functionLambda.ReturnType.GetGenericArguments()[0] : functionLambda.ReturnType);
198+
Type returnType = returnsTask ? functionLambda.ReturnType.GetGenericArguments()[0] : functionLambda.ReturnType;
199+
bool userType = ObjectHandles.TaskObjectHandler.IsUserType(returnType);
200+
if (userType)
201+
{
202+
if (!ObjectHandles.ObjectHandleRegistration.HasExcelHandle(returnCustomAttributes))
203+
throw new Exception($"Unsupported task return type {returnType}.");
204+
205+
ObjectHandles.ObjectHandleRegistration.ClearExcelHandle(returnCustomAttributes);
206+
}
191207

192208
// Either RunTask or RunAsTask, depending on whether the method returns Task<string> or string
193209
string runMethodName = returnsTask ? "RunTaskWithCancellation" : "RunAsTaskWithCancellation";
@@ -341,7 +357,7 @@ static LambdaExpression WrapMethodNativeAsyncTaskWithCancellation(LambdaExpressi
341357
return Expression.Lambda(callTaskRun, functionLambda.Name, allParams);
342358
}
343359

344-
static LambdaExpression WrapMethodObservable(LambdaExpression functionLambda)
360+
static LambdaExpression WrapMethodObservable(LambdaExpression functionLambda, List<object> returnCustomAttributes)
345361
{
346362
/* Either, from a lambda expression that looks like this:
347363
*
@@ -359,10 +375,19 @@ static LambdaExpression WrapMethodObservable(LambdaExpression functionLambda)
359375
*/
360376

361377
// mi returns some kind of IObservable<T>. What is T?
362-
var returnType = functionLambda.ReturnType.GetGenericArguments()[0];
378+
Type returnType = functionLambda.ReturnType.GetGenericArguments()[0];
379+
bool userType = ObjectHandles.TaskObjectHandler.IsUserType(returnType);
380+
if (userType)
381+
{
382+
if (!ObjectHandles.ObjectHandleRegistration.HasExcelHandle(returnCustomAttributes))
383+
throw new Exception($"Unsupported observable return type {returnType}.");
384+
385+
ObjectHandles.ObjectHandleRegistration.ClearExcelHandle(returnCustomAttributes);
386+
}
387+
363388
// Build up the Observe method with the right generic type argument
364389
var obsMethod = typeof(ExcelAsyncUtil)
365-
.GetMember("Observe", MemberTypes.Method, BindingFlags.Static | BindingFlags.Public)
390+
.GetMember(userType ? "ObserveObject" : "Observe", MemberTypes.Method, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
366391
.Cast<MethodInfo>().First(i => i.IsGenericMethodDefinition)
367392
.MakeGenericMethod(returnType);
368393

Source/ExcelDna.Integration/ExtendedRegistration/ExcelFunction.cs

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Linq;
55
using System.Linq.Expressions;
66
using System.Reflection;
7+
using System.Threading.Tasks;
78

89
namespace ExcelDna.Integration.ExtendedRegistration
910
{
@@ -134,6 +135,11 @@ public ExcelFunction(MethodInfo methodInfo)
134135
ReturnRegistration = new ExcelReturn();
135136
ReturnRegistration.CustomAttributes.AddRange(methodInfo.ReturnParameter.GetCustomAttributes(true));
136137

138+
Type returnType = methodInfo.ReturnType;
139+
if (returnType.IsGenericType && (returnType.GetGenericTypeDefinition() == typeof(Task<>) || returnType.GetGenericTypeDefinition() == typeof(IObservable<>)))
140+
returnType = returnType.GetGenericArguments()[0];
141+
ReturnRegistration.CustomAttributes.AddRange(ExcelTypeDescriptor.GetCustomAttributes(returnType));
142+
137143
// Check that we haven't made a mistake
138144
Debug.Assert(IsValid());
139145
}

Source/ExcelDna.Integration/ExtendedRegistration/ExcelParameter.cs

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public ExcelParameter(ParameterInfo parameterInfo)
4747
}
4848
}
4949

50+
CustomAttributes.AddRange(ExcelTypeDescriptor.GetCustomAttributes(parameterInfo.ParameterType));
51+
5052
// Check that the ExcelArgumentAttribute has been set
5153
if (ArgumentAttribute == null)
5254
{

Source/ExcelDna.Integration/ObjectHandles/ObjectHandleRegistration.cs

+39-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System;
33
using System.Collections.Concurrent;
44
using System.Collections.Generic;
5+
using System.Linq;
56
using System.Linq.Expressions;
67
using System.Threading;
78

@@ -15,7 +16,7 @@ public static IEnumerable<ExcelFunction> ProcessObjectHandles(this IEnumerable<E
1516

1617
foreach (var reg in registrations)
1718
{
18-
if (!AssemblyLoader.IsPrimitiveParameterType(reg.FunctionLambda.ReturnType))
19+
if (HasExcelHandle(reg.Return.CustomAttributes))
1920
{
2021
reg.FunctionLambda = LazyLambda.Create(reg.FunctionLambda);
2122

@@ -37,6 +38,39 @@ public static ParameterConversionConfiguration GetParameterConversionConfigurati
3738
return new ParameterConversionConfiguration().AddParameterConversion(GetParameterConversion());
3839
}
3940

41+
public static bool IsMethodSupported(ExcelFunction reg)
42+
{
43+
if (HasExcelHandle(reg.Return.CustomAttributes))
44+
return true;
45+
46+
return reg.Parameters.Any(paramReg => HasExcelHandle(paramReg.CustomAttributes));
47+
}
48+
49+
public static bool HasExcelHandle(List<object> customAttributes)
50+
{
51+
return customAttributes.OfType<ExcelHandleAttribute>().Any();
52+
}
53+
54+
public static void ClearExcelHandle(List<object> customAttributes)
55+
{
56+
customAttributes.RemoveAll(att => att is ExcelHandleAttribute);
57+
}
58+
59+
public static int ProcessAssemblyAttributes(IEnumerable<object> attributes)
60+
{
61+
List<object> excelHandleAttribute = new List<object>();
62+
excelHandleAttribute.Add(new ExcelHandleAttribute());
63+
64+
int result = 0;
65+
foreach (Type t in attributes.OfType<ExcelHandleExternalAttribute>().Select(i => i.Type))
66+
{
67+
ExcelTypeDescriptor.AddCustomAttributes(t, excelHandleAttribute);
68+
++result;
69+
}
70+
71+
return result;
72+
}
73+
4074
static ParameterConversionConfiguration.ReturnConversion CreateReturnConversion<TFrom, TTo>(Expression<Func<TFrom, TTo>> convert)
4175
{
4276
return new ParameterConversionConfiguration.ReturnConversion((unusedReturnType, unusedAttributes) => convert, null, false);
@@ -57,7 +91,7 @@ static Func<Type, IExcelFunctionParameter, LambdaExpression> GetParameterConvers
5791
static LambdaExpression HandleStringConversion(Type type, IExcelFunctionParameter paramReg)
5892
{
5993
// Decide whether to return a conversion function for this parameter
60-
if (AssemblyLoader.IsPrimitiveParameterType(type) || type == typeof(CancellationToken))
94+
if (!HasExcelHandle(paramReg.CustomAttributes))
6195
return null;
6296

6397
var input = Expression.Parameter(typeof(object), "input");
@@ -69,6 +103,9 @@ static LambdaExpression HandleStringConversion(Type type, IExcelFunctionParamete
69103
Expression.Invoke(parse, Expression.Constant(type), input),
70104
type),
71105
input);
106+
107+
ClearExcelHandle(paramReg.CustomAttributes);
108+
72109
return result;
73110
}
74111

0 commit comments

Comments
 (0)