Using Groups with .Net Com Controls Part 1

Tags: #<Tag:0x00007f98be667db8>

I have been working with .Net for a client to create a COM control to be used in his clarion product over the last 3 years.

For the most part it has been very successful and I have even shared some of my findings with Clarion Live

One of the things that I thought was impossible to achieve based on other articles I had read was that it is not possible with a standard OLE control to be able to pass a clarion group to the control, until last week I still held this belief.

However after a conversation I had with @MarkGoldberg on Skype, I started to experiment, and within a week I now have quite a good understanding of how to achieve the goal.

The .net code I write here is in C# and you will have to salt to taste for any other .net language.

Firstly though, I think it best to talk data types, From my experimentation I have found I am not able to use all of clarions data types.The following list are the data types I have had success with, but I would need to experiment more to see what else I find, also I have not had a need to test all of clarion’s types, and maybe if you do find these articles useful if you find others, let me know.

CLARION         .NET
LONG            int
BYTE            byte
REAL            double
CSTRING(x)      string *See later info
DATE            clarion date struct, see below
TIME            clarion time struct, see below

To match a DATE and TIME type I created two structures in .Net:

namespace Clarion.DataTypes {
    public struct ClarionDate {
        byte day;
        byte month;
        ushort year;
    }

    public struct ClarionTime {
        byte hs;
        byte seconds;
        byte minutes;
        byte hour;
    }
}

I have put these in a Clarion.DataTypes namespace, so that it is easy to reference in my projects.

The next thing you will probably want to do in your .Net project is create a struct to map against a group you want to work with. The following is a simple clarion group I will use to show how to define your structures in .net

TestGroup    GROUP
LongField        LONG
ByteField        BYTE
StringField      CSTRING(101)
DateField        DATE
TimeField        TIME
RealField        REAL
FinalString      CSTRING(51)
             END 

The structure in .net would look like this.

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TestStruct {
int 		LongField;
byte 		ByteField;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 101)]
string 		StringField;
ClarionDate     DateField;
ClarionTime     TimeField;
double 		RealField;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 51)]
string		FinalString;
            }

The first thing to note is the MetaData tags used in the structure, they are from the System.Runtime.InteropServices name space and you will need to reference that in your class and add a using statement.

The following tag, StructLayout(LayoutKind.Sequential, Pack = 1)]

Is telling the c# compiler that we are going to create a sequential structure and that we want to pack the structure in such a way that each byte will follow the previous one. Obviously as a com control being used in clarion is going to be a 32 com control, the project will have already indicated this (see videos on clarion live).

So now we have instructed the compiler to make our structure layout in the same way that clarion group’s are laid out, and that each byte will follow the last one, in exactly the same way that a clarion group works.

The next MetaData tag of importance is the one proceeding strings. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 101)]

This tag tells the compiler to expect an unmanaged string and the size of that string (in this case 101 bytes).

As mentioned before I am passing CSTRINGS over rather than a clarion STRING's, and this is because the .Net string is expecting a /0 terminated string. You may have success passing a clarion STRING however I am not sure what size you should tell the compiler it is.

In the next post, I will explain how we can then pass the address of our GROUP over to .net and get .net to reference the pointed to group.

Part 2 of this article

2 Likes

So @Mark_Sarson showed several data types as already working :star::
BYTE, LONG, REAL, DATE, TIME, CSTRING

I imagine that some others would work easily
SREAL, SHORT, USHORT, ULONG

Community projects

Some other data types that were might be nice to see worked out

  • String
    maybe just pass as an array of bytes, and expose a .NET String property, with a set/get

  • Decimal
    Internals are documented, so a ClaDecimal class could be written

  • Memo
    Does this just behave like a string?

Harder, but possibly of interest

  • REFERENCES
  • BLOB
  • ANY
  • QUEUE
  • PSTRING
  • ASTRING
  • BSTRING

BSTRINGS are easy:

Clarion BSTRING == .net [MarshalAs(UnmanagedType.BStr)] string

I was wondering about this today and doing some reading:

https://docs.microsoft.com/en-us/dotnet/framework/interop/default-marshaling-for-strings

Why UnmanagedType.BStr and not UnmanagedType.AnsiBStr when doing interop with Clarion?

Ha, I just tested and answered my own question. You cannot use UnmanagedType.AnsiBStr because it doesn’t work :smiley:

Another thing I discovered is that by default, if you don’t specify a MarshalAs then this is what works:

(I am playing around with the examples from the webinar from @hdsoftware , thanks Ole!)

In C# we DllImport like this:

[DllImport("ClarionDllInNet.dll")]
public static extern int Clarion_ShowWindow(string caption);

In Clarion we do this:

MAP
  wndProcedure(const *CSTRING pCaption), LONG, PASCAL, NAME('Clarion_ShowWindow')
END

The advantage of this is that we don’t need to specifically marshal everything (the default seems to be UnmanagedType.LPStr), the only disadvantage I can see is if you need to support null values in your string. For some reason I keep trying to avoid using the undocumented Clarion BSTRING :slight_smile:

1 Like