Building ActiveX Servers in NI LabWindows™/CVI™

Publish Date: Jun 21, 2018 | 7 Ratings | 4.00 out of 5 | Print | Submit your review

Table of Contents

  1. Introduction
  2. LabWindows/CVI Tools
  3. LabWindows/CVI ActiveX Library Server Functions
  4. Building and Distributing ActiveX Servers
  5. ActiveX Registration
  6. COM Threading Models
  7. Debugging ActiveX Servers in LabWindows/CVI
  8. Other Suggestions, Tips, and Tricks

1. Introduction

This document discusses ActiveX technology and building ActiveX servers using the tools and libraries available in LabWindows/CVI.

ActiveX Technologies

ActiveX is a generic term applied to a number of Microsoft technologies for developing components that you can reuse from various development environments. The following technologies are available:

  • ActiveX Automation–Automate one application from within another application. For example, a LabWindows/CVI program can control Microsoft Word using ActiveX Automation.
  • ActiveX Controls–Use stand-alone, interactive objects in external applications. For example, you can embed and use a Microsoft Calendar control or a Measurement Studio 3D Graph control in containers such as LabWindows/CVI panels.
  • ActiveX Documents–View documents, such as Microsoft Word documents or Excel spreadsheets, in containers such as LabWindows/CVI panels.


The preceding technologies are based on the Microsoft Component Object Model (COM), which defines the rules for building, deploying, and using ActiveX applications. COM is a binary standard and consists of a set of rules used to build language-independent, object-oriented applications. As a result, COM applications are characterized by Application Programming Interfaces (APIs) that are object oriented and language independent. These APIs provide considerable advantages over traditional APIs, which are either flat (like a C library) or are language bound (like a C++ class hierarchy). COM encourages interface-based programming rather than object-based programming, which leads to better encapsulation, proper versioning, and easier reuse of applications.

Using the Distributed Component Object Model (DCOM), most COM applications also can be deployed on remote server machines and reused from client machines on the same network. For example, you can use DCOM to build a remote data acquisition application in which the acquisition is performed by a DCOM server application running on a Windows NT server machine connected to a Local Area Network (LAN). ActiveX client applications running on other Windows machines on the same LAN can connect to the server machine and get the acquired data. The client applications can then perform analysis on the data, publish the data on the Internet, and so on.

COM Applications, Objects, and Interfaces

A COM application consists of one or more COM objects. For example, the Microsoft Word ActiveX server contains COM objects to represent a Word document. A COM object is an entity that encapsulates some data ("properties") and implements some functionality ("methods"). These properties and methods are similar to the attributes and functions associated with LabWindows/CVI User Interface controls. The generic type of a COM object is referred to as a COM class (or coclass). The methods and properties are arranged in groups called interfaces, and each COM object implements one or more interfaces. A COM interface is a table of pointers to functions that implement the methods and properties. This table is commonly referred to as a v-table because in C++, the table contains virtual functions.

Typically, COM programmers design interfaces by grouping the data and functionality required to solve their problem in logically coherent sets. An interface typically represents a "behavior" in the problem domain. Programmers then design their COM classes by developing different object types to represent entities that implement various combinations of interfaces, based on the different behaviors that these entities manifest. This process is called interface-based programming. Finally, programmers design the COM application as a framework or hierarchy of these COM objects. The design process, like all object-oriented design processes, is iterative and goes through many cycles before the final interfaces, object types, and object hierarchy are finalized.

For example, consider building a GPIB instrument driver that can be used as an ActiveX server. The design process involves creating interfaces that expose the various capabilities of the instrument, such as acquisition and waveform generation, and then building a hierarchy of ActiveX objects that implement these interfaces. A trivial hierarchy can contain a single object that exposes all the interfaces. A nontrivial, but simple, hierarchy can contain multiple ActiveX objects. Each object exposes a single interface, and the interfaces have methods/properties that can be used to access other objects in the hierarchy. In the second hierarchy, one object may implement the acquisition interface, another may implement the waveform generation interface, and so on. In any hierarchy, one or more objects must be top-level objects, which ActiveX clients can create directly, and the other objects must be sub-objects, which ActiveX clients can obtain only by accessing the methods and properties of other objects. Instrument drivers, like IVI drivers, might require hierarchies that are more complex than the preceding examples.

The following figure shows the standard representation of a COM object. The lines represent the interfaces implemented by the object. The IUnknown interface is distinguished by its upper-right corner position in the representation.


Representation of a COM Object

Back to Top

2. LabWindows/CVI Tools

You can use most popular programming languages, such as C, C++, Microsoft Visual Basic, and Java, to build COM applications. Each language offers a distinct set of advantages and disadvantages for writing COM applications. If you are familiar with C++, you can think of COM objects as instances of C++ classes and COM interfaces as C++ abstract base classes. If you are familiar with C, you can think of a COM interface as a table of function pointers and a COM object as being similar to a user interface control that has attributes and functions.

A significant portion of COM programming consists of coding a number of generic tasks and writing boilerplate system code. Consider the following points when you create a COM application:

  • All COM interfaces are derived from an interface called IUnknown that provides basic functionality such as verifying object identity, getting information about the interfaces implemented by the object, and managing the lifetime of the object. The IUnknown interface contains the following three functions:
    • QueryInterface – This function queries a COM object as to whether it implements a specific interface.
    • AddRef – This function controls the lifetime of a COM object. AddRef increments the internal reference count of the object.
    • Release – This function controls the lifetime of a COM object. Release decrements the internal reference count of the object. Use this function to release interface pointers, as shown in the following sample code:

      IFoo *pFoo = NULL;
      FuncThatGetsFoo (&pFoo);
      if (pFoo) {

      DoWorkWithFoo (pFoo);
      pFoo->lpVtbl->Release (pFoo);// release pFoo
      pFoo = NULL;  
      }
    The implementation of these functions is critical. The IUnknown interface is based on well-tested boilerplate code and is reused for all the interfaces in the application.
  • All well-written COM applications should perform self-registration. This requirement means that a COM application should contain the code to register and unregister itself on a particular computer. Given the objects and interfaces in a COM application, this task consists of adding and deleting a number of keys and values in the system registry.
  • Most COM applications contain embedded libraries called type libraries that programming environments use to determine the interfaces and objects of the COM applications. The LabWindows/CVI ActiveX Controller Wizard reads the type libraries of ActiveX applications and generates wrapper code organized as a LabWindows/CVI instrument driver that can be used to control the servers.
  • COM applications that are Dynamic Link Libraries (DLLs) must export the following four functions:
    • DllGetClassObject – The COM runtime uses this function to create a class factory object. The class factory object is used to create COM objects. The COM runtime is the part of the operating system that provides COM services and performs COM-related tasks.
    • DllCanUnloadNow – The COM runtime uses this function to determine if the COM DLL can be unloaded from a process.
    • DllRegisterServer – Installers and programmers use this function to register a COM DLL.
    • DllUnregisterServer – Installers and programmers use this function to unregister a COM DLL.
  • COM interfaces and coclasses are written in a language called Interface Definition Language (IDL). This language is very similar to C and C++. The IDL code then is compiled in the Microsoft IDL (MIDL) compiler to build the type library, which is embedded in the COM application.


The preceding tasks – coding the IUnknown functions, writing self-registration code, embedding type libraries, and writing/compiling IDL – are new to application programmers. Therefore, different programming environments provide various tools that simplify COM application development. Most of these tools are wizards, dialog-based user-interactive programs, or scripting-based programs.

To build an ActiveX server in LabWindows/CVI, complete the following steps:

  1. Create a new project or open an existing project.
  2. Select Tools»Create ActiveX Server to launch the Create ActiveX Server Wizard.
  3. Use the Create ActiveX Server wizard to specify settings that LabWindows/CVI uses to make the project an ActiveX server project.
  4. When you finish specifying settings for the ActiveX server project, the Edit ActiveX Server tool appears. You also can select Tools»Edit ActiveX Server to open the tool. Use Edit ActiveX Server tool to specify ActiveX interfaces and objects and to generate the ActiveX server code.
  5. Implement the entry-point function, ActiveX server functions, and callback functions in a source file. These functions are prototyped in the generated include file.
  6. Add the implementation source file to the project and build the project.


The following sections describe the LabWindows/CVI tools that simplify the development of COM servers and simplify the development of the ActiveX code generated by the ActiveX server tools.

Create ActiveX Server Wizard

Use the Create ActiveX Server Wizard to create an ActiveX server project. NI recommends that you run the wizard on a new project or make a backup copy of the current project before running this wizard. If you run the wizard on an existing project, you can backup the current project when prompted. The Create ActiveX Server Wizard contains the following panels in which you can specify module-level settings.

  • Target Files Panel – Specify the following four files that LabWindows/CVI generates to store the ActiveX server-related code and information.
    • Specification File Path – Contains the ActiveX server configuration.
    • Source File Path – Contains C code used by the ActiveX server.
    • Include File Path – Contains the declarations of C functions that you implement in your source files. The ActiveX server code generated in the above source file calls these functions.
    • IDL File Path – Contains the IDL code used in building the type library. The type library is embedded in the target DLL or EXE.
  • Server Settings Panel – Customize server settings.
    • Server Name – Used in the type library and generated code to identify the server.
    • Type – Determines whether the server is a DLL or an EXE.
    • Help File – The name of a Windows help file (either a .hlp or a .chm file) containing help information about the server. This file must be in the same folder as the server. The generated type library makes this information available to programming environments and browsers whose providers want to document the servers for their users.
    • Help Context – The help context identifier that links to specific information about the server within the help file. Provide the help context identifier as a decimal number.
    • Help String – A description of the ActiveX server.
    • Import from Function Tree (.fp File) – Imports the classes and functions of a function tree file into the server. LabWindows/CVI imports each class in the function tree as an interface composed of the functions that belong to that class. Top-level functions in the function tree are imported into a default top-level interface with the same name as the function tree. For example, class A contains subclass B. In the interface representing class A, LabWindows/CVI generates an additional method that returns the interface representing class B. LabWindows/CVI generates one object to implement each interface. An object is a top-level object if and only if it represents a top-level class. The imported function tree file must have an associated header file in the same directory.
    • FP File Path – The full path of the function tree file to import.
    • Advanced Options – Opens the Server Advanced Options Panel, in which you customize the following advanced options for the ActiveX server.
      • Threading Model – The server threading model. Refer to the COM Threading Models section for more information.
      • Callback Function – A callback function that you can specify for executable servers. If you specify a callback function, it must be implemented. The semantics of this callback function are described in the Generated Code section.
      • Version – The major and minor version numbers of the type library.
      • Requires /Automation in command line – Option that you can enable to force the EXE servers to run only when invoked through ActiveX Automation. If you enable this option, the initialization function for the server returns false in the runServer output parameter when the EXE is launched by double-clicking the EXE file in Windows Explorer. You can detect the false in the runServer parameter and return from the server main function, thus preventing the server from continuing execution.
      • Locale ID – The locale identifier for the server. A locale is a collection of language-related, user-preference information. The default is zero and specifies the default language-neutral locale. Notice that servers created in LabWindows/CVI can handle only a single locale. In LabWindows/CVI version 6.0 and later, you should choose the neutral locale, which is zero, so that the server can be used with any language/user-preference settings.
      • GUID (LIBID) (Library Identifier) – The Globally Unique Identifier (GUID) of the type library. LabWindows/CVI automatically generates a GUID for the LIBID. You can replace the generated GUID by entering your own GUID in registry format. To generate GUIDs, you can run the guidgen.exe utility from Microsoft. For more information and to obtain guidgen.exe, refer to the Microsoft Web site.

When you click Next in the Server Settings panel, the Create ActiveX Server Wizard converts the current LabWindows/CVI project to an ActiveX server project. You cannot reverse this conversion.

Once you specify the settings in the Target Files, Server Settings, and Server Advanced Options panels, the wizard creates the target files and adds them to the project. The target files are empty because you have not specified the ActiveX interfaces and objects. After the Create ActiveX Server Wizard finishes, LabWindows/CVI opens the Edit ActiveX Server tool, which you can use to specify the ActiveX interfaces and objects to implement in the server.

Edit ActiveX Server Tool

You can edit an existing ActiveX server project using the Edit ActiveX Server tool, shown in the following figure. Use this tool to specify the ActiveX interfaces and objects to implement in the server. You also can edit the settings specified in the Create ActiveX Server Wizard with the Edit ActiveX Server tool, but you cannot change the target specification file.

NI recommends that you specify the interfaces before specifying the objects because an object must implement one or more interfaces. After you specify the ActiveX interfaces and objects in the server, generate the ActiveX code by clicking Save and Generate in the Edit ActiveX Server tool. Click Save to save the changes without generating the ActiveX code. You must specify at least one ActiveX object for the Edit ActiveX Server tool in order to generate the ActiveX code.

 



Adding and Editing Interfaces

Click Edit Interface(s) in the Edit ActiveX Server tool to open the Edit ActiveX Interfaces dialog box. Use this dialog box to add new interfaces, edit or delete existing interfaces, and import interfaces from other specification files.

Click Add in the Edit ActiveX Interfaces dialog box to open the Edit ActiveX Interface dialog box, which you can use to specify an interface and add, edit, and delete methods and properties in that interface.

Click Import in the Edit ActiveX Interfaces dialog box to open the Import ActiveX Interfaces dialog box, which you can use to import interfaces from existing specification files. Importing interfaces is useful and timesaving when you develop a number of ActiveX servers that expose the same or similar interfaces. For example, you might build a number of instrument drivers that are ActiveX servers, which all have the same interface for initialization. You can specify the interface for one server, and when specifying the other servers, import the interface from the first server's specification file. When you import an interface, LabWindows/CVI generates a new Interface Identifier (IID), which is a special GUID required by COM, for the interface so that the importing server and the server you imported from do not depend on each other. The new IID ensures that registration and unregistration of one of the servers does not affect the other. In addition, this decoupling makes it possible for you to modify an imported interface without affecting the server from which it was imported.

Each interface is identified by its name in the generated code. For each interface, LabWindows/CVI automatically generates an IID, a special GUID required by COM. For each interface, you can provide a help string, which is stored in the type library, and a help context identifier, which links to the information in the help file. LabWindows/CVI classifies interfaces into four types, which you can specify using the Interface Type option in the Edit ActiveX Interface dialog box. Interfaces can be one of the following types :

  • Custom – These interfaces are derived directly from the IUnknown interface and contain the three IUnknown functions in addition to the user-defined methods and properties. Custom interfaces also are known as IUnknown-derived interfaces or early-bound interfaces.
  • Dispinterface – These interfaces are exposed as IDispatch interfaces. IDispatch is derived from the IUnknown interface. Dispinterfaces contain the three IUnknown functions in addition to the four IDispatch functions: GetTypeInfoCount, GetTypeInfo, GetIDsOfNames, and Invoke. These interfaces do not contain additional entries for functions representing user-defined methods and properties in their v-tables. Instead, they use numbers called DISPIDs to represent the user-defined methods and properties. Dispinterface interfaces also are known as pure Dispatch, or late-bound, interfaces.
  • Dual – These interfaces are derived from the IDispatch interface. In addition to the IUnknown functions and the IDispatch functions, these interfaces contain additional functions in their v-tables to represent user-defined methods and properties. A dual interface can act as both a custom interface as well as a dispinterface.
  • Event Interface – COM objects do not implement these interfaces but use them to send COM events. Custom, dispinterface, and dual interface types also are known as incoming interfaces because calls come to the object through these interfaces. Event interfaces also are known as outgoing interfaces because calls (events) go out of the object through them.

    If you select an event interface in the Edit ActiveX Object dialog box for an ActiveX object, then LabWindows/CVI generates helper functions for each member of the event interface, which you can call to fire the corresponding events. Sample use of these generated event wrapper functions also is generated in the header file. The members of an event interface can be methods as well as properties. Notice that event interfaces are not implemented by the ActiveX object but by the ActiveX client application that receives events from the object. If you use LabWindows/CVI to generate the ActiveX client for your ActiveX server, then you can call the generated Event Registration functions to register callback functions in the client program to be called when the ActiveX object fires the corresponding events.


Each of the preceding incoming interface types has advantages and disadvantages. Keep the following points in mind when you choose an incoming interface type:

  • The custom interface is the most efficient interface because it has only three additional functions.
  • Scripting clients, which are client applications written in scripting languages like VBScript or JavaScript, can call functions only in late-bound interfaces, that is, indirectly through the IDispatch mechanism. If you want to make an ActiveX interface that scripting clients can use, you must choose a dispinterface or a dual interface. In addition, interface pointers in dispinterface methods and properties are treated as IUnknown or IDispatch based on their type. Therefore, NI recommends that you specify interface pointer types in dispinterface methods and properties as IUnknown or IDispatch.
  • Once a custom or dual interface is registered, you must not change the entries and the order of the entries in the interface. This restriction is referred to as interface immutability and is one of the fundamental rules of COM. If you violate this rule, run-time errors result when applications use the interface. This rule implies that you cannot add new methods and properties to custom and dual interfaces when you distribute a new version of your server. In COM, you should add new functionality to objects by making the objects implement new interfaces that represent the new functionality rather than by adding new methods and properties to existing interfaces. Because dispinterfaces contain only the IDispatch and IUnknown functions and rely on the IDispatch functions to call their method and property functions using DISPIDs, you can add new methods and properties to dispinterfaces safely, without violating the immutability of these interfaces. However, the old methods and properties still are represented by the old DISPIDs. Notice that in this case, when a client connects to an older version of the server and calls a new method with a new DISPID that is not recognized in the older version, the client will receive a run-time error and not cause an access violation. For this reason, you might prefer to use dispinterfaces.

    The following figure shows an example of COM versioning with custom/dual interfaces. In the example, IFoo is a custom or dual interface containing one method Foo and is implemented by version 1.0 of a COM object. Suppose you want to add another method called Bar to the object in version 2.0. The correct way to add the method is to create a new interface, IFoo2, which exposes both Foo and Bar methods, and make the object in version 2.0 implement both interfaces IFoo and IFoo2.

    COM Versioning with Custom/Dual Interfaces


    The following figure shows COM versioning with dispinterfaces. In this case, you can add the Bar method to the IFoo dispinterface and implement it in the version 2.0 object.


    COM Versioning with Dispinterfaces

  • The late-bound mechanism dispinterfaces use to call the method and property functions is slower than the early-bound mechanism.
  • LabWindows/CVI and many other ActiveX client development environments support calling ActiveX server objects through custom interfaces, dispinterfaces, and dual interfaces. Other development environments support calling ActiveX server objects only through dispinterfaces.


Adding and Editing Methods and Properties

Click Add Method in the Edit ActiveX Interface dialog box to open the Edit ActiveX Method dialog box, which you can use to specify an ActiveX method. For each ActiveX method, you can provide a help string, which is stored in the type library, and a help context identifier, which links to the information in the help file.

An ActiveX method is used to represent a function and, in that way, is similar to normal C functions. Keep the following points in mind about ActiveX methods:

  • The return value of an ActiveX method must be an HRESULT, which is a 32-bit data type that COM uses to indicate error status. You optionally can mark the last parameter of an ActiveX method as the logical return value of that method. As a result, some client development environments generate client code that treats this parameter as the return value. The following HRESULT status values and meanings are useful:
    • S_OK – success
    • E_UNEXPECTED – catastrophic failure
    • E_OUTOFMEMORY – out of memory
    • E_INVALIDARG – one or more arguments are invalid
    • E_POINTER – invalid pointer
    • E_HANDLE – invalid handle
    • E_FAIL – unspecified error
    • E_ACCESSDENIED – general access denied error
    • E_ABORT – operation aborted
  • ActiveX methods must use the CVIFUNC (__stdcall) calling convention.
  • Each ActiveX method and property in a dispinterface must have a DISPID that is unique in that interface. The Edit ActiveX Server tool automatically generates unique DISPIDs for methods and properties. You can overwrite the generated DISPID and provide a different DISPID.
  • Each ActiveX method has a parameter list with zero or more parameters. You can specify these parameters using the Edit ActiveX Argument dialog box, which you can access by clicking Add in the Edit ActiveX Method dialog box.
  • You can import methods based on function prototypes in C header files by clicking Import Methods in the Edit ActiveX Interface dialog box. Notice that you can import only those functions that satisfy the restrictions of ActiveX methods. If a function has a name that also is used by a method in the importing interface, the function will not be imported.
  • LabWindows/CVI works with only automation types for ActiveX method parameters and ActiveX properties. You can choose the type from the Argument Type option in the Edit ActiveX Argument dialog box and the Type option in the Edit ActiveX Property dialog box. The types that you can select from the Argument Type option are all automation types. The only array types that are offered are 1 and 2-dimensional arrays. The type cannot be a custom type, such as a structure, pointer to a structure, or union. The type can be an interface pointer, and you can enter the name of the interface in the Argument Type option to add it to the list.
  • You must mark the data direction of each parameter as input, output, or input-output. Input parameters send data from client to server. Output parameters return data from server to client. Input-output parameters carry data in both directions.


Click Add Property in the Edit ActiveX Interface dialog box to open the Edit ActiveX Property dialog box, which you can use to specify an ActiveX property.

An ActiveX property represents data as opposed to functionality. ActiveX properties are similar to user interface control attributes in that attributes belong to a particular control, and different types of controls have different sets of attributes. Use the Edit ActiveX Property dialog box to edit these properties. Keep the following points in mind about ActiveX properties:

  • Like ActiveX methods, ActiveX properties also have an associated name, help string, and help context. If the ActiveX property belongs to a dispinterface, it also has an associated DISPID.
  • For each ActiveX property, based on the property access, the Edit ActiveX Server tool internally generates get/put support functions. The Put by Ref access is the same as the Put access, except that some client development environments, such as Visual Basic, expose these differently to the client programmers. Visual Basic supports Put by Ref properties only if these properties are interface pointers. For example, if there is an interface pointer property called MyProp in an interface implemented by a server object called MyObj, then

    Dim obj As MyObj    ‘Declare a MyObj variable in VB
    Set obj = New MyObj ‘Create a MyObj object
    obj.MyProp = h2     ‘VB calls put_MyProp accessor
    Set obj.MyProp = h2 ‘VB calls putref_MyProp accessor

    It is strongly recommended that you use Put by Ref access only for interface pointer properties and, for those properties, select both Put and Put by Ref for write access so that Visual Basic programmers can use either of the above syntaxes to set them.

  • You can add an extra 32-bit parameter to the ActiveX property support functions by selecting the Add Index Argument option. You can use the extra parameter as an index when the property is used to represent collections. For example, in an ActiveX interface that represents DAQ hardware, you can use a property to represent the collection of DAQ channels. You can use the index argument in the property functions to identify a particular channel. This is especially useful when the hardware has a large number of channels, in which case exposing each channel with a separate property is inconvenient.



Editing Objects

Specify ActiveX objects in the Edit ActiveX Object dialog box, which you can access by selecting Edit Objects in the Edit ActiveX Server tool and then selecting Add in the Edit ActiveX Objects dialog box. Each ActiveX object has a name by which it is identified in the generated code. You can provide a help string, which is stored in the type library, and a help context identifier, which links to the information in the help file, for each ActiveX object. In addition, you can provide an object description that is stored in the registry. An ActiveX object must implement one or more ActiveX interfaces, which you can select in the Select Interfaces for Object list. In this list, you also can select event interfaces that your ActiveX object uses to send events to ActiveX clients. You also can create new interfaces and edit existing interfaces in the Edit ActiveX Object dialog box.

Click Advanced Options to open the Edit ActiveX Object Advanced Options dialog box. You can specify an object callback function in the Edit ActiveX Object Advanced Options dialog box. If you specify this function, the function must be implemented. The semantics of this callback function are described in the Generated Code section. LabWindows/CVI generates a Class Identifier (CLSID) GUID for each ActiveX object. You can overwrite this generated CLSID. The COM runtime uses the CLSID to uniquely identify the object. In addition, LabWindows/CVI generates a default Programmatic Identifier (ProgID) for each ActiveX object. ProgIDs provide ActiveX clients with a user-friendly way to identify the ActiveX object. The version number attached to the ProgID can be used to version the ActiveX object. LabWindows/CVI supports four types of ActiveX objects, which you can select in the Object Type option.

  • SDI Application Object – Select this type for the top-level object of a Single Document Interface (SDI) executable server. A document is an object that encapsulates the data, such as the text in Microsoft Word or the spreadsheet data in Microsoft Excel, relevant to the application. SDI applications can manage only a single document at a time. If you need to edit multiple documents, you must open each document in a different instance of the application.
  • MDI Application Object – Select this type for the top-level object of a Multiple Document Interface (MDI) executable server. With an MDI application, you can open and edit multiple documents concurrently. Notice that although there is no direct way to create MDI applications in LabWindows/CVI, the LabWindows/CVI User Interface Library provides enough flexibility and functionality for you to create an MDI-like Graphical User Interface (GUI) for your applications. If you create an MDI GUI for your application, such as a report generation application, NI recommends that you select MDI Application Object as the object type for the application object.
  • Document Object – Select this type for the document objects, which normally have a user interface, in an SDI or MDI executable server. Document objects encapsulate the data relevant to the application.
  • Custom Object – The only type available in DLL servers. You can customize all the advanced options for a custom object.


You can specify the following options for an object in the Edit ActiveX Object Advanced Options dialog box.

  • Top-level Object – This setting is available only for custom objects. ActiveX clients can directly create only top-level objects. ActiveX clients can get references to objects that are not top-level, also called sub-objects, only by accessing the methods and properties of other objects. By default, custom objects are top-level objects. SDI and MDI application objects always are top-level objects. Document objects are top-level in an MDI application but are not top-level in an SDI application.
  • Support Aggregation – Select this option if the object is to be aggregated. For information about aggregation, refer to the description of CA_ServerAggregateObject in the LabWindows/CVI Help.
  • Support ErrorInfo – Select this option if the object interfaces must permit the setting of detailed error information using the IErrorInfo interface and related functions. For more information about setting error information, refer to the description of CA_ServerSetErrorInfo in the LabWindows/CVI Help.
  • Serve Single Client – Select this option if the object must serve only a single client at a time.
  • Register As Active Object – Select this option if you want the object to be registered in the Running Object Table (ROT) as an active object. If an ActiveX object is registered in the ROT, then other ActiveX clients can get references to this particular object by requesting the COM runtime for the active object. This option can be useful if you want to expose resources like hardware ports/channels that cannot be accessed by multiple clients simultaneously. Such resources can be exposed by an ActiveX object that registers itself as the active object when it is created by an ActiveX client. Other clients that also want to access the resource can get a reference to this object and access the resource. By default, an ActiveX object is not registered as an active object. This setting is available only for custom objects. SDI and MDI application objects always are registered in the ROT. Document objects are not registered in the ROT. ActiveX objects are registered as weak references in the ROT. When a client releases a weak reference, the active object is removed from the ROT. Subsequent attempts to get the active object will fail. Call CA_ServerLockActiveObject to prevent the active reference from being removed from the ROT when clients release their references to the active object.



Generated Code

When you click Save and Generate, the Edit ActiveX Server tool saves the changes in the specification file and generates the ActiveX code in the target header, source, and IDL files. Do not make changes to the generated code because the ActiveX Server tools overwrite the target files when the tools generate code and reinitialize target files. If you need to make changes to the generated code, make new copies of the target files and make changes in the new files and not the target files. You can open the generated source, include, and IDL files using the LabWindows/CVI source editor. Double-clicking the specification (.axs) file opens the Edit ActiveX Server tool. LabWindows/CVI generates the following files:

Include File (MyServer_axs.h)

  • Feature function prototypes – LabWindows/CVI generates prototypes for functions in which you must implement the functionality of the server. You must implement the feature functions in your source files. These functions are called from the interface member functions in the generated source file. Refer to the Source File section for more information about the role of the interface member functions.

    For example, the following code demonstrates the feature function for the Add method in the ICalc interface implemented by MyObj:

    HRESULT CVIFUNC MyObjICalcAdd (CAServerObjHandle objHandle, short n1, short n2, short* sum)
    {
       if (sum == NULL) {
          return E_INVALIDARG;     // invalid argument
       }
       else {
          *sum = n1 + n2;
          return S_OK;
       }
    }

  • Event wrapper functions – LabWindows/CVI generates helper functions that you can call to fire events registered by your ActiveX objects. Sample use of these functions also is generated in the header file as comments.
  • Callback functions
    • Server callback function – The prototype of the server callback function is generated in the include file. You must implement this function in your source code. The LabWindows/CVI ActiveX Library calls this callback function with the CA_SERVER_EVENT_MODULE_EXIT event. This event occurs when an EXE server is ready to exit, which is when all references to ActiveX objects in the server have been released. You can prevent the server from shutting down by returning a nonzero value from this function. The server callback function is available only for EXE servers. The following code is a sample server callback function:

      // Example Server callback function
      int CVIFUNC ServerCb (int event)
      {
         HRESULT hr = S_OK;
         
         if (event == CA_SERVER_EVENT_MODULE_EXIT) {
            // Uninitialize and cleanup the server
         }

         return hr;
      }

    • Object callback functions – The prototypes of the object callback functions are generated in the include file. You must implement these functions in your source code. These callback functions are called with the CA_SERVER_EVENT_OBJECT_CREATE and CA_SERVER_EVENT_OBJECT_DESTROY events. The CA_SERVER_EVENT_OBJECT_CREATE event occurs when an object is being created. When this event occurs, the callbackData passed to this function is the IUnknown pointer of the object. You can use this pointer to aggregate other objects. The object is not created if the return value of the callback function indicates failure. You must return a value indicating success for the object to be created. The CA_SERVER_EVENT_OBJECT_DESTROY event occurs when an object is being destroyed. For this event, the callbackData parameter will be NULL, and LabWindows/CVI ignores the return value of the function. The following code is a sample object callback function.

      // Example object callback function

      HRESULT CVIFUNC SrvrObjCb (CAServerObjHandle objHandle, const CLSID *pClsid, int event, void *callbackData)
      {
         HRESULT     hr = S_OK;

         if (event == CA_SERVER_EVENT_OBJECT_CREATE) {
            // Initialize object state
         }
         else if (event == CA_SERVER_EVENT_OBJECT_DESTROY) {
            // Uninitialize object state and cleanup
         }

         return hr;
      }

  • Init and Uninit function prototypes – These functions initialize and uninitialize the ActiveX server and must be called in the main function of the server (WinMain for EXE servers and DllMain for DLL servers). These functions are implemented in the generated source file. Refer to the comments in the generated code for help about using these functions. You can use the generated example as a template.

    The following code is a sample WinMain (for EXE servers):

    int __stdcall WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
    {
       int    runServer = 0;
       char   errBuf [500] = {0};

       if (InitCVIRTE (hInstance, 0, 0) == 0)
          return -1;     // Out of memory

       // ActiveX server initialization
       if (FAILED (MyServerInit (hInstance, lpszCmdLine, &runServer, errBuf,
          sizeof(errBuf))))
             return -1; // Server initialization error

       if (runServer)
          {
          // ...
          // Your initialization code
          // ...

          RunUserInterface ();     // Process messages

          // ...
          // Your cleanup code
          // ...
    }

       // ActiveX server cleanup
       MyServerUninit (hInstance);
       return 0;
    }

    The following code is a sample DllMain (for DLL servers):

    int __stdcall DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
    {
       char    errBuf [500] = {0};
       
       switch (fdwReason)
          {
          case DLL_PROCESS_ATTACH:
             if (InitCVIRTE (hinstDLL, 0, 0) == 0)
                return 0;     // out of memory
             // ActiveX server initialization
             if (FAILED (MyServerInit (
                hinstDLL, errBuf, sizeof(errBuf))))
                return 0;     // Server initialization error
             break;
          case DLL_PROCESS_DETACH:
             // ActiveX server cleanup
             MyServerUninit (hinstDLL);
             CloseCVIRTE ();
             break;
          }

       return 1;
    }

    int __stdcall DllEntryPoint (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
    {
       // Included for compatibility with Borland
       return DllMain (hinstDLL, fdwReason, lpvReserved);
    }

  • ActiveX Interface definitions – The definitions for the ActiveX interfaces specified in the server are generated in the target include file. You can define and use interface pointers like other C pointers.
  • GUIDs – The GUIDs (IIDs, CLSIDs, and LIBID) specified in the server are defined in the generated source file and declared in the generated header file.


Source File (MyServer_axs.c)

  • GUIDs – The specified GUIDs (IIDs, CLSIDs, and LIBID) are defined in this file.
  • ActiveX interface member functions – The definitions of functions that represent the methods and properties in the ActiveX interfaces are generated in the target source file. These functions call the feature functions that you implement. The addresses of these ActiveX functions are stored in the ActiveX interfaces; therefore, these functions are referred to as interface members. For example, the following code demonstrates the interface member function for the Add method in the ICalc interface implemented by MyObj.

    static STDMETHODIMP CaSrvrMyObjICalcAdd (ICalc* This, short n1, short n2, short* sum)
    {
       CAServerObjHandle objHandle = 0;
       HRESULT __result = 0;
       
       __caErrChk (CA_ServerGetObjHandleFromIface (This, &objHandle));
       __caErrChk (MyObjICalcAdd (objHandle, n1, n2, sum));

    Error:
       return __result;
    }

    ActiveX interface member functions differ from feature functions in the following aspects:

    • The first parameter of the interface member functions is a pointer to the interface. The first parameter of the feature functions is a variable of type CAServerObjHandle and identifies the object associated with the interface.
    • The data types of interface member function parameters are ActiveX Automation types, whereas the data types of feature function parameters are equivalent C data types (except for VARIANT, because it is not possible to determine the actual data type present in the VARIANT at compile time).
    • The interface member functions convert ActiveX Automation types to equivalent C data types before calling the corresponding feature functions and then reconvert the returned C data types to ActiveX Automation data types before returning to the client.
  • Event wrapper function definitions – The definitions of the event wrapper functions, if any, are generated in this section. You must call these functions to fire the corresponding events from ActiveX objects.
  • Data structures – A number of structures are generated to represent the ActiveX server configuration. The types of these structures are defined in the cviauto.h include file. The LabWindows/CVI ActiveX Library uses these structures to provide the COM functionality at run time.
  • Init and Uninit functions – The definitions of the Init and Uninit functions that initialize and uninitialize the ActiveX server are generated in the source file. These functions call the CA_InitActiveXServer and CA_CloseActiveXServer functions in the LabWindows/CVI ActiveX Library. Refer to the sample code generated in the target header file for the recommended use of these functions.


IDL File (MyServer_axs.idl)

  • IDL code – This file contains the IDL code to build the type library. LabWindows/CVI compiles the type library and embeds it in the target DLL or EXE when you build the ActiveX server.

    Back to Top

    3. LabWindows/CVI ActiveX Library Server Functions

This section describes the use of ActiveX server functions in the LabWindows/CVI ActiveX Library. Use these functions in conjunction with the generated code to implement an ActiveX server. The Server Creation Functions class of the LabWindows/CVI ActiveX Library function tree includes the server functions, as shown in the following figure:


ActiveX Server Functions


Initialization Functions

The Edit ActiveX Server tool generates Init and Uninit functions that call CA_InitActiveXServer and CA_CloseActiveXServer to initialize and uninitialize the server, respectively. Sample use of these functions in WinMain and DllMain is generated in the target header file as commented code. WinMain is the Windows version of main, the normal C entry-point for executables. DllMain is a standard function in DLLs that Windows calls to initialize and uninitialize the DLL when necessary.

CA_InitActiveXServer initializes an ActiveX server. You must call this function in the main function of the ActiveX server (in WinMain for EXE servers and in the DllMain DLL_PROCESS_ATTACH case for DLL servers). If CA_InitActiveXServer returns a failure error code, the calling module should exit. If the ActiveX initialization fails, the module is not treated as an ActiveX server.

When CA_InitActiveXServer returns successfully, the Run Server parameter indicates whether the module should continue execution. If an EXE server is launched for self-registration, the function sets the Run Server parameter to false.

The first parameter of CA_InitActiveXServer, Server hInstance, must be the server module’s HINSTANCE. Windows passes this value to WinMain and DllMain. If your code uses main instead of WinMain as the module entry point, this value is not available. Therefore, EXE servers must use WinMain as their entry point.

You must pass the EXE server command line in the Command Line parameter of CA_InitActiveXServer. If you pass a character buffer in the Error Buffer parameter, then on function failure, the buffer contains a string describing the failure.

CA_CloseActiveXServer uninitializes an ActiveX server. You must call this function in the main function of the ActiveX server (WinMain for EXE servers and the DllMain DLL_PROCESS_DETACH case for DLL servers).

Global Data Functions

Use the CA_ServerSetGlobalData, CA_ServerGetGlobalData, and CA_ServerReleaseGlobalData functions to manage any global data that your ActiveX functions access. For example, in a DAQ application the device number can be treated as global data. These functions ensure that the server's threading model restrictions are properly enforced. As these functions cache and retrieve a 32-bit address, place all the global data in a structure and call CA_ServerSetGlobalData with the address of the structure. Make sure this call is in the module's main function, after the Init function succeeds. In a DLL server, call CA_ServerSetGlobalData in the DllMain DLL_PROCESS_ATTACH case. In an EXE server, call CA_ServerSetGlobalData before calling RunUserInterface or any other function that processes events. CA_ServerSetGlobalData caches the pointer that is passed in but does not make any copies of your data. When you need to access the global data, you must call CA_ServerGetGlobalData to acquire the cached pointer. After using the global data, you must call CA_ServerReleaseGlobalData to relinquish the cached pointer back to the LabWindows/CVI ActiveX Library. Every function that calls CA_ServerGetGlobalData successfully also must call CA_ServerReleaseGlobalData. Internally, CA_ServerGetGlobalData acquires a lock, and CA_ServerReleaseGlobalData releases the lock. By using the global data functions, you can avoid providing threading synchronization, which requires the use of LabWindows/CVI multithreading functions or Windows synchronization functions. Advanced users can choose not to use the global data functions and instead manage their global data using a synchronization mechanism based on the COM threading model of their server.

The following code demonstrates the use of global data functions:

// Structure to represent the global data
typedef struct __MyGlobalData {
   double     dVal;
   int        iVal;
} MyGlobalData;

// Global variable to hold the global data
// This memory will always be valid
MyGlobalData     g_Data = {3.1415, 4};

// Fragment from main function (DllMain or WinMain)
// Initializes the server and sets the global data
if (FAILED (ServerInit (hInstance, ...)))
   return failureCode;
if (FAILED (CA_ServerSetGlobalData (hInstance, &g_Data))) {
   ServerUninit (hInstance);
   return failureCode;
}

// Simple function that uses the global data
HRESULT UseGlobalData (HINSTANCE hInstance, double dV, int iV)
{
   HRESULT     hr;
   void        *pvGlobalData = NULL;
   
   // Get ptr to global data
   hr = CA_ServerGetGlobalData (hInstance, &pvGlobalData);
   if (FAILED (hr))
      return hr;

   if (pvGlobalData) {
      // Use global data
      MyGlobalData pData = (MyGlobalData*) pvGlobalData;
      printf ("Double Data: %f, Int Data: %d\n", pData.dVal, pData.iVal);
      pData.dVal = dV;
      pData.iVal = iV;

      // Prevent further use of ptr to global data
      pvGlobalData = NULL;
   }
   
   // Release ptr to global data
   // MUST be called because CA_ServerGetGlobalData succeeded
   hr = CA_ServerReleaseGlobalData (hInstance);

   return hr;

}

Object Functions

CA_ServerCreateObject creates an ActiveX object programmatically. ActiveX clients cannot create sub-objects (objects that are not top-level) directly. The server creates sub-objects and returns the object interface pointer to ActiveX clients through method/property calls. Consider the following example: the application object of an SDI application normally has an interface member, such as a method called GetDocument or a Document property, that can be used to access the document object. In this example, the application object is the top-level object, and the document object is the sub-object. Use CA_ServerCreateObject when the server needs to create a sub-object to return to the client. This function takes the CLSID of the object to be created and returns a pointer to the requested interface, if that object implements the interface.

You can create an object as an aggregate of another object by passing the IUnknown pointer of the aggregating (outer) object to CA_ServerCreateObject. Aggregation is used by an ActiveX object, called the outer object or aggregator, to expose functionality that is actually present in another ActiveX object, called the inner object or aggregatee. Aggregation is a form of binary reuse of COM objects. The outer object exposes an interface, representing the reused functionality, that actually belongs to the inner object. Calls to the outer object through the aggregated interface are delegated to the inner object. After creating the inner object using CA_ServerCreateObject, call CA_ServerAggregateObject to aggregate it in the outer object. Pass the aggregating object's CAServerObjHandle in the Server Object Handle parameter, and pass the aggregatee's IUnknown pointer in the Inner Object IUnknown parameter. Pass the IID of the inner object interface to be exposed by the outer object in the Interface Id To Aggregate parameter. The inner object can be any ActiveX object that supports aggregation.

For example, in the following figure, Obj1 is the outer object and implements interface IFoo, and Obj2 is the inner object and implements IBar. Using aggregation, Obj1 exposes the IBar interface of Obj2 as its own interface.


COM Aggregation


The following code demonstrates aggregation:

HRESULT CVIFUNC OuterObjCb (CAServerObjHandle objHandle, const CLSID *pClsid, int event, void *callbackData)
{
   HRESULT     hr = S_OK;
   IUnknown    *pUnkAgg = NULL;
   IUnknown    *pUnk = NULL;

   if (event == CA_SERVER_EVENT_OBJECT_CREATE) {
      caErrChk (CA_ServerGetIfaceFromObjHandle (objHandle, &IID_IUnknown,
               &pUnk));
      caErrChk (CA_ServerCreateObject (&CLSID_InnerObj, pUnk, &IID_IUnknown,
               &pUnkAgg));
      caErrChk (CA_ServerAggregateObject (objHandle, &IID_IAggregatedIface,
               pUnkAgg));
   }

Error:
   if (pUnk)
      pUnk->lpVtbl->Release (pUnk);
   if (pUnkAgg)
      pUnkAgg->lpVtbl->Release (pUnkAgg);
   return hr;
}

The CA_ServerSetObjData, CA_ServerGetObjData, and CA_ServerReleaseObjData functions are similar to the corresponding functions that manage global data. These functions manage object-owned data. The per-object data can be any data required by an ActiveX object implementation. For example, the ActiveX properties of an object are normally stored on a per-object basis. In some cases, the object data might not be an ActiveX property. For example, an ActiveX object that represents an instrument stores the configuration information of that instrument in object-specific data. Notice that while the ActiveX properties of an object are exposed to ActiveX clients through interfaces, the object-specific data is internal to the server. The data pointer passed to these functions should be object specific and should be stored in the heap (dynamic memory). Place all the object-specific data in a structure. You must allocate an instance of the structure with the CA_SERVER_EVENT_OBJECT_CREATE event in the object callback function when you call the function during object creation. You also must pass the allocated pointer to CA_ServerSetObjData. When the code needs to access the object data, call CA_ServerGetObjData with the object handle. After using the pointer of the object data returned by CA_ServerGetObjData, call CA_ServerReleaseObjData to relinquish the pointer. Every function that calls CA_ServerGetObjData also must call CA_ServerReleaseObjData. Free the memory for the allocated data when the object is destroyed, that is, in the CA_SERVER_EVENT_OBJECT_DESTROY case of the object callback function. Advanced users can choose to manage their object data using synchronization primitives based on the threading model of their ActiveX server.

The following code demonstrates the use of object data functions.

// Structure to represent the object data
typedef struct __MyObjData {
   double     dVal;
   int        iVal;
} MyObjData;

// Object callback function

HRESULT CVIFUNC SrvrObjCb (CAServerObjHandle objHandle, const CLSID *pClsid, int event, void *callbackData)
{
   HRESULT        hr = S_OK;
   MyObjData      *pData = NULL;

   if (event == CA_SERVER_EVENT_OBJECT_CREATE) {
      // Allocating per-object data on the heap
      // Memory will be valid for the lifetime of the object
      pData = CA_AllocMemory (sizeof (MyObjData));
      if (pData) {
         pData->dVal = 3.1415;
         pData->iVal = 4;
         hr = CA_ServerSetObjData (objHandle, pData);
         pData = NULL;
      }
      else {
         hr = E_OUTOFMEMORY;
      }
   }
   else if (event == CA_SERVER_EVENT_OBJECT_DESTROY) {
      hr = CA_ServerGetObjData (objHandle, &pData);
      if (SUCCEEDED (hr)) {
         if (pData) {
            CA_FreeMemory (pData);
            pData = NULL;
         }
         hr = CA_ServerReleaseObjData (objHandle);
      }
   }

   return hr;
}

// Simple function that uses the object data
HRESULT UseObjectData (CAServerObjHandle objHandle, double dV, int iV)
{
   HRESULT     hr;
   void        *pvObjData = NULL;

   // Get ptr to object data
   hr = CA_ServerGetObjData (objHandle, &pvObjData);
   if (FAILED (hr))
      return hr;

   if (pvObjData) {
      // Use object data
      MyObjData pData = (MyObjData*) pvObjData;
      printf ("Double Data: %f, Int Data: %d\n", pData.dVal, pData.iVal);
      pData.dVal = dV;
      pData.iVal = iV;

      // Prevent further use of ptr to object data
      pvObjData = NULL;
   }

   // Release ptr to object data
   // MUST be called because CA_ServerGetObjData succeeded
   hr = CA_ServerReleaseObjData (objHandle);

   return hr;
}

You can use CA_ServerGetObjHandleFromIface to get object handles from interface pointers and CA_ServerGetIfaceFromObjHandle to get interface pointers from object handles. Because an ActiveX object can implement more than one interface, the CA_ServerGetIfaceFromObjHandle function takes an IID that identifies the interface to return. Use these functions only with the object handles and interface pointers of server objects created in LabWindows/CVI. These functions can be useful in the following situations:

  • Sometimes the clients might pass a server object interface pointer to another server object. When that happens, you can use CA_ServerGetObjHandleFromIface to get the object handle from the interface pointer, as shown in the following sample code:

    HRESULT CVIFUNC ObjIFooCacheObject (CAServerObjHandle objHandle, IUnknown* srvrObj)
    {
       CAServerObjHandle     hObj = 0;

   if (srvrObj == NULL)
      return E_INVALIDARG;     // Check input
  •    // Get object handle from interface pointer
       if (SUCCEEDED (CA_ServerGetObjHandleFromIface (srvrObj, &hObj))) {
          CacheObjectHandle (hObj);     // Cache the handle
          return S_OK;            // Return success
       }

       else
          return E_FAIL;    // If cannot get handle return failure
    }

  • Some ActiveX objects might have an interface with a method that returns a pointer to another interface implemented by the same object to requesting ActiveX clients. In this case, use CA_ServerGetIfaceFromObjHandle to get the interface pointer to return, as shown in the following sample code:

    HRESULT CVIFUNC ObjIFooGetIBar (CAServerObjHandle objHandle, IBar** barIface)
    {
       if (barIface == NULL)
          return E_INVALIDARG;     // Check input

   

   // Get IBar interface using object handle
   if (SUCCEEDED (CA_ServerGetIfaceFromObjHandle (objHandle, &IID_IBar,

                  barIface)))

      return S_OK;     // Return success
   else
      return E_FAIL;    // If cannot get interface return failure
}


Advanced Functions

The Object Helper Functions class contains functions that you can use to perform advanced object-specific tasks. Calling CA_ServerLockActiveObject on an ActiveX object results in an additional internal reference to the object, independent of the external ActiveX references to the object. Call CA_ServerLockActiveObject when the ActiveX object displays a user interface that appears on the screen. Lock the object to prevent the user interface from disappearing when the last external reference to the ActiveX object is released. You must call CA_ServerUnlockActiveObject when the user interface of a previously locked ActiveX object is being hidden or discarded. Call CA_ServerDestroyActiveObject to destroy, in response to an interactive user command, an ActiveX object that was locked when it appeared. CA_ServerDestroyActiveObject destroys an ActiveX object even though clients might be holding external references to the object. Use CA_ServerDestroyActiveObject only for the preceding purpose. The following sample demonstrates locking, unlocking, and destroying user interface objects:

// User ActiveX function for method Show of interface IFoo in object UIObj

HRESULT CVIFUNC UIObjIFooShow (CAServerObjHandle objHandle)
{
   HRESULT     hr = S_OK;
   int         panel = 0;

   panel = GetUserInterfacePanel (objHandle); // Internal call: Get the UI panel

   if (panel > 0 && !IsPanelVisible (panel)) {
      // Lock the object as its lifetime is now determined by the user interface
      hr = CA_ServerLockActiveObject (objHandle);
      if (FAILED (hr))
         return hr;
      DisplayPanel (panel);     // Display the user interface
   }
   return hr;
}
// User ActiveX function for method Hide of interface IFoo in object UIObj
HRESULT CVIFUNC UIObjIFooHide (CAServerObjHandle objHandle)
{
   HRESULT     hr = S_OK;
   int         panel = 0;

   panel = GetUserInterfacePanel (objHandle); // Internal call: Get the UI panel


   if (panel > 0 && IsPanelVisible (panel)) {
      HidePanel (panel);     // Hide the user interface
      // Unlock the object as its lifetime is now determined by COM
      hr = CA_ServerUnlockActiveObject (objHandle);
      if (FAILED (hr))
         return hr;
   }
   return hr;
}
// User interactive callback to quit the user interface of the ActiveX object
int CVICALLBACK QuitCb (int panel, int control, int event, void *callbackData, int
                        eventData1, int eventData2)
{
   if (event == EVENT_COMMIT) {
      CAServerObjHandle objHandle = 0;

      // Internal call: Get the ActiveX object's handle
      objHandle = GetActiveXObjHandle (panel);
      if (objHandle && IsPanelVisible (panel)) {
         // Destroy the ActiveX object cleanly, as it was locked when it was
         // made visible
         CA_ServerDestroyActiveObject (objHandle);
      }
      // Quit the user interface
      QuitUserInterface (0);
   }
}

If an ActiveX object provides rich error information (that is, you enabled the Support ErrorInfo option in the Edit ActiveX Object Advanced Options dialog box), you must call CA_ServerSetErrorInfo in the feature functions to specify error information for the returned failure code. If the feature functions return a success return value, do not call CA_ServerSetErrorInfo. On error, a feature function compatible with ErrorInfo must free resources, call CA_ServerSetErrorInfo, and return immediately after CA_ServerSetErrorInfo returns. The following code sample demonstrates the use of CA_ServerSetErrorInfo:

// User ActiveX function for method Foo of interface IFoo in object Obj
HRESULT CVIFUNC ObjIFooFoo (CAServerObjHandle objHandle)
{
   HRESULT     hr = S_OK;
   char        *pcBuf = NULL;


   // Try allocating a 1K buffer
   pcBuf = CA_AllocMemory (sizeof (char) * 1024);
   if (pcBuf) {
      // Make some internal call with buffer
      hr = MyInternalCall (pcBuf);
      if (FAILED (hr))
         goto Error;
   }
   else {
      hr = E_OUTOFMEMORY;
      goto Error;
   }

Error:
   // Clean up resources
   if (pcBuf)
      CA_FreeMemory (pcBuf);
   // Set error info
   if (FAILED (hr)) {
      CA_ServerSetErrorInfo (
         objHandle,           // handle of object setting error
         &IID_IFoo,           // IID of interface setting error
         "Error occurred",    // error description
         "MyServer.hlp",      // help file containing more info
         HLP_CTX_ERROR        // help context in help file
      );
   }
   // Return immediately after setting error info
   return hr;
}

CA_ServerGetEventObjHandles and CA_ServerReleaseEventObjHandles get and release handles to ActiveX client objects registered to receive ActiveX events. Use these two functions with the generated ActiveX event wrapper functions to fire ActiveX events. Sample usage of these functions is generated as comments in the ActiveX event wrapper function prototypes section of the ActiveX server header file.

The IUnknown Functions, IDispatch Functions, and DLL Server Entry Points classes contain standard implementations of the IUnknown interface functions, IDispatch interface functions, and COM DLL exports. The generated code uses these functions; you must not call the functions directly.

Back to Top

4. Building and Distributing ActiveX Servers


After you implement the feature functions that are prototyped in the generated include file, you can build the ActiveX server in the same way you build an EXE or a DLL project in LabWindows/CVI. When you build the server by invoking the Build»Create Dynamic Link Library or Build»Create Executable commands, LabWindows/CVI compiles all the source files, links them to create the server, compiles the generated IDL file into a type library, and embeds the type library into the target. If you enable the Register ActiveX server after build option in the Target Settings dialog box, LabWindows/CVI registers the target server each time that it builds the target server. By default, this option is enabled for a new ActiveX server project.

For an ActiveX server project, the installer built when you create a distribution automatically registers and unregisters the DLL or EXE during installation and uninstallation, respectively. If the ActiveX server has an associated help file, you can add that file in the Files tab of the Edit Installer dialog box.

Back to Top

5. ActiveX Registration


For ActiveX clients to detect and use ActiveX servers, the servers must be registered in the machine. ActiveX registration involves adding a number of entries in various locations in the system registry. Well-written ActiveX servers register themselves when they are installed on a system and unregister themselves when they are uninstalled. This process is known as self-registration. For an ActiveX server built using LabWindows/CVI, the code for self-registration is present in the target itself. The server can be manually registered and unregistered in the following ways:

  • DLL servers:
    • Registration: regsvr32 <Full Path of Server>
    • Unregistration: regsvr32 /u <Full Path of Server>

You can find the regsvr32 utility executable in the Windows system folder.

  • EXE servers:
    • Registration: <Full Path of Server> /RegServer
    • Unregistration: <Full Path of Server> /UnregServer

Back to Top

6. COM Threading Models

COM threading is one aspect of ActiveX programming that many programmers find difficult to understand at first. This section is a brief high-level introduction to this topic. If you are an advanced user, refer to the Microsoft Visual Studio Documentation for more information about this subject. If you installed the Interface to Win32 API feature of LabWindows/CVI, you can access this help file by selecting Help»Win32 API.

Every ActiveX server notifies the Windows COM run-time layer about its threading restrictions, whether it can be accessed concurrently from multiple threads, if so, what kind of threads can access it, and so on. These restrictions are referred to as the threading model of the server. You must satisfy the restrictions imposed by the published threading model of the server. For the most part, threading model restrictions apply to thread protection of the server's global and object data when the data are accessed by multiple threads. For this reason, NI recommends that you use the Set/Get/Release Server/Object Data functions in the LabWindows/CVI ActiveX Library to protect your server and object data. A DLL ActiveX server publishes its threading model by registering it in the system registry. An ActiveX server executable publishes its threading model by calling a system function when it is run. For servers built in LabWindows/CVI, the LabWindows/CVI ActiveX Library and the generated code handle these tasks automatically.

In the following threading model tables, all references to the creation and access of objects refer to the creation and access from the ActiveX server perspective. For example, consider a situation in which a client uses three different threads to create and access three different objects implemented by a single-threaded server. To the server, all objects in the server are created and accessed in a single thread. This is the intended perspective when stating that "All objects in the server are created and accessed in a single thread". The LabWindows/CVI ActiveX Library offers the following COM threading models:


Table 1. EXE Server Threading Models
Model
Object Creation
Data Protection

(Thread Safety)

Operating Systems
Comments
Single Threaded All objects in the server are created and accessed through a single thread. You do not need to protect global or per-object data. All versions of Windows The main thread must process messages by calling RunUserInterface, ProcessSystemEvents, or GetUserEvent.
Multithreaded Objects can be created and accessed by any number of threads simultaneously. Both global and object data must be protected. All versions of Windows Threads that create user interface objects must process Windows messages so that the user interface is responsive. Because multithreaded objects are created by Remote Procedure Call (COM-RPC) threads that do not process messages, you must not use the multithreaded model for a server that contains components with a user interface.

Table 2. DLL Server Threading Models
Model
Object Creation
Data Protection (Thread Safety)
Operating Systems
Comments
Single Threaded All objects in the server are created and accessed in a single thread. There is no need to protect global or per-object data. All versions of Windows The Single Threaded model's performance is not optimal if a client accesses one or more object instances simultaneously from different threads.
Apartment Threaded Objects can be created in multiple threads. However, each object is accessed only by the thread in which it was created. Global data must be protected. All versions of Windows You must use the Apartment Threaded model or the Single Threaded model for components with a user interface. For performance reasons, use the Apartment Threaded model.
Neutral Threaded Objects can be created and accessed by multiple threads. Any number of threads can call into an object at the same time. Both global and object data must be protected. Windows 2000 and later. On earlier versions of Windows, the LabWindows/CVI ActiveX Library treats this threading model as Both with Free Threaded Marshaller (FTM). The Neutral Threaded model is the preferred model for components that are not part of the user interface. Because this threading model is treated as Both with FTM on systems earlier than Windows 2000, the server also should satisfy any restrictions imposed by the Both with FTM threading model.
Free Threaded, Both Threaded, and Both with FTM Objects can be created and accessed by multiple threads simultaneously. Both global and object data must be protected. All versions of Windows Refer to the Microsoft Visual Studio Documentation for more information about these threading models. Both with FTM is the recommended model for components that are not part of the user interface on versions of Windows that do not support the Neutral Threaded model. In the Both with FTM model, if the server holds interface pointers, it must hold these references in the Global Interface Table (GIT) or as CAObjectHandles with multithreading support enabled.

Miscellaneous Notes on COM Threading Models

The LabWindows/CVI ActiveX Library initializes the COM library and sets the COM threading model of an EXE server's main thread based on the server threading model. If a DLL or EXE server creates additional threads that call ActiveX code, then those threads must set their COM threading model by calling CA_InitActiveXThreadStyleForCurrentThread with the required threading model.

If an ActiveX object holds interface pointers to other objects, it should hold the objects as type CAObjHandle. The LabWindows/CVI ActiveX Library uses the type CAObjHandle to hold an external ActiveX object reference. If the CAObjHandle must be used from a thread other than the thread in which it was obtained, then multithreading must be enabled for the CAObjHandle. To enable multithreading for the CAObjHandle, specify the multithreading setting in the creation function that gets the CAObjHandle or call CA_SetSupportForMultithreading after getting the CAObjHandle. If you do not use a CAObjHandle to hold interface pointers, you must use the GIT to store and access interface pointers from multiple threads. The GIT is described in the Microsoft Visual Studio Documentation.

Be careful when creating in-process DLL servers that perform user interface tasks. The user interface components in a DLL server should be completely self-contained in the DLL. Try to avoid any possible conflicting interactions with the client process that is using the DLL server. For example, be careful when the server accesses windows that were created by the client process - if the server and client run on different threads, this can lead to a deadlock. Note that these kinds of deadlocks can happen even when using functions like printf from the DLL server because, if you use the LabWindows/CVI stdio window, the windows written to by the server would have been created by the client process during program start-up. In this situation, NI recommends that you create the program as a console application or use some other printing mechanism.

Back to Top

7. Debugging ActiveX Servers in LabWindows/CVI


ActiveX servers are like other executables and DLLs. You can debug the ActiveX servers in the usual manner in LabWindows/CVI. If you need to debug the ActiveX server, you must build it in debug configuration. Sometimes ActiveX servers are harder to debug because they are loaded and executed by ActiveX clients, and you have less control over the invocation of the server. This is especially true if you cannot debug the client code, which occurs when the client is built in release configuration or is a third-party application.

If the server is an executable, it runs in a separate process and not in the client process. For this reason, executable servers also are called out-of-process servers. Notice that currently it is not possible to start an instance of LabWindows/CVI to debug a running process. NI recommends the following methods to debug EXE servers:

  • Select the Register As Active Object option in the Edit ActiveX Object Advanced Options dialog box for the first server object the client will use. Run and debug the ActiveX server from LabWindows/CVI like any executable. In the ActiveX client, connect to the active server object instead of creating a new instance of the server object. Now the client can make calls to the server as it is being debugged in LabWindows/CVI. Notice that this debugging method requires the client to connect to an active object, instead of creating a new instance.
  • Add an illegal instruction to the server code that will cause a run-time error. The following code is an example:

    int *ptr = NULL;
    *ptr = 3;     // dereferencing null pointer will cause run-time error

    If the executable is built in debug configuration, the run-time error results in a dialog box similar to the following figure.

     

    Fatal Run-Time Error Dialog


    Clicking the Break button causes LabWindows/CVI to launch, and you can debug the server code. You can use this method when the ActiveX client needs to create a new instance of the server object and connecting to an active object is not possible. When using this method, NI recommends that you build the server without the illegal instruction, register it, add the illegal instruction, disable the Register ActiveX server after build option in the Target Settings dialog box, and rebuild the server for debugging. These steps prevent the run-time error from occurring when LabWindows/CVI runs the server to register it and ensures that the server registers properly.


When an ActiveX client connects to a DLL server, the server is loaded in the client process. For this reason, DLL servers also are referred to as in-process servers. Debugging DLL servers is easier than debugging EXE servers. To debug a DLL server, build the DLL in debug configuration, add breakpoints, and run the ActiveX client executable from within LabWindows/CVI by using the Run»Specify External Process item.

At times, you might need to debug both the ActiveX client and server code. For EXE servers, follow the preceding recommendations to debug the server in one instance of LabWindows/CVI while you debug the ActiveX client separately in a different instance of the debugger. If the server is a DLL and the client is an executable built in LabWindows/CVI, load the client project in LabWindows/CVI, open the server source code from this instance of LabWindows/CVI, add breakpoints, and debug the client project. Now you can use this instance of LabWindows/CVI to debug the client source code and the server source code. Notice that for this method, the client must be an executable built in LabWindows/CVI. When none of the preceding techniques are possible, you can add DebugPrintf, printf, fprintf, MessagePopup, and other LabWindows/CVI function calls to the ActiveX server code to trace and debug the code. This method is useful when a bug occurs in the release configuration of the server but cannot be reproduced in the debug configuration.

Back to Top

8. Other Suggestions, Tips, and Tricks


  • Notice the difference between CAServerObjHandle and CAObjHandle. Use CAObjHandle in ActiveX client applications to identify external ActiveX objects. Use CAServerObjHandle in an ActiveX server application to identify the server's own ActiveX objects. Do not pass a CAServerObjHandle to functions expecting a CAObjHandle or pass a CAObjHandle to functions expecting a CAServerObjHandle. A CAServerObjHandle is discarded automatically when the last reference to the server is released and should not be passed to CA_DiscardObjHandle, which is used to discard a CAObjHandle.
  • Use the SUCCEEDED and FAILED macros in sdk\include\winerror.h to test HRESULT values for success and failure conditions. The winerror.h file is automatically included when you include the generated header file or cviauto.h.
  • For executables created in LabWindows/CVI, the LabWindows/CVI Run-Time Engine automatically adds a button to the taskbar. To conceal the execution of the server from the interactive user of the ActiveX client, you can hide the taskbar button by calling the LabWindows/CVI User Interface Library SetSystemAttribute function and setting the ATTR_TASKBAR_BUTTON_VISIBLE attribute to zero.
  • Maintain a well-documented ActiveX server. Document the objects, interfaces, methods, and properties of an ActiveX server and the possible errors that the server can return. Because ActiveX servers generated in LabWindows/CVI use the LabWindows/CVI ActiveX Library functions, include in your documentation that these servers can return the LabWindows/CVI ActiveX Library error codes, in addition to any other error codes. The error codes returned by the LabWindows/CVI ActiveX Library are listed in the cviauto.h header file and documented in ActiveX Library function panels.

 

Back to Top

Bookmark & Share


Ratings

Rate this document

Answered Your Question?
Yes No

Submit