Calling COM in C# from VBScript with Array - c#

Calling a COM function written in C# from VBScript is OK in first example with strings.
public bool IsEqualTo(string firstString, string SecondString)
{
bool areEqual = String.Equals(firstString, SecondString, StringComparison.Ordinal);
if (areEqual){
return true;
}
else return false;
}
VBScript: MsgBox oTestCom.IsEqualTo(one,one)
Unsure how to pass and manipulate arrays in second example. Is it best to use Array, ArrayList or Object?
public Array SortAscending (Array firstArray)
{
firstArray.Sort;
return firstArray;
}
VBScript: arrout = oTestCom.SortAscending("car","plane","boat")

In C#, use object as the array parameter type. In VBScript, construct the array using the Array function. Here is an example:
C# code:
using System;
using System.Linq;
using System.Runtime.InteropServices;
namespace MySampleComX
{
[ComVisible(true)]
public class Class1
{
public void SortIntArray(ref object array)
{
if (array.GetType() != typeof(object[]))
{
throw new ArgumentException("Argument must be an array of integers");
}
array = ((object[]) array).OrderBy(Convert.ToInt32).ToArray();
}
}
}
VBScript code:
Set obj = CreateObject("MySampleComX.Class1")
arr = Array(3, 1, 2)
obj.SortIntArray arr
MsgBox Join(arr) ' Outputs "1 2 3"

please try this, I try and can be work
C# code
public object[] TestAray(ref object array)
{
if (array.GetType() != typeof(object[]))
{
throw new ArgumentException("Argument must be an array of integers");
}
var StrArray = ((object[])array).Cast<string>().ToArray();
string[] res = StrArray;
for (int i = 0; i < res.Length; i++)
{
string tids;
tids = Convert.ToString(res[i]);
}
}
VBScript code
set obj=createObject("JustLib.TP_RateSchedules")
dim a
a=Array("5","10","15","20")
obj.TestAray(a)

Related

How to create optional parameters using object array

I am creating a method which MUST be use with or without parameters.
I am using object array so that I can hold string, integer, binary types, etc at the same time.
Method:
SQLDB_UsingReader(string strSQL_WithParam, params object[,] obj)
Error:
The array must be a single dimensional array.
P.S
This method SQLDB_UsingReader(string strSQL_WithParam, object[,] obj) is working, but when I add "params" as the solution I searched when creating optional parameter, the error occurs.
CODE
public void SQLDB_UsingReader(string strSQL_WithParam, params object[,] obj)
{
try
{
using (SqlCommand mCmd = new SqlCommand(strSQL_WithParam, mConn))
{
for (int i = 0; i < obj.Length / 2; i++)
{
if (obj[i, 1] == null || obj[i, 1].ToString() == "" || obj[i, 1].ToString().Length == 0)
{ mCmd.Parameters.Add(new SqlParameter(obj[i, 0].ToString(), DBNull.Value)); }
else
{ mCmd.Parameters.Add(new SqlParameter(obj[i, 0].ToString(), obj[i, 1])); }
}
mConn.Open();
mDataReader = mCmd.ExecuteReader();
mConn.Close();
}
}
catch (Exception ex) { ex.ToString(); }
}
Use Dictionary<string, object>. You have two values: a string and an object per parameter you wish to set. Either create a class that has one of each and use params for an array of that class or simply use the built in type for a collection of named values.
public void SQLDB_UsingReader(string strSQL_WithParam, IDictionary<string, object> obj)
{
try
{
string where = obj.Any() ? ("where " + string.Join("AND", obj
.Select(x => x.Key +"==#" + x.Key)) : "";
using (SqlCommand mCmd = new SqlCommand(strSQL_WithParam + where, mConn))
{
foreach pair in obj
{
... (use pair.Value and pair.Key)
}
...
}
}
...
}

C# Invoke with out parameter using .net 3.5

I want to invoke a function with two out parameters and bool as return value. Now my problem is I see that those two parameters are changed when I debug, but still they are back to length = 0 (the way they are initialized) when the function returns.
I've seen that there's a great solution for the .net-framework 4, but unfortunaly I have to use the .net-framework 3.5.
Here's my code:
public delegate bool GetAllCheckedItemsDelegate(out int[] cTypes, out int[] cFiles);
public bool GetAllCheckedItems(out int[] cTypes , out int[] cFiles )
{
if (ListView.InvokeRequired)
{
cTypes = new int[0];
cFiles = new int[0];
return (bool)ListView.Invoke(new GetAllCheckedItemsDelegate(GetAllCheckedItems), new object[] { cTypes, cFiles });
}
else
{
cTypes = new int[ListView.CheckedItems.Count];
cFiles = new int[ListView.CheckedItems.Count];
for (int i = 0; i < ListView.CheckedItems.Count; i++)
{
// ......code......
}
return (ListView.CheckedItems.Count > 0);
}
}
I do not really like "out" keyword, so what about of using a class (Row) that contains information:
using SCG = System.Collections.Generic;
using System.Linq;
public class Row {
public int CheckedType { get; set; }
public int CheckedFile { get; set; }
}
...
public delegate SCG.IEnumerable<Row> GetAllCheckedItemsDelegate();
public bool GetAllCheckedItems() {
if (ListView.InvokeRequired) {
var rows = ListView.Invoke(new GetAllCheckedItemsDelegate(GetAllCheckedItems)
, new object[] {});
return rows.Count() > 0;
} else {
var rows = new SCG.List<Row>();
for (int i = 0; i < ListView.CheckedItems.Count; i++) {
// create and set row
var row = new Row { CheckedType = x, CheckedFile = y };
...
rows.Add(row);
}
return rows.AsReadOnly();
}
}
return (bool)ListView.Invoke(..., new object[] { cTypes, cFiles });
That modifies the object[] elements. C# does not provide syntax to get it to update the arguments of the method, there's an extra level of indirection that you can't bridge with code. It is up to you to copy the references back. Nothing to worry about, you are not actually copying the array content, just the references:
var args = new object[] { null, null };
var dlg = new GetAllCheckedItemsDelegate(GetAllCheckedItems);
var retval = (bool)ListView.Invoke(dlg, args);
cTypes = (int[])args[0];
cFiles = (int[])args[1];
return retval;
Nothing pretty about it of course. Do keep in mind that you are certainly doing something very unpretty, you have no guarantee whatsoever that you get the items you expect to get. This code runs at a very unpredictable moment in time, quite disjoint from the code that's running on the UI thread. If the user is busy checking items while the worker is running then you get a quite random selection. A snapshot of the list box state that very quickly turns stale.
This almost always requires you to disable the list box while the worker is running so you provide a guarantee that the result you compute matches the list. That in turn means that it is no longer useful to write this code at all, you might as well obtain the list before you start the thread. Which is the real solution.
I've found a solution that works for me. Still I'm open to different (and better) approaches if you think that this is not a good way to solve this.
public delegate bool BoolDelegate();
public bool GetAllCheckedItems(out int[] cTypes , out int[] cFiles )
{
if (ListView.InvokeRequired)
{
int[] cTypesHelpVar = new int[0];
int[] cFilesHelpVar = new int[0];
bool ret = (bool)ListView.Invoke((BoolDelegate) (() => GetAllCheckedItems(out cTypesHelpVar, out cFilesHelpVar)));
cTypes = cTypesHelpVar;
cFiles = cFilesHelpVar;
return ret;
}
else
{
cTypes = new int[ListView.CheckedItems.Count];
cFiles = new int[ListView.CheckedItems.Count];
for (int i = 0; i < ListView.CheckedItems.Count; i++)
{
//.... code ....
}
return (ListView.CheckedItems.Count > 0);
}
}

How can I shuffle my list of strings? [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Randomize a List<T> in C#
I thought I had my code working but now it seems not. Here's what I have:
public class NoteDetail
{
public NoteDetail()
{
_noteDetails = new List<string>();
}
public IList<string> NoteDetails { get { return _noteDetails; } }
private readonly List<string> _noteDetails;
}
I populate my details like this:
var noteDetail = new NoteDetail ();
noteDetail.NoteDetails.Add("aaa");
noteDetail.NoteDetails.Add("bbb");
noteDetail.NoteDetails.Add("ccc");
Now I want to shuffle so I used this routine:
public static void ShuffleGenericList<T>(IList<T> list)
{
//generate a Random instance
var rnd = new Random();
//get the count of items in the list
var i = list.Count();
//do we have a reference type or a value type
T val = default(T);
//we will loop through the list backwards
while (i >= 1)
{
//decrement our counter
i--;
//grab the next random item from the list
var nextIndex = rnd.Next(i, list.Count());
val = list[nextIndex];
//start swapping values
list[nextIndex] = list[i];
list[i] = val;
}
}
My problem is that I am not sure how to do the shuffle. I have tried the following but it gives:
Error 237 Argument 1: cannot convert from 'System.Collections.Generic.IList' to 'System.Collections.Generic.IList<.Storage.Models.NoteDetail>'
Sort.ShuffleGenericList<NoteDetail>(noteDetail.NoteDetails);
Can anyone see what I am doing wrong. It all looks okay to me and I can't see why I should get this error :-(
You should change this:
Sort.ShuffleGenericList<NoteDetail>(noteDetail.NoteDetails);
To:
Sort.ShuffleGenericList<string>(noteDetail.NoteDetails);
Because noteDetail.NoteDetails is a List<string>, not a List<NoteDetail>.
You are using the wrong type to parametrize your generic method, do this instead:
Sort.ShuffleGenericList(noteDetail.NoteDetails);
or more explicit (but unneccessary):
Sort.ShuffleGenericList<string>(noteDetail.NoteDetails);
You were passing NoteDetail as type, rather than string - that won't work.
I took your code and threw it into VS. The below execustes okay with a few small modifications:
using System;
using System.Collections.Generic;
using System.Linq;
namespace MsgBaseSerializeationTest
{
class StackOverflow
{
public void Test()
{
var noteDetail = new NoteDetail<string>();
noteDetail.NoteDetails.Add("aaa");
noteDetail.NoteDetails.Add("bbb");
noteDetail.NoteDetails.Add("ccc");
NoteDetail<string>.ShuffleGenericList(noteDetail);
}
}
public class NoteDetail<T> : List<T>
{
public NoteDetail()
{
_noteDetails = new List<string>();
}
public IList<string> NoteDetails { get { return _noteDetails; } }
private readonly List<string> _noteDetails;
public static void ShuffleGenericList(IList<T> list)
{
//generate a Random instance
var rnd = new Random();
//get the count of items in the list
var i = list.Count();
//do we have a reference type or a value type
T val = default(T);
//we will loop through the list backwards
while (i >= 1) {
//decrement our counter
i--;
//grab the next random item from the list
var nextIndex = rnd.Next(i, list.Count());
val = list[nextIndex];
//start swapping values
list[nextIndex] = list[i];
list[i] = val;
}
}
}
}

Can a variant array be passed to C# using com interop?

I try to transfer some data between Excel and C#. For this I wrote a simple C# class with a property to set and get the data.
[Guid("xxx")]
[ClassInterface(ClassInterfaceType.AutoDual)]
[ComVisible(true)]
public class Vector
{
private object[,] _values;
public object[,] ExcelValues
{
get { ... }
set { ... }
}
}
Getting the ExcelValues property in VBA works well, but it is not possible to set it in VBA. The VBA code does not compile if I try to set the property:
Dim values As Variant
With myRange
' typo: Set values = Range(.Offset(0, 0), .Offset(100, 0))
values = Range(.Offset(0, 0), .Offset(100, 0))
End With
Dim data As New Vector
' this doesn't compile
data.ExcelValues = values
' this works
values = data.ExcelValues
Any suggestions how I can acomplish this, without setting each value from the variant Array one at a time?
I found a solution based on code that was posted here. A variant array has to be passed from VBA to C# as an object (not object[,]). Then it can be converted to something that is more handy by using reflection:
[Guid("xxx")]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class Vector
{
[ComVisible(false)]
public IList<double> Values { get; set; }
public object[,] GetExcelValues()
{
// own extension method
return Values.ConvertToExcelColumn();
}
public void SetExcelValues(object comArray)
{
IEnumerable<object> values = ConvertExcelCloumnToEnumerable(comArray);
Values = new List<double>(values.Select(Convert.ToDouble));
}
private static IEnumerable<object> ConvertExcelCloumnToEnumerable(object comObject)
{
Type comObjectType = comObject.GetType();
if (comObjectType != typeof(object[,]))
return new object[0];
int count = (int)comObjectType.InvokeMember("Length", BindingFlags.GetProperty, null, comObject, null);
var result = new List<object>(count);
var indexArgs = new object[2];
for (int i = 1; i <= count; i++)
{
indexArgs[0] = i;
indexArgs[1] = 1;
object valueAtIndex = comObjectType.InvokeMember("GetValue", BindingFlags.InvokeMethod, null, comObject, indexArgs);
result.Add(valueAtIndex);
}
return result;
}
}
The other way - from C# to VBA - it can be passed more comfortable as object[,] or double[,].
Hope there are no syntax typos :).
By using Set you're telling VBA that "values" is an object (in this case a Range), but then you're not using set when assigning that to ExcelValues. Try dropping the Set when you're reading the values.
With myRange
values = Range(.Offset(0, 0), .Offset(100, 0)).Value
End With

Adding data to a generic string Array C#

I'm learning about generics in C#, and I'm trying to make a generic array and add some strings to it. I can easily add int values to it, but I can't figure out how to make it work with strings. When I'm trying to use strings I get NullReferenceException.
I have a class called myArray, and it looks like this:
class MyArray<T> : IComparable<T>, IEnumerable<T>, IEnumerator<T>
{
T[] data = new T[10];
int current = -1;
public T[] Data
{
get { return data; }
}
public void Add(T value)
{
for (int i = 0; i < data.Length; i++)
{
if (data[i].Equals(default(T)))
{
data[i] = value;
return;
}
}
T[] tmp = new T[data.Length + 10];
data.CopyTo(tmp, 0);
Add(value);
}
In my mainform I add the data like this:
class Program
{
static void Main(string[] args)
{
MyArray<string> StringArray = new MyArray<string>();
StringArray.Add("ONE");
StringArray.Add("TWO");
}
}
The default of string is null as it is a reference type, not a value type. You are constructing a new array of type T[], which results in an array filled with the default value of T, which in this case is null.
Following that, data[i].Equals(default(T)) throws NRE as you are trying to call null.Equals(...).
Your array is being initialized with null values, so you're getting a NRE at this line:
if (data[i].Equals(default(T)))
if (data[i].Equals(default(T))) is where the issue lies. In a new string (or any other reference type) array,
var array = new String[10];
each element in the array is null by default. So when you say
data[i].Equals(default(T)
and data[i] is null, you're calling a method on a null reference, thus causing the exception to be thrown.
This doesn't happen for value types, as the array is initialized to the default value of whatever the value type is.
This is part of the problem with generics--you can't always treat reference types and value types the same.
Try this instead, to avoid default:
private int _currentIndex = 0;
public void Add(T value)
{
data[_currentIndex] = value;
_currentIndex++;
if(_currentIndex == data.Length)
{
T[] tmp = new T[data.Length + 10];
data.CopyTo(tmp, 0);
data = tmp;
_currentIndex = 0;
}
}

Categories