The API was originally designed for C, but has been used perfectly successfully in many programming languages (C++,Delphi/pascal even Cobol and VB)
The API is like this:
One declares a structure (actually a utility generates the declarations), which in C would be something like this
Expand|Select|Wrap|Line Numbers
- struct Customer
- {
- short l;
- short typenr;
- /* more header fields */
- /* data fields */
- double cust_acc_nr;
- char short_name[10];
- char tel_nr[12];
- char address[4][30];
- double balance;
- /* more data fields */
- }
Expand|Select|Wrap|Line Numbers
- memcpy(customer.cust_ref,"XYZA1234",8);
- fetch(&customer,EQUAL);
the application may also have calls like
fetch(&employee) where employee is an analogous declaration for another table.
The API "knows" which table the application is working with from the header fields - in particular the
customer.l specifies the size of the customer record buffer, and the customer.typenr is a number unique to the customer table.
Now I am trying to implement this API in C#. Unfortunately C# makes it very difficult to handle fixed size character arrays. I made my program for generating declarations do the following for C#
Expand|Select|Wrap|Line Numbers
- [StructLayout(LayoutKind.Explicit)]
- public class _Customer: DP4.ITable
- {
- public static short _CUSTOMER=4117;
- [FieldOffset(0)]
- ushort l = 312;
- [FieldOffset(2)]
- short typenr = _CUSTOMER;
- [FieldOffset(4)]
- ushort generation;
- [FieldOffset(6)]
- ushort flags;
- [FieldOffset(8)]
- int timestamp;
- [FieldOffset(16)]
- internal double _cust_acc_nr;
- [FieldOffset(24)]
- [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
- internal char [] _short_name;
- [FieldOffset(34)]
- [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
- internal char [] _tel_nr;
- [FieldOffset(48)]
- double balance;
- [DllImport(Provider.name, EntryPoint = Provider.rec_fetch, CharSet = CharSet.None)]
- static extern int rec_fetch(IntPtr dconn,
- [MarshalAs(UnmanagedType.LPStruct), In, Out ] _Customer rec,
- int flags, int index, int index_role, int match_keys);
- public int Fetch(DP4.Database dconn,TableFetchFlags flags, int index,int index_role,int match_keys)
- {
- return rec_fetch(dconn.Dconn,this,(int)flags,index,index_role,match_keys);
- }
- }
When I prototyped this, it seemed like it was going to work OK. Unfortunately when I tried it with a more complex table, although the code compiled OK, it refuses to run. I get this message:
Unhandled Exception: System.TypeLoadException: Could not load type 'Itim.DP4.Schemas.TABLES.INP._Customer' from assembly 'p4, Version=0.0.0.0, Culture=neutral,PublicKeyToken=null' because it contains an object field at offset 34 that is in correctly aligned or overlapped by a non-object field.
at Itim.NETTest2.Program.Main(String[] args)
I'm confused about this error message. I thought that the declaration I'm generating above was managing to declare the analogue of the C structure at the top. But the error message implies that what I'm really getting is something where _short_name is some kind of object being allocated in the heap, so that the name would be elsewhere. But my prototype worked fine, so that cannot be the case. So now I'm thinking that maybe C# has more onerous data alignment requirements than C/C++, and wants short_name to start on a four byte boundary or something. (There's no reason why it should - I'm laying out data in a manner that would satisfy all the alignment restrictions of any processor I've ever heard of)
I'd be really grateful for any insight and advice C# experts can offer. I'm hoping this is the only time in my life I have to write any C#, so I don't want to get into a religious debate about whether my C code should be transferring data like this. I need to implement this API, and the DLL has to be able to return some kind of customer object preferably with as little interference from C# marshalling as possible.
I know C# allows fixed char declarations. But that can only be used in unsafe code, which is not acceptable, and also they can only be used in structs and since I need a class object that would complicate things. So Layout.Explicit layout seems the way to go.
As long as I know how C# wants to lay out the data I can supply the data in that format. The API is more sophisticated that my explanation may have suggested, and can "swizzle" or "swab" data into whatever format a particular language demands. But I have to be able to return data in a single block. I can't create some complicated object containing sub-objects since the database itself is unmanaged C code.
If the worst comes to the worst I'd even be prepared to declare _tel_nr (for example) as 12 individual chars, since I can always provide get() and set() methods that turn it into a TelNr string property. But obviously, I'd prefer not to have to do that if at all possible. The code for generating C# declarations is already about five times more complex than the code for doing the C++ API, which in turn is twice as complex the C code.
Sorry this is a long post. Thanks in advance to anyone who can offer help.