Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing an OPOS printer service object for capturing printed text

We're trying to create something like a printer driver wrapper for POS applications, that would allow us to capture the printed receipt, and then forward it again to the original printer.

So far, we implemented one as a service object on top of "POS for .Net", it worked excellent and everything, but it turned out that some legacy POS applications only support OPOS. In order to support them, we either have to make our "POS for .Net" service object available as an OPOS service object, or we have to write our own OPOS service object using the CCOs.

My questions are:

  • In these legacy POS apps, is it even possible to use our POS for .Net solution? (if yes, how?)
  • How to build an OPOS service object? and can it be using .Net framework (C# for example)?
  • Are we doing the right thing? is there a better way to capture the receipts (especially for these legacy apps)?
like image 778
Omar Avatar asked Mar 26 '13 08:03

Omar


2 Answers

Q) In these legacy POS apps, is it even possible to use our POS for .Net solution? (if yes, how?)

A) No, these apps don’t use the POS for .Net libraries and don’t search for service objects using the POS for .Net registry keys, these apps only use the OPOS(OLE POS) registry entries to search for registered service objects, and usually call the CCO which in turn call the service object.

Q) How to build an OPOS service object? and can it be using .Net framework (C# for example)?

A) Yes it can be done using .Net, but you need to expose it as a COM library, a good way is to implement the interfaces in the CCO, there is a DLL for each device, reference the one for the device you need, implement its interface, and mark your type as COM visible, add a GUID and a ProgId, register it using the regasm “path” /register /codebase /tlb command, add the needed registry keys –can be found in the UPOS specification\development guide document- and you will be done, or that’s at least what I thought, with this you will get an error stating that there are missing methods from your service object which are needed to run it correctly, well I found this the hard way, but there is 7 methods which are not referenced in the interfaces – although are referenced in the UPOS specification\development guide document- these methods are:

  1. COFreezeEvents: Same as the property FreezeEvents.
  2. GetPropertyNumber: Used to get the value of a numeric\Boolean property by the property’s index, more on that later.
  3. SetPropertyNumber: Used to set the value of a numeric\Boolean property by the property’s index, more on that later.
  4. GetPropertyString: Used to get the value of a string property by the property’s index, more on that later.
  5. SetPropertyString: Used to set the value of a string property by the property’s index, more on that later.
  6. OpenService: Same as the method open.
  7. CloseService: Same as the method close.

After implementing these methods everything works fine, which is strange since none are referenced in the CCO interfaces, however as I said each of these is referenced in the UPOS specification and have a full description.

It seem the reason OpenService and CloseService methods exist, is that when the CCO libraries were implemented as a com the Open and Close method names were not suitable and had to be changed to OpenService and CloseService, the same apply for Claim and Release with new names ClaimDevice and Release Device – however these are exposed correctly in the interfaces, as for the rest of the methods I couldn’t find a reason.

Get\Set Property Methods

These 4 methods are used to access all the properties in your object, why? I am not sure, but it seems these should be used from the Dispatch interface to access your object, why is that interface not available by default? Are C++ service objects implemented in the same way? I don’t have an answer.

To implement these in a correct way, one should look at the Include directory under the OPOS installation – CCO installation - and check the *.hi files, mainly Opos.hi and OposPtr.hi(depends on the device, in our case printer), you will see that these include constants for CCO, like the success or failure enums, and for those 4 methods the properties index and the device index offset.

With the use of the numbers from the OPOS constants you only need to switch on the PropIndex parameter value, and get\set the correct property value.

    if (PropertyIndexHelper.IsStringPidx(PropIndex))
    {
        switch (PropIndex)
        {
            case PropertyIndexHelper.PIDX_CheckHealthText:
                return _physicalPrinter.CheckHealthText;

            case PropertyIndexHelper.PIDX_DeviceDescription:
                return _physicalPrinter.DeviceDescription;

            case PropertyIndexHelper.PIDX_DeviceName:
                return _physicalPrinter.DeviceName;
                                  .
                                  .
                                  .
                                  .
                                  .
like image 59
Mahmoud Darwish Avatar answered Oct 22 '22 22:10

Mahmoud Darwish


To further MEYWD's answer I just wanted to post what a basic interface should look like in C#.

[ComVisible(true), Guid("Put a GUID here")]
public interface IMSR
{
    //Common Opos
    [DispId(0x01)]
    int CheckHealth([In] int lLevel);
    [DispId(0x02)]
    int ClaimDevice([In] int lTimeOut);
    [DispId(0x03)]
    int ClearInput();
    [DispId(0x04)]
    int ClearInputProperties();
    [DispId(0x05)]
    int ClearOutput();
    [DispId(0x06)]
    int CloseService();
    [DispId(0x07)]
    int COFreezeEvents([In, MarshalAs(UnmanagedType.VariantBool)] bool Freeze);
    [DispId(0x08)]
    int CompareFirmwareVersion([In, MarshalAs(UnmanagedType.BStr)] string FirmwareFileName, [In, Out]ref int pResult);
    [DispId(0x09)]
    int DirectIO([In] int lCommand, [In, Out] ref int pData, [In, Out, MarshalAs(UnmanagedType.BStr)] ref string pString);
    [DispId(0x0A)]
    int OpenService([In, MarshalAs(UnmanagedType.BStr)] string lpclDevClass, [In, MarshalAs(UnmanagedType.BStr)] string lpclDevName, [In, MarshalAs(UnmanagedType.IDispatch)] object lpDispatch);
    [DispId(0x0B)]
    int ReleaseDevice();
    [DispId(0x0C)]
    int ResetStatistics([In, MarshalAs(UnmanagedType.BStr)] string StatisticsBuffer);
    [DispId(0x0D)]
    int RetrieveStatistics([In, Out, MarshalAs(UnmanagedType.BStr)] ref string pStatisticsBuffer);
    [DispId(0x0E)]
    int UpdateFirmware([In, MarshalAs(UnmanagedType.BStr)] string FirmwareFileName);
    [DispId(0x0F)]
    int UpdateStatistics([In, MarshalAs(UnmanagedType.BStr)] string StatisticsBuffer);
    [DispId(0x10)]
    int GetPropertyNumber([In] int lPropIndex);
    [DispId(0x11)]
    string GetPropertyString([In] int lPropIndex);
    [DispId(0x12)]
    void SetPropertyNumber([In] int lPropIndex, [In] int nNewValue);
    [DispId(0x13)]
    void SetPropertyString([In] int lPropIndex, [In, MarshalAs(UnmanagedType.BStr)] string StringData);
    //MSR Specific
    [DispId(0x14)]
    int AuthenticateDevice([In, MarshalAs(UnmanagedType.BStr)] string deviceResponse);
    [DispId(0x15)]
    int DeauthenticateDevice([In, MarshalAs(UnmanagedType.BStr)] string deviceResponse);
    [DispId(0x16)]
    int RetrieveCardProperty([In, MarshalAs(UnmanagedType.BStr)] string propertyName, [Out, MarshalAs(UnmanagedType.BStr)] out string cardProperty);
    [DispId(0x17)]
    int RetrieveDeviceAuthenticationData([In, Out, MarshalAs(UnmanagedType.BStr)] ref string challenge);
    [DispId(0x18)]
    int UpdateKey([In, MarshalAs(UnmanagedType.BStr)]string key,[In, MarshalAs(UnmanagedType.BStr)] string keyName);
    [DispId(0x19)]
    int WriteTracks([In] object data,[In] int timeout);
}

Granted this is for a MSR, but the CommonOPOS methods will be the same on all types of devices. SO the only thing you would have to change is from DispID 0x14 (20) on down. What I did was compare with the OPOS document the signature that they put and converted it to C#. I've created about 6 SO's this way and all are working just fine in a variety of different scenarios.

Another note is in the OpenService method. You'll see that the last argument is a object. That is the instance of the Control object. What you'll need to do is make another interface in your project that exposes the COM object for you. To stick with my MSR example here is what you would put.

[ComImport, Guid("CCB91121-B81E-11D2-AB74-0040054C3719"), InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface COPOSMSR
{
    void SOData([In] int Status);
    void SODirectIO([In] int EventNumber, [In, Out] ref int pData, [In, Out, MarshalAs(UnmanagedType.BStr)] ref string pStrIng);
    void SOError([In] int ResultCode, [In] int ResultCodeExtended, [In] int ErrorLocus, [In, Out] ref int pErrorResponse);
    void SOOutputCompleteDummy([In] int OutputID);
    void SOStatusUpdate([In] int Data);
    void SOProcessID([Out] out int pProcessID);
}

I the signature in the source code for OPOS. if you search the source code you'll see a little comment like this.. (from the msr OPOS source code) that is so you know what to implement so you can fire events.

c:\Program Files(x86)\Opos\oposSrc\zMSR\MSR.idl

[
    object,
    uuid(CCB91121-B81E-11D2-AB74-0040054C3719),
    dual,
    helpstring("IOPOSMSR 1.5 Interface"),
    pointer_default(unique)
]
interface IOPOSMSR_1_5 : IDispatch
{
// Methods for use only by the Service Object
    [id(1), hidden, helpstring("method SOData")] HRESULT SOData( [in] long Status );
    [id(2), hidden, helpstring("method SODirectIO")] HRESULT SODirectIO( [in] long EventNumber, [in, out] long* pData, [in, out] BSTR* pString );
    [id(3), hidden, helpstring("method SOError")] HRESULT SOError( [in] long ResultCode, [in] long ResultCodeExtended, [in] long ErrorLocus, [in, out] long* pErrorResponse );
    [id(4), hidden, helpstring("method SOOutputCompleteDummy")] HRESULT SOOutputCompleteDummy( [in] long OutputID );
    [id(5), hidden, helpstring("method SOStatusUpdate")] HRESULT SOStatusUpdate( [in] long Data );
    [id(9), hidden, helpstring("method SOProcessID")] HRESULT SOProcessID( [out, retval] long* pProcessID );

With those 2 basic things you'll be able to make a SO.. To fire an event is super easy too. Here is how I did it as a test

    public int OpenService(string lpclDevClass, string lpclDevName, object lpDispatch)
    {
        controlObject = (COPOSMSR)lpDispatch;
        controlObject.SOData(1)//I just fired a Data Event
    }

It has been my experience too that building a SO in C++ is harder than in C#. There are many more steps that C# just makes super simple.

Some good reads which was enough information for me to get started.

COM Interop Part1: C# Client Tutorial

http://msdn.microsoft.com/en-us/library/aa645736(v=vs.71).aspx

COM Interop Part 2: C# Server Tutorial

http://msdn.microsoft.com/en-us/library/aa645738(v=vs.71).aspx

COM Data Types

https://msdn.microsoft.com/en-us/library/sak564ww%28v=vs.100%29.aspx

like image 33
Robert Snyder Avatar answered Oct 22 '22 22:10

Robert Snyder