In a previous blog post I discussed how to access the recurrence information that the VCL ExpressScheduler stores in its DB from a .NET application. Reading and writing the recurrence information is possible from .NET given a knowledge of the struct layout that the Delphi control uses.
However, if you want to read the resource information that ExpressScheduler stores in its DB from .NET, this requires using a Delphi DLL. This is because the Delphi scheduler control stores the resource ID’s for appointments in a single field but in multiple formats depending on the appointment. For instance, if the appointment has only one associated resource, its resource ID will be stored as a Delphi variant. However, the value will be stored as a Delphi variant array of variants if the appointment is associated with multiple resources.
Luckily, both Delphi and .NET make it straight-forward to interoperate between them and DevExpress has a simple utility method we can use to simplify things as well.
Start off with a new Delphi DLL project by clicking File>New>Other in Delphi XE2. On the New Items dialob, select Delphi Projects followed by Dynamic-link Library.
Start by adding both Variants and cxSchedulerStorage to the uses clause of the DPR. Then, add the following exported method to the DPR:
procedure ResourceIDBlobToString(const AResourceID: PByteArray; out AResult: PByteArray); export; stdcall; var ResourceIDVar: Variant; begin ResourceIDVar := cxFieldValueToVariant(AnsiString(AResourceID)); if VarIsArray(ResourceIDVar) then AResult := PByteArray(VarArrayToArrayStr(ResourceIDVar)) else //the cast to AnsiString below is necessary or a Unicode string will sneak in and gum up the works AResult := PByteArray(AnsiString(VarToStr(ResourceIDVar))); end; exports ResourceIDBlobToString;
This method uses the cxFieldValueToVariant utility method found in cxSchedulerStorage to translate the string representation of the field value to a Delphi variant. We then use VarIsArray to test if the variant is an array. If it is not, we simply return the variant as a string. If the variant is a variant array, we will use the following method to parse it:
//input is a variant array of strings //output is a comma delimited string composed of the members of the input array function VarArrayToArrayStr(const ResourceIDArray: Variant): AnsiString; var I: Integer; begin Result := ''; for I := VarArrayLowBound(ResourceIDArray, 1) to VarArrayHighBound(ResourceIDArray, 1) do begin if Result <> '' then Result := Result + ','; Result := Result + VarToStr(ResourceIDArray[I]); end; end;
And that’s it for the Delphi DLL.
Next we’ll create a new C# WinForms solution to test out our DLL. Click File>New>Project and select a Windows Forms Application.
I’ll skip the data access code as it’s straight-forward. The interesting bit here is the DllImport attribute we’ll use to call our Delphi DLL:
[DllImport("ExpressSchedulerInterop.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)] static extern void ResourceIDBlobToString([MarshalAs(UnmanagedType.AnsiBStr)] string resourceID, [Out, MarshalAs(UnmanagedType.AnsiBStr)] out string result);
With this method defined properly, the following code can be used to read the resource data from the DB in C#:
string result = null; ResourceIDBlobToString((string)reader["ResourceID"], out result);
UPDATE: I updated the code in this Blog post, along with the interop Delphi project source, to address a bug when using Unicode versions of Delphi. A cast to AnsiString is necessary or only the first character in the resource ID will be returned.
UPDATE #2: There’s a real-world example of this technique you can find here. It is an open source project for transferring data from ExpressScheduler to XtraScheduler.