I'm trying desperately to do this.
I've been able to replicate the behavior found on this post.
http://damianblog.com/2009/07/05/excel-wcf/comment-page-1/#comment-64232
however, I am unable to pass an array to an exposed wcf function.
My WCF Service works like this (I have also tried to use arrays of int)
public object[] GetSomeArray()
{
return new object[] { 1, 2, 3, 4};
}
public object[] ReturnSomeArray(object someArray)
{
object[] temp = (object[]) someArray;
for (int i = 0; i < temp.Length; i++)
{
temp[i] = (int)temp[i] + 1;
}
return temp;
}
my VBA code looks like this.
Dim addr As String
...
Dim service1 As Object
Set service1 = GetObject(addr)
Dim columnsVar
columnsVar = Array(1, 2, 3)
Dim anotherArray As Variant
anotherArray = service1.ReturnSomeArray(columnsVar)
I always have problems on the last line above. I don't understand why if I'm able to return an array from my WCF service that I'm not able pass that same array as a parameter to another WCF function.
I am getting a serialization error.
Any help would be appreciated.
I have similar problems with Type mismatch error only if I declare array variable in VBA in this way:
Dim anotherArray() As Variant
but the error disappears if the variable is defined in this way:
Dim anotherArray As Variant
Some other differences between your and my similar solutions are:
//C#- my solution- without array[] definition:
public object[] ReturnSomeArray(object someArray)
//VBA- my solution -without array() definition:
Dim someArray As Variant
EDIT: 2013-08-28
Working with C#-Excel-Interop I prefer try&test way of searching solution. If anything works then I stick to it and sometime I miss to indicate the source of the solution or logic.
Below you will find code which includes LINQ to operate with arrays. These code snippets works in both direction- get data from C# to VBA >> pass it back to C# for sorting >> return to VBA. I hope it will help you more to finally solve your problems.
First: some C# code
public object[] GetSomeArray()
{
return new object[] { 5, 2, 1, 7, 9, 1, 5, 7 };
}
public double[] ArraySorted(object tablica)
{
object[] obj = (object[])tablica;
var filtr = from i in obj
orderby Convert.ToDouble(i)
select Convert.ToDouble(i);
double[] wynik = (double[])filtr.ToArray();
return wynik;
}
Second: some VBA code
Sub qTest_second_attempt()
'declare array variable
Dim tmp()
'and other variables
Dim addr As String
addr = "UDFArrayLinqTest.ArrayLinq"
'get references
Dim service1 As Object
Set service1 = CreateObject(addr)
'get array from C#
tmp = service1.GetSomeArray()
'pass this array to C# back to sort it
Dim someArray As Variant
someArray = service1.ArraySorted(tmp)
'check the result in Immediate window
Debug.Print Join(WorksheetFunction.Transpose(WorksheetFunction.Transpose(someArray)))
'result: 1 1 2 5 5 7 7 9
End Sub
Related
I was trying to pass a 3D array into a MATLAB function from C# with COM. Here's my code:
// here's the code in C#
MLApp.MLApp matlab = new MLApp.MLApp();
//load function
matlab.Execute(#"cd C:\Users\1303092.local\Matlab\EmGm");
//initialized input and output
int[,,] data1 = new int[2,3,5]{{{1,2,3,4,5},{1,2,3,4,5},{1,2,3,4,5}},{{1,2,3,4,5},{1,2,3,4,5},{1,2,3,4,5}}};
object result1 = null;
object result2 = null;
//use function with the data from C#
matlab.Feval("myfunc", 2, out result1, data1);
//type casting
object[] res1 = result1 as object[];
double[, ,] data2 = res1[1] as double[, ,];
//use function with the casted data from MATLAB
matlab.Feval("myfunc", 2, out result2, data2);
object[] res2 = result2 as object[];
// here's the MATLAB function
function [x,y] = myfunc(a)
x = a;
y = ones(2,3,5);
end
I have inserted a breakpoint at the end of the code to check the value in the variables, which is shown as:
click here for image
It seems C# has no difficulty with taking the return values since it did recognise the returned parameter "y". However, there may be some type casting issue when MATLAB takes the array from C#, since the returned parameter "x" was always been recognised as NULL in C#.
I have also tried 2D array in the exactly same way, and everything was running well. Is the error related to the dimensionality here? Any idea guys?
My C# COM DLL has a method that accepts a float array and a long int array. It returns a float.
From VBA in an MS Access module, I create an array of type single and another of type long, populate them, create the DLL app.class object and then call its method with the two arrays. But I get a "type mismatch" error.
The following is the actual code, but it is simple because I'm trying to work out the communications before adding the "real" code.
C# code:
public float JustTesting(float[] Array1, long[] Array2)
{
return 96.0F;
}
VBA code:
Public Sub Test()
Dim a1(0 To 0) As Single, a2(0 To 0) As Long, sng As Single
a1(0) = 5
a2(0) = 10
Dim o As Variant
Set o = CreateObject("MyApp.MyClass")
sng = o.JustTesting(a1, a2)
Debug.Print CStr(sng)
Set o = Nothing
End Sub
Where is the data type mismatch?
A Long in VBA is only 32 bits, the same was as an int in C#. So your method needs to take an array of ints
public float JustTesting(float[] Array1, int[] Array2)
{
return 96.0F;
}
What I am trying to do is populate an ArrayList using .AddRange() method in VBA using late binding on native C# ArrayList, but I can't figure out how to pass an object other than another ArrayList to it as argument... anything else I have tried so far fails...
So basically what I am doing now (Note: list is C#'s ArrayList via mscorlib.dll)
Dim list as Object
Set list = CreateObject("System.Collections.ArrayList")
Dim i As Long
For i = 1 To 5
list.Add Range("A" & i).Value2
Next
But this is quite inefficient and ugly if for example i can be = 500K.
In VBA this also works:
ArrayList1.AddRange ArrayList2
But what I really need/would like is to pass an array instead of ArrayList2
So I heard I can pass an array to the .AddRange() parameter in .NET. I tested it in a small C# console application and it seemed to work just fine. The below works just fine in a pure C# console application.
ArrayList list = new ArrayList();
string[] strArr = new string[1];
strArr[0] = "hello";
list.AddRange(strArr);
So going back to my VBA module trying to do the same it fails..
Dim arr(1) As Variant
arr(0) = "WHY!?"
Dim arrr As Variant
arrr = Range("A1:A5").Value
list.AddRange arr ' Fail
list.AddRange arrr ' Fail
list.AddRange Range("A1:A5").Value ' Fail
Note: I have tried passing a native VBA Array of Variants and Collection, Ranges - everything except another ArrayList failed.
How do I pass a native VBA array as a parameter to an ArrayList?
Or any alternative for creating a collection from Range without looping??
Bonus question: *Or maybe there is another built-in .Net COM Visible Collection that can be populated from VBA Range Object or Array without looping and that already has a .Reverse?
NOTE: I am aware that I can make a .dll wrapper to achieve this but I am interested in native solutions - if any exist.
Update
To better illustrate why I want to completely avoid explicit iteration - here's an example (it uses only one column for simplicity)
Sub Main()
' Initialize the .NET's ArrayList
Dim list As Object
Set list = CreateObject("System.Collections.ArrayList")
' There are two ways to populate the list.
' I selected this one as it's more efficient than a loop on a small set
' For details, see: http://www.dotnetperls.com/array-optimization
list.Add Range("A1")
list.Add Range("A2")
list.Add Range("A3")
list.Add Range("A4")
list.Add Range("A5") ' It's OK with only five values but not with 50K.
' Alternative way to populate the list
' According to the above link this method has a worse performance
'Dim i As Long
'Dim arr2 As Variant
'arr2 = Range("A1:A5").Value2
'For i = 1 To 5
' list.Add arr2(i, 1)
'Next
' Call native ArrayList.Reverse
' Note: no looping required!
list.Reverse
' Get an array from the list
Dim arr As Variant
arr = list.ToArray
' Print reversed to Immediate Window
'Debug.Print Join(arr, Chr(10))
' Print reversed list to spreadsheet starting at B1
Range("B1").Resize(UBound(arr) + 1, 1) = Application.Transpose(arr)
End Sub
Please notice: the only time I have to loop is to populate the list (ArrayList) what I would love to do would be just to find a way to load the arr2 into an ArrayList or another .NET compatible type without loops.
At this point I see that there is no native/built-in way to do so that's why I think I am going to try to implement my own way and maybe if it works out submit an update for the Interop library.
list.AddRange Range("A1:A5").Value
The range's Value gets marshaled as an array. That's about the most basic .NET type you can imagine of course. This one however has bells on, it is not a "normal" .NET array. VBA is a runtime environment that likes to create arrays whose first element starts at index 1. That's a non-conformant array type in .NET, the CLR likes arrays whose first element starts at index 0. The only .NET type you can use for those is the System.Array class.
An extra complication is that the array is a two-dimensional array. That puts the kibosh on your attempts to get them converted to an ArrayList, multi-dimensional arrays don't have an enumerator.
So this code works just fine:
public void AddRange(object arg) {
var arr = (Array)arg;
for (int ix = ar.GetLowerBound(0); ix <= arr2.GetUpperBound(0); ++ix) {
Debug.Print(arr.GetValue(ix, 1).ToString());
}
}
You probably don't care for that too much. You could use a little accessor class that wraps the awkward Array and acts like a vector:
class Vba1DRange : IEnumerable<double> {
private Array arr;
public Vba1DRange(object vba) {
arr = (Array)vba;
}
public double this[int index] {
get { return Convert.ToDouble(arr.GetValue(index + 1, 1)); }
set { arr.SetValue(value, index + 1, 1); }
}
public int Length { get { return arr.GetUpperBound(0); } }
public IEnumerator<double> GetEnumerator() {
int upper = Length;
for (int index = 0; index < upper; ++index)
yield return this[index];
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
return GetEnumerator();
}
Now you can write it the "natural" way:
public void AddRange(object arg) {
var arr = new Vba1DRange(arg);
foreach (double elem in arr) {
Debug.Print(elem.ToString());
}
// or:
for (int ix = 0; ix < arr.Length; ++ix) {
Debug.Print(arr[ix].ToString());
}
// or:
var list = new List<double>(arr);
}
Here's a proof of concept as an expansion of #phoog's comment. As he points out, the AddRange method takes an ICollection.
Implementing ICollection
In the VBA IDE, add a reference to mscorlib.tlb: Tools--->References, then browse to find your .NET Framework mscorlib.tlb. Mine was at "C:\Windows\Microsoft.NET\Framework\vX.X.XXXXX\mscorlib.tlb".
Create a new class called "clsWrapLongArray" as follows:
Option Compare Database
Option Explicit
Implements ICollection
Dim m_lngArr() As Long
Public Sub LoadArray(lngArr() As Long)
m_lngArr = lngArr
End Sub
Private Sub ICollection_CopyTo(ByVal arr As mscorlib.Array, ByVal index As Long)
Dim i As Long
Dim j As Long
j = LBound(m_lngArr)
For i = index To index + (ICollection_Count - 1)
arr.SetValue m_lngArr(j), i
j = j + 1
Next
End Sub
Private Property Get ICollection_Count() As Long
ICollection_Count = UBound(m_lngArr) - LBound(m_lngArr) + 1
End Property
Private Property Get ICollection_IsSynchronized() As Boolean
'Never called for this example, so I'm leaving it blank
End Property
Private Property Get ICollection_SyncRoot() As Variant
'Never called for this example, so I'm leaving it blank
End Property
Here is the Array.SetValue method used.
Create a new module called "mdlMain" to run the example:
Option Compare Database
Option Explicit
Public Sub Main()
Dim arr(0 To 3) As Long
arr(0) = 1
arr(1) = 2
arr(2) = 3
arr(3) = 4
Dim ArrList As ArrayList
Set ArrList = New ArrayList
Dim wrap As clsWrapLongArray
Set wrap = New clsWrapLongArray
wrap.LoadArray arr
ArrList.AddRange wrap
End Sub
If you put a breakpoint on the End Sub and run Main(), you can see by inspecting ArrList in the immediate window that it contains the 4 values added from the Long array. You can also step through the code to see that ArrayList actually calls the ICollection interface members Count and CopyTo to make it happen.
I could see that it might be possible to expand on this idea to create Factory functions to wrap types like Excel Ranges, VBA Arrays, Collections, etc.
This same method works with String arrays! :D
I found some very close answers by Googling "System.ArrayList" "SAFEARRAY" and "System.Array" "SAFEARRAY" and refining with "COM Marshaling", since VBA arrays are COM SAFEARRAYs, but nothing quite explained how one might convert them for use in .NET calls.
It's inefficient because .Value2 is a COM call. Calling it thousands of times adds a lot of interop overhead. Adding items to an array in a loop should be MUCH faster:
Dim list as Object
Set list = CreateObject("System.Collections.ArrayList")
Dim i As Long
Dim a as Variant
a = Range("A1:A5").Value2
For i = 1 To 5
list.Add a(i,1)
Next
How do I pass a native VBA array as a parameter to an ArrayList?
I don't think you can - a native VBA array is not an ICollection, so the only way to create one would be to copy the values in a loop.
The reason it works in a C# console application is because arrays in C# are ICollections, so the AddRange method accepts them just fine.
I have a VB6 assembly which I need to use in my .NET application and generated the Interop DLL for usage with .NET via tlbimp.exe.
The VB6 assembly has a function that has a byref array parameter. I don't want to change anything in the VB6 assembly, so I hope there is a solution to get the following working.
It is filling the array and I want to use it in my .NET code (c# or vb.net).
Example of the VB6 function (file NativeClass.cls):
Public Function GetData(ByRef data() As String) As Integer
Dim tResults() As String
Dim sRecordCount As String
Dim lCount As Long
' load data
sRecordCount = dataDummyObject.RecordCount
ReDim tResults(sRecordCount, 2)
' fill the array in a loop
For lCount = 0 To sRecordCount - 1
tResults(lCount, 0) = dataDummyObject.Fields("property1")
tResults(lCount, 1) = dataDummyObject.Fields("property2")
If (sRecordCount - 1 - lCount) > 0 Then
Call dataDummyObject.MoveNext
End If
End For
data = tResults
GetData = sRecordCount
End Function
Now I want to use it from VB.NET:
Private _nativeAssembly As New NativeClass()
Public Function GetDataFromNativeAssembly() As String()
Dim loadedData As String() = Nothing
_nativeAssembly.GetData(loadedData)
Return loadedData
End Function
C# version:
private NativeClass _nativeAssembly = null;
public string[] GetDataFromNativeAssembly()
{
string[] loadedData = null;
_nativeAssembly.GetData(loadedData);
return loadedData;
}
But when executing the code I get following Exception:
System.Runtime.InteropServices.SafeArrayRankMismatchException: SafeArray of rank 2 has been passed to a method expecting an array of rank 1.
I really need help to solve this problem! Thanks for any piece of advice!
I don't think you can solve this without modifying the VB6 code. Try declaring the function as
Public Function GetData(ByRef data As Variant) As Integer
or
Public Function GetData(ByRef data As Object) As Integer
The ReDim to string array should work fine from Variant. I remember doing it like this all the time because of VB6 not letting a 2D array as a parameter.
When inspecting it from .NET you should see the type. I don't have a VB6 IDE on my machine to verify this.
If one works you should be able to cast over to the String(,) you expect.
This is air code, but you could try this in the VB.Net? Note the additional comma to indicate a 2-D array.
Dim loadedData As String(,) = Nothing
I'm using Matlab R2010b and I've got an enum under C# :
[Flags()]
public enum MyFormat
{
value1 = 0,
value2 = 1,
value3 = 2,
value4 = 4,
value5 = 8
}
In a method, I've got an argument which is a format :
public void MyMethod(MyFormat format, double number)
{
....
}
Then I work with Matlab, and I want to use the method MyMethod. In a standard way, here is the code :
>>format = MyNamespace.MyFormat.value1;
>>MyNamespace.MyMethod(format, 15);
The issue comes when I try to pass a "multiple value" as a MyFormat :
>>format = MyNamespace.MyFormat.value1 | MyNamespace.MyFormat.value2;
>>MyNamespace.MyMethod(format, 15);
I found a easy solution but it takes a more recent version of Matlab R2011a. Another solution found here was to implement this function in Matlab :
function enum = EnumParse(typename, value)
type = System.Type.GetType(typename);
values = regexp(value, ', ', 'split');
enum = cell(1, length(values));
for i = 1:length(values)
enum{i} = System.Enum.Parse(type, values{i});
end
end
However, the line System.Type.GetType('MyNamespace.MyFormat') returns me a null value, whereas it is not null with a type System.Type.GetType('System.String') for example.
My question is then how to parse multiple values to an enum ?
Thanks !
Does MatLab, for it's Enum.Parse take any multiple overloads for example
type, value, value ...?