Question

I am trying to get binary data from .NET into FoxPro (a COM compliant language). I have a .NET object that is ComVisible and an event interface with an event with a string parameter.

In my example below I have a dummy implementation that returns a string containing every consecutive character from 0 to 255. On the .NET side the string appropriately stores every character unmolested but when handling the event on the client side, the characters between 128 and 154 get converted to question marks. Above 154 the characters are again unchanged.

Any idea what is causing this issue? FoxPro doesn't have a way to natively represent binary data unfortunately and requires currying it around in strings.

[Guid("974E3133-9925-4148-8A2B-F4B811072B17"), ComVisible(true), ComSourceInterfaces(typeof(IStreamEvents))]
public class DumbSerialPort {
    readonly string _buf;

    public event DataReceivedHandler DataReceived;
    public event EmptyDelegate Error;

    public DumbSerialPort() {
        var bbuf = new char[255];
        for (int c = 0; c < 255; c++)
            bbuf[c] = (char)c;

        _buf = new string(bbuf);
    }

    public void Fire() {
        if(DataReceived != null)
            DataReceived(_buf);
    }
}

[Guid("0F38F3C7-66B2-402B-8C33-A1904F545023"), ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IStreamEvents {
    void DataReceived(string data);
    void Error();
}
Was it helpful?

Solution

The Problem

So the reason for the problem problem is a little complex and is rooted in the fact that FoxPro does not support Unicode strings and COM is defined explicitly to ONLY use Unicode for string data. Passing strings with opaque binary data would work fine except for this impedance mismatch.

Any time FoxPro calls a function that returns or has string arguments it internally does a codepage conversion before returning to user code. This conversion obviously causes all kinds of issues moving binary data hidden in strings.

The Solution (with a snag)

Well a byte[] should work, and it partly does work, this "partly" is what was caused me to try to hide binary data in strings.

Here's the deal (and I've only verified this with VFP 9 SP2 because that's what I'm using); on the C# COM side, FoxPro CAN handle a method defined as follows:

public byte[] GetData() { ... }

When calling that method FoxPro will properly return the data as a string "marked" as binary (see CreateBinary() for an explanation of "marked binary strings"). These strings support all the standard string manipulation functions just like a non-binary string; exactly what I needed. This is also true for COM source event interfaces implemented by FoxPro and passed to C# that have a byte[] parameter, like in my original example.

To keep FoxPro from doing a codepage conversion on a string when sending it to a COM object, that string needs to be created with the CreateBinary() function which marks the string as binary and will bypass the conversion.

However, what FoxPro does NOT handle is passing a "binary" string to a method defined like this:

public void SendData(byte[] data) { ... }

If you try to call this you will get an invalid parameter type COM exception.

There are a couple of reasons this doesn't work properly which basically boils down to FoxPro not handling the marshaling automatically.

The Workaround Solution

So, what can we do? Define a function like this.

public void SendData(object data) { ... }

Ok, so now we can call the function, with a binary marked string and FoxPro will not do any codepage conversion and the data will come over to .NET. But what is the data type of the data parameter? It is a System.Byte[*]. What is that asterisk for? I had no clue so I asked the brilliant people on SO.

Turns out that it is an array with non-zero lower bound.

So when we binary data from FoxPro we would be able to cast directly to byte[] except for the fact that FoxPro arrays are 1-based.

So to fix this issue, here is what I do in C#:

public void SendData(object data) {
    byte[] buf = FPHelper.ToSZArray(data);
    // Use buf here
}

public class FPHelper {
    public static byte[] ToSZArray(object param) {
        var array = param as Array;

        if (array == null)
            throw new ArgumentException("Expected a binary array, (did you use CREATEBINARY()?)");
        if (array.Rank != 1)
            throw new ArgumentException("Expected array with rank 1.", "param");

        var dest = new byte[array.Length];
        Buffer.BlockCopy(array, 0, dest, 0, array.Length);

        return dest;
    }
}

And in FoxPro the only requirement is to call it with a string "marked" as binary:

cData = "Hello World!" + CHR(13) + CHR(12) + CHR(0)
oComObject.SendData(CREATEBINARY(cData))

OTHER TIPS

While my experience with FoxPro is very rusty, I do remember it can pass arrays into COM objects, but has issues with receiving them back. So, consider doing it the other way round and have Foxpro provide an array for C# to fill in, created with COMARRAY. From C#, you'd fire DataReceived event and provide a callback interface IProvideData. FoxPro would call it from inside its DataReceived event handler and supply you with an array to fill in:

public interface IStreamEvents {
    void DataReceived(int count, IProvideData obj);
    void Error();
}

public interface IProvideArray {
    void ProvideData([In, Out] 
        MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UI1) byte[] buff);
}

When you create an array on the FoxPro side, keep the following in mind (from MSDN):

When you use a byte array (VT_UI1) to communicate with a COM server, Visual FoxPro converts the byte array into a string. The additive nValue of 1000 retains the original proper type of the array and does not convert the result to a string. If a client passes a byte array by reference to a Visual FoxPro COM Server, the Visual FoxPro COM Server must also set the nValue additive to 1000.

On the C# side, you simple deal with an array:

public ProvideData(byte[] buff) {
    for (int c = 0; c < 255; c++)
        buff[c] = (byte)c;

}

public void Fire() {
    if(DataReceived != null)
        DataReceived(this); // this implements `IProvideArray`
}
StringBuilder stringB = new StringBuilder();
foreach (char c in asciiStr)
{
    uint ii = (uint)c;
    stringB .AppendFormat("{0:X2}", (ii & 0xff));
}
return stringB.ToString();

hope this helps

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top