[VBA] Need help with MDL Wrapper Function for mdlSheetDef_setBorderAttachmentId

The documentation when followed generates the following error message:

Bad DLL calling convention (Error 49)

While this error has a help page, it does not explain any specific corrective action that can be done.

The Wrapper statement, from the MDL help is:

Declare Function mdlSheetDef_getBorderAttachmentId Lib "stdmdlbltin.dll" ( ByVal sheetDefIn As Long , ByRef borderAttachmentIdOut As DLong ) As Long

There is also a property in the VBA SheetDefinition object named MdlSheetDef that, according to its help "Retrieves the associated SheetDef pointer that a program can use as an argument to MDL mdlSheetDef functions."

With no example, we cannot determine how to use this or even if this is the way to use the subject wrapper function.

We seem to have hit a brick wall on this and need help from a Bentley Programmer.

  • Unknown said:
    ElementID is not a structure, it is just a base type __int64

    Correct — but only for MDL and C++, which support the __int64 data type. Microsoft invented VBA before 64-bit processors existed and does not support the 64-bit integer data type, which is why Bentley Systems invented the DLong UDT. MDL's ElementID is an alias for __int64.

    The problem is this …

     ElementID id = 0;
    mdlSheetDef_getBorderAttachmentId (..., &id);

    In the above fragment, the variable that contains the element ID (a 64-bit number) is passed by address. C/C++ languages can modify the contents of an address. It's the equivalent of VBA ByRef. However, because VBA can't cope with 64-bit integers, we have to use the DLong UDT. In this case the MDL wrapper declaration fo the getter function from MDL help is correct …

    Declare Function mdlSheetDef_getBorderAttachmentId _
       Lib "stdmdlbltin.dll" ( _
       ByVal sheetDefIn As Long , _
       ByRef borderAttachmentIdOut As DLong ) As Long

    The MDL wrapper for the setter function is incorrect, because it wants to pass a DLong by value …

    Declare Function mdlSheetDef_setBorderAttachmentId _
    Lib "stdmdlbltin.dll" ( _
    ByVal sheetDefIn As Long , _
    ByVal borderAttachmentIdIn As DLong ) As Long

    We can edit that declaration so that VBA will parse it correctly …

    Declare Function mdlSheetDef_setBorderAttachmentId _
    Lib "stdmdlbltin.dll" ( _
    ByVal sheetDefIn As Long , _
    ByRef borderAttachmentIdIn As DLong ) As Long

    However, changing that declaration does not change the underlying code. We don't know how that MDL wrapper is implemented. I suspect that there may be some internal confusion in the conversion from the internal __int64 and the VBA DLong passed by address.

    Regards, Jon Summers
    LA Solutions

     
    Regards, Jon Summers
    LA Solutions

  • Jon - I will file a bug report on mdlSheetDef_setBorderAttachmentId, thank you for pointing this out.

    Charles - Although you could call mdlSheetDef_setBorderAttachmentId with CExpression similar to Item 1 below, your original question was on mdlSheetDef_getBorderAttachmentId.  Once you had the Sheet ID, what were you going to do with it after?  The reason I ask is if your end goal is to get/set the ModelReference Sheet Properties, than you could do so using the ModelReference Object directly, or via the Properties of the ModelReference Object via CreatePropertyHander using the current MdlSheetDef.  See Item 2 below.

    Thank you,
    Bob

    1. This is a sample setting a border attachment id:

      Dim theAddr As Long
      Dim id As DLong
      id.Low = 1111
      expr = "mdlSheetDef_setBorderAttachmentId((void*)" & theAddr & ", " & DLongToString(id) & ")"
      GetCExpressionValue expr
    2. This is a sample of SheetDefinition properties that you can get/set using - ModelRef Object and Properties

      ModelReference Object:
          Model Name: ModelSheetName
          Model Type: msdModelTypeSheet
          MasterUnit.Base: 1
          MasterUnit.Label: in
          MasterUnit.System: msdMeasurementSystemEnglish
          MasterUnit.UnitsPerBaseDenominator: 254
          MasterUnit.UnitsPerBaseNumerator: 10000
          StorageUnit.Base: 1
          StorageUnit.Label:
          StorageUnit.System: msdMeasurementSystemEnglish
          StorageUnit.UnitsPerBaseDenominator: 254
          StorageUnit.UnitsPerBaseNumerator: 10000
          SubUnit.Base: 1
          SubUnit.Label: th
          SubUnit.System: msdMeasurementSystemEnglish
          SubUnit.UnitsPerBaseDenominator: 254
          SubUnit.UnitsPerBaseNumerator: 100000
          UORsPerMasterUnit: 1000000
          UORsPerStorageUnit: 1000000
          UORsPerSubUnit: 100000
          ================================================================================
      ModelReference Object Properties:
          [ModelReference] object id (N/A) has (17) defined properties:
          [1] Name: ModelSheetName
          [2] Type: 1
          [3] IsActive: True
          [4] Is3D: False
          [5] TreatAs3D: False
          [6] CellType: 0
          [7] Description: ModelSheetDescr
          [8] DefaultRefLogical: ModelSheetLogicalName
          [9] PropagateAnnotationScale: True
          [10] AnnotationScale: 6000
          [11] IsEnabled: True
          [12] SheetOrigin: 10.0000in, 10.0000in
          [13] SheetWidth: 11
          [14] SheetHeight: 8.5
          [15] SheetRotation: 0.785398163397448
          [16] SheetNumber: 10
          [17] SheetName: PropertiesSheetName



  • I'm sorry to say the wrapper function matches the documentation.  The wrapper function requires an ElementId passed by value but VBA does not support it. The only way to pass the element ID to the wrapped function is to use the CExpression support.  For example,

        Dim theAddr As Long

        Dim id As DLong

        id.Low = 1111

        expr = "mdlSheetDef_setBorderAttachmentId((void*)" & theAddr & ", " & DLongToString(id) & ")"

        GetCExpressionValue expr

    The CExpression scanner only supports 32-bit integers so this only supports element ID's up to 2147483647.  I don't think that is a problem as it takes a long time to go through 2 billion element ID's.

    John Gooding

  • OK, this is my code from the earlier post:

    Sub changeModelType2Sheet()
        Dim thisModel As ModelReference, refBorder As ModelReference
        Dim thisSheet As SheetDefinition
        Dim refBorderElID As DLong
        
        Set thisModel = ActiveModelReference
        ' Assume 1st reference file slot for now
        Set refBorder = ActiveModelReference.Attachments(1)
        ' This next line changes the model type to a sheet
        thisModel.Type = msdModelTypeSheet

        ' This appears to read the element ID of the 1st reference file
        refBorderElID = refBorder.AsAttachment.ElementID
        Set thisSheet = thisModel.GetSheetDefinition
        With thisSheet
            'This code fails to set the sheet size.
            .FormName = "ANSI D"
            'However, this code will cause the ANSI D sheet site
            'to be set as the form name

            'It took some trial and error to develop this result.
            'VBA Help was no help

            .Width = thisModel.WorkingUnitsToDouble("34'") * 1000
            .Height = thisModel.WorkingUnitsToDouble("22'") * 1000
            .AnnotationScaleFactor = 10

            'This next code will throw the error. Comment it out and most
            'everything else works.

            mdlSheetDef_setBorderAttachmentId thisSheet, refBorderElID
        End With
    End Sub

    When It go to modify this, I will be replacing my code:

    mdlSheetDef_setBorderAttachmentId thisSheet, refBorderElID

    with some version of this:

    expr = "mdlSheetDef_setBorderAttachmentId((void*)" & theAddr & ", " & _
            DLongToString(id) & ")"
    GetCExpressionValue expr

    As I understand your example, theAddr is the address of the sheet definition(?) and id is the DLong containing the element ID of the model Reference that contains the sheet border.

    My code appears to successfully retrieve the element ID as the Sheet Definition (address of ... ?)

    1. So, can I change DLongToString(id) to DLongToString(refBorderElID) and skip using the code id.Low = 1111 since I have the correct value in my DLong already?

    2. And if that is OK, what syntax can I use to put my thisSheet which contains my SheetDefinition already, into the expr line of code?

    TIA


    Charles (Chuck) Rheault
    CADD Manager

    MDOT State Highway Administration

    • MicroStation user since IGDS, InRoads user since TDP.
    • AutoCAD, Land Desktop and Civil 3D, off and on since 1996
  • Bump!

    I'm getting close, but need a little more help. See my last post - two questions at the bottom.


    Charles (Chuck) Rheault
    CADD Manager

    MDOT State Highway Administration

    • MicroStation user since IGDS, InRoads user since TDP.
    • AutoCAD, Land Desktop and Civil 3D, off and on since 1996
  • Unknown said:
    Can I change DLongToString(id) to DLongToString(refBorderElID)?

    Yes, noting John Gooding's warning.  As a work-around it will do until this gets fixed in a future version of MicroStation VBA.  It should be some time before you use up the first two billion element IDs.

    Unknown said:
    What syntax can I use to put my thisSheet which contains my SheetDefinition already, into the expr

    ' Retrieves the associated SheetDef pointer that a program can use as an argument to MDL mdlSheetDef functions
    thisSheet.MdlSheetDef

     
    Regards, Jon Summers
    LA Solutions

    Answer Verified By: caddcop 

  • Jon,

    You "Da Man!"

    Worked a treat!

    You really earned your BeMVP star today!

    Here is the code: (Note - At this time, it is essential to modify a number of fixed values and strings for this to function. I also had to change the slot number of my reference files, which can be done in the reference file dialog box. But it was also necessary to close and reopen the file for this to work. Next on the agenda is for the code to try and determine which slot contains the sheet border and work its magic based upon what it finds so no fixed values are required. As for the slot number issue, In my anticipated work flow, InRoads creates the model with the attached border and I believe, it is always in slot 1. And in that workflow, all sizes and annotation scales will be identical.)  

    Sub changeModelType2Sheet()

       Dim thisModel As ModelReference, refBorder As ModelReference

       Dim thisSheet As SheetDefinition

       Dim refBorderElID As DLong

       Dim sheetDefRef As Long

       Set thisModel = ActiveModelReference

       Set refBorder = ActiveModelReference.Attachments(1)

       If thisModel.Type <> msdModelTypeSheet Then thisModel.Type = msdModelTypeSheet

       refBorderElID = refBorder.AsAttachment.ElementID

       Set thisSheet = thisModel.GetSheetDefinition

       With thisSheet

            sheetDefRef = VarPtr(.MdlSheetDef)

            'this next line actually does not work

           .FormName = "Arch D"

           'these next two lines result in a correct FormName if there is a FormName defined with a matching size

           .Width = thisModel.WorkingUnitsToDouble("36'") * thisModel.UORsPerMasterUnit

           .Height = thisModel.WorkingUnitsToDouble("24'") * thisModel.UORsPerMasterUnit

           .AnnotationScaleFactor = 30

       End With

       expr = "mdlSheetDef_setBorderAttachmentId((void*)" & thisSheet.MdlSheetDef & ", " & _

              DLongToString(refBorderElID) & ")"

       GetCExpressionValue expr

       thisModel.SetSheetDefinition thisSheet

       thisModel.PropagateAnnotationScale

    End Sub


    Charles (Chuck) Rheault
    CADD Manager

    MDOT State Highway Administration

    • MicroStation user since IGDS, InRoads user since TDP.
    • AutoCAD, Land Desktop and Civil 3D, off and on since 1996