LabWindows/CVI

cancel
Showing results for 
Search instead for 
Did you mean: 

VISA driver won't unload from memory

I have the source code for a Tektronix 7000 series oscilloscope (circa 2003) being built into a VISA driver (tktds7k.dll) using Labwindows CVI/2019. My application DLL (Visual C++ 2017) links against it and will load and unload that driver DLL as well as NiViPpiP.dll and NiViPpiD.dll normally if my application code doesn't execute any functions in tktds7k.dll. However if I call the tktds7k_init() function and later call tktds7k_close(), when my application DLL attempts to unload, the thread hangs between unloading NiViPpiP.dll and NiViPpiD.dll. Any function calls into the tktds7k.dll I make between init() and close() seem to work correctly to setup and take readings from the scope.

 

We've been using this source code from Tektronix for years on older versions of Windows with older versions of CVI, but now I'm trying to integrate this into a Windows 10 application.

 

Here's the init function:

 

ViStatus _VI_FUNC tktds7k_init (ViRsrc resourceName, ViBoolean IDQuery, ViBoolean resetDevice, ViPSession instrumentHandle)
{

  ViSession resMgr; /* Resource Manager Session */
  ViStatus retError; /* Returned error status */
  tktds7k_PdrvrStruct drvrHdl; /* pointer to instrument specific data */


  /* Open Instrument */

  retError = viGetDefaultRM(&resMgr);
  if (retError < VI_SUCCESS)
  {
    *instrumentHandle = VI_NULL;
    return retError;

  }

 

  retError = viOpen(resMgr, resourceName, VI_NULL, VI_NULL, instrumentHandle);

 

  if (retError < VI_SUCCESS)
  {
    *instrumentHandle = VI_NULL;
    return retError;
  }

 

  /* No user data yet, so null it out */

  retError = viSetAttribute (*instrumentHandle, VI_ATTR_USER_DATA, (ViAttr)VI_NULL);

 

  if (retError < VI_SUCCESS)
  {
    tktds7k_close (*instrumentHandle);
    *instrumentHandle = VI_NULL;
    return retError;
  }

 

  /* Perform ID Query */

  if (IDQuery)
  {
    retError = tktds7k_verifyID(*instrumentHandle);

 

    if (retError < VI_SUCCESS)
    {
      tktds7k_close (*instrumentHandle);
      *instrumentHandle = VI_NULL;
      return retError;
    }
  }

 

  /* Initialize the instrument to a known state. */
  if (resetDevice)
  {
    retError = tktds7k_reset (*instrumentHandle);

 

    if (retError < VI_SUCCESS)
    {
      tktds7k_close (*instrumentHandle);
      *instrumentHandle = VI_NULL;
      return retError;
    }
  }

 

  /* Set VERBOSE State ON */
  retError = tktds7k_SetVerboseState(*instrumentHandle);

 

  if (retError < VI_SUCCESS)
  {
    tktds7k_close (*instrumentHandle);
    *instrumentHandle = VI_NULL;
    return retError;
  }

 

  /* Set HEADERS State OFF */
  retError = tktds7k_SetHeadersOff(*instrumentHandle);
 

  if (retError < VI_SUCCESS)
  {
    tktds7k_close (*instrumentHandle);
    *instrumentHandle = VI_NULL;
    return retError;
  }

 

  /* Set default timeout */

  retError = viSetAttribute (*instrumentHandle, VI_ATTR_TMO_VALUE, tktds7k_DEFAULT_TIMEOUT);

 

  if (retError < VI_SUCCESS)
  {
    tktds7k_close (*instrumentHandle);
    *instrumentHandle = VI_NULL;
    return retError;
  }

 

  /* Initialize interface for instrument. */

  drvrHdl = (tktds7k_PdrvrStruct)malloc(sizeof(tktds7k_drvrStruct));

 

  if (drvrHdl == NULL)
  {
    tktds7k_close (*instrumentHandle);
    *instrumentHandle = VI_NULL;
    return VI_ERROR_ALLOC;
  }

 

  retError = viSetAttribute (*instrumentHandle, VI_ATTR_USER_DATA, (ViAttr)drvrHdl);


  if (retError<VI_SUCCESS)
  {
    tktds7k_close (*instrumentHandle);
    *instrumentHandle = VI_NULL;
    return retError;
  }

 

  drvrHdl->instrDesc = (ViString)malloc(strlen(resourceName)+1);

 

  if (drvrHdl->instrDesc == NULL)
  {
    tktds7k_close (*instrumentHandle);
    *instrumentHandle = VI_NULL;
    return VI_ERROR_ALLOC;
  }

 

  strcpy(drvrHdl->instrDesc, resourceName);

  /* CHANGE: Add instrument specific initialization here */

  return VI_SUCCESS; /* return */
}

 

And here's the close function:

 

ViStatus _VI_FUNC tktds7k_close (ViSession instrumentHandle)
{
  ViStatus retError; /* Returned error status */

  ViAddr userData; /* User data stored in VI_ATTR_USER_DATA */
  tktds7k_PdrvrStruct drvrHdl; /* pointer to instrument specific data */

 

  if (instrumentHandle != VI_NULL)
  {
    retError = viGetAttribute (instrumentHandle, VI_ATTR_USER_DATA, &userData);


    if (retError<VI_SUCCESS) return retError;

 

    drvrHdl = (tktds7k_PdrvrStruct)userData;

 

    if (drvrHdl != NULL)

    {
      /* Free any mallocs in the tktds7k_instr_array struct */
      free(drvrHdl->instrDesc);

      free(drvrHdl);
    }

 

    retError = viClose(instrumentHandle);
    return retError;
  }
  else
  {

    return VI_SUCCESS;
  }
}

0 Kudos
Message 1 of 5
(1,414 Views)

Sorry for deleting the previous reply but it was nonsense to disguise a spam link.

 

Where do you try to load and unload your other DLLs? Note that it was never safe to do that in DLLMain() in any of the events. Microsoft documented that in the LoadLibrary() description since many moons as there is a loader lock acquired during the LoadLibrary() and FreeLibrary() calls that can end in a mutual exclusion lock if you call these functions reentrantly.

It used to work in the past for some reason in many cases but somewhere around 2020 Windows 10 acquired the unfortunate habit to make this warning a real issue and almost consistently lock.

Rolf Kalbermatter
My Blog
0 Kudos
Message 2 of 5
(1,351 Views)

The tktds7k DLL is being loaded implicitly by my application DLL, which itself is explicitly loaded/unloaded by an executable. All of the other DLLs my app is using are also implicitly loaded, if that answers your question.

0 Kudos
Message 3 of 5
(1,338 Views)

Not really. Implicit and explicit loading is a bit ambiguous. I assume with implicit you simply mean that you link your program with the according import library, explicit that you call LoadLibrary yourself in the code.

 

I’s kind of related but not the same. The question was where do you call LoadLibrary() and especially FreeLibrary(). LoadLibrary() is seldom the problem as you usually call that at runtime the first time you want to call a function from the library. Makes not much sense to do that in PROCESS_ATTACH as you can get that simpler by simply linking with the import library. But calling FreeLibrary() in PROCESS_DETACH From within DllMain() is unsupported. When Windows calls your DllMain() function it usually has already acquired the loader lock mutex and when calling FreeLibrary() in there this can result in a mutal exclusion lock as the FreeLibrary() call is trying to acquire  that lock but it never can get freed since your DllMain() function never returns control to the Windows module loader as it is stuck in the FreeLibrary() call.

Rolf Kalbermatter
My Blog
0 Kudos
Message 4 of 5
(1,326 Views)

The only explicit calls to LoadLibrary() and FreeLibrary() are in the executable that's calling my application DLL, which I don't have any control over. Neither my application DLL nor any of the dependency DLLs it loads (of which tktds7k.dll is only one of several VI based instrument drivers) have a DllMain() function, and all the linking is done with .lib import files.

0 Kudos
Message 5 of 5
(1,311 Views)