Link to home
Start Free TrialLog in
Avatar of sapbucket
sapbucket

asked on

PERL: Don't understand the "Variant" type returned by COM objects

Hello,

  VB6 has great built in support for the Variant type, for example:

  Private Sub cmdStart_Click()
    Dim ImageData As Variant
    Dim x As Integer, y As Integer
    ICImagingControl1.MemorySnapImage
    ImageData = ICImagingControl1.MemoryGetImageData
    For y = 0 To ICImagingControl1.ImageHeight - 1
        For x = 0 To ICImagingControl1.ImageWidth - 1
            ImageData(x, y) = 255 - ImageData(x, y)  '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        Next x
    Next y
    ICImagingControl1.MemoryReleaseImageData ImageData
    ICImagingControl1.Display
End Sub



I would like to perform the same thing as above (esp. '!!!!!!!!!!!!!!!!!!!!!!!!) but using PERL.

I have a testscript:

#!/usr/bin/perl -w
#use strict;

use Win32::GUI;
use Win32::GUI::DIBitmap;
use Win32::OLE::Variant;

use Win32::OLE;
$imaging = Win32::OLE->new('IC.ICImagingControl') or die "oops\n";

#--------------------------------
# Use the Dialog to get settings:
#       If we want to change settings we need to use the dialog and and save
#       the state so we can load from file.
#$imaging->ShowDeviceSettingsDialog();
#$imaging->SaveDeviceStateToFile("c:\\ocr\\device_state.xml");
#--------------------------------

$imaging->LoadDeviceStateFromFile("c:\\ocr\\device_state.xml",1);
$imaging->MemorySnapImage();


$dib=newFromFile Win32::GUI::DIBitmap ("c:\\ocr\\test.bmp");

print "dib is: $dib\n";
print "width / height \n";
print $dib->GetWidth(); print ", ";
print $dib->GetHeight(); print "\n";

$imagedata=$imaging->MemoryGetImageData(); # so, $imagedata is of type VARIANT because MemoryGetImageData() returns VARIANT type
#my $var = Variant(VT_DATE, $imagedata);

#---------------------
# And here we can process the image:
#       This first method doesn't work yet. I can't figure out how to get X,Y
#      
for($h=0;$h<$imaging->ImageHeight();$h++) {
    for($w=0;$w<$imaging->ImageWidth();$w++) {
        $imagedata->($w,$h) = 255 - $imagedata->($w,$h); # ???????????????????????????????? DONT UNDERSTAND HOW TO USE VARIANT!!!!
        #$imagedata->y($w) = 255 - $imagedata->y($w);
    }
}
#---------------------

$imaging->MemorySaveImage("c:\\ocr\\test.bmp");
$imaging->MemoryReleaseImageData();




So, compare the lines (??????????????) and ('!!!!!!!!!!!!!!!!!!!!!!!!!!). I don't understand how to handle the variant in the testscript (whereas I DO know how to handle it in the VB6 code because of the built in type support for VARIANT).


Can someone please show me the way?

I have been looking at use Win32::OLE::Variant; but I'm not sure how to use. All of the examples are of creating a NEW Variant, whereas I already have an existing variant to mess around with. =confusing to me

Thanks for the help!!

Avatar of clockwatcher
clockwatcher

Checking the documentation on the IC.ICImagingControl, the MemoryGetImageData() method returns a SAFEARRAY.  You should be able to access values within a VT_ARRAY with the Get and Put methods of the Variant class.

   $imagedata->Put($w, $h, 255 - $imagedata->Get($w, $h));




 

Avatar of sapbucket

ASKER

clockwatcher,
I get this error message when i try the code example u give

Can't call method "Get" on unblessed reference at testActiveX.pl line 39.

easy fix?
ok, so I got a reply from the manufacture:
The VARIANT is a multi purpose data structure.

The declarations are as follows:

typedef struct tagSAFEARRAYBOUND {
   unsigned long cElements;
   long lLbound;
} SAFEARRAYBOUND;


typedef struct tagSAFEARRAY
    {
    USHORT cDims;
    USHORT fFeatures;
    USHORT cbElements;
    USHORT cLocks;
    USHORT handle;
    PVOID pvData;
    SAFEARRAYBOUND rgsabound[1];
    }  SAFEARRAY;


typedef struct tagVARIANT  {
   WORD vt;
   unsigned short wReserved1;
   unsigned short wReserved2;
   unsigned short wReserved3;
   SAFEARRAY      FAR* parray;        
} VARIANT;

This C functions shows how to access the image data:

//////////////////////////////////////////////////////////////////////////////
/*! This functions gets the variant data structure from a Visual Basic application.
    In  the variant structure an imagebuffer is saved.
*/
DWORD WINAPI ProcessImage( long iImageWidth, long iImageHeight, long iBitsPerPixel, VARIANT
*pImageStruct)
{
    DWORD dwResult = 0;
    BYTE *pImageBytes = (BYTE*)pImageStruct->parray->pvData; // Cast directly to pointer, that
                                                             // points to the image data to get
                                                             // an easier access to the bytes of
                                                             // of the image.


    ChangeFast(  iImageWidth,  iImageHeight,  iBitsPerPixel, pImageBytes);
    //AccessSinglePixels(  iImageWidth,  iImageHeight,  iBitsPerPixel, pImageBytes);
    //ChangeAllWithIndex(  iImageWidth,  iImageHeight,  iBitsPerPixel, pImageBytes);

    return dwResult;
}



And I haven't programmed in C since college. What is this mess? I thought a struct was all a programmer needed for complex data in C? Now they have more craziness?

What I would really like to get is the pointer to the image data. If I could "get my hands" on that from a PERL script I would call this problem solved!



Still not sure how to access data within a Variant from PERL.....
Here is my latest attempt:

#!/usr/bin/perl -w
#use strict;

use Win32::GUI;
use Win32::GUI::DIBitmap;
use Win32::OLE::Variant;

use Win32::OLE;
$imaging = Win32::OLE->new('IC.ICImagingControl') or die "oops\n";

#--------------------------------
# Use the Dialog to get settings:
#       If we want to change settings we need to use the dialog and and save
#       the state so we can load from file.
#$imaging->ShowDeviceSettingsDialog();
#$imaging->SaveDeviceStateToFile("c:\\ocr\\device_state.xml");
#--------------------------------

$imaging->LoadDeviceStateFromFile("c:\\ocr\\device_state.xml",1);
$imaging->MemorySnapImage();

#---------------------------------
# Use a DIBsect instread of the image pointer in the VARIANT (crappy patch...)
#$dib=newFromFile Win32::GUI::DIBitmap ("c:\\ocr\\test.bmp");
#print "dib is: $dib\n";
#print "width / height \n";
#print $dib->GetWidth(); print ", ";
#print $dib->GetHeight(); print "\n";
#---------------------------------


#---------------------------------
# Use the Variant returned by MemoryGetImageData to gain direct acces to image:
#       Unfortunately, PERL doesn't have built-in Variant handling?
#$imagedata=$imaging->MemoryGetImageData();
my $Array = Win32::OLE::Variant->new(VT_ARRAY|VT_UI1, $imaging->MemoryGetImageData());    # ^^^^^^^^^^^

for($h=0;$h<$imaging->ImageHeight();$h++) {
    for($w=0;$w<$imaging->ImageWidth();$w++) {
        $i=$h*$imaging->ImageWidth()+$w; # tried with Put($w,$h,DATA) = error.
        $Array->Put($i, 255 - $Array->Get($i)); #@@@@@@@@@@@@@@
    }
}
#----------------------------------

$imaging->MemorySaveImage("c:\\ocr\\test.bmp");
$imaging->MemoryReleaseImageData(); # used to free up memory





And you can see that I am trying to use Win32::OLE::Variant. The documentation (http://search.cpan.org/~jdb/Win32-OLE-0.17/lib/Win32/OLE/Variant.pm) demonstrates different ways of creating a NEW variant (my own, from scratch) but now how to make a NEW variant using existing Variant from function return.

Note that I HAVE to instanstiate a variant using Win32::OLE::Variant->new(), otherwise, if I try to use the variant returned by the COM object ($imaging->MemoryGetImageData()) I get this error:

>>>   Can't call method "Get" on unblessed reference at testActiveX.pl line 39.



However, since I HAVE been instancing a Win32::OLE::Variant type, using the code from line (#^^^^^^^^^^^^), I get a new set of errors:

# using ($w,$h) at (@@@@@@@@@@@) gives:

Win32::OLE(0.1701): Win32::OLE::Variant->Get(): Wrong number of indices;  dimension of SafeArray is 1 at testActive
X.pl line 40.
Use of uninitialized value in subtraction (-) at testActiveX.pl line 40.
Win32::OLE(0.1701): Win32::OLE::Variant->Put(): Wrong number of indices;  dimension of SafeArray is 1 at testActive
X.pl line 40.


# using ($i) at (@@@@@@@@@@@) gives:

Use of uninitialized value in subtraction (-) at testActiveX.pl line 41.
Win32::OLE(0.1701) error 0x8002000b: "Invalid index" at testActiveX.pl line 41
        eval {...} called at testActiveX.pl line 41
Win32::OLE(0.1701) error 0x8002000b: "Invalid index" at testActiveX.pl line 41
        eval {...} called at testActiveX.pl line 41



Which basically indicates that I do not know how to access the Variant.

still lost in this problem...

Thanks for any help!!!




Youre trying to pull a manure spreader with a Cadillac.  

Or in E! terms, you're trying to get Deline Dion and Eminem to sing a duet.

Even if you can get the combinations to run, the results are not going to be pretty.

Perl was not designed with OLE objects in mind.  

OLE was not designed with much of anything in mind.

They share almost no commonality in data structures, calling sequences, basic datatypes, or anything else.

Now in C, Microsoft provides a horrendous slew of macros, inlines, imracks, geegaws, binders, linkers, marshallers, macros, macro-macros, resources, ad-intelligibium.  So in the end it's possible, oh just barely, to talk to OLE.

In Perl, it's harder.

In fact, even if you do get this cross-system calling and data passing to work, Perl was never designed for graphics manuipulation, so your explicit 255 - loop is going to run MIGHTY SLOW.   Perl is really whizzy on text patterns, mighty slow on graphics.

I suggest you rething your idea, so it uses more compatible pieces.  

I really am beginning to dislike OLE. and Microsoft.

Couldn't they be more reasonable?
BTW, which language IS best for explicit graphics processing?
I'd use some compiled language, for the speed.  It would also be nice if the language had a OLE interface built-in.  

That is the trick isn't it. It would seem that OLE is synonymous with $$$. Damn that paradigm.

I downloaded Ecplise IDE, which is Java. I'm not sure what support Java offers for OLE, but they must have better resources then PERL. I'm all about solutions, so if I find a JAVA one I will post the results here.

BTW, anyone else trying to complete a OLE project (that is, you were handed a third-party ActiveX control and now you need to build an app with it), DO NOT USE PERL!!!! This is for a very un-obvious reason. PERL does not have built-in support for the VARIANT data type. OLE objects sometimes return VARIANTs when you call a function or method. PERL just doesn't know how to handle them. If someone tries to tell you that PERL can't handle COM objects - that isn't true - it handles COM objects beautifully, that is, until you find yourself trying to comprehend a VARIANT. Things like manipulating WORD and EXCEL docs works very well from PERL. But wait until you need to get an array reference from a Variant....

grg99, thank you for the discussion. :)

I'm going to leave this open for a little while to see if anyone can clear up this Variant mess.

sapbucket



ASKER CERTIFIED SOLUTION
Avatar of clockwatcher
clockwatcher

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Sorry that inner loop should have looked like:

   for(my $w=0;$w < $width; $w++) {
        my $data = $imagedata->Get($w, $h);
        print "($w, $h) = $data\n";
        $imagedata->Put($w, $h, 255 - $data);
     }
I found a machine that has a WDM device on it.  The following works (kinda).  Win32::OLE marshalls in the the safearray as a reference to a perl array.  So you can deal with it as a perl array or you can override the autoconvert and deal with it as a variant (if for some odd reason you wanted to).  Here's a sample that shows both:

use strict;

use Win32::OLE;
use Win32::OLE::Variant;

my $imaging = Win32::OLE->new('IC.ICImagingControl') or die "oops\n";
$imaging->ShowDeviceSettingsDialog();
$imaging->MemorySnapImage();

my $height = $imaging->{ImageHeight};
my $width = $imaging->{ImageWidth};

print "imaging: $imaging\n";
print "height: $height\n";
print "width: $width\n";

my $data;
my $imagedata;
my $AS_VARIANT = 0;

if ($AS_VARIANT)  
{
     # deal with it as a variant
     $imagedata = Variant(VT_EMPTY, undef);
     $imaging->Dispatch('MemoryGetImageData', $imagedata);

     for(my $h=0; $h < $height; $h++) {
          for(my $w=0;$w < $width;$ w++) {
               my $data = 255 - $imagedata->Get($w, $h);
               print "($w, $h) = $data\n";
               $imagedata->Put($w, $h, $data);
          }
     }
}

else
{
     # dealing with it marshalled as a perl array -- default way of dealing with it

     $imagedata = $imaging->MemoryGetImageData();

     for (my $h=0; $h < $height; $h++)
     {
          for (my $w=0; $w < $width; $w++)
          {
               $data = 255 - $$imagedata[$w][$h];
               print "($w, $h) = $data\n";
               $$imagedata[$w][$h] = $data;
          }
     }
}

$imaging->MemorySaveImage("c:\\vidcap\\test.bmp");
$imaging->MemoryReleaseImageData($imagedata);

# ===============================================

Now to the problem...  Just a guess, but it looks like Win32::OLE is making a copy of the variant and releasing the original variant reference returned to it.  That messes up a couple of related things:  the original variant reference returned is released without the call to MemoryReleaseImageData (which screws with your imaging control buffer and you end up with a black buffer on the save image) and you're actually working on a copy of the buffer rather than the buffer itself.

You get the same results as the perl code above with the following VB:

Sub testing()
   
    Dim ImageData As Variant
    Dim x As Integer, y As Integer
   
    Dim icimagingcontrol1 As New ICImagingControl.ICImagingControl
    icimagingcontrol1.ShowDeviceSettingsDialog
   
    icimagingcontrol1.MemorySnapImage
    ImageData = icimagingcontrol1.MemoryGetImageData
   
    datacopy = ImageData
   
    Debug.Print "height: " & UBound(datacopy, 2)
    Debug.Print "width: " & UBound(datacopy, 1)
   
    ImageData = ""   ' improperly release the reference to imagedata
   
    For y = 0 To icimagingcontrol1.ImageHeight - 1
        For x = 0 To icimagingcontrol1.ImageWidth - 1
            Debug.Print "(" & x & "," & y & ") = " & 255 - datacopy(x, y)
            datacopy(x, y) = 255 - datacopy(x, y)
        Next x
    Next y
   
    icimagingcontrol1.MemorySaveImage "c:\vidcap\mybmp.bmp"
    icimagingcontrol1.MemoryReleaseImageData datacopy

End Sub


If all you're concerned with is the image data then you can get to that.  But if you want to mess with the actual buffer and have it reflected in the control then it'll take some more digging.  I think you might be out of luck with perl.
Wow, alot to digest clockwatcher.

I am going to try this out:
if ($AS_VARIANT)  
{
     # deal with it as a variant
     $imagedata = Variant(VT_EMPTY, undef);   #### THIS IS WHERE I HAVE A PROBLEM ####
     $imaging->Dispatch('MemoryGetImageData', $imagedata);

     for(my $h=0; $h < $height; $h++) {
          for(my $w=0;$w < $width;$ w++) {
               my $data = 255 - $imagedata->Get($w, $h);
               print "($w, $h) = $data\n";
               $imagedata->Put($w, $h, $data);
          }
     }
}

else
{
     # dealing with it marshalled as a perl array -- default way of dealing with it

     $imagedata = $imaging->MemoryGetImageData();

     for (my $h=0; $h < $height; $h++)
     {
          for (my $w=0; $w < $width; $w++)
          {
               $data = 255 - $$imagedata[$w][$h];
               print "($w, $h) = $data\n";
               $$imagedata[$w][$h] = $data;
          }
     }
}


You can see where I have a problem.


I'll hack for a few hours and see if anything falls through.

Thanks
Here is the solution to my problems:

NOTE: PERL does not support a variant (at least not in a useful way). When purchasing an OLE component from a vendor make sure that they offer non-variant functional support so you are not forced into using a VB solution. THAT is what I did here with this solution: I got around the variant and instead accessed the memory directly.

#!/usr/bin/perl -w

#--------------------------------
# start of profiler:
# start section
use Time::HiRes qw( usleep ualarm gettimeofday tv_interval );
$t0 = [gettimeofday];
# end section
#--------------------------------


#--------------------------------
# instance an imaging control using Win32::OLE:
# start section:
use Win32::OLE;
$imaging = Win32::OLE->new('IC.ICImagingControl') or die "oops\n";
# end section
#--------------------------------


#--------------------------------
# Use the Dialog to get camera settings:
# start section
#       If we want to change settings we need to use the dialog and and save
#       the state so we can load from file.
#$imaging->ShowDeviceSettingsDialog();
#$imaging->SaveDeviceStateToFile("c:\\ocr\\device_state.xml");
# end section
#--------------------------------

#---------------------------------
# This section is used by all following sections.
# start section
$imaging->LoadDeviceStateFromFile("c:\\ocr\\device_state.xml",1);
$imaging->MemorySnapImage();
$imaging->MemorySaveImage("c:\\ocr\\test1.bmp");
$buffer = $imaging->ImageBuffers->Item(1);
# end section
#---------------------------------


#-------------------------------------------
# This section tries to use ImageDataPtr() to access image data.
# Hopefully it will not interfere with saving the image.
# start section
$t3 = [gettimeofday];
$ptrData=$buffer->ImageDataPtr();
print "ptrData prior to muck() is $ptrData\n";
$indexForImage = 640*480*3; # HARDCODED!!!! BEWARE CHANGES TO HARDWARE SETTINGS
for($i=0;$i<$indexForImage; $i++) {    
    muck($ptrData); # will print output from muck...
    $ptrData++;
}
$imaging->MemorySaveImage("c:\\ocr\\test2.bmp"); # nope! saves great!!!
($seconds, $microseconds) = gettimeofday;
$elapsed = tv_interval ( $t3, [$seconds, $microseconds]);
print "elapsed time is $elapsed s\n";
# end section
#-------------------------------------------

#-------------------------------------------
# second half of profiler:
# start section
print $imaging->ImageWidth();print ", ";
print $imaging->ImageHeight();print "\n";
($seconds, $microseconds) = gettimeofday;
$elapsed = tv_interval ( $t0, [$seconds, $microseconds]);
print "elapsed time is $elapsed s\n";
# end section
#-------------------------------------------


use Inline C;
__END__
__C__

void muck(long memory_address) {
    // convert using a type cast:
    unsigned char* Adr = (unsigned char*)memory_address;
    // perform some type of image processing:
    *Adr = 255 - (*Adr);
    // uncomment the following to see process from command line:
    // remember to use %ld, NOT %l!!!!
    //printf("Hello long: %ld!\n", memory_address);
    //printf("Hello address: %ld\n", Adr);
    //printf("Hello address contents: %ld\n", *Adr);

}
If there are no objections I am going to close this question.

clockwatcher deserves points for his hard work, unfortunately his solution did not work for me.

I will leave this open to catch any arguments before closing.
I suggest a refund of his/her points.
I gave the points to clockwatcher

thanks for the help

sorry someone else who expects a solution...