public static unsafe int TryExcelImpl(int xlFunction, out object result, params object[] parameters) { int xlReturn; // TODO: EXCEL2007 - Review for multithreaded.... // Set up the memory to hold the result from the call XlOper resultOper = new XlOper(); resultOper.xlType = XlType.XlTypeEmpty; XlOper* pResultOper = &resultOper; // No need to pin for local struct // Special kind of ObjectArrayMarshaler for the parameters (rank 1) using (XlObjectArrayMarshaler paramMarshaler = new XlObjectArrayMarshaler(1, true)) { XlOper** ppOperParameters = (XlOper**)paramMarshaler.MarshalManagedToNative(parameters); xlReturn = Excel4v(xlFunction, pResultOper, parameters.Length, ppOperParameters); } // pResultOper now holds the result of the evaluated function // Get ObjectMarshaler for the return value ICustomMarshaler m = XlObjectMarshaler.GetInstance(""); result = m.MarshalNativeToManaged((IntPtr)pResultOper); // And free any memory allocated by Excel Excel4v(xlFree, (XlOper*)IntPtr.Zero, 1, &pResultOper); return xlReturn; }
public unsafe IntPtr MarshalManagedToNative(object ManagedObj) { // CONSIDER: Checking for null, checking object type // DOCUMENT: This function is not called if the return is null! // DOCUMENT: A null pointer is immediately returned to Excel, resulting in #NUM! // CONSIDER: Managing memory differently // Here we allocate and clear when the next array is returned // we might also return XLOPER and have xlFree called back. // If array is too big!?, we just truncate // TODO: Remove duplication - due to fixed / pointer interaction Reset(true); ushort rows; int rowBase; ushort columns; // those in the returned array int columnBase; int allColumns; // all in the managed array if (rank == 1) { object[] objects = (object[])ManagedObj; rows = 1; rowBase = 0; allColumns = objects.Length; columns = (ushort)Math.Min(objects.Length, ushort.MaxValue); columnBase = objects.GetLowerBound(0); } else if (rank == 2) { object[,] objects = (object[,])ManagedObj; rows = (ushort)Math.Min(objects.GetLength(0), ushort.MaxValue); rowBase = objects.GetLowerBound(0); allColumns = objects.GetLength(1); columns = (ushort)Math.Min(objects.GetLength(1), ushort.MaxValue); columnBase = objects.GetLowerBound(1); } else { throw new InvalidOperationException("Damaged XlObjectArrayMarshaler rank"); } // Some counters for the multi-pass int cbNativeStrings = 0; int numReferenceOpers = 0; int numReferences = 0; // Allocate native space int cbNative = Marshal.SizeOf(typeof(XlOper)) + // OPER that is returned Marshal.SizeOf(typeof(XlOper)) * (rows * columns); // Array of OPER inside the result pNative = Marshal.AllocCoTaskMem(cbNative); // Set up returned OPER XlOper* pOper = (XlOper*)pNative; // Excel chokes badly on empty arrays (e.g. crash in function wizard) - rather return the default erro value, #VALUE! if (rows * columns == 0) { pOper->errValue = (ushort)IntegrationMarshalHelpers.ExcelError_ExcelErrorValue; pOper->xlType = XlType.XlTypeError; } else { // Some contents to put into an array.... pOper->xlType = XlType.XlTypeArray; pOper->arrayValue.Rows = rows; pOper->arrayValue.Columns = columns; pOper->arrayValue.pOpers = ((XlOper*)pNative + 1); } // This loop won't be entered in the empty-array case (rows * columns == 0) for (int i = 0; i < rows * columns; i++) { // Get the right object out of the array object obj; if (rank == 1) { obj = ((object[])ManagedObj)[columnBase + i]; } else { int row = i / allColumns; int column = i % allColumns; obj = ((object[,])ManagedObj)[rowBase + row, columnBase + column]; } // Get the right pOper pOper = (XlOper*)pNative + i + 1; // Set up the oper from the object if (obj is double) { pOper->numValue = (double)obj; pOper->xlType = XlType.XlTypeNumber; } else if (obj is string) { // We count all of the string lengths, string str = (string)obj; cbNativeStrings += Math.Min(str.Length, 255) + 1; // mark the Oper as a string, and // later allocate memory and return to fix pointers pOper->xlType = XlType.XlTypeString; } else if (obj is DateTime) { pOper->numValue = ((DateTime)obj).ToOADate(); pOper->xlType = XlType.XlTypeNumber; } else if (IntegrationMarshalHelpers.IsExcelErrorObject(obj)) { pOper->errValue = (ushort)IntegrationMarshalHelpers.ExcelErrorGetValue(obj); pOper->xlType = XlType.XlTypeError; } else if (IntegrationMarshalHelpers.IsExcelMissingObject(obj)) { pOper->xlType = XlType.XlTypeMissing; } else if (IntegrationMarshalHelpers.IsExcelEmptyObject(obj)) { pOper->xlType = XlType.XlTypeEmpty; } else if (obj is bool) { pOper->boolValue = (bool)obj ? (ushort)1 : (ushort)0; pOper->xlType = XlType.XlTypeBoolean; } else if (obj is short) { pOper->numValue = (double)((short)obj); // int16 in XlOper pOper->xlType = XlType.XlTypeNumber; } else if (obj is ushort) { pOper->numValue = (double)((ushort)obj); pOper->xlType = XlType.XlTypeNumber; } else if (obj is int) { pOper->numValue = (double)((int)obj); pOper->xlType = XlType.XlTypeNumber; } else if (obj is uint) { pOper->numValue = (double)((uint)obj); pOper->xlType = XlType.XlTypeNumber; } else if (obj is long) { pOper->numValue = (double)((long)obj); pOper->xlType = XlType.XlTypeNumber; } else if (obj is ulong) { pOper->numValue = (double)((ulong)obj); pOper->xlType = XlType.XlTypeNumber; } else if (obj is decimal) { pOper->numValue = (double)((decimal)obj); pOper->xlType = XlType.XlTypeNumber; } else if (obj is float) { pOper->numValue = (double)((float)obj); pOper->xlType = XlType.XlTypeNumber; } else if (IntegrationMarshalHelpers.IsExcelReferenceObject(obj)) { pOper->xlType = XlType.XlTypeReference; // First we count all of these, // later allocate memory and return to fix pointers numReferenceOpers++; numReferences += IntegrationMarshalHelpers.ExcelReferenceGetRectangleCount(obj); // ((ExcelReference)obj).InnerReferences.Count; } else if (obj is object[]) { XlObjectArrayMarshaler m = new XlObjectArrayMarshaler(1); nestedInstances.Add(m); XlOper* pNested = (XlOper*)m.MarshalManagedToNative(obj); if (pNested->xlType == XlType.XlTypeArray) { pOper->xlType = XlType.XlTypeArray; pOper->arrayValue.Rows = pNested->arrayValue.Rows; pOper->arrayValue.Columns = pNested->arrayValue.Columns; pOper->arrayValue.pOpers = pNested->arrayValue.pOpers; } else { // This is the case where the array passed in has 0 length. // We set to an error to at least have a valid XLOPER pOper->xlType = XlType.XlTypeError; pOper->errValue = (ushort)IntegrationMarshalHelpers.ExcelError_ExcelErrorValue; } } else if (obj is object[,]) { XlObjectArrayMarshaler m = new XlObjectArrayMarshaler(2); nestedInstances.Add(m); XlOper* pNested = (XlOper*)m.MarshalManagedToNative(obj); if (pNested->xlType == XlType.XlTypeArray) { pOper->xlType = XlType.XlTypeArray; pOper->arrayValue.Rows = pNested->arrayValue.Rows; pOper->arrayValue.Columns = pNested->arrayValue.Columns; pOper->arrayValue.pOpers = pNested->arrayValue.pOpers; } else { // This is the case where the array passed in has 0 length. // We set to an error to at least have a valid XLOPER pOper->xlType = XlType.XlTypeError; pOper->errValue = (ushort)IntegrationMarshalHelpers.ExcelError_ExcelErrorValue; } } else if (obj is System.Reflection.Missing) { pOper->xlType = XlType.XlTypeMissing; } else if (obj == null) { // DOCUMENT: I return Empty for nulls inside the Array, // which is not consistent with what happens in other settings. // In particular not consistent with the results of the XlObjectMarshaler // (which is not called when a null is returned, // and interpreted as ExcelErrorNum in Excel) // This works well for xlSet though. // CONSIDER: Create an ExcelEmpty type to allow this to be more explicit, // and return ErrNum here pOper->xlType = XlType.XlTypeEmpty; } else { // Default error return pOper->errValue = (ushort)IntegrationMarshalHelpers.ExcelError_ExcelErrorValue; pOper->xlType = XlType.XlTypeError; } } // end of first pass // Now handle strings if (cbNativeStrings > 0) { // Allocate room for all the strings pNativeStrings = Marshal.AllocCoTaskMem(cbNativeStrings); // Go through the Opers and set each string byte* pCurrent = (byte*)pNativeStrings; for (int i = 0; i < rows * columns; i++) { // Get the corresponding oper pOper = (XlOper*)pNative + i + 1; if (pOper->xlType == XlType.XlTypeString) { // Get the string from the managed array string str; if (rank == 1) { str = (string)((object[])ManagedObj)[i]; } else { int row = i / allColumns; int column = i % allColumns; str = (string)((object[,])ManagedObj)[rowBase + row, columnBase + column]; } XlString* pXlString = (XlString*)pCurrent; pOper->pstrValue = pXlString; int charCount = Math.Min(str.Length, 255); fixed (char* psrc = str) { // Write the data and length to the XlString // Support for system codepage by hmd // int written = Encoding.ASCII.GetBytes(psrc, charCount, pXlString->Data, 255); Encoding enc = Encoding.GetEncoding(ASCIIEncoding.Default.CodePage, EncoderFallback.ReplacementFallback, DecoderFallback.ReplacementFallback); int written = enc.GetBytes(psrc, charCount, pXlString->Data, 255); pXlString->Length = (byte)written; // Increment pointer within allocated memory pCurrent += written + 1; } } } } // Now handle references if (numReferenceOpers > 0) { // Allocate room for all the references int cbNativeReferences = numReferenceOpers * sizeof(ushort) + numReferences * Marshal.SizeOf(typeof(XlOper.XlRectangle)); pNativeReferences = Marshal.AllocCoTaskMem(cbNativeReferences); IntPtr pCurrent = pNativeReferences; // Go through the Opers and set each reference int refOperIndex = 0; for (int i = 0; i < rows * columns && refOperIndex < numReferenceOpers; i++) { // Get the corresponding oper pOper = (XlOper*)pNative + i + 1; if (pOper->xlType == XlType.XlTypeReference) { // Get the reference from the managed array object /*ExcelReference*/ r; if (rank == 1) { r = /*(ExcelReference)*/((object[])ManagedObj)[i]; } else { int row = i / allColumns; int column = i % allColumns; r = /*(ExcelReference)*/((object[,])ManagedObj)[rowBase + row, columnBase + column]; } int refCount = IntegrationMarshalHelpers.ExcelReferenceGetRectangleCount(r); // r.InnerReferences.Count int numBytes = Marshal.SizeOf(typeof(ushort)) + refCount * Marshal.SizeOf(typeof(XlOper.XlRectangle)); IntegrationMarshalHelpers.SetExcelReference(pOper, (XlOper.XlMultiRef*)pCurrent, r); pCurrent = new IntPtr(unchecked(pCurrent.ToInt32() + (int)numBytes)); refOperIndex++; } } } if (!isExcel4v) { // For big allocations, ensure that Excel allows us to free the memory if (rows * columns * 16 + cbNativeStrings + numReferences * 8 > 65535) pOper->xlType |= XlType.XlBitDLLFree; // We are done return pNative; } else { // For the Excel4v call, we need to return an array // which will contain the pointers to the Opers. int cbOperPointers = columns * Marshal.SizeOf(typeof(XlOper*)); pOperPointers = Marshal.AllocCoTaskMem(cbOperPointers); XlOper** pOpers = (XlOper**)pOperPointers; for (int i = 0; i < columns; i++) { pOpers[i] = (XlOper*)pNative + i + 1; } return pOperPointers; } }
public static ICustomMarshaler GetInstance(string marshalCookie) { // marshalCookie denotes the array rank // must be 1 or 2 if (marshalCookie == "1") { if (instance1 == null) instance1 = new XlObjectArrayMarshaler(1); return instance1; } else if (marshalCookie == "2") { if (instance2 == null) instance2 = new XlObjectArrayMarshaler(2); return instance2; } throw new ArgumentException("Invalid cookie for XlObjectArrayMarshaler"); }