9

I know this has been asked before, but I am unsuccessfully trying to convert some C++ structures/unions to Delphi to use the Hikvision SDK.

The C++ structures/unions I’m trying to convert are as follow:

struct{
  BYTE                           byEnable;
  BYTE                           byRes1[3];
  DWORD                          dwTriggerType;
  NET_ITC_TRIGGER_PARAM_UNION    uTriggerParam;
  BYTE                           byRes[64];
}NET_ITC_SINGLE_TRIGGERCFG,*LPNET_ITC_SINGLE_TRIGGERCFG;

union{
  DWORD                              uLen[1070];
  NET_ITC_POST_IOSPEED_PARAM         struIOSpeed;
  NET_ITC_POST_SINGLEIO_PARAM        struSingleIO;
  NET_ITC_POST_RS485_PARAM           struPostRs485;
  NET_ITC_POST_RS485_RADAR_PARAM     struPostRadar;
  NET_ITC_POST_VTCOIL_PARAM          struVtCoil;
  NET_ITC_EPOLICE_IOTL_PARAM         struIOTL;
  NET_ITC_EPOLICE_RS485_PARAM        struEpoliceRs485;
  NET_ITC_EPOLICE_RS485_PARAM        struPERs485;
}NET_ITC_TRIGGER_PARAM_UNION,*LPNET_ITC_TRIGGER_PARAM_UNION;

I have tried the following:

  PNetItcSingleTriggerCfg = ^TNetItcSingleTriggerCfg;
  TNetItcSingleTriggerCfg = record
    byEnable:      Byte;
    byRes1:        array [0..2] of Byte;
    dwTriggerType: DWord;
    uTriggerParam: TNetItcTriggerParamUnion;
    byRes:         array [0..63] of Byte;
  end;

  PNetItcTriggerParamUnion = ^TNetItcTriggerParamUnion;
  TNetItcTriggerParamUnion = record
    case integer of
      0: (uLen:             array [0..1069] of DWord);
      1: (struIOSpeed:      TNetItcPostIOSpeedParam);
      2: (struSingleIO:     TNetItcPostSingleIOParam);
      3: (struPostRs485:    TNetItcPostRS485Param);
      4: (struPostRadar:    TNetItcPostRS485RadarParam);
      5: (struVtCoil:       TNetItcPostVTCoilParam);
      6: (struHvt:          TNetItcPostHvtParam);
      7: (struIOTL:         TNetItcEPoliceIOTLParam);
      8: (struEpoliceRs485: TNetItcEPoliceRS485Param);
      9: (struPERs485:      TNetItcEPoliceRS485Param);          
      10:(struPostMpr:      TNetItcPostMprParam);
      11:(struViaVtCoil:    TNetDvrViaVtCoilParam);
      12:(struPostImt:      TNetItcPostImtParam);
      13:(struPostPrs:      TNetItcPostPrsParam);
      14:(struIpcHvt:       TNetIpcPostHvtParam);
      15:(struHvtV50:       TNetIpcPostHvtParamV50);
  end;

And I have also tried having it as a nested record (as suggested here http://rvelthuis.de/articles/articles-convert.html#unions )

  PNetItcSingleTriggerCfg = ^TNetItcSingleTriggerCfg;
  TNetItcSingleTriggerCfg = record
    byEnable:      Byte;
    byRes1:        array [0..2] of Byte;
    dwTriggerType: DWord;
    uTriggerParam: record
      case integer of
        0: (uLen:             array [0..1069] of DWord);
        1: (struIOSpeed:      TNetItcPostIOSpeedParam);
        2: (struSingleIO:     TNetItcPostSingleIOParam);
        3: (struPostRs485:    TNetItcPostRS485Param);
        4: (struPostRadar:    TNetItcPostRS485RadarParam);
        5: (struVtCoil:       TNetItcPostVTCoilParam);
        6: (struHvt:          TNetItcPostHvtParam);
        7: (struIOTL:         TNetItcSingleIOTLParam);
        8: (struEpoliceRs485: TNetItcEPoliceRS485Param); 
        9: (struPERs485:      TNetItcEPoliceRS485Param);
        10:(struPostMpr:      TNetItcPostMprParam);
        11:(struViaVtCoil:    TNetDvrViaVtCoilParam);
        12:(struPostImt:      TNetItcPostImtParam);
        13:(struPostPrs:      TNetItcPostPrsParam);
        14:(struIpcHvt:       TNetIpcPostHvtParam);
        15:(struHvtV50:       TNetIpcPostHvtParamV50);
      end;
    byRes:         array [0..63] of Byte;
  end;

I have seen similar questions on here (i.e. How do I translate a C union into Delphi? ), but the union in my example is in the middle of the structure, and as I understand the ‘end’ of the case statement also ends the record.

I think I understand the theory behind a variant record having the same memory allocation for the fields in the case statement, so the fields used would be either/or, but what I can’t work out is how to tell how the DLL is accessing these records, whether it is struName.unionName.fieldName or struName.fieldName and also how the union is defined (i.e. what is the selector of the case statement, and how to know what data type the selector is).

I have three similar structures to translate, and figure if I can crack one, I can crack them all.

With the records as described above, I keep getting the error message ‘Parameter error. Input or output parameter in the SDK API is NULL’ whilst calling a function from the DLL (if you need more information on this, please ask), which makes me think that my records have not been converted correctly.

I’m using the HCNetSDK.dll SDK version 5.0.3.20 and my IDE is XE7 if that helps.

Any help would be appreciated.

tobi-wan
  • 93
  • 3
  • 2
    FWIW, more on converting records and unions here: http://rvelthuis.de/articles/articles-convert.html#unions But as @DavidHeffernan already said, your conversion is OK, but the alignment may be off. There is a section about alignment in that article too. – Rudy Velthuis Aug 25 '16 at 12:34
  • This is a dupe actually. https://stackoverflow.com/search?q=%5Bdelphi%5D+union – Free Consulting Aug 25 '16 at 12:59
  • Not a dupe really. The asker has done good research and wants to know why the resulting code does not appear to behave as expected. – David Heffernan Aug 25 '16 at 13:35
  • Check whether C structures compiled with `#pragma pack` or not. – kludg Aug 25 '16 at 13:53
  • @Rudy I did read your article (I even quoted it in my question) - it's what got me on the right track to start with :) – tobi-wan Aug 25 '16 at 15:00
  • Probably a silly question, because a 64 bit app cannot directly access a 32 bit DLL, but your app is being compiled 32 bit isn't it? The reason I ask is because you use pointers and even with the techniques to call a 32 bit DLL from a 64 bit app the pointers will be the wrong size. – Dsm Aug 25 '16 at 15:00
  • @tobi-wan: Yeah sorry, I noticed that afterward. – Rudy Velthuis Aug 25 '16 at 15:14

1 Answers1

6

Assuming that structure alignment settings match then both of your attempts to convert the union are correct. You can use whichever one you prefer. For what it is worth, I would prefer the first approach where you declare a type to represent the union.

Whatever your actual problem is, it does not appear to lie with the union conversion. A simple way to test this is to check that the size of the types match in the C++ and Delphi versions, and that the offsets to each member match.

For test the size of a type use sizeof in C++ and SizeOf in Delphi. For the offsets use the offsetof macro for C++ and the trick shown in my answer here for Delphi.

Community
  • 1
  • 1
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Actually, thinking about it: a proper conversion of a union should probably use a packed record. AFAIK, the C standard makes it obligatory that all elements are aligned at the same address, but there is no Delphi standard and I don't know if that is true for all compilers currently in use.Packed records ensure that all parts are aligned at the same address. – Rudy Velthuis Aug 25 '16 at 13:25
  • 2
    @RudyVelthuis We've had this discussion so many times before. Packed forces the record to be aligned. That will have no impact on the layout because a variant record always places the variant members at the same offset. But it will lead to misalignment of the structure. For instance a union containing an int and a double has size 8, and alignment 8. When aligned it should always be placed on 8 byte boundaries. If you pack it then that will not happen. If the C++ code force the union to have alignment 1, then pack it, otherwise don't. – David Heffernan Aug 25 '16 at 13:33
  • @David Thanks for putting the time in to answer my question. At least now I can look elsewhere to see where my error lies. So what defines the selector of the union? – tobi-wan Aug 25 '16 at 14:58
  • There isn't really a selector. In a classic Pascal variant type you'd write `case ValueType: Integer` in your record and that would declare a variable before the variant part. But nothing would enforce that you referred to the field corresponding to the actual value of `ValueType`. Just view it as verbose Pascal boilerplate syntax that you have to regurgitate in order to make a union. – David Heffernan Aug 25 '16 at 15:05