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:
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; /* 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; /* 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:
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).
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.
Peter Jones said:I have some legacy VBA code
Any variable that is a C pointer must be declared as LongPtr in VBA 7.
Peter Jones said: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 LongLong) As LongLong
Private Declare PtrSafe Function mdlRefFile_getInfo Lib "stdmdlbltin.dll" (ByVal modelRef As LongPtr) As LongPtr
Peter Jones said: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
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?
Peter Jones said:The MSDN Article states that it transforms to a LongLong in 64bit environments
Yes -- a 64-bit integer.
Peter Jones said: 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.
Peter Jones said: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.
Peter Jones said: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.
Peter Jones said:any further documentation on CExpression would be helpful!
It would, I agree.
Peter Jones said: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 Robert Hook can advise?
So realistically then I'm out of luck?
Hi Peter Jones,
Jon Summers 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...
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.
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.
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.
Private Declare PtrSafe Function mdl2vbaTest_RefP Lib "vba2mdl.dll" (ByVal sRefName As String) as Boolean
Dim oAtt As Attachment
For Each oAtt In ActiveModelReference.Attachments
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
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);
Peter Jones said:I can call the function from VBAPrivate Declare PtrSafe Function mdl2vbaTest_RefP Lib "vba2mdl.dll" (ByVal sRefName As String)
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.
Thanks - I did wonder what the expected type for C would be and I couldn't find any information on it with my google foo.
I still can't figure out why the pointer (passed through as LongPtr) from MdlModelRefP results in garbage. I'll keep looking!
Edit: Back to my original problem - I can see an enum "ProxyCacheStatus" as a member of DgnPlatform - the documentation states that it is "Returned by GetProxyCacheStatus to indicate the status of the ProxyCache of this DgnAttachment".
That's exactly what I'm looking for, however doing a search on the documentation and the contents of all of the include files turns up nothing for GetProxyCacheStatus - I can't find the function anywhere - does that mean it's simply not there?