Detecting a Cached Saved View Attachment in MicroStation CONNECT

Hi All,

I have some legacy VBA code from v8i that I'm trying to port over to the CONNECT versions - it's using a CExpression with a pointer into the referenceFile struct for a specific reference:

Private Declare PtrSafe Function mdlRefFile_getInfo Lib "stdmdlbltin.dll" (ByVal modelRef As LongLong) As LongLong //Declare MDL Function

//Do other stuff

RefInfoP = mdlRefFile_getInfo(modelRef:=oRef.MdlModelRefP) //Get a Pointer to the Reference File

strRefName = "((struct referenceFile *)" & RefInfoP & ")" //Construct the beginning of the CExpression

iCacheValue = GetCExpressionValue(CExpression:=strRefName & "->display.flags.hiddenLineCaching") //Grab the value from the nested members of the struct

This is from the refernce.h header file included with the v8i SDK:

struct  referenceFile
    {
    File_id_desc            file_id;                    /* file identification */
    Ref_display             display;                    /* display information */
    Ref_attach_desc         attach;                     /* attachment description */
    Clip_desc               clip;                       /* clipping descriptor(points separate) */
    UInt32                  *colorMap_deprecated[2];    /* DEPRECATED: element to screen color mappings */
    RefLevelMasks           levelMask;                  /* Level mask */
    MSElementDescrP         readOnlyRefAttachEdP;       /* Attachment descriptor for reference attached during readonly session */
    MiscellaneousRefData    *extraDataP;                /* variable sized part of reference structure. More can be added to it. */
    void                    *additionalDataP[2];        /* Future use */
    };

The struct Ref_display named "display" contains the variable I want, "hiddenLineCaching".

The v8i code generates an error "expected struct/union name" when running in CONNECT, so I went hunting through the documentation.

I think I know (from what I can gather) that the MicrostationAPI has superseded MDL, so looking at the documentation and headers in the CONNECT SDK I found the struct Bentley::DgnPlatform::DgnAttachment.

In the header DgnAttachment.h is a similar struct to what was nested within the original referenceFile struct:

struct  RefDisplayFlags
    {
    UInt32      symbologySet:1;                 //!< The symbology has been set.
    UInt32      hiliteBoundary:1;               //!< Hilite the clip boundary when the DgnAttachment is selected in the reference dialog box (not persistent).
    UInt32      displayHilitedManip:1;          //!< Hilite all elements in the DgnAttachment when the DgnAttachment is the subject of a DgnAttachmnet manipulation command.
    UInt32      displayHilitedSelected:1;       //!< Hilite all elements in the DgnAttachment when the DgnAttachment is selected in the reference dialog box (not persitent).
    UInt32      dontUpdateChildren:1;           //!< Don't display child references (not persistent).
    UInt32      redundant:1;                    //!< This display of this DgnAttachment is the the same as another in the tree of DgnAttachments, so it is not displayed (not persistent).
    UInt32      missingModel:1;                 //!< The file for this DgnAttachment is found, but the model is not (not persistent).
    UInt32      rightNotGranted:1;              //!< The file is found, but we don't have View permissions (not persistent).
    UInt32      namedGroupFound:1;              //!< There is a named group limiting display to only members of that named group (not persistent).
    UInt32      revisionNotAvailable:1;         //!< There is a History revision specified, but that revision was not found in the file (not persistent).
    UInt32      resolvedHistoryTag:1;           //!< A revision is specfied as a tag and found.
    UInt32      revisionFromParent:1;           //!< The revision came from a parent history tag.
    UInt32      findModelByID:1;                //!< Use file_id.modelId to find the target model.
    UInt32      missingGeoCoordApp:1;           //!< This is a Geo referenced attachment, but the GeoCoord capabilities are not available.
    UInt32      missingGeoCoordSys:1;           //!< This is a Geo referenced attachment, but there is no GCS in either the parent model or the DgnAttachment model.
    UInt32      isCircular:1;                   //!< This is a circular reference? (Usually, such a DgnAttachment is not loaded at all).
    UInt32      calculatedTransformUsed:1;      //!< The DgnAttachment is set to be GeoCoord reprojected, but DgnPlatform was were able to substitue an identical linear transform.
    UInt32      namedGroupProcessingComplete:1; //!< We have done the named group processing for this reference already.
    UInt32      hiddenLineCaching:2;            //!< How hidden lines are saved in the file (stored in linkage).
    UInt32      proxyCacheSynchOption:2;
    UInt32      dontAddRefClip:1;
    UInt32      unused:9;
    };

I can see this contains the variable I want to access: hiddenLineCaching.

I have amended the CExpression thus:

 strRefName = "((struct Bentley::DgnPlatform::DgnAttachment *)" & RefInfoP & ")"
 
 iCacheValue = GetCExpressionValue(CExpression:=strRefName & "->RefDisplayFlags.hiddenLineCaching")

When this runs I receive an error "expected struct/union name".

Reading through the documentation it seems that not all structs are published / accessible, and I'm a long way from a C++ expert, so I'm stumped.

I can't figure out what struct I need to access and get the members of.

Any insight would be greatly appreciated.


Related problem is changing the slot number as well:

strRfName = "((struct referenceFile *)" & RefInfoP & ")"

lngSlotNumber = GetCExpressionValue(CExpression:=strRfName & "->file_id.referenceNum")

I haven't looked into this yet, but the struct is the same, so also doesn't work.

Parents
  • I have some legacy VBA code

    Any variable that is a C pointer must be declared as LongPtr in VBA 7.

    Private Declare PtrSafe Function mdlRefFile_getInfo Lib "stdmdlbltin.dll" (ByVal modelRef As LongLong) As LongLong
    Private Declare PtrSafe Function mdlRefFile_getInfo Lib "stdmdlbltin.dll" (ByVal modelRef As LongPtr) As LongPtr
    The v8i code generates an error "expected struct/union name" when running in CONNECT

    struct RefDisplayFlags is defined in C++ header file DgnAttachment.h.  However, I don't see where it's used: it is not a member of class DgnAttachment.  LIkewise, I see no methods in class DgnAttachment to do with display or hidden line caching.

     
    Regards, Jon Summers
    LA Solutions

  • Hi Jon,

    Thanks for getting back to me!

    The MSDN Article states that it transforms to a LongLong in 64bit environments, so it should function as written I would have thought?

    I've amended the code to LongPtr nevertheless.

    As for the struct - if it's defined then it should be accessible, should it not?

    The software allows for caching of dynamic views, which is indicative that it must be available through some means, do you have any suggestions of other places to hunt?

    Attempting to access the DgnAttachment struct with any members that are listed in the documentation (e.g. IsMissingFile() ) results in the same error: "expected struct/union name" which makes me think it's not publicly available.

    Is there documentation which I am missing that lists the publicly available structs for CExpression?

    In fact any further documentation on CExpression would be helpful!

    Additionally, I was unable to find any published documentation or articles that mentioned the referenceFile struct, yet it is accessible in v8i - is there a resource I am missing or should be looking in?

  • The MSDN Article states that it transforms to a LongLong in 64bit environments

    Yes -- a 64-bit integer.

    it should function as written I would have thought?

    It does, but probably not for the reason you think.

    C libraries, such as MDL and Win32, sprinkle pointers liberally throughout their APIs.  For MicroStation V8, a 32-bit app, pointers are 32-bit, which is a Long in VBA 6.  For MicroStation CONNECT, as 64-bit app, pointers are 64-bit.  In other words, a pointer is a 64-bit integer: a LongLong in VBA 7.

    It's not incorrect to write LongLong, but it is semantically misleading when the variable is a C pointer.  LongPtr expresses accurately what the variable is intended for.

    As for the struct - if it's defined then it should be accessible, should it not?

    I wouldn't bank on it.  We usually have to work out the equivalent UDT in VBA.  And, even if it is defined, there's no way that I can see to fill the struct with data from an Attachment.

    Is there documentation which I am missing that lists the publicly available structs for CExpression?

    No.  One suggestion is to record a VBA macro while you twiddle the settings in the References dialog.  Examine the macro recording, which sometimes reveals C expressions generated to manipulate application variables.

    any further documentation on CExpression would be helpful!

    It would, I agree.

    I was unable to find any published documentation or articles that mentioned the referenceFile struct, yet it is accessible in v8i

    Not everything made it into the CONNECT API.  I doubt that it's a deliberate omission, and your request may be the first time someone has sought to use it.  Maybe  can advise?

     
    Regards, Jon Summers
    LA Solutions

  • Hi Jon,

    Thanks again!

    So realistically then I'm out of luck?

    Cheers,

    PJ

  • Hi ,

     has provided some very experienced and well-guided advice for the current path of customization you have chosen/have to take.

    For MicroStation VBA if the VBA/COM APIs do not provide the functionality needed, then (as Jon mentions) using the MicroStation VBA or Bentley Macro Recorder(s) are often the next best step to gain insights and reliable path forward.  If none available the next step (especially for new/beginning API developers) would be to determine if any product Key-ins exist that may produce the desired results.  Sometimes key-ins may take some time to identify, may not exist at all, and if/when found and implemented are likely to seem obscure/strange to most future VBA API developers yet are a viable option to work until a better fit solution can be created (or required). Similarly, extending MicroStation VBA code with (Declare) MDL function calls is also likely to look obscure to a future VBA developers and can be fraught with implementation issues, testing and/or not being able to implement due to API implementation specifics, or (Microsoft or Bentley) incompatibilities when attempting to cross native, managed, or COM; memory and runtime boundaries.

    So, imho and given the above...

    First suggestion:

    From what I can tell an Attachment's previous display.flags.hiddenLineCaching was migrated internally to using a public enum of ProxyCachingOption that I do not see a direct public API accessor for.  The closest and possibly easiest way to access this specific item would be use a VBA Property Handler on the VBA attachment object and see if you can locate a "Visible Edge Display" property and value.

    Additional suggestion:

    Since you appear to be doing and have a fair/significant amount and need to access the MDL API, why not consider taking some time to learn and create a more robust and reliable custom MDL "helper application".  This path would certainly provide more: direct, performant, reliable and easily debuggable MDL code/functionality; that can be called from any/many custom VBA applications via your own simple wrapper exported function calls that can survive crossing VBA/COM, native and manage code boundaries via your own custom app key-ins, connections, etc.?  Doing also best ensure consistent access and a desired clean separation between APIs and Interfaces/Implementations.

    HTH,
    Bob



  • Hi Bob,


    Thanks for your reply - I've tried the Key-ins, Recording, and Property Handler to no avail.

    I'm going down the road of writing a wrapper, but I'm not making much headway.

    I can call the function from VBA but when debugging is VS2017 I can see that my arguments are not coming through.

    At first I tried to use MdlModelRefP to get a pointer to the Attachment, then pass it through to get the DgnModelP members.

    This method gave me memory exceptions (in the region of 0x00000040).

    Now I'm simply trying to pass through a string but all I get is a null-terminated empty string (i.e. sRefName = "").

    It seems that none of my arguments to my wrapper function are being passed to the body of the wrapper method correctly.

    Additionally the Debug.Print call works properly (true / false), so at least that makes it back to VBA.

    At the moment I'm just trying to match the name of reference passed as an argument to the name of a reference that is in the DgnAttachmentsCP array (there's only one reference in my test file) as a test to see if I can get anything working. A pointer would be ideal as it would save scanning the references in C++ and would allow me to cast it directly to DgnAttachmentCP to access those members.

    I've put in my code in VBA and C++ below.

    Is there documentation on how to write these wrappers?

    It builds successfully and I can break at my wrapper in VS2017 and step through the code, the communication between VBA and the wrapper just seems broken.

    VBA:

    Private Declare PtrSafe Function mdl2vbaTest_RefP Lib "vba2mdl.dll" (ByVal sRefName As String) as Boolean
    
    Sub mdlfromvba()
    
    Dim oAtt As Attachment
    
    For Each oAtt In ActiveModelReference.Attachments
       Debug.Print mdl2vbaTest_RefP(oAtt.AttachName)
    Next oAtt
    
    End Sub

    C++ (This is an excerpt, I can post the rest if needed). For the sRefName argument variable I've tried WCharP, WChar, wchar_t *, char *, all come through empty (or not at all).

    extern "C" DLLEXPORT bool __stdcall mdl2vbaTest_RefP
    (
    	WCharCP sRefName
    )
    	{
    	//Do something
    
    	if (!sRefName)
    	{
    		return false;
    	}
    	
    	DgnModelP model = ISessionMgr::GetActiveDgnModelP();
    	
    	if (model->GetDgnAttachmentsCP() != NULL)
    	{
    		for each (DgnAttachmentCP attachment in *model->GetDgnAttachmentsCP())
    		{
    			if (sRefName == attachment->GetAttachFileName())
    			{
    				mdlOutput_messageCenter(DgnPlatform::OutputMessagePriority::Info, attachment->GetAttachFileName().c_str(), attachment->GetAttachFileName().c_str(), DgnPlatform::OutputMessageAlert::None);
    				return true;
    			}
    		}
    	}
    	
    	return false;
    	}
    

  • I can call the function from VBA
    Private Declare PtrSafe Function mdl2vbaTest_RefP Lib "vba2mdl.dll" (ByVal sRefName As String)

    You're passing a VBA String to a C-style function that expects a NULL-terminated array of wchar_t.  That won't work.  You need to read about Microsoft's various types of string in C and VBA.

    This article should give you some hints about string conversion between C and VBA.  Pay attention to the StrPtr() function.

    There are some things for which we should be grateful: MDL in V8 had the MSWideChar data type that was only used in, AFAIK, two functions.  MicroStation CONNECT is mostly Unicode, with good support in the API.  That data type has vanished.

     
    Regards, Jon Summers
    LA Solutions

Reply Children