1
$\begingroup$

I am trying to use NETLink to control a camera exposure in our experiment. I have successfully connected to the camera and taken images. However, downloading it off the camera is trickier. The code that works in C# looks like this.

//Expose for image and wait for triggering
ApogeeCamera.Expose(0.1, true);
do { } while (ApogeeCamera.ImagingStatus != Apn_Status.Apn_Status_ImageReady);
//Download image from camera
ushort[] pixels = new ushort[width * height];
unsafe
{
    fixed (ushort* ptr = pixels)
    {
        int ptrValue = (int)ptr;
        ApogeeCamera.GetImage(ptrValue);
    }
}

The signature for the GetImage process is

4.7 GetImage
4.7.1 Format:
GetImage(long pImageBuffer)
4.7.2 Parameters:
pImageBuffer: Returns a pointer to 16 bit, unsigned short data located in memory. The image
data region should be allocated by the application prior to calling this method.
4.7.3 Description:
The GetImage method returns a pointer to a previously-allocated region of memory (allocated by
the calling application) that will be filled with image data.
The application must take care to assure that it properly allocates the image memory region
before calling this method. 

My understanding is that I have to pass the pointer that points to a preallocated array of the right size to GetImage. My attempt in Mathematica looks like this

camera@Expose[0.1, True];
While[camera@ImagingStatus@ToString[] != "Apn_Status_ImageReady", 
  Null];
buf = Table[0, {imgHeight*imgWidth}];
camera@GetImage[buf]
NET::methodargs: Improper arguments supplied for method named GetImage.
buf = 2;
camera@GetImage[buf]
NET::netexcptn: A .NET exception occurred: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
   at NETApogee.Camera2Class.GetImage(Int32 pImageBuffer).

Neither passing in an integer nor an array works. I feel like I need to use a pointer. I can't use DefineDLLFunction either, as the method belongs to a class that is in an assembly. I don't know how to use DefineDLLFunction in this situation anyway.

My sleep deprived self greatly appreciate any help.


Update - working version

@WReach, thank you for your help. Here's the working version of Mathematica/NETLink code, based off of WReach's

camera@Expose[0.1, True];
While[camera@ImagingStatus@ToString[] != "Apn_Status_ImageReady", 
  Null];
LoadNETType["System.Runtime.InteropServices.GCHandle"];
LoadNETType["System.Runtime.InteropServices.GCHandleType"];
buffer = NETNew["System.UInt16[]", imgHeight*imgWidth];
hBuffer = GCHandle`Alloc[buffer, GCHandleType`Pinned];
pBuffer = hBuffer@AddrOfPinnedObject[]@ToInt32[];
camera@GetImage[pBuffer];
hBuffer@Free[];
pixels = buffer // NETObjectToExpression;
Dimensions[pixels]
(* {1048576} *)
shapedPixels = ArrayReshape[pixels, {imgHeight, imgWidth}];
Image[shapedPixels, "Bit16"]

And you are rewarded with a grainy picture of my laser probe beam. Probe beam picture

Thank you WReach. I would have never figured this one out on my own.


Update 10/5/15

@WReach, your answer makes a lot of sense. Your example works perfectly fine on my copy of Mathematica. However, I can't seem to get it to work with GetImage Method. Using NETTypeInfo, the signature of GetImage is this

virtual "void" GetImage "(int pImageBuffer)"

It seems that Mathematica/NETLink convert from the parameter type from long to int. I have tried the both of the following codes.

buf = NETNew["System.Int32[]", imgHeight*imgWidth];
camera@GetImage[buf@Address[buf]]

buf = NETNew["System.UInt16[]", imgHeight*imgWidth];
camera@GetImage[buf@Address[buf]]

both gave the same error of "Improper arguments supplied for method named GetImage."

I suspect this is because of some funny conversion that NETLink does to import the class into Mathematica. I can't seem to figure it out though. I have noticed that System.Int32 has this method

"int*" Address "(int noname)"

Could we maybe try to get this to work somehow? I have tried without success.

$\endgroup$

1 Answer 1

2
$\begingroup$

Updated Response - Accessing Unmanaged Memory

The signature of GetImage as returned by NETTypeInfo tells us that the function wants a pointer to the image buffer as a simple integer. We will be accessing this method through the .NET managed memory environment. Therefore, we must pin any allocated managed memory before passing it to the function.

What follows is a self-contained example that uses the Win32 API function GetUserName to simulate the unmanaged memory operations required by GetImage.

CAUTION Working with unmanaged memory is dangerous. The slightest error in array sizes or pointer arithmetic can cause the kernel and/or the NETLink connection to fail catastrophically requiring a restart. Save early and often!!

We start with a bit of preamble:

Needs["NETLink`"]
InstallNET[];

LoadNETType["System.Runtime.InteropServices.GCHandle"];
LoadNETType["System.Runtime.InteropServices.GCHandleType"];

We need to import GetUserName into the Mathematica environment. For the purpose of illustration, we will be lying about the signature of the function by saying that it takes int parameters instead of pointers:

getUserName =
  DefineDLLFunction["GetUserNameW", "advapi32.dll", "BOOL", {"int", "int"}];

The function actually takes pointers to two arrays - a character buffer and an in/out size parameter:

maxSize = 128;
buffer = NETNew["System.Char[]", maxSize];
size = MakeNETObject[{maxSize}, "System.UInt32[]"];

Here is the key part... we must pin those objects in memory so that they have a fixed memory location:

hBuffer = GCHandle`Alloc[buffer, GCHandleType`Pinned];
hSize = GCHandle`Alloc[size, GCHandleType`Pinned];

pBuffer = hBuffer @ AddrOfPinnedObject[] @ ToInt32[];
pSize = hSize @ AddrOfPinnedObject[] @ ToInt32[];

Now that we have our pointers masquerading as ints, we can use them:

getUserName[pBuffer, pSize]
(* 1 *)    (* i.e. "success" *)

The pinned memory should be unpinned as soon as the pointers are no longer needed:

hBuffer @ Free[]
hSize @ Free[]

We can extract the final results using the usual NETLink methods:

size @ GetValue[0]
(* 7 *)      (* 1 extra character for the terminating zero byte *)

buffer // NETObjectToExpression // Take[#, 6]& // FromCharacterCode
(* "wreach" *)

Original Response - Accessing Managed Memory

An appropriate array can be created using NETNew:

NETNew["System.UInt16[]", imgHeight * imgWidth]

Here is a self-contained example that uses List.CopyTo to simulate the array-overwriting action of Camera.GetImage:

Needs["NETLink`"]
InstallNET[];

list = NETNew["System.Collections.Generic.List`1[System.UInt16]"];
list @ Add[1];
list @ Add[10];
list @ Add[100];

array = NETNew["System.UInt16[]", list @ Count];

array // NETObjectToExpression
(* {0, 0, 0} *)

list @ CopyTo[array]

array // NETObjectToExpression
(* {1, 10, 100} *)
$\endgroup$
6
  • $\begingroup$ I don't have access to my work computer till Monday. I'll try it out and let you know. Thank you for the help. $\endgroup$ Commented Oct 3, 2015 at 14:28
  • $\begingroup$ I have tried your suggestion. Updated the original post with some outputs. It didn't work. $\endgroup$ Commented Oct 5, 2015 at 19:46
  • $\begingroup$ I completely missed the unsafe/fixed combination the first time around. I have updated the response to deal with unmanaged memory. $\endgroup$
    – WReach
    Commented Oct 5, 2015 at 22:02
  • $\begingroup$ @WReach I am trying to add a copy after the active sheet. I can add it before as follows: excelApplication@ActiveSheet@Copy[excelApplication@ActiveSheet] but I need to add it after which is the second parameter. How do I pass the active sheet as the second parameter? $\endgroup$
    – user13892
    Commented Jun 13, 2022 at 5:28
  • 1
    $\begingroup$ @user13892 Not really related to the present post, but... you must LoadNETType["System.Type"] and then pass System`Type`Missing for omitted leading parameters. For example: xl@ActiveSheet@Copy[System`Type`Missing, xl@ActiveSheet]. $\endgroup$
    – WReach
    Commented Jun 14, 2022 at 23:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.