Getting started
The Riegl LMS Scanner Library is a set of functions that will help you
- interface to a Riegl scanner via parallel port, serial port or network,
- read logged data from a disk file,
- decode data in a scanner independent manner,
- set scanner parameters,
- integrate the scanner interface using your preferred programming environment using COM technology,
- convert geometry data to cartesian or spherical coordinates (and apply corrections).
The library is packaged as COM objects, that are located in files named scannermod.dll
and scancnfmod.dll
. Once the
library has been registered into your system, your compiler will be able to read its type library and generate
the necessary interfaces. You then call the functions as if they were native functions in your programming
environment (e.g Visual C++, Delphi, ...). Since the library is coded in COM technology you even will be able
to create multiple instances of interfaces, as you would like when interfacing to more than one scanner at a time
or when reading data files at the same time.
The library is running in a separate thread asynchronously to your application. This will ease your interface
coding, since you almost never will be blocked when you call any of the interface functions, thereby avoiding
the annoying hourglass cursor. To this end the library maintains an internal data buffer for storage of the
scanner data, and controls access to it via a semaphore mechanism. The windows message passing system is used
to inform you of the relevant events, such as start of a scan, receipt of a single scan-line or end of a scan.
A logging facility also is built into the library. You can use this feature to create files that will store any
data the scanning unit is able to deliver, even if there is no interface function to directly access it. Storing
the data in this native format will allow to use an even more improved version of the library without compromising
compatibility.
Geometry data can be retrieved in spherical or cartesian coordinates. You choose the amount of information you
need to get in a single call. You may specify to get any number from a single point up to an entire scan per call.
The same is true for intensity (reflectivity) RGB (true color) or time data.
Error situations (such as end of file, wrong file or scanner types, ...) also are signaled via the windows message
loop. The library even will give you a plain text message string, that can be used for display in your interface.
This message string automatically will be translated to the language that is installed on your system. (currently
english and german available).
- Windows 95/98, Windows NT/2k/XP.
- A Riegl LMS scanning device (Z210, LPM, Q140, ...).
- A programming environment supporting COM objects (Visual C++, Delphi, ...).
RiPort
device driver when running on Windows NT/2000 and PC with ECP compatible parallel port
- or,
- Riegl LMS IB90-ETH ethernet - TCP/IP interface box or built in TCP/IP interface.
The library is able to interface to the scanner data port either through a PC parallel port or a TCP/IP socket using
the IB90-ETH box / built in TCP/IP. On Windows NT/2000 interfacing is via device driver RiPort
, resulting in very low
processor overhead because of the use of interrupts. On Windows95/98 there is no device driver available, resulting
in the need to constantly poll the port, thereby incuring high processor load. On Windows 95/98 in principle it
is possible to use even an simple bidirectional (not ECP capable) port. This interface however is not recommended
at all since only very low data transfer rates are obtainable.
Windows95/98 also needs a further precaution since none of the resource sharing functions for the parallel
port are in use. (A simultaneous print attempt might crash the system.)
Installation of RiSCANLIB comes in several flavours. First you need to find out how you intend to use it.
- You are writing an application that only needs to read 3dd-files.
- You are writing an application to control the scanner and get online data display.
- You are using RiSCANLIB as part of a larger application, and need to embed the setup within your scripts.
- You are installing the developer version onto your computer.
These are just some typical usage scenarios. In each case you will need to do the following steps.
- Find out which files to copy to the target computer.
- Find out if you need to install licenses for the copied files.
Developer Machine
Most likely the first thing you will do, is install the developer version onto your machine. As you are reading
this, most likely you already have installed the library, but might yet have skipped the licensing step.
You may make up for this whenever you like, using the LicenseManager
utility that can be found in
the support group of the Riegl LMS programs under the Start menue.
Launch the License Manager and locate the "Products of all users". You should find there the scancnfmod
and
scannermod
entries. Select one entry, and use the respective button to add your license code(s).
You may add as many licenses as you need. Licenses are usually bound to a specific scanner, so installing
all the licenses you have will enable to communicate with any of these scanners.
If you are just using the library for reading 3dd files, you do not need to apply any licenses, but you
cannot use any of the online functions of the library.
Target Machine
The scannermod.dll
and scancnfmod.dll
(refsrchmod.dll
) are shipped as self
registering COM servers. Therefore you need to copy the files to a central place on the system. We strongly
recommend to copy the files to the %SYSTEM%\system32 directory, where %SYSTEM% is a location dependent
environment variable that usually evaluates to WINDOWS or WINNT or similar. Your favorite install
program generator tool will know how to handle this correctly. Please avoid copying the files into an application
subdirectory of your own, since this effectively will defeat the versioning capability of the dll's and may
render them unusable.
Next you need to register with the COM subsystem of windows. Depending on your needs you may use the
windows regsvr32.exe
utility, or the respective commands of your install script language.
You even might code this yourself, given the code snippet that follows.
But please do not run this code on every startup of your application. This code is intended to run
once during the installation process.
Finally if you intend to use the online capabilities of the library, you will need to install the licenses.
You might instruct your useres to use the Riegl LMS LicenseManager or if you want to make this process more
comfortable for them, you can use a special function, that is exported from the DLL to this end. Also integrating
this step into your setup avoids the necessity to manually apply the same code twice, once to scannermod and once to
scancnfmod.
By the way: The DLL files are mostly independent of each other, therefore you will need to register and license them
separately. This also implies, that you are safe to copy only the scannermod.dll
file if you
need only the "read 3dd files" capability.
- Do it manually. (discouraged) :
- open a command prompt window, copy the DLL files to the intended dircetory, and in this
dircetory specify
regsvr32.exe scannermod.dll
.
or
regsvr32.exe scancnfmod.dll
.
(Do this step for all DLL's.)
You should see a messages box, confirming registration has succeeded. Depending on your intended use
you still might need to apply a valid license code. Doing this manually is tedious, but possible. Open
regedit, and under
HKEY_LOCAL_MACHINE\Software
create a subkey named
Riegl_LMS\scannermod\Lic
.
Under this key create a value named Val
of binary type. Enter the 16 byte license key
omitting the hyphens.
Add additional licences as values named Val1, Val2, ...
, but be careful not create "holes".
Alternatively you can use the LicenseManager utility, that will do the latter job.
- Use your setup script (highly recommended):
- Copy all DLL files to %SYSTEM%\system32 directory.
Register all DLL files as COM objects.
Use the capability of your setup script to write the licenses into the registry keys, that have been
described under "Do it manually".
- Using Windows API calls to install the library:
- The following code snippet shows how to register the COM server and apply the license key:
Run this code only once during installation!
typedef HRESULT (STDAPICALLTYPE *PSTDAPI_VOID)(void);
typedef HRESULT (STDAPICALLTYPE *PSTDAPI_PBYTE_UINT)(BYTE*, UINT);
PSTDAPI_VOID pDllRegisterServer;
PSTDAPI_PBYTE_UINT pDllSetLicense;
HMODULE hModule;
// put your license key here
BYTE rgLic = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
hModule = ::LoadLibrary(_T("C:\\scannermod\\scannermod.dll"));
if (NULL != hModule) {
pDllRegisterServer = (PSTDAPI_VOID)::GetProcAddress(hModule, _T("DllRegisterServer"));
if (NULL != pDllRegisterServer) {
if (S_OK == (pDllRegisterServer)()) {
pDllSetLicense = (PSTDAPI_PBYTE_UINT)::GetProcAddress(hModule, _T("DllSetLicense"));
if (NULL != pDllSetLicense) {
// you may call the next function multiple times to add more licenses
// this example adds only one license
if (S_OK == (pDllSetLicense)(rgLic, 16)) {
// registration and licensing ok
// do something with this information
}
}
}
}
::FreeLibrary(hModule);
}
Programming
The library basically consists of two modules, one for configuration, and one for data retrieval (measurement).
These modules are realized as COM objects, packaged as in process servers, each of which lives in a DLL. COM
objects as such are never accessible by a pointer that will give you control over its memory, but instead
you access the object by means of one (ore more) of its interfaces.
One of the main objectives of the library is, to decouple application code from hard coded file formats and from
scanner hardware details. This might be compared to a high level printer driver which does not (need not)
know much about the actual hardware. Ideally, once the application is in place you simply would need to copy
the newest DLL code onto the client PC to obtain access to more advanced scanner hardware.
As said above, you access the library by means of some of its interfaces. The COM specifications needs
an interface to be unique, e.g. it may not change, once it has been specified and deployed. To your
application this means, even when you are plugging in newer DLL's, you still will be able to use an
possibly older interface. It is the responsibility of the library writer to support older interfaces
as well. At least your application will automatically recieve the information, that an interface is not
avalaible any more.
During evolution of a software project it is natural that some things are changing. It is a always a challenge
not to break compatibility unadvertently by this changes however. The RiSCANLIB handles compatibility in the
following way:
- An interface, once deployed in a released version will not disappear. However its semantic might
be meaningless when called on an inapropriate instrument type. Normally this should result in a
regular error code, that your application will be able to handle smoothly.
- An interface might be extended. The newer, extended interface will contain all the functionality
of its predecessor. You will therefore only need to change the declaration of only one variable in your
code to get the extended functionality. Please note that this is not a required step, when you do not need
the new functionality. In this case simply copying the new library to your PC will not disable your
application from accessing the library. You need not even recompile your program. Such extended interfaces are
named by appending numbers at their back. e.g.: IScanner, IScanner2, IScanner3, ...
- A new interface might be added to a library object. When the need arises to access the scanner
object by a different concept, which is not easily captured by the main interface, but needs access to
the same object instance, a new interface will be introduced. However it is intended that this will only
occur infrequently. An example for this is the
INativeCnf
interface of the configuration
interface.
The library comes with built in type information, so your compiler should be able to generate the necessary
stub code to bind the library into your application. Depending on level of support from your compiler you then
will be able to call into the library just as you would, using native objects. There are some differences however,
so i.e. using Delphi you cannot use the assign operator "=" to read/write to member variables of the library
object, but instead have to use get/set functions.
As already has been mentioned, there are two objects available. A configuration object named ScanCnf,
and a measurement object named Scanner. Each of these objects corresponds to mutually exclusive states
of the scanning device. At any time instance a scanner can be either in scanning or in configuration mode.
After you have created the respective object, you will call Open
on its interface. This call will
only succeed when the scanner is not connected by any other means, so as e.g. a measurement object. The rule of
thumb is to have only one object connected to a scanner at any time instance. An object is in connected state after
Open
succeded until Close
has been successfully executed. You need not delete the object
from memory, after a Close
but can reuse it for following connections. In the case of the Scanner
object even the state data (the measurement data) is valid after a Close
. This is useful for overlapped
processing, where you can read and process the measurement results, while starting to configure the scanner
at the same time. Of course you can instantiate multiple objects to get simultaneous connections to more than
one scanner on a single machine.
Most of the interface functions execute asynchronously, i.e. they initiate a operation on the scanner without
waiting for the results. However this does not mean, that you do not need to care for the results. When the
operation has been carried out, either successfully or with error, a windows message is sent back to
the caller, informing the application about the outcome of the operation. The message is sent to a window, the
handle of which is specified in the Open
call. You may specify 0 for the window handle, in which the
calls become synchronous, i.e. they wait for the completion of the operation and cannot be interrupted. In case of
a failure, such as a non existent scanner on a port this might render your application frozen, and the only
remedy would be to "kill" it. Obviously such programming practice is not recommended. So this use on a live connection
to a scanner therefore is not recommended at all. You should use this mode mainly to read data from files.
These instructions do not attempt to tell you how to program to the COM interface, instead it will try to give
you a fast path to use the library. There are a lot of different ways you can access COM objects. In this
example we use the 'smart pointer' feature of the ATL.
First you must make sure the com library is initialized. You typically will need to call CoInitialize(NULL)
from the InitInstance
of your application. You should also add a call to CoUninitialize()
in ExitInstance
.
Next you will include an import statement to generate the class definitions from the type library.
E.g.:
#import "scannermod.dll" no_namespace
Then you will declare a variable of type IScannerPtr as
IScannerPtr pScanner(__uuidof(Scanner));
Now you are ready to access any of the documented functions of the class. The code snippet
below shows you how to open the connection, acquire a frame and retrieve geometry data. Afterwards the connection
will be closed.
pScanner->Open("\\.\\RiPT0","",(DWORD)hWnd, SCN_MFRAME, WM_USER);
.
.
.
// somewhere in the message handler for WM_USER of the window hWnd:
switch(wParam) {
case SCN_OPEN:
pScanner->GetDimension(&nLines, &nMeas);
pdVertex = (float*)calloc(3*nLines*nMeas, sizeof(float));
for (n=0; n<3*nLines*nMeas; n++) *(pdVertex+n) = 0.0;
pScanner->NextFrame();
break;
case SCN_FRAME:
m_nFrame = lParam;
pScanner->GetVertex(&nTransfered, pdVertex, SCN_CARTESIAN, 3, 0, nLines*m_nMeas);
pScanner->Close();
// now display the data
break;
case SCN_ERROR:
m_pScanner->GetErrNmb(&nError);
m_pScanner->GetErrStr(nError, &BSTRError);
// e.g. display error
break;
case SCN_CLOSE:
// do anything necessary after file has been closed
// e.g update UI
break;
}
.
.
.
The project named simple
on the distribution media shows a working
example of how to use library from Visual C++. Using the library from a console application
is demonstrated in the example project conv2asc
.
The example project ctrl
demonstrates how to use the ScanCnf
library
to control the scanner settings.
For making the scanmod functions accessible to your DELPHI application, you need to
1. Register the library as described in Installation.
2. add a unit specifying the library functions, types and constants to your application project.
Delphi supports an automatic generation of an interface unit when importing a type library.
This is done by the following steps:
- Select "Project", then "Import Type Library"
- Select the scan library "scannermod x.y Type library (Version x.y)"
- Enter an "unit dir name" where you want to place the automatic generated unit for the library.
- Now click on "Create unit" to automatically create the interface unit (SCANNERMODlib_TLB.pas file )
for the library.
After specifiying SCANNERMODlib_TLB in the uses clause of your source file like
uses SCANNERMODlib_TLB
you then need to declare a scanner object of type IScanner
var
Scanner: IScanner; // the scanner object for interfacing the RIEGL scanner
and create the object with a line
(e.g. in your main formular "FormCreate" event)
Scanner := CoScanner.Create;
Additionally you need a message handler to handle the scanner messages
procedure OnScanner(var aMessage:TMessage); message WM_SCANNER;
where WM_SCANNER
is a constant which is usually set to a value within a user
reserved area of windows messages starting at WM_USER
.
const
WM_SCANNER = WM_USER + 0; // user defined messages
To connect to the scanner (or to a data file) you call the open function in a way like
Scanner.Open('\\.\ECP0','', Self.Handle, SCN_MLINE or SCN_MFRAME, WM_SCANNER);
To start acquistion of a frame call the NextFrame function
Scanner.NextFrame;
and in the handler for the scanner messages you read the data in the SCN_LINE
or SCN_FRAME
event via the GetVertex function (e.g. for reading 2D coordinates)
procedure TMainForm.OnScanner(var aMessage:TMessage);
var
DataPointsTransfered: SYSINT; // to hold number of data points actually read
begin
case aMessage.wParam of
SCN_OPEN:
begin
// get the size of the frame
// LinesPerImage and MeasPerLine are global SYSINTs
Scanner.GetDimension(LinesPerImage,MeasPerLine);
end;
SCN_LINE:
begin
// get the scanner coordinates
// it is assumed that a data array
// Data: array[0..1][0..MeasPerLine-1] of single
// for data storage exists
Scanner.GetVertex(DataPointsTransfered,Data[0][0],SCN_CARTESIAN,2,MeasPerLine*aMessage.lParam,1);
end;
end;
end;
The project named ScanLibExample
on the distribution media shows a working
example of how to use library from Delphi.
For making the scanmod functions accessible to your Borland C++Builder application, you need to
1. Register the library as described in Installation.
2. add a unit specifying the library functions, types and constants to your application project.
CBuilder supports an automatic generation of an interface unit when importing a type library.
This is done by the following steps:
- Select "Project", then "Import Type Library"
- Select the scan library "scannermod x.y Type library (Version x.y)"
- Enter an "unit dir name" where you want to place the automatic generated unit for the library.
- Now click on "Create unit" to automatically create the interface unit
(SCANNERMODlib_TLB.cpp and SCANNERMODlib_TLB.h) for the library.
After specifiying SCANNERMODlib_TLB using the include directive in your source file like
#include <SCANNERMODlib_TLB.h>
you then need to declare a scanner object of type IScannerPtr
IScannerPtr pScanner; // the scanner interface object
Call CoInitialize(NULL); if necessary
and create the object with a line
(e.g. in your main formular "FormCreate" event)
pScanner.CreateInstance(CLSID_Scanner); // create scanner object
Additionally you need a message handler to handle the scanner messages (e.g. a private method of a TForm object)
void OnScanner(TMessage& aMessage);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_SCANNER, TMessage, OnScanner)
END_MESSAGE_MAP(TForm)
where WM_SCANNER
is a constant which is usually set to a value within a user
reserved area of windows messages starting at WM_USER
.
const
WM_SCANNER = WM_USER + 0; // user defined messages
To connect to the scanner (or to a data file) you call the open function in a way like
pScanner->Open('\\.\ECP0', '', (unsigned long)this->Handle, SCN_MLINE | SCN_MFRAME, WM_SCANNER);
To start acquistion of a frame call the NextFrame function
pScanner->NextFrame();
and in the handler for the scanner messages you read the data in the SCN_LINE
or SCN_FRAME
event via the GetVertex function (e.g. for reading 2D coordinates)
void TMainForm::OnScanner(TMessage& aMessage)
{
int DataPointsTransfered; // to hold number of data points actually read
switch (aMessage.wParam)
{
case SCN_OPEN:
// get the size of the frame
// LinesPerImage and MeasPerLine are global SYSINTs
pScanner->GetDimension(LinesPerImage, MeasPerLine);
break;
case SCN_LINE:
// get the scanner coordinates
// it is assumed that a data array
// Data: array[0..1][0..MeasPerLine-1] of single
// for data storage exists
pScanner->GetVertex(DataPointsTransfered,Data[0][0],SCN_CARTESIAN,2,MeasPerLine*aMessage.lParam,1);
break;
}
}
When you do not need the scanner object anymore, release it with
pScanner.Release(); // release scanner object
The project named ScanLibConfigExample
on the distribution media shows a working
example of how to use library from CBuilder.