I have a relatively simple C#class that I would like to marshal into a VB project. It looks like this (I simplified a bit for this post):
[Guid("AAAAAAAA-AAAA-AAAA-AAAA-123456789ABC", ClassInterface(ClassInterfaceType.AutoDual), ComVisible(true)]
[ProgId("MyBiz.MyResponse")
[Serializable]
public class MyResponse
{
public bool Success { get; set; }
public int ID{ get; set; }
public string Location{ get; set; }
public ArrayList Messages { get; set; }
}
Messages contains 0 or more strings. I compile this and create a type library to be used by VB6. Everything work well in terms of data getting passed from the simple types, but the Messages variable, while the VB runtime recognizes it as an ArrayList, does not contain any data in it even when it should. What am I missing, in terms of marshaling the data? I know that generics do not marshal, but I believe an ArrayList does. Am I missing an attribute, or something else?
No need to offer alternative solutions, as I am asking this because I want to know how to do it, not because I don't have alternatives if I can get this to work. Thanks!
One way to handle this is to use a COM SafeArray to pass data back and forth from .NET to COM. I have had better luck with this technique than with a ArrayList. The declaration for your Messages could look like:
[return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)]
public string[] Messages
This would be seen in VB6 or similar COM client as
Public Messages() as String
a COM SafeArray of Strings.
Related
I have a C# library that I'm trying to expose to VBA. I can pass parameters to functions just fine (ie "ref byte[] someArray"), but passing objects or structs just won't work.
If I try passing a byte array as a property of a class, I get the following error in VB-
Function or interface marked as restricted, or the function uses an Automation type not supported in Visual Basic
If I try passing a byte array as a property of a struct, I get the following error in VB-
I've been fighting this for two days now and while I keep finding posts that claim to have the answer, none of them have worked for me.
So here's my code as it currently sits:
[ComVisible(true)]
[Guid("7F53F7A5-15C9-4A99-A855-38F5E87702D0")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)] // Tried as InterfaceIsDual and as InterfaceIsIDispatch
public interface IDetail
{
[DispId(1)] // Tried with and without these
int SomeInt { get; set; }
[DispId(2)]
string SomeString { get; set; }
[DispId(3)]
byte[] SomeByteArray {
return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UI1)]
get;
[param: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UI1)]
set;
}
}
[ComVisible(true)]
[Guid("F77FB3D4-27E0-4BFA-A21E-5ACB671151E9")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("G4COMTest.Detail")]
public class Detail:IDetail
{
public int SomeInt { get;set; }
public string SomeString { get; set; }
// Tried MarshalAs in all combinations of class and interface
public byte[] SomeByteArray {
[return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UI1)]
get;
[param: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UI1)]
set;
}
}
[ComVisible(true)]
[Guid("5E8F9FF0-3156-479E-A91D-0DADD43881FB")]
[ClassInterface(ClassInterfaceType.None)]
public class Worker:IWorker
{
// works with the 'ref'
public int ReturnIntWByteArrayParam(ref byte[] testByteArray)
{
return testByteArray.Count();
}
public int ReturnIntWObjParam(IDetail detail)
{
return detail.SomeInt;
}
public IDetail ReturnObjNoParams()
{
var o = new Detail();
o.SomeInt = 87;
o.SomeString = "What are you doing Dave";
return o;
}
}
[ComVisible(true)]
[Guid("04962F29-DBBD-48AC-B4FB-180EEF562771")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IWorker
{
int ReturnIntWByteArrayParam(ref byte[] testByteArray);
int ReturnIntWObjParam(IDetail detail);
IDetail ReturnObjNoParams();
}
Calling it from VB6:
Dim o As New G4COMTest.Worker
Dim d As New G4COMTest.Detail
Dim byt(2) As Byte
d.SomeInt = 356 '// Works
d.SomeString = "Hello from client" '// Works
d.SomeByteArray = byt '// Errors as either class or struct
MsgBox mWorker.ReturnIntWObjParam(d)
Thanks in advance for any help!
The C# array property exposes a getter and a setter to COM exactly as you would expect it to (the MarshalAs attribute is unnecessary, the marshaler does detect it correctly by default).
The problem is that the setter, like all property setters in .NET, passes the value parameter by value. Unfortunately, VBA does not support passing arrays by value. It's a fundamental limitation of the language that's been there since day one. Even more unfortunately, COM interop does not provide any way to override this behaviour with attributes. You have two choices:
A - Define your own setter method and call it from VBA instead of the property setter, e.g.
void SetSomeByteArray(ref byte[] value) { SomeByteArray = value; }
B - Change the property type to object and use variant arrays instead of strongly-typed arrays.
PS: Be careful with string properties too. These normally work just fine, but if you pass a null string value to VBA, it will error because the VBA String type can't store null references.
In your code class Detail has the ClassInterfaceType set to None, if you set it to AutoDispatch the code you have should work. From MSDN:
Using the class interface is an acceptable option for scripted clients, Microsoft Visual Basic 6.0 clients, or any late-bound client that does not cache the DispIds of interface members."
http://msdn.microsoft.com/en-us/library/4fcadw4a(v=vs.110).aspx
Since the client you are calling from is VB6 - you can set the ClassInterfaceType to AutoDispatch or even omit it (as default is AutoDispatch). This will generate only the Dispatch only class interface and not include any members from the interface. When calling from VB6, assigning an array directly to a property should work as it uses IDispatch Invoke function (late binding).
We tested this with a string array and it works.
I have a WCF service that i cannot touch which returns List<FilesWithSettings>.
I need to enter few PC which are grouped together and retrieve List<FilesWithSettings> for each one along with PCIdentifier which brings me to Dictionary<PCIdentifier,List<FilesWithSettings>> or List<PCIdentifier> and List<List<FilesWithSettings>> which isn't elegant and unreadable.
Can you give me more elegant solution ?
I guess you've got three options:
List<List<T>> // Which is pretty nasty
or:
Dictionary<PCIdentifier, List<T>>
Which better enunciates your intent or even:
class PCResult
{
PCIdentifier Identifier { get; set; };
List<T> Results { get; set; }
}
and
List<PCResult>
Personally I prefer the third, but the second is fine too.
I would have something like
[DataContract]
public class PCState // need a better name
{
[DataMember]
public PCIdentifier Identifier {get;set;}
[DataMember]
public List<FilesWithSettings> Files {get;set;}
}
and return a List<PCState>. This avoids all the issues with complex overly-generic types and nested lists, etc, and is easily consumed.
Dictionary<PCIdentifier,List<FilesWithSettings>> is actually pretty elegant. You can clearly identify individual PCs and iterate over all PCs, but also get all data you need for each PC.
I have a collection of simple elements in C#. Element is like:
public class Element
{
public string AName { get; private set; }
public string PName { get; private set; }
public string Value { get; set; }
}
I need to pass this collection as a string to a Python script. And in python for each element I need to call function like Func(AName, PName, Value). I've done this by serializing collection to JSON in C# and in python write code:
elements = json.loads(JSON)
for element in elements:
Func(routeLayer, element['AName'], element['PName'], element['Value'])
But now it turns out that I cannot use Python's json module. So I need a new way to pass this collection to script and dont use any additional modules. I am a real noob in Python, so solutions I can imagine are ugly. Can you give me an advice?
If there is some character which you can guarantee is not in the strings for AName,PName and Value then you could use that character as a separator.
In C#, you could "serialize" the information by simply joining the three strings with the separator, e.g. "foo,bar,baz".
Then in Python, the information could be deserialized with
aName,pName,value = element.split(',')
PS. Just out of curiosity, why can't you import modules in the standard library?
The most common standard, which should be available in Python as well is XML. Did you consider this option?
UPDATE:
You can use XmlSerializer in C# to serialize as XML. And here is something, what i've found for Python
In my .proto I have some messages that have optional fields. There is no native protogen for Debian so I don't have one to experiment with (too lazy to compile it myself :).
Could you tell me how to implement an optional field in a class in C#? I would like to have a function or whatever that idicate the field is set (in C++ I have something like hasfoo() ). In examples I found in the internet there is nothing like that.
It supports a number of patterns here, to help transition from other serializers. And note that there are options in the protobuf-net protogen to include such members for you automatically.
Firstly, anything null is omitted; this includes both null references and Nullable<T> for structs. So:
[ProtoMember(1)]
public int? A {get;set;}
will behave.
Another option is default values; using .NET conventions:
[ProtoMember(2), DefaultValue(17)]
public int B {get;set;}
no values of 17 will be serialized.
For more explicit control, the ShouldSerialize* pattern (from XmlSerializer) and the *Specified pattern (from DataContractSerializer) are observed, so you can do:
[ProtoMember(3)]
public string C {get;set;}
public bool ShouldSerializeC() { /* return true to serialize */ }
and
[ProtoMember(4)]
public string D {get;set;}
public bool DSpecified {get;set;} /* return true to serialize */
These can be public or private (unless you are generating a standalone serialization assembly, which requires public).
If your main classes are coming from code-gen, then a partial class is an ideal extension point, i.e.
partial class SomeType {
/* extra stuff here */
}
since you can add that in a separate code file.
My application consist of C# code with unmanaged C dll calls.
In my C# code I have an object/class where its properties are both system types such as string and int and other objects I have defined.
I would like to pass this complex (Graph.cs) object to my C (dll) code, what implementation would you suggest here?
I have tried moving structs but I fail to do so with anything other then string and int.
Thanks.
Code:
public Class Grpah {
TupleCollection m_TupleCollection;
int m_nGeneralIndex;
bool m_bPrintWRF;
string m_sLink;
}
public Class TupleCollection {
IList<Tuple> _collection;
}
public Class Tuple {
Globals.TupleType m_ToppleType;
ArrayList m_Parameters;
}
public class TupleArgs {
public string Value { get; set; }
public Globals.PAS PAS;
public RefCollection RefCollection { get; set; }
public int Time{ get; set; }
}
public class RefCollection {
public List<int> SynSet{ get; set; }
public Globals.PAS PAS;
}
Try:
How to: Marshal Structures Using PInvoke
I think the easiest way for you to make progress is to modify the native code, giving it the ability to work with CLR types.
Now, you're almost certainly using Visual Studio, and hopefully it's VS2005 or later. This means that although your existing native code is in C, you have the option to delve into a little C++. And not only that - you also have C++/CLI.
So I would make a new C++/CLI DLL, and link your C library to it, so that it can call into the C code. Then write a thin translation layer in the C++/CLI library: it will expose true CLR classes (written with ref class) and will call onto the native C code.
e.g. in a C header:
void do_something_with_foo_data(int a, int b, int c);
In C++/CLI:
public ref class FooWrapper
{
static void DoSomethingWithFoo(Foo ^foo)
{
// pick apart the Foo class and pass the pieces on to the C function
do_something_with_foo_data(foo->A, foo->B, foo->C);
}
};
In C#:
public class Foo
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
}
...
var foo = new Foo { A = 1, B = 2, C = 3 };
FooWrapper.DoSomethingWithFoo(foo);
When I did this I used Marshal exclusively. This was with standard C for the native code. I did not want to convert my C code to "managed C++" or whatever they call it :-)
The hard and tedious part is that you have to manually marshal your data structure into something that maps directly to the receiving C function. In my case, I had to create separate structs for each data bearing C#-class I wanted to send. In essence, you should convert your nice C# object hierarchy into a more basic form consisting of structs which you then marshal into a memory chunk which you then use as an argument in your native call.
You should use the method Marshal.AllocHGlobal for allocing memory and Marshal.StructureToPtr for copying your C# struct to that memory and then Marshal.FreeHGlobal to free it.
Once you have your IntPtr (from StructureToPtr), you should be able to simply call your C-dll with that pointer as argument. Note that the struct you are sending to your C function must have the same layout as the native C struct, or you will get very odd results.
Returning data is pretty much the same thing, but you are using the opposite functions (PtrToStructure etc) instead.
Thats the basic of it anyway.
Your model looks pretty complicated, and incomplete: Tuple.m_parameters is an ArrayList, so it can contain just about anything, and the Globals.PAS type is not defined here.
Perhaps you should think of a different strategy: in your DLL, make a small model that contains whatever you need in your C code, and make it as simple as possible (but not simpler!).
Then, learn whatever you need to marshal that C model from your managed code, and fill in the model from your Graph.cs class. Preferably, Graph.cs shouldn't be responsible to do this, your marshalling code should be.