Drawings SDK: Safely using OdGsView Pointers in Database Viewport Objects

Andrew Markovich

November 22, 2018

OdDbViewportTableRecord (model space viewport) and OdDbViewport (paper space viewport) objects store OdGsView pointers of the current internal graphics device. These pointers are accessible using these methods:

OdGsView* OdDbViewportTableRecord::gsView() const;

and

OdGsView* OdDbViewport::gsView() const

An OdGsView pointer represents the current runtime representation of a viewport inside the current graphics device (if it exists).

But what to do with these pointers if your application invokes multiple devices for a single database? There is only one pointer in the object, and it can’t store pointers for multiple devices.

Example of problematic case

These OdGsView pointers are installed in database viewport objects during construction of a layout helper, as in this example:

OdGsModulePtr pGs = ::odrxDynamicLinker()->loadModule(OdWinGDIModuleName);
OdGsDevicePtr pDevice = pGs->createDevice();
pDwgContext->setDatabase(pDatabase);
pDevice = OdDbGsManager::setupActiveLayoutViews(pDevice, pDwgContext);

The OdDbGsManager::setupActiveLayoutViews call in this example creates a layout helper object and returns it. After this call, you can access related OdGsView objects from the database viewport objects directly or using the OdDbAbstractViewportData helper class, as in this example:

OdDbObjectPtr pViewportObject = pDatabase->activeViewportId().safeOpenObject();
OdDbAbstractViewportDataPtr pVpData(pViewportObject);
OdGsView *pGsView = pVpData->gsView(pViewportObject);

pGsView points to an actual runtime viewport data representation inside a previously constructed graphics device.

For example, the application currently uses this graphics device to draw graphics on the screen. But now suppose you also require sending these graphics to a printer or drawing a print preview using another graphics device:

OdGsDevicePtr pPrintDevice = pGs->createDevice();
pPrintDwgContext->setDatabase(pDatabase);
pPrintDevice = OdDbGsManager::setupActiveLayoutViews(pPrintDevice, pPrintDwgContext);

After this second OdDbGsManager::setupActiveLayoutViews call, OdGsView pointers inside database viewport objects are switched to the new (printing) device pointers.

For example, after printing is complete, you can destroy the printing device. But database viewport objects still can contain pointers to printing device views, so the following code can potentially cause an application crash even if you’ve correctly checked the retuned pointer on null:

OdDbObjectPtr pViewportObject = pDatabase->activeViewportId().safeOpenObject();
OdDbAbstractViewportDataPtr pVpData(pViewportObject);
OdGsView *pGsView = pVpData->gsView(pViewportObject);
if (pGsView != NULL)
  pGsView->zoomExtents(OdGePoint3d(-10.0, -10.0, -10.0), OdGePoint3d(-10.0, -10.0, -10.0)); // <-- potential crash

Of course, during deletion, devices safely set invoked pointers as zeroes, but these pointers are not actual for the application after secondary device usage.

Solution

The problem can be solved using a special layout helper method that restores OdGsView pointers inside database viewport objects for the actual graphics device:

virtual void OdGsLayoutHelper::restoreGsViewDbLinkState() = 0;

When you’ve completed working with another device (for example, printing) and switched back to using the previous device, you can simply update all OdGsView links in the database:

OdGsLayoutHelper::cast(pDevice)->restoreGsViewDbLinkState();

The final solution is very simple and clear. Just don’t forget the last item in these steps:

  • Construct secondary device.
  • Work with secondary device.
  • Complete work with secondary device.
  • Switch to main device and update OdGsView database links.

You can switch from one graphics device to any other graphics device at any time, but always update OdGsView links before working with them to keep your database state consistent and safe.