2022-04-03

70: Create Any Global UNO Service in C++

<The previous article in this series | The table of contents of this series | The next article in this series>

In order to instantiate the UNO component from a remote programming language environment or to create an artifact like a spread sheet cell function.

Topics


About: UNO (Universal Network Objects)
About: LibreOffice
About: Apache OpenOffice
About: C++

The table of contents of this article


Starting Context



Target Context


  • The reader will know how to create his or her own global UNO service in C++.

Orientation


There is an article on how to create and register any UNO Interface and generate the mapping images.

There is an article on how to build any sample program of this series.

There is an article on how to build an environment for developing UNO programs in Linux or in Windows.


Main Body

Stage Direction
Here are Special-Student-7, Morris (a C++ programmer), and Chloe (a C++ programmer) in front of a computer.


0: What We Mean by "UNO Service" Here


Special-Student-7
What we mean by "UNO service" here is what is explained in an article.

To state succinctly, UNO service is an item in an UNO objects factory, which is called 'UNO services manager'.

In fact, "service" is a very confusedly and confusingly used term in the official terminology, and we do not mean the 2nd meaning or the 3rd meaning in the official terminology by "UNO service".

If you are wondering how you can create a "service" in the 2nd meaning in the official terminology, I am pretty sure that it is not something you need to create.

Some people may deem "service" in the 3rd meaning in the official terminology handy, but also it is not something you particularly need to create, and as I choose not to create any, I do not explain about it.


1: Why Is a UNO Service Desired?


Special-Student-7
The purpose of creating a UNO service is to instantiate the UNO component from a remote programming language environment or to create an artifact that requires the UNO component to be registered as a UNO service, like a spread sheet cell function.

Please correctly understand what "remote programming language environment" means.

Morris
What does it mean?

Special-Student-7
Any remote programming language environment is a programming language environment that is not the programming language environment in which the UNO component is to be instantiated

The Java environment or the Python environment inside office instance is a remote programming language environment for the C++ main body of the office instance, because only the C++ main body is the local programming environment for it.

Chloe
It is the matter of whether the UNO component can be instantiated by the 'new' operator from the environment, isn't it? If it can be, there is no necessity for the UNO component to be instantiated via the UNO services manager, which is a factory of UNO component instances.

Special-Student-7
Exactly, madam.

Chloe
So, even if the UNO component is going to be instantiated from a remote programming language environment, if it is going to be instantiated by another UNO object in the local programming language environment, the UNO service is unnecessary.

Special-Student-7
Exactly, again. Usually, the 1st of your UNO objects is instantiated from a UNO services manager, and then, the 1st UNO object could instantiate some of your UNO components, and so on.

Morris
What is that "to create an artifact that requires . . ." blah-blah-blah part?

Special-Student-7
Some kinds of artifacts require UNO components to be registered as UNO services regardless of whether you have a remote programming language environment or not.

For example, any spread sheet cell function requires the corresponding UNO component to be registered as a UNO service.


2: A Note: We Are Creating a Global UNO Service


Special-Student-7
Although not every UNO service is a global UNO service, when I talk about creating any UNO service, I am talking about creating a global UNO service.

Morris
Huh? I'm not happy about such a restriction.

Special-Student-7
Are you, sir?

Morris
Look at my face! Do I look happy?

Chloe
I have never seen your looking happy.

Special-Student-7
Well, as each non-global UNO services manager is of a specific purpose and a specific implementation, usually, registering your UNO service there is not useful at all unless the UNO services manager has such a customization in its mind.

And I guess that there is no such a non-global UNO services manager.


3: Create the UNO Service



3-1: Prepare the UNO Component


Special-Student-7
The UNO component has to satisfy a certain prerequisite for it to be registered as a UNO service.

Namely, it has to implement 2 UNO interfaces, '::com::sun::star::lang::XServiceInfo' and '::com::sun::star::lang::XInitialization', like this.

theBiasPlanet/tests/models/CplusplusTest1UnoComponent.hpp

@C++ Source Code
#ifndef __theBiasPlanet_tests_models_CplusplusTest1UnoComponent_hpp__
	#define __theBiasPlanet_tests_models_CplusplusTest1UnoComponent_hpp__
	
	#include <set>
	#include <com/sun/star/lang/IllegalArgumentException.hpp>
	#include <com/sun/star/lang/XInitialization.hpp>
	#include <com/sun/star/lang/XServiceInfo.hpp>
	#include <com/sun/star/uno/Reference.hxx>
	#include <com/sun/star/uno/RuntimeException.hpp>
	#include <com/sun/star/uno/XComponentContext.hpp>
	#include <cppuhelper/implbase3.hxx>
	#include <rtl/ustring.hxx>
	#include "theBiasPlanet/unoDatumTypes/tests/XTest1.hpp"
	
	using namespace ::std;
	using namespace ::com::sun::star::lang;
	using namespace ::com::sun::star::uno;
	using namespace ::cppu;
	using namespace ::rtl;
	using namespace ::theBiasPlanet::unoDatumTypes::tests;
	
	namespace theBiasPlanet {
		namespace tests {
			namespace models {
				class CplusplusTest1UnoComponent: public WeakImplHelper3 <XServiceInfo, XInitialization, XTest1> {
					private:
						static string const c_nameOfThisClass;
						static set <string> const c_unoServiceNames;
						static set <string> const c_unoCompoundInterfaceNames;
						Reference <XComponentContext> i_underlyingRemoteUnoObjectsContextInXComponentContext;
						// # Add the other member variables Start
						string i_message;
						// # Add the other member variables End
					public:
						static OUString SAL_CALL getImplementationNameStatically ();
						static Sequence <OUString> SAL_CALL getSupportedUnoCompoundInterfaceNamesStatically ();
						static ::com::sun::star::uno::Reference <XInterface> createInstance (::com::sun::star::uno::Reference <XComponentContext> const & a_underlyingUnoObjectsContextInXComponentContext);
						CplusplusTest1UnoComponent (::com::sun::star::uno::Reference <XComponentContext> const & a_underlyingUnoObjectsContextInXComponentContext);
						~CplusplusTest1UnoComponent ();
						void SAL_CALL initialize (Sequence <Any> const & a_arguments) override;
						OUString SAL_CALL getImplementationName () override;
						sal_Bool SAL_CALL supportsService (OUString const & a_unoCompoundInterfaceName) override;
						Sequence <OUString> SAL_CALL getSupportedServiceNames () override;
						OUString SAL_CALL test1 (OUString const & a_name) override;
				};
			}
		}
	}
#endif


theBiasPlanet/tests/models/CplusplusTest1UnoComponent.cpp

@C++ Source Code
#include "theBiasPlanet/tests/models/CplusplusTest1UnoComponent.hpp"
#include "theBiasPlanet/unoUtilities/stringsHandling/UnoExtendedStringHandler.hpp"

using namespace ::theBiasPlanet::unoUtilities::stringsHandling;

namespace theBiasPlanet {
	namespace tests {
		namespace models {
			OUString CplusplusTest1UnoComponent::getImplementationNameStatically () {
				return UnoExtendedStringHandler::getOustring (c_nameOfThisClass);
			}
			
			Sequence <OUString> CplusplusTest1UnoComponent::getSupportedUnoCompoundInterfaceNamesStatically () {
				Sequence <OUString> l_unoCompoundInterfaceNamesSequence;
				l_unoCompoundInterfaceNamesSequence.realloc (c_unoCompoundInterfaceNames.size ());
				int l_sequenceItemIndex = 0;
				for (string const & l_unoCompoundInterfaceName: c_unoCompoundInterfaceNames) {
					l_unoCompoundInterfaceNamesSequence [l_sequenceItemIndex] = UnoExtendedStringHandler::getOustring (l_unoCompoundInterfaceName);
					l_sequenceItemIndex ++;
				}
				return l_unoCompoundInterfaceNamesSequence;
			}
			
			::com::sun::star::uno::Reference <XInterface> SAL_CALL CplusplusTest1UnoComponent::createInstance (::com::sun::star::uno::Reference <XComponentContext> const & a_underlyingUnoObjectsContextInXComponentContext) {
				return Reference <XInterface> ( (XInterface *) (void *) (new CplusplusTest1UnoComponent (a_underlyingUnoObjectsContextInXComponentContext)));
			}
			
			CplusplusTest1UnoComponent::CplusplusTest1UnoComponent (::com::sun::star::uno::Reference <XComponentContext> const & a_underlyingUnoObjectsContextInXComponentContext): i_underlyingRemoteUnoObjectsContextInXComponentContext (a_underlyingUnoObjectsContextInXComponentContext) {
			}
			
			CplusplusTest1UnoComponent::~CplusplusTest1UnoComponent () {
			}
			
			void CplusplusTest1UnoComponent::initialize (Sequence <Any> const & a_arguments) {
				if (a_arguments.getLength () != 1) {
					throw IllegalArgumentException (UnoExtendedStringHandler::getOustring (string ("There must be one argument.")), Reference <XInterface> ( (XInterface *) (void *) this), 0);
				}
				Type l_datumType;
				l_datumType = a_arguments [0].getValueType ();
				if (! (l_datumType == UnoType <OUString>::get ())) {
					throw IllegalArgumentException (UnoExtendedStringHandler::getOustring (string ("The first argument must be a string")), Reference <XInterface> ( (XInterface *) (void *) this), 0);
				}
			    else {
					i_message = UnoExtendedStringHandler::getString (* ( (OUString *) a_arguments [0].getValue ()));
				}
			}
			
			OUString CplusplusTest1UnoComponent::getImplementationName () {
				return getImplementationNameStatically ();
			}
			
			sal_Bool CplusplusTest1UnoComponent::supportsService (OUString const & a_unoCompoundInterfaceName) {
				set <string>::iterator l_unoCompoundInterfaceNamesIterator = c_unoCompoundInterfaceNames.find (UnoExtendedStringHandler::getString (a_unoCompoundInterfaceName));
				return (l_unoCompoundInterfaceNamesIterator != c_unoCompoundInterfaceNames.end ());
			}
			
			Sequence <OUString> CplusplusTest1UnoComponent::getSupportedServiceNames () {
				return getSupportedUnoCompoundInterfaceNamesStatically ();
			}
			
			OUString CplusplusTest1UnoComponent::test1 (OUString const & a_name) {
				return UnoExtendedStringHandler::getOustring (i_message + ", ") + a_name + UnoExtendedStringHandler::getOustring ("!");
			}
		}
	}
}


theBiasPlanet/tests/staticVariablesInitializer/StaticVariablesInitializer.cpp

@C++ Source Code
#include "theBiasPlanet/unoUtilities/stringsHandling/UnoExtendedStringHandler.hpp"
#include "theBiasPlanet/tests/globalUnoServicesProvider/TestsGlobalUnoServicesProvider.hpp"
#include "theBiasPlanet/tests/models/CplusplusTest1UnoComponent.hpp"

using namespace ::theBiasPlanet::unoUtilities::stringsHandling;
using namespace ::theBiasPlanet::tests::models;

namespace theBiasPlanet {
	namespace tests {
		~
		namespace models {
			string const CplusplusTest1UnoComponent::c_nameOfThisClass (string ("theBiasPlanet.tests.models.CplusplusTest1UnoComponent"));
			set <string> const CplusplusTest1UnoComponent::c_unoServiceNames ( [] {
				set <string> l_unoServiceNames;
				l_unoServiceNames.insert (string ("theBiasPlanet.tests.CplusplusTest1UnoComponent"));
				return l_unoServiceNames;
			} ());
			set <string> const CplusplusTest1UnoComponent::c_unoCompoundInterfaceNames;
		}
	}
}

Morris
. . . I can't discern your taste from the necessities.

Special-Student-7
The necessities are that you need to implement the constructor and those "override" methods somehow appropriately, and have those 3 static methods, "getImplementationNameStatically", "getSupportedUnoCompoundInterfaceNamesStatically", and "createInstance" with arbitrary names but with the same argument types and the return types.

Morris
I'm asking what is "appropriate".

Special-Student-7
The constructor takes the single '::com::sun::star::uno::Reference <XComponentContext> const &' argument, whose value should be usually stored somewhere, because the UNO objects context is the access point to the UNO environment.

Note that the instantiation arguments for the UNO service are not passed into the constructor; they are passed into the "initialize" method.

Morris
So, I have to create the constructor in that exact signature.

Special-Student-7
The "getImplementationName" method returns the full class name.

"supportsService" and "getSupportedServiceNames" methods are basically about "service" in the 2nd meaning in the official terminology, not about UNO service.

Morris
The word, "basically", is an evasion, I say.

Special-Student-7
Maybe, but I should be honest than be baselessly assertive.

In fact, "service" is so jumbled up in the office product code as well as in the documentation that the name of a "service" in the 2nd meaning is sometimes opportunistically abused to mean the UNO service of the same name and vice versa.

Morris
Ah, so, what should I do with those 2 methods?

Special-Student-7
Each of some kinds of artifacts requires the UNO component to support some specific "services" in the 2nd meaning, so, the 2 methods should support them in that case; otherwise, the 2 methods do not need to support any "service", although supporting the UNO service name will not cause any problem.

Morris
Does "support" mean just answering 'true'?

Special-Student-7
Yes for "supportsService"; for "getSupportedServiceNames", it means including the "service" name in the return.

Chloe
Well, that is the reason why "getSupportedServiceNames" can support multiple "services" . . .

Special-Student-7
Supporting multiple UNO services is usually not useful; the method can support multiple "services" because they are of the 2nd meaning, yes.

Anyway, the 3 static methods have to be static, and the names do not matter, but have to have those argument types and return types.

Well, the rest is from my taste: for example, I have 'c_unoServiceNames' because I do not want to scatter literals across methods


3-2: Create a Global UNO Services Provider


Special-Student-7
We need to create a global UNO services provider.

Or in fact, C++ UNO does not really have any global UNO services provider as a class as Java UNO has, but 2 global methods needed in order for UNO services to be registered and instantiated. I am calling the set of 2 global methods 'global UNO services provider'.

This is an example global UNO services provider.

theBiasPlanet/tests/globalUnoServicesProvider/TestsGlobalUnoServicesProvider.hpp

@C++ Source Code
#ifndef __theBiasPlanet_tests_globalUnoServicesProvider_TestsGlobalUnoServicesProvider_hpp__
	#define __theBiasPlanet_tests_globalUnoServicesProvider_TestsGlobalUnoServicesProvider_hpp__

	#include <cppuhelper/implementationentry.hxx>

	using namespace ::cppu;

	namespace theBiasPlanet {
		namespace tests {
			namespace globalUnoServicesProvider {
				class TestsGlobalUnoServicesProvider {
					private:
					public:
						static ImplementationEntry * s_unoComponentEntriesArray;
				};
			}
		}
	}
#endif


theBiasPlanet/tests/globalUnoServicesProvider/TestsGlobalUnoServicesProvider.cpp

@C++ Source Code
#include "theBiasPlanet/tests/globalUnoServicesProvider/TestsGlobalUnoServicesProvider.hpp"
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <cppuhelper/factory.hxx>
#include <uno/lbnames.h>

using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::registry;

extern "C" {
	using namespace ::theBiasPlanet::tests::globalUnoServicesProvider;
	
	SAL_DLLPUBLIC_EXPORT void * SAL_CALL component_getFactory (char const * a_implementationName, XMultiServiceFactory * a_globalUnoServicesManager, XRegistryKey * a_registryKey) {
		return component_getFactoryHelper (a_implementationName, a_globalUnoServicesManager, a_registryKey, TestsGlobalUnoServicesProvider::s_unoComponentEntriesArray);
	}
	
	SAL_DLLPUBLIC_EXPORT void SAL_CALL component_getImplementationEnvironment (char const ** a_unoEnvironmentTypeNameAddress, uno_Environment ** a_unoEnvironment) {
		*a_unoEnvironmentTypeNameAddress = CPPU_CURRENT_LANGUAGE_BINDING_NAME;
	}
}


theBiasPlanet/tests/staticVariablesInitializer/StaticVariablesInitializer.cpp

@C++ Source Code
#include "theBiasPlanet/unoUtilities/stringsHandling/UnoExtendedStringHandler.hpp"
#include "theBiasPlanet/tests/globalUnoServicesProvider/TestsGlobalUnoServicesProvider.hpp"
#include "theBiasPlanet/tests/models/CplusplusTest1UnoComponent.hpp"

using namespace ::theBiasPlanet::unoUtilities::stringsHandling;
using namespace ::theBiasPlanet::tests::models;

namespace theBiasPlanet {
	namespace tests {
		namespace globalUnoServicesProvider {
			ImplementationEntry * TestsGlobalUnoServicesProvider::s_unoComponentEntriesArray ( [] {
				ImplementationEntry * l_unoComponentEntriesArray (new ImplementationEntry [2]);
				(*l_unoComponentEntriesArray).create = CplusplusTest1UnoComponent::createInstance;
				(*l_unoComponentEntriesArray).createFactory = createSingleComponentFactory;
				(*l_unoComponentEntriesArray).getImplementationName = CplusplusTest1UnoComponent::getImplementationNameStatically;
				(*l_unoComponentEntriesArray).getSupportedServiceNames = CplusplusTest1UnoComponent::getSupportedUnoCompoundInterfaceNamesStatically;
				(*l_unoComponentEntriesArray).moduleCounter = 0;
				(*l_unoComponentEntriesArray).nFlags = 0;
				(* (l_unoComponentEntriesArray + 1)).create = 0;
				(* (l_unoComponentEntriesArray + 1)).createFactory = 0;
				(* (l_unoComponentEntriesArray + 1)).getImplementationName = 0;
				(* (l_unoComponentEntriesArray + 1)).getSupportedServiceNames = 0;
				return l_unoComponentEntriesArray;
			} ());
		}
		~
	}
}


Chloe
Hmm, the point is preparing that "HiUnoExtensionsGlobalUnoServicesProvider::s_unoComponentEntriesArray" data.


3-3: Create the Extension Configuration Files


Special-Student-7
We are going to register the UNO service via a LibreOffice or Apache OpenOffice extension, and we need some configuration files for that.

How to create any LibreOffice or Apache OpenOffice extension is explained in an article.

Morris
Well . . .

Special-Student-7
We are creating that "UnoServiceComponentsForLinux64Bits.xml" file, like this.

@XML Source Code
<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://openoffice.org/2010/uno-components">
	<!-- # Change the shared objects library file uri -->
	<component loader="com.sun.star.loader.SharedLibrary" uri="linux64Bits/libtheBiasPlanet.tests.unoExtension.so">
		<!-- # Add service implementation class names -->
		<implementation name="theBiasPlanet.tests.models.CplusplusTest1UnoComponent">
			<!-- # Add service names -->
			<service name="theBiasPlanet.tests.CplusplusTest1UnoComponent"/>
		</implementation>
	</component>
</components>

And we are creating that "UnoServiceComponentsForMicrosoftWindows64Bits.xml" file, like this.

@XML Source Code
<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://openoffice.org/2010/uno-components">
	<!-- # Change the shared objects library file uri -->
	<component loader="com.sun.star.loader.SharedLibrary" uri="microsoftWindows64Bits/theBiasPlanet.tests.unoExtension.dll">
		<!-- # Add service implementation class names -->
		<implementation name="theBiasPlanet.tests.models.CplusplusTest1UnoComponent">
			<!-- # Add service names -->
			<service name="theBiasPlanet.tests.CplusplusTest1UnoComponent"/>
		</implementation>
	</component>
</components>

Morris
"component" means a shared library file or a dynamic link library file now . . .

Special-Student-7
Yes, another case of "component"'s being abused in the lax official terminology: "component" could mean anything, now a shared library file or dynamic link library.

Note that the UNO service name should be specified there as well as "service" in the 2nd meaning names should.

Morris
It is called "hold with the hare and run with the hounds", I should say: those 2 methods do not need the UNO service name, but the configuration file needs the UNO service name . . .

Special-Student-7
"service" is really opportunistically abused.

Anyway, we need to create also "manifest.xml" of course, but I do not show it here, because it is exactly as in the article.


3-4: An Aside: What Are Not Necessary


Special-Student-7
You do not need to create any "service" IDL file, because that is for creating a "service" in the 2nd meaning or a "service" in the 3rd meaning, not for creating a UNO service.

You may have some opportunities to make the UNO component support some existing "services" in the 2nd meaning, but no need to create your own "service" in the 2nd meaning, I guess.

You could create your own "service" in the 3rd meaning, but I do not make any explanation on how to create one, because I choose not to create any.


3-5: Create the Extension File


Special-Student-7
Now, we are going to create the extension file.

I do not need to explain how, because it has been already detailed in a previous article.


3-6: Register the Extension


Special-Student-7
We are going to register the extension.

It can be done via the GUI menu item "Tools" -> "Extension Manager...".

Or you can use the 'unopkg' command, if your build script wants to register the extension, like this.

@bash or CMD Source Code
unopkg add -f -v %the extension file path%

Note that LibreOffice or Apache OpenOffice instance is supposed to have been shut down before the command.


3-7: Setting the Additional Shared Library or Dynamic Link Library Paths into the Office Instance


Special-Student-7
Supposing that your UNO service uses some shared libraries or dynamic link libraries made by you or otherwise, those libraries can be located somewhere probably in the local file system and the paths can be set into the office instance.

Chloe
It is just a matter of setting the 'LD_LIBRARY_PATH' environment variable, isn't it?

Special-Student-7
As a matter of fact, yes, it is, for Linux; it is setting the 'PATH' environment variable for Microsoft Windows.

And a reasonable way for setting it is to write it in the '%the office product directory%/program/soffice' file for Linux, like this, somewhere in the file.

@bash Source Code
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:%the development directory path%/unoUtilitiesToBeDisclosed/target:%the development directory path%/coreUtilitiesToBeDisclosed/target"

Morris
Can it be anywhere?

Special-Student-7
Almost, I guess; it has to be before the main body is invoked, of course.

Putting it at the top of the file is a safe option.

For Microsoft Windows, there is no 'soffice' file as a batch file, so, a reasonable way is to create the '%the office product directory%\program\soffice.bat' file like this.

@CMD Source Code
setlocal
        set Path=%Path%;%the office SDK directory path%\lib;%the office directory path%\program;%the development directory path%\unoUtilitiesToBeDisclosed\targetInWindows;%the development directory path%\coreUtilitiesToBeDisclosed\targetInWindows;%the wxWidgets product directory path%\lib\vc_x64_dll;%the OpenSSL product directory path%\lib;%the OpenSSL product directory path%
        call soffice.exe
endlocal

And as '.exe' files usually have precedence over '.bat' files, I set the 'PATHEXT' environment variable like this from the project build script or something, if I want 'soffice' to execute the 'soffice.bat' file.

@CMD Source Code
setlocal
        set PATHEXT=.bat;.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.com;.exe;.cmd;.vbs;.vbe;.js;.jse;.wsf;.wsh;.msc
		~
endlocal



4: Instantiate the UNO Service


Special-Student-7
The UNO service can be instantiated, as any built-in global UNO service can.

For example, this is a Python macro.

@Python Source Code
from typing import Any
from typing import cast
import uno
~
from com.sun.star.script.provider import XScriptContext
from com.sun.star.uno import XComponentContext
XTest1: Any = uno.getClass ("theBiasPlanet.unoDatumTypes.tests.XTest1")

XSCRIPTCONTEXT: XScriptContext
c_underlyingRemoteUnoObjectsContextInXComponentContext: XComponentContext = XSCRIPTCONTEXT.getComponentContext ()
c_cplusplusTest1UnoComponent: "XTest1" = cast (XTest1, c_underlyingRemoteUnoObjectsContextInXComponentContext.getServiceManager ().createInstanceWithArgumentsAndContext ("theBiasPlanet.tests.CplusplusTest1UnoComponent", ["Hey"], c_underlyingRemoteUnoObjectsContextInXComponentContext))

def test1 () ->None:
	~
	c_cplusplusTest1UnoComponent.test1 ("sport")
	~

Chloe
Now, the C++ UNO component can be instantiated from a remote programming language environment.


5: Here Is a Workable Sample


Special-Student-7
Here is a workable sample extension.

How to build the sample project is explained in an article.

The main project is 'hiUnoExtensionsUnoExtension', which uses some utility projects and the additional UNO datum types project.

The UNO datum types merged file has to be registered into the office product according to the way 1) stated in a previous article.

And the paths of the shared libraries directories (for Linux '%the development directory path%/unoUtilitiesToBeDisclosed/target' and '%the development directory path%/coreUtilitiesToBeDisclosed/target') or dynamic link libraries directories (for Microsoft Windows '%the development directory path%\unoUtilitiesToBeDisclosed\target', '%the development directory path%\coreUtilitiesToBeDisclosed\target', '%the office SDK directory\lib', '%the office product directory%\program', '%the wxWidgets product directory\lib\vc_x64_dll', '%the OpenSSL product directory\lib', and '%the OpenSSL product directory%') have to be set into the office instance according to the way explained in a previous section.

Morris
A lot of preparations . . .

Special-Student-7
If the sample does not work, probably, the preparations were not right.

The extension should have registered a tool buttons group, from which the testing macro can be called.


References


<The previous article in this series | The table of contents of this series | The next article in this series>