Michel Deslierres

AHSDK - Listening to the CM15A

Bad surprise

The receive event handler in the test application is not very sophisticated. It merely tries to convert all the OleVariant variables to strings and concatenates all non empty strings into a single string that is added to the memo.

procedure TForm1.ActiveHome1RecvAction(ASender: TObject; bszAction, bszParm1,
  bszParm2, bszParm3, bszParm4, bszParm5, bszReserved: OleVariant);
var
  s, ns: string;
begin
  s := bszAction;
  ns := bszParm1;
  if ns <> '' then begin
    s := s + ' ' + ns;
    ns := bszParm2;
    if ns <> '' then begin
      s := s + ' ' + ns;
      ns := bszParm3;
      if ns <> '' then begin
        s := s + ' ' + ns;
        ns := bszParm4;
        if ns <> '' then begin
          s := s + ' ' + ns;
          ns := bszParm5;
          if ns <> '' then begin
            s := s + ' ' + ns;
            ns := bszReserved;
            if ns <> '' then begin
              s := s + ' ' + ns;
            end;
          end;
        end;
      end;
    end;
  end;
  Memo1.Lines.Add(Format('Received: "%s"', [s]));
end;

When the CM15A receives a command it responds with  message. Unfortunately, if the object ActiveHome1's OnRecvAction is set to the above handler, an invalid variant type exception (EVariantBadVarTypeError) will be raised when the CM15A responds: 


Without any idea about the source of the problem, I tried converting only the bszAction OLE variable to a string, ignoring all the other variables. But that did not work.

X10 Forum to the rescue

It turns out that back in 2008, the problem was resolved by ddv2005 who posted the following on the X10 forum:

ddv2005
Newbie


Helpful Post Rating: 0
Posts: 2


Reply #1 on: January 04, 2008, 07:30:15 PM

It is Delphi problem (all version from 5 to latest). Delphi generate all parameters of RecvAction event as Olevariant but TOleControl.InvokeEvent resolve all params from OleVariant to actual type and application failed on locking OleVariant parameters because it is NOT olevariant. More that, last parameter is datetime and some time it is null and TOleControl.InvokeEvent resolve it to integer (4 bytes). But when last parameter is actual datetime then TOleControl.InvokeEvent resolve it to 8 bytes!!! but RecvAction event expect only 4!!! bytes and it is SHIFT ALL PARAMETERS!!!! It is crazy but it is DELPHI CODE in ALL VERSIONS! I resolve this problem by overriding TOleControl.InvokeEvent in generated TLB code as

procedure TActiveHome.InvokeEvent(DispID: TDispID; var Params: TDispParams);
begin
  if Params.cArgs=7 then
    if Assigned(FOnRecvAction) then
      FOnRecvAction(Self,OleVariant(Params.rgvarg[6]),OleVariant(Params.rgvarg[5]),
      OleVariant(Params.rgvarg[4]),OleVariant(Params.rgvarg[3]),
      OleVariant(Params.rgvarg[2]),OleVariant(Params.rgvarg[1]),
      OleVariant(Params.rgvarg[1]));
end;

I don't pretend that I understand exactly what the problem is, but the solution works. Here's how to test it.

Copy the imported type library ActiveHomeScriptLib_TLB.pas to the folder containing the example application's code. That way we will be changing the code in a copy and not the original version.

1. In the protected part of TActiveHome's declaration, add the following line

procedure TActiveHome.InvokeEvent(DispID: TDispID; var Params: TDispParams); override;

2. In the implementation part of the file, add the code for the InvokeEvent procedure as given above.

Because I cannot envisage the need to have the component visible, I also suggest the following.

3. In the public part of TActiveHome's declaration, add the following line

constructor Create(aOwner: TComponent); override;

4. In the published part of TActiveHome's declaration, change property Visible; to property Visible default false;

5. In the implementation part of the file add the following constructor

constructor TActiveHome.Create(aOwner: TComponent);
begin
  inherited Create(aOwner);
  visible := false;
end;

Now rebuild the test application, hook the receive event by checking Try to listen to CM15A and send a command. If all went well, you should see the activex component's reply. Here's a look :

Making the changes permanent

If everything now works well, you should consider making permanent the changes to the file ActiveHomeScriptLib_TLB.pas. You could copy the changed file back on top of the original file, deleting the ActiveHomeScriptLib_TLB.dcu file to ensure that Delphi regenerates it with the changes.

However that might not be the best. Recall the warning at the beginning of the file. You will lose the changes if you ever directly or indirectly imported the type library again. To avoid that problem, I removed the warning at the beginning of the modified file and replaced it with an explanation of the source of the changes:

//************************************************************************ //
// This unit is a revised version of the imported type library
// ActiveHomeScriptLib_TLB.pas with two changes
//
//  1. The protected procedure
//     TOleControl.InvokeEvent(DispID: TDispID; var Params: TDispParams);
//     is overriden in the TActiveHome class to correct for the parameter
//     error on receiving messages from the ahscript.dll as per
//     ddv2005's January 04, 2008 message on the X10 forum
//     http://forums.x10.com/index.php?topic=14192.msg79406#msg79406
//
//  2. The default value of property TActiveHome.visible is changed to
//     false
//
//  Michel Deslierres 2013/07/12
//************************************************************************//

Then I copied the changed file back to it's original folder under a new name ActiveHomeScriptLib.pas, changed the name of dcr file to ActiveHomeScriptLib.dcr,  and then modified the package files to point to this new import library file.

In Delphi 2010

1. Select Open Project... in Delphi's File menu:

2.  Open the x10Server project created when importing the type library. On my system this was the file x10Server.dproj in folder C:\Program Files (x86)\Embarcadero\RAD Studio\7.0\OCX\Servers\.

3. If you expand the Contains node in the Project Manager window, you will see where the original import library resides. Copy the modified imported library file to that folder (in my case: C:\Users\Michel\Documents\RAD Studio\7.0\Imports) under the name ActiveHomeScriptLib.pas.

4. Click with the right mouse button on x10Server.Bpl in the Project Manager window and then click on Add... in the pop up menu.

5. Navigate to the folder containing the modified file and select it.

6. Click with the right mouse button on the old imported type library file in the Project Manager window and then click on Remove from Project in the pop up menu.

7. Click with the right mouse button on x10Server.Bpl in the Project Manager window and this time click on Install in the pop up menu.

If you have problems installing, you may need to remove the package. To do that

8. Click on Install Packages... in Delphi's Component menu:

9 Then, after selecting the package, click on the Remove button:

10. Don't forget to save changes to the package file when you close it.

To make sure that everything was done correctly, open a new VCL projects and drop a TActiveHome component from the ActiveX palette on the project's form. The component's Visible property should be set to false. If this almost empty application will not compile because ActiveHomeScriptLib.dcu has not been found, you can move the file from  C:\Users\{user}\Documents\RAD Studio\7.0\Imports to the folder C:\Program Files (x86)\Embarcadero\RAD Studio\7.0\Imports which contains many other imported type library units and which is in Delphi 2010's default search path.

If everything worked, you can now move on to the next page.

In Delphi 7

Here are the steps to modify the package x10Server.dpk that contains the Active Home component.

1. Select Install packages... from the Component menu:



2. Select the package from the list. Packages are listed according to their description. In this case I had entered X10 ActiveX script server in step 4 when importing AHSDK's type library. Click on Modify



3. Click on Yes when asked to open x10Server.dpk.

4. Click on Add in the package editor, click on Browse in the Add window and then navigate to the import folder (the path can be seen in the package editor). Select the file ActiveHomeScriptLib.pas, click on Open. The open file dialog window will disappear, the name of the unit will be in the Unit name edit in the Add dialog; click on Ok.

5. Remove the old files from the package by clicking on Remove in the package editor, by selecting the two files  ActiveHomeScriptLib_TLB.dcr and  ActiveHomeScriptLib_TLB, by clicking on Ok...

6.  ...and by clicking Yes twice when asked to confirm removal of the files:

7. Then, back in the package editor, click on the Compile button.

8. Click on the Ok button when Delphi informs you that the component ActiveHomeScriptLib.TActiveHome has been added to the palette and that ActiveHomeScriptLib_TLB.TActiveHome has been removed.

9. Don't forget to save changes to the package file when you close it.

To make sure that everything was done correctly, open a new VCL projects and drop a TActiveHome component from the ActiveX palette on the project's form. The component's Visible property should be set to false.