Working with external DLL - am I doing this correctly?

We’ve got an application that controls a motion picture film scanner (via serial messages to an Arduino). The application, written in (RealStudio 2011 r1.1) also needs to control the camera that’s making the images. We’re using an Epix PIXCI camera, and have purchased their C library/dll, which we should be able to call from RB. Here’s a high level overview of the XCLIB library, with some basic calls to the frame grabber.

Section 6.2 has some code examples, and I’m trying to use the second example, which should write the frame to a TIF file. It seems that we’re successfully able to load the DLL, as RB isn’t complaining about the function calls to it. However, nothing seems to be happening - no file is written, no errors are caught.

It may be that this is an Epix question, and I’ll pose it to them as well, but first I want to make sure I’m doing this correctly in RB. Here’s my code:

  dim confFile as WString = "C:\\Users\\perry\\Documents\\Pixci_Configs\\pixciConfig.fmt"
  
  Declare function pxd_PIXCIopen lib  "XCLIBWNT.dll" (driverParms as WString, formatName as WString,  confFile as WString) as Integer
  Declare function pxd_doSnap lib  "XCLIBWNT.dll" (unitmap as Integer, buffer as Int64,  timeout as Int64 ) as Integer
  Declare function pxd_saveTiff lib  "XCLIBWNT.dll" (unitmap as Integer, pathname as WString,  framebuf as Int64,  ulx as Integer, uly as Integer, lrx as Integer, lry as Integer, loadmode as Integer, savemode as Integer ) as Integer
  Declare function pxd_PIXCIclose lib  "XCLIBWNT.dll" ( ) as Integer
  
  
  Try
    Call pxd_PIXCIopen("", "Default", confFile)
    //break
  Catch e as RunTimeException
    MsgBox(e.Message)
  End Try
  
  
  Try
    Call pxd_doSnap(val("&H" + "0x1"), 1, 0)
    //break
  Catch e as RunTimeException
    MsgBox(e.Message)
  End Try
  
  Try
    Call pxd_saveTiff( val("&H" + "0x1"), "C:\\Users\\perry\\Documents\\image.tif", 1, 0, 0, -1, -1, 0, 0)
    //break
  Catch e as RunTimeException
    MsgBox(e.Message)
  End Try
  
  Try
    Call pxd_PIXCIclose()
    //break
  Catch e as RunTimeException
    MsgBox(e.Message)
  End Try

Whenever calling external DLLs you must ensure that the size of the parameters matches

If the C DLL expects a 32bit integer, you might just get away with having a function declared as

 Declare function pxd_doSnap lib  "XCLIBWNT.dll" (unitmap as Integer, buffer as Int64,  timeout as Int64 ) as Integer

But you really should use the named variable sizes like INT32, INT16, UINT16 etc
That way when Xojo goes 64bit, it wont die.

Looking at your code, I’d check and change those.
Then consider what is happening here:

   Call pxd_doSnap(val("&H" + "0x1"), 1, 0)

Val() returns a double… not an integer.
That’s not likely to lead to anything good.

Are the Strings supposed to be WString, or CString?

Truthfully, I don’t know the answers to several of these questions. The folks at Epix have no RealBasic support, and the closest examples they give are for VB, which I’ve never used. Their suggestion was to use the code at the link above as a starting point. I’ve managed to at least get RealBasic to stop complaining about not finding the functions in the DLL, so my assumption at this point is that it’s actually being called.

for pxd_saveTiff, here’s the documentation from their manual. Some of the variable types I used are guesses, since I really wasn’t sure what to use, never having done this before with an external DLL. If you can make some suggestions based on this documentation, maybe it’ll get me going in the right direction:

[code]int pxd_saveTiff(unitmap, pathname, framebuf, ulx, uly, lrx, lry, savemode, options);

int unitmap; // Unit selection bit map (1 for single unit)
char *pathname; // File path name to load from, or save to
pxbuffer_t framebuf; // Image frame buffer
pxcoord_t ulx; // Upper left x coord. of area of interest
pxcoord_t uly; // Upper left y coord. of area of interest
pxcoord_t lrx; // Lower right x coord. exclusive of AOI
pxcoord_t lry; // Lower right y coord. exclusive of AOI
int loadmode; // Resize: ‘b’: Bilinear, ‘n’: Nearest neighbor
// Color Space: 0x100: convert, 0: no convert
int savemode; // Reserved, should be 0
int options; // Reserved, should be 0

Description
An image frame buffer, or an area of interest within the frame buffer, is loaded from, or saved to, a file named pathname. For pxd_loadBmp and pxd_saveBmp the image is loaded or saved in the Windows Device Independent Bitmap File format (i.e. ‘’.bmp’’ or ‘’.dib’’). For pxd_loadTiff and pxd_saveTiff the image is loaded or saved in the Tagged Image File Format (TIFF) (i.e. ‘’.tif’’). For pxd_savePcx the image is saved in the ‘‘PCX’’ graphics file format (i.e. ‘’.pcx’’). For pxd_saveTga the image is saved in the ‘‘Targa’’ file format (i.e. ‘’.tga’’).

The pathname may include a directory path,[21] and may include a file suffix (i.e. ‘’.bmp’’, ‘’.tif’’, ‘’.tga’’, or ‘’.pcx’’) as desired; these functions do not require, or force, any specific suffix. For pxd_saveBmp, pxd_saveTiff, pxd_savePcx, and pxd_saveTga any existing file pathname is deleted and a new file pathname created.

The unitmap specifies the single unit from which, or to which, an image is to be saved or loaded. The framebuf, 1<=framebuf<=pxd_imageZdim(), specifies the image frame buffer from which, or to which, an image is to be saved or loaded. The ulx and uly respectively specify the horizontal (x) and vertical coordinate (y) of the upper left corner of an area of interest in the frame buffer to be used. The lrx and lry respectively specify a value one greater than the horizontal and vertical coordinate of the lower right corner of an area of interest in the frame buffer (i.e. an exclusive corner point) to be used. The lrx and lry may each be abbreviated to -1 to specify the extreme right horizontal coordinate (i.e. pxd_imageXdim) or the extreme bottom vertical coordinate (i.e. pxd_imageYdim), respectively.

For pxd_loadBmp and pxd_loadTiff: The loaded image is resized, as necessary, to fit the specified frame buffer and area of interest. If loadmode=‘b’, the image is resized by bilinear interpolation, which is suitable for continuous tone images. If loadmode=‘n’, the image is resized by nearest neighbor interpolation, which is particularly suitable for graphics or false colored images, as it avoids the generation of pixel values, via interpolation, which may not exist in the original image. If loadmode&0x100 the loaded image’s pixels are converted to the specified frame buffer’s color space. Pixel values are converted to be most significant bit aligned; for example a TIFF file with 1 bit per pixel loaded into an image frame buffer with 8 bit pixels yields pixel values of 0 and 128.

For pxd_saveBmp: Color image frame buffers are saved as 24 bit RGB pixels without compression. Monochrome image frame buffers are saved as 8 bit pixels with a monochrome, identity, palette without compression.

For pxd_saveTiff: Color image frame buffers are saved as RGB pixels without compression, monochrome image frame buffers are saved as monochrome pixels without compression, each with the same number of bits per pixel as the image frame buffer.

For pxd_savePcx: Color image frame buffers are saved as 24 bit RGB pixels. Monochrome image frame buffers are saved as 8 bit pixels without a palette.

For pxd_saveTga: Color image frame buffers are saved as 24 bit RGB pixels. Monochrome image frame buffers are saved as 8 bit pixels without a palette.

Returns

0
Image frame buffer loaded or saved.

PXERNOTOPEN
The library is not open for use.

PXERROR
Invalid parameters.

PXERNOMODE
File format uses unsupported options (for pxd_loadBmp and pxd_loadTiff).

PXERFILEFORM
Invalid file format (for pxd_loadBmp and pxd_loadTiff).

PXERMALLOC
Memory allocation error.

PXERDOSIO
Operating system I/O error while reading or writing file.

PXERNEWFILE
Can’t create pathname (for pxd_saveBmp, pxd_saveTiff, pxd_savePcx, and pxd_saveTga).

PXERNOFILE
Can’t find pathname (for pxd_loadBmp and pxd_loadTiff).

PXER…
Other error codes as appropriate.[/code]

Unless I’m not catching the errors correctly, the thing that has me really confused is that I’d expect to get some kind of complaint back from the DLL if I’m sending it the wrong data type. Instead it’s failing silently.

-perry

I’m still not clear on whether it needs to be a WString or CString in the instances where I’m passing strings, but I did go through and change all the Int64s to plain Integers and the Wstrings to Cstrings, and lo and behold, there’s a tif file when I run this! So it’s working at least. Thanks for the suggestions!

I’ll play with this some more tomorrow to see if WString breaks it, but for now at least, this is a major step.

Thanks!

[quote=175980:@Perry Paolantonio]I’m still not clear on whether it needs to be a WString or CString in the instances where I’m passing strings, but I did go through and change all the Int64s to plain Integers and the Wstrings to Cstrings, and lo and behold, there’s a tif file when I run this! So it’s working at least. Thanks for the suggestions!

I’ll play with this some more tomorrow to see if WString breaks it, but for now at least, this is a major step.

Thanks![/quote]

Could you post the VB declare ? We maybe able to clarify the data type equivalents in Xojo.

Val("&H" + “0x1”) returns 529. I’d try replacing that with plain old 1 & see what happens.

Sorry - didn’t have a chance to look at any of this yesterday. So to answer questions:

I can’t find one that calls the same function in the VB examples, unfortunately.

This is what I’m doing and it’s working. I was trying to use 0x1 as the value passed to the function because that’s what all the examples show. but passing it 1 works. Since there’s only one unit (camera) in our setup, this is fine.

I am now dealing with something else that I’m not really understanding. I need to display the frame in an ImageWell in my application, after it runs doSnap. There is a function for this, which Epix recommended I call. It looks very much like pxd_saveTiff() documented above, and you pass it almost all the same parameters. However, instead of writing it to disk, it puts the image in memory for your application to access.

So the function is: pxd_readushort()

[code]int pxd_readushort (unitmap, framebuf, ulx, uly, lrx, lry, membuf, cnt, colorspace);

int unitmap; // Unit selection bit map (1 for single unit)
pxbuffer_t framebuf; // Image frame buffer
pxcoord_t ulx; // Upper left x coord. of area of interest
pxcoord_t uly; // Upper left y coord. of area of interest
pxcoord_t lrx; // Lower right x coord. exclusive of AOI
pxcoord_t lry; // Lower right y coord. exclusive of AOI
ushort membuf; // Program’s buffer to receive (readushort) or
// originate (writeushort) unsigned short values
size_t cnt; // Size of program’s buffer referenced by membuf
// in unsigned char’s or unsigned short’s
char *colorspace; // Name of requested color representation

Pixel values from an image frame buffer and area of interest are copied into, or copied from, an array or other memory buffer provided by the application program.

[…]

The membuf specifies a program provided array or buffer which pixel values are to be copied into, or copied from. The cnt specifies the number of unsigned chars (for pxd_readuchar and pxd_writeuchar) or unsigned shorts (for pxd_readushort and pxd_writeushort), in the array or buffer referenced by membuf. The membuf and cnt must be large enough to contain all of the pixel values implied by ulx, uly, lrx, lry, and colorspace; the cnt is not intended to be used to limit the number of pixel values transferred, but is rather intended to assert the maximum allowable transfer size and induce an error if the membuf and cnt is too small.

[…]

Each pixel value component is copied from, or copied into, a single ‘‘uchar’’ or ‘‘ushort’’; pixel values are not ‘‘bit-packed’’. If the data type has fewer bits that the pixel value component, the most significant bits of the pixel value component are copied into the data type, or are copied from the data type with lower order bits set to 0; e.g. a 10 bit per pixel component value of 1023 is stored in an 8 bit ‘‘uchar’’ as value 255. If the data type has more bits than the pixel value component, the pixel value component is copied into, or copied from, the data type’s least significant bits; e.g. a 10 bit per pixel component value of 1023 is stored in an 16 bit ‘‘ushort’’ as value 1023.

Pixels are read or written, from or to the image, to or from membuf, in the order left-to-right and top-to-bottom. For color pixels with multiple components, the component values are read or written in packed, not planer, order.
[/code]

And this is where I’m getting a bit lost. It seems that the membuf parameter is what holds the image data - How do I access that data from RB?

Dangerous.

But membuf will be a memory block which you initialise before calling the routine.
Passing the length of the memory block in bytes into the cnt parameter.

Assuming you select a colorspace that represents the colours in a single byte (Not sure how), then you access the results pixel by pixel by using
memoryblock.byte(somevalue)

Probably a bad plan… better to save the image to special folder.temporary and operate on it from there?

0x is C-style notation whereas &h is Xojo-style, but you’re using both.

i.e.

Val("&h" + "1")

Not

Val("&h" + "0x1")

Hi Jeff,

Dangerous how?

On the color space, I didn’t include the list, but you pass it a name for the desired colorspace. In this case, I’m using RGB, so I’m pass it a CString: “RGB”

Here’s my code, added to what I already have above:

[code]Declare function pxd_readushort lib “XCLIBWNT.dll” (unitmap as Integer, framebuf as Integer, ulx as Integer, uly as Integer, lrx as Integer, lry as Integer, mb as ptr , cnt as Int32, colorspace as CString) as Integer
Declare function pxd_imageXdim lib “XCLIBWNT.dll” () as Integer
Declare function pxd_imageYdim lib “XCLIBWNT.dll” () as Integer

dim xDim as Integer = pxd_imageXdim()
dim yDim as Integer = pxd_imageYdim()
dim frameBufferSize as integer = pxd_imageXdim * pxd_imageYdim

Dim mb as new MemoryBlock(frameBufferSize)

//just test that we’re getting the right size
//for a 4608x3288 image this should be 15151104
//MsgBox( str(frameBufferSize) )

dim result as integer

Try
mb.Byte(0) = 0
result = pxd_readushort( 1, 1, 0, 0, -1, -1, mb, frameBufferSize, “RGB”)
Catch e as RunTimeException
MsgBox(e.Message )
End Try
[/code]

So this compiles and it doesn’t fail, but I don’t really get how to get this data into the ImageWell. I’m trying this:

  // display the image
  ImageWell1.Image = Picture.FromData(mb)

Nothing happens though - not getting any exceptions or errors.

This seems like a ton of overhead. While the frame will be written to disc, so that I can manipulate it using ImageMagick, we also need to display it in the UI, along with some metadata (like the frame number). The first iteration of the scanner will be working with 4.5k files (4608x3288 pixels), but subsequent versions will have a different camera that can capture higher resolutions - potentially as high as 8k.

It seems to me that grabbing it from memory while it’s already there, and dropping it into the Imagewell makes a lot more sense than having to read in the file that was just written to disc. There are also situations where we’ll need to display a frame in the UI, without writing the file, such as when shuttling the scanner and grabbing every x frames for display.

[quote=176430:@Andrew Lambert]0x is C-style notation whereas &h is Xojo-style, but you’re using both.
[/quote]

Ahh - that makes sense. Thanks!

I would agree… the potential problem being that what this DLL puts into a buffer may very well not be what Xojo considers to be a picture object that can be assigned in this way. (based on a brief reading of the docs you have provided)
Saving to disc and opening as a picture: that will work.
But using the raw data from the dll… what exactly have they put into that buffer?
A tiff file? BMP? RLE?
one byte per pixel with a color table?
3 bytes per pixel?
4?

(Dangerous if the buffer was overrun)

Ok, I think there is a way to do this, but I’m really having a hard time understanding how the DLL and RB interact, and how to get this data into my application. There’s a function in the XCLIB library that creates a “Windows Device Independent Bitmap.” Documentation is below.

If someone could walk me through the basic procedure for getting this information to display in an ImageWell (or whatever other object makes sense), I’d appreciate it. Thanks!

[code]HGLOBAL pxd_renderDIBCreate(unitmap, framebuf, ulx, uly, lrx, lry, mode, options);
int pxd_renderDIBFree(hDIB);

int unitmap; // Unit selection bit map (1 for single unit)
pxbuffer_t framebuf; // Image frame buffer
pxcoord_t ulx; // Upper left x coord. of area of interest
pxcoord_t uly; // Upper left y coord. of area of interest
pxcoord_t lrx; // Lower right x coord. exclusive of AOI
pxcoord_t lry; // Lower right y coord. exclusive of AOI
int mode; // Reserved, should be 0
int options; // Reserved, should be 0

HGLOBAL hDIB; // Previously created bitmap

Description
An image frame buffer, or an area of interest within the image frame buffer, is copied to a newly created Windows Device Independent Bitmap (DIB) in global memory (e.g. using the Windows’ GlobalAlloc) and the handle to the DIB returned by pxd_renderDIBCreate. The bitmap created must be freed by pxd_renderDIBFree when no longer needed.

The unitmap specifies the single unit from which the image frame buffer is to be copied. The framebuf, 1<=framebuf<=pxd_imageZdim(), specifies the image frame buffer to be copied. The ulx and uly respectively specify the horizontal (x) and vertical coordinate (y) of the upper left corner of an area of interest in the frame buffer to be used. The lrx and lry respectively specify a value one greater than the horizontal and vertical coordinate of the lower right corner of an area of interest in the frame buffer (i.e. an exclusive corner point) to be used. The lrx and lry may each be abbreviated to -1 to specify the extreme right horizontal coordinate (i.e. pxd_imageXdim) or the extreme bottom vertical coordinate (i.e. pxd_imageYdim), respectively.

Color image frame buffers are copied as 24 bit RGB pixels without compression. Monochrome image frame buffers are copied as 8 bit pixels with a monochrome, identity, palette without compression.

The number of pixels per line, i.e. lrx-ulx, should be a multiple of four; as some Windows drivers do not properly support Device Independent Bitmaps which violate this condition.

Returns

The pxd_renderDIBCreate returns a handle to GlobalAlloc memory, or 0 if the library is not open for use, or a parameter is invalid.

The pxd_renderDIBFree returns 0 if successful or a PXER… code as appropriate.

[/code]