Sign in to follow this  
Followers 0
NinerSevenTango

Noob question - get handle passed by ref in dllcall SOLVED

14 posts in this topic

#1 ·  Posted (edited)

I've hit a brick wall trying to figure out what I'm doing wrong here.

I'm trying to use a dll. The dll talks to a labjack, which is a data acquisition device.

The 'open' call is supposed to return a handle to the device, to a variable passed by reference.

Here's one version of the code I tried:

AutoItSetOption("MustDeclareVars",1)
Dim $err1, $err2, $dllpath, $hLabJack, $hDLL
If @AutoItX64 Then
    MsgBox(0, "Error", "Can't run as x64 => 32bit dll")
    Exit
EndIf
If InitializeLabJack() > 0 Then
    MsgBox(0, "Error", "LabJack Initialize Failed"&@CRLF&"Check USB Connection")
    DllClose($hDLL)
    Exit
EndIf

Func InitializeLabJack()
$dllpath=@ScriptDir & "\LabJackUD.dll"
$hDLL=DllOpen($dllpath)
ConsoleWrite("$hDLL is  "& String($hDLL) & @CRLF)
if @error Then
    MsgBox(0,"","Error Opening DLL " & @error);
    Return(1)
endif
$err1 = DllCall($hDLL,"none","OpenLabJack","long",3,"long",1,"str","1","long",1,"HANDLE*",$hLabJack)
if @error Then
    ConsoleWrite("Error Opening LabJack " & @error & @CRLF)
    Return(2)
Else
    ConsoleWrite("err1 is " & String($err1[0])&@CRLF & _
    "$hLabJack is  " & String($hLabJack) &@CRLF & _
    $err1[1] & @CRLF & _
    $err1[2] & @CRLF & _
    $err1[3] & @CRLF & _
    $err1[4] & @CRLF & _
    $err1[5] & @CRLF )
    $Run = True
EndIf

$err2 = DllCall($hDLL,"double","GetDriverVersion")
ConsoleWrite("  Driver Version  " & String($err2[0]) & @CRLF)
EndFunc

DllClose($hDLL)
Exit

What it returns is:

$hDLL is 1

err1 is

$hLabJack is

3

1

1

1

0x04172408

Driver Version 3.15

As you can see from the array returned, the device is sending the handle back. The problem for me is I can't figure out how to properly get it assigned to the variable $hLabJack.

I've spent all day trying to figure this out.

Thanks in advance for anyone who can show me what I'm missing.

--97T--

Edited by NinerSevenTango

Share this post


Link to post
Share on other sites



There is nothing to figure out.

Nothing is assigned to $hLabJack. It doesn't work that way in AutoIt. Array is filled and you have read it correctly. You are after $err1[5].

Maybe even like this:

Func InitializeLabJack(ByRef $hLabJack)
    ;...
    Local $err1 = DllCall($hDLL, "none", "OpenLabJack", "long", 3, "long", 1, "str", "1", "long", 1, "HANDLE*", 0)
    ;...error checking here...
    $hLabJack = $err1[5]
    ;...
EndFunc

♡♡♡

.

eMyvnE

Share this post


Link to post
Share on other sites

#3 ·  Posted (edited)

There is nothing to figure out.

Nothing is assigned to $hLabJack. It doesn't work that way in AutoIt. Array is filled and you have read it correctly. You are after $err1[5].

Maybe even like this:

Func InitializeLabJack(ByRef $hLabJack)
    ;...
    Local $err1 = DllCall($hDLL, "none", "OpenLabJack", "long", 3, "long", 1, "str", "1", "long", 1, "HANDLE*", 0)
    ;...error checking here...
    $hLabJack = $err1[5]
    ;...
EndFunc

Thanks, Trancexx. I wasn't sure if that was legitimate. I'll give the ByRef a try.

I'll test. The documentation says the dll expects to return all requested data to pre-initialized arrays at the address pointed to by ref in the call. Some of these different calls are expected to be quite lengthy. Does AutoIt expect all data to be returned in the $err1 array, and can $err1 be pre-initialized or does AutoIt expect to dynamically size the array as the data comes in? (I thought I read that the assignment would re-initialize if it didn't specify the array elements.)

Thanks so much for your expertise. It's an uphill battle trying to learn so many little details.

--97T--

Edit: Didn't fully read the example code on the first pass.

Edit2: It appears from testing that the entire ByRef thing is unnecessary for this particular call. Now to test a call to the device ....

Edit3: It looks like the answers to my questions are in DllStruct ... testing ...

Edited by NinerSevenTango

Share this post


Link to post
Share on other sites

#4 ·  Posted (edited)

Never really got any farther.

I can't get the DllCall to the device (at the handle) to work at all.

I've read everything I could find on how DllCall works, there is precious little information out there.

I've found one page in the help file, and one write-up on how to use it, and a few posts here and there.

Are there any more resources I could use to find out what the command expects, what the syntax rules are, and how it works under the hood, etc.?

Here's the code, commented as I went:

AutoItSetOption("MustDeclareVars",1)
Dim $err1, $err2, $err3, $dllpath, $hLabJack, $hDLL
If @AutoItX64 Then
    MsgBox(0, "Error", "Can't run as x64 => 32bit dll")
    Exit
EndIf
If InitializeLabJack() > 0 Then
    MsgBox(0, "Error", "LabJack Initialize Failed"&@CRLF&"Check USB Connection")
    DllClose($hDLL)
    Exit
EndIf

GetSerialNumber($hLabJack)

Func InitializeLabJack()
$dllpath=@ScriptDir & "\LabJackUD.dll"
$hDLL=DllOpen($dllpath)
ConsoleWrite("$hDLL is  "& String($hDLL) & @CRLF)
if @error Then
    MsgBox(0,"","Error Opening DLL " & @error);
    Return(1)
endif
$err1 = DllCall($hDLL,"none","OpenLabJack","long",3,"long",1,"str","1","long",1,"HANDLE*",0)
if @error Then
    ConsoleWrite("Error Opening LabJack " & @error & @CRLF)
    Return(2)
ElseIf $err1[0] > 0 Then
    ConsoleWrite("Error Opening LabJack " & @error & @CRLF)
    Return(2)
Else
    $hLabJack = $err1[5]
    ConsoleWrite("err1 is " & String($err1[0])&@CRLF & _
    "$hLabJack is  " & String($hLabJack) &@CRLF & _
    $err1[1] & @CRLF & _
    $err1[2] & @CRLF & _
    $err1[3] & @CRLF & _
    $err1[4] & @CRLF & _
    $err1[5] & @CRLF )
EndIf

$err2 = DllCall($hDLL,"double","GetDriverVersion")
ConsoleWrite("  Driver Version  " & String($err2[0]) & @CRLF)
EndFunc
;=======================================
;EVERYTHING ABOVE WORKS
;=======================================

Func GetSerialNumber(ByRef $hLabJack)
;=====================================
;From LabJackUD.h; This is what the native code in the dll expects;
;
;LJ_ERROR _stdcall eGet(LJ_HANDLE Handle, long IOType, long Channel, double *pValue, long x1);
;=======================================
;                       ^^^^^^^^^^^^^^^^^^                             ^^^^^^^^^^^^^
;                      Is this what's getting me?                         Or this?
;
;=====================================
    ;THIS ROUTINE JUST CRASHES
;=====================================
;$err3 = DllCall($hDLL,"none","eGet","ptr*",$hLabJack,"long",1001,"long",12,"DWORD*",0,"long",0,"long",0)
;ConsoleWrite(@error)
;ConsoleWrite("err3 is " & String($err3[0])&@CRLF & _
;    $err3[1] & @CRLF & _
;    $err3[2] & @CRLF & _
;    $err3[3] & @CRLF & _
;    $err3[4] & @CRLF & _
;    $err3[5] & @CRLF )
;========================================
; Tried the above with every combination of * and no * and ByRef and not.  Crashes every time with exit code -1073741819
;=========================================================
;    OK, so I'll try to Create the EGet struct
;    struct {
;        ptr                $LabJack;
;        long            iotype;
;        long            Channel;
;        DWORD            Result;
;        long            x1;
;    }
;=========================================================
Global $EGetstr        = "ptr hLabJack;long iotype;long Channel;DWORD Result;long x1"
;The * character anywhere in there results in DLLStructCreate error "not a string"
Global $EGet = DllStructCreate($EGetstr)
if @error Then
    MsgBox(0,"","Error in DllStructCreate " & @error);
    exit
endif
; The struct gets created without throwing an error
;=========================================================
;    Set data in the struct
;=========================================================
DllStructSetData($EGet,"hLabJack",$hLabJack)
DllStructSetData($EGet,"iotype",1001)
DllStructSetData($EGet,"Channel",12)
DllStructSetData($EGet,"Result",0)
DllStructSetData($EGet,"x1",0)
;No errors setting data
DllCall($hDLL,"none","eGet","ptr",DllStructGetPtr($EGet))
;================================================================
; Never gets past DLLCall, crashes with exit code -1073741819
;Doesn't matter if ByRef used or not
;tried ptr, HANDLE, DWORD, long, etc in $EGetstr
;================================================================
ConsoleWrite(@error)
ConsoleWrite(DllStructGetData($EGet,"Result"))
EndFunc
DllClose($hDLL)
Exit

Any variation I've tried returns:

$hDLL is 1

err1 is

$hLabJack is 0x04152408

3

1

1

1

0x04152408

Driver Version 3.15

!>15:18:42 AutoIT3.exe ended.rc:-1073741819

>Exit code: -1073741819 Time: 4.430

Thanks for any help you can give me.

What am I doing wrong?

--97T--

Edited by NinerSevenTango

Share this post


Link to post
Share on other sites

$aCall = DllCall($hDLL, "none", "eGet", "handle", $hLabJack, "long", 1001, "long", 12, "double*", 0, "long", 0)
    $nVal = $aCall[4]

Just think. That's what's required of anyone using these functions.


♡♡♡

.

eMyvnE

Share this post


Link to post
Share on other sites

#6 ·  Posted (edited)

$aCall = DllCall($hDLL, "none", "eGet", "handle", $hLabJack, "long", 1001, "long", 12, "double*", 0, "long", 0)
    $nVal = $aCall[4]

Just think. That's what's required of anyone using these functions.

Aaaah! --->Slapping forehead<---

I was trying to think but I must have bent my brain instead. Thank you, trancexx, for pointing out the blindingly obvious. You are most generous, and I owe you a couple of very big favors now. Especially for your work on the winhttp.au3, which I used to finish another project some time back.

I hate to press my luck, but I have another question:

The call above works when the byref type "double*" is used, but it doesn't when the asterisk is not there (as would be expected from the c documentation).

How do I build that string to use in the dllstructcreate? The asterisk bombs it out with error 2 "not a string" or error 1 "unknown datatype" no matter which way I concatenate things to smuggle it in there. And now I've spent another half day looking for a clue. (Now you know why they call me clueless and why I have a flat forehead!)

Thanks again,

--97T--

Edit: No, it's something else wrong. Crashing every time I call DllCall with a struct embedded in the middle of it. Looking for dumb mistakes...

;This line works --

$err3 = DllCall($hDLL, "none", "eGet", "handle", $hLabJack, "long", 1001, "long", 12, "double*", 0, "long", 0)


;this snippet crashes dllcall --

$EGetstr= "long iotype;long Channel"
$EGet = DllStructCreate($EGetstr)
DllStructSetData($EGet,"iotype",1001)
DllStructSetData($EGet,"Channel",12)
$err3 = DllCall($hDLL, "none", "eGet", "handle", $hLabJack, "ptr", DllStructGetPtr($EGet), "double*", 0, "long", 0)

;but this one works --

$EGetstr2 = "long x1"
$EGet2 = DllStructCreate($EGetstr2)
DllStructSetData($EGet2,"x1",0)
$err3 = DllCall($hDLL, "none", "eGet", "handle", $hLabJack, "long", 1001, "long", 12, "double*", 0, "ptr", DllStructGetPtr($EGet2))

Not getting it ... for now I can hand-build these calls but shortly I'm going to need to automate it.

--97T--

Edited by NinerSevenTango

Share this post


Link to post
Share on other sites

That's terrible.

It's clear you want to learn but things you ask are really dumb.

Quick google search returned this:

• LJ_ERROR – A LabJack specific numeric error code. 0 means no error. (long, signed
32-bit integer).
• LJ_HANDLE – This value is returned by OpenLabJack, and then passed on to other
functions to identify the opened LabJack. (long, signed 32-bit integer).

LJ_ERROR _stdcall eGet (

LJ_HANDLE Handle,
long IOType,
long Channel,
double *pValue,
long x1)

Parameter Description:
Returns:
LabJack errorcodes or 0 for no error.
Inputs:
• Handle – Handle returned by OpenLabJack().
• IOType – The type of request.
• Channel – The channel number of the particular IOType.
• pValue – Pointer to Value sends and receives data.
• x1 – Optional parameter used by some IOTypes.
Outputs:
• pValue – Pointer to Value sends and receives data.

That's just about enough info to make the call working as it's intended.

Read more. Maybe try with that monoceres's tutorial about DllCall() and stuff.


♡♡♡

.

eMyvnE

Share this post


Link to post
Share on other sites

#8 ·  Posted (edited)

That's terrible.

It's clear you want to learn but things you ask are really dumb.

Quick google search returned this:

... Stuff that was already included in my code comments .....

That's just about enough info to make the call working as it's intended.

Read more. Maybe try with that monoceres's tutorial about DllCall() and stuff.

Thanks for your reply, I guess I wasn't clear.

It WORKS when hand-built without the dllstruct. (Thanks for that.)

I have the documentation for the device, and a bunch of examples of implementation in other languages.

Just having trouble with implementing the dllstructcreate and dllcall together now. I've put together one that works and one that doesn't work, and I can't find out why.

To be more specific,

;this snippet crashes dllcall --

$EGetstr= "long iotype;long Channel"
$EGet = DllStructCreate($EGetstr)
DllStructSetData($EGet,"iotype",1001)
DllStructSetData($EGet,"Channel",12)
$err3 = DllCall($hDLL, "none", "eGet", "handle", $hLabJack, "ptr", DllStructGetPtr($EGet), "double*", 0, "long", 0)

;but this one works --

$EGetstr2 = "long x1"
$EGet2 = DllStructCreate($EGetstr2)
DllStructSetData($EGet2,"x1",0)
$err3 = DllCall($hDLL, "none", "eGet", "handle", $hLabJack, "long", 1001, "long", 12, "double*", 0, "ptr", DllStructGetPtr($EGet2))

Sorry for such a dumb question. (What is wrong with the first snippet?)

Thanks for your help.

--97T--

Edited by NinerSevenTango

Share this post


Link to post
Share on other sites

You don't have to thank me thousand times.

May I ask a question now?

-why in the world would you think you can wrapp few function parameters following each other into one bigger structure and then pass pointer to it to the function thinking it should work? What logic is that?

... Stuff that was already included in my code comments ..... as you say, strictly specify function parameters.

Nothing more to say about that.

As I said already, read more. Try with, for example, MessageBox and MessageBoxIndirect functions considering differences between them and your misunderstanding of exactly that.


♡♡♡

.

eMyvnE

Share this post


Link to post
Share on other sites

-why in the world would you think you can wrapp few function parameters following each other into one bigger structure and then pass pointer to it to the function thinking it should work? What logic is that?

Pretty sure I saw it done in some UDF's. I only tried it because putting all the parameters in as shown in the help file example caused it to crash (or else it would throw an error if I tried to create the dllstruct with the "*" character). And with days of googling and reading, no indication why. So what else to do but start looking in UDF's for examples? I'm guessing at this point that you can't use a dllstruct if one of the required parameters needs to read like "long* var1".

As I said already, read more. Try with, for example, MessageBox and MessageBoxIndirect functions considering differences between them and your misunderstanding of exactly that.

Will read up, appreciate the links.

If I ever find out what's up with it, I'll write it up so people new to the concept can understand it.

For now it's all working with variable substitution. But I don't like giving up.

--97T--

P.S. Thanks again!

Share this post


Link to post
Share on other sites

Pretty sure I saw it done in some UDF's. I only tried it because putting all the parameters in as shown in the help file example caused it to crash (or else it would throw an error if I tried to create the dllstruct with the "*" character). And with days of googling and reading, no indication why. So what else to do but start looking in UDF's for examples? I'm guessing at this point that you can't use a dllstruct if one of the required parameters needs to read like "long* var1".

No you haven't. There is no UDF in the world that do that, unless not-working one.

At this point you should know that your last sentence, in this what I quoted, makes no sense whatsoever.

If I ever find out what's up with it, I'll write it up so people new to the concept can understand it.

Ahhhh!

♡♡♡

.

eMyvnE

Share this post


Link to post
Share on other sites

No you haven't. There is no UDF in the world that do that, unless not-working one.

At this point you should know that your last sentence, in this what I quoted, makes no sense whatsoever.

Func _WinNet_AddConnection2($sLocalName, $sRemoteName, $sUserName = 0, $sPassword = 0, $iType = 1, $iOptions = 1)
    Local $tLocalName = DllStructCreate("wchar Text[1024]")
    Local $pLocalName = DllStructGetPtr($tLocalName)
    DllStructSetData($tLocalName, "Text", $sLocalName)

    Local $tRemoteName = DllStructCreate("wchar Text[1024]")
    Local $pRemoteName = DllStructGetPtr($tRemoteName)
    DllStructSetData($tRemoteName, "Text", $sRemoteName)

    Local $pUserName = 0
    If IsString($sUserName) Then
        Local $tUserName = DllStructCreate("wchar Text[1024]")
        $pUserName = DllStructGetPtr($tUserName)
        DllStructSetData($tUserName, "Text", $sUserName)
    EndIf

    Local $pPassWord = 0
    If IsString($sPassword) Then
        Local $tPassword = DllStructCreate("wchar Text[1024]")
        $pPassWord = DllStructGetPtr($tPassword)
        DllStructSetData($tPassword, "Text", $sPassword)
    EndIf

    Local $iFlags = 0
    If BitAND($iOptions, 1) <> 0 Then $iFlags = BitOR($iFlags, $CONNECT_UPDATE_PROFILE)
    If BitAND($iOptions, 2) <> 0 Then $iFlags = BitOR($iFlags, $CONNECT_INTERACTIVE)
    If BitAND($iOptions, 4) <> 0 Then $iFlags = BitOR($iFlags, $CONNECT_PROMPT)
    If BitAND($iOptions, 8) <> 0 Then $iFlags = BitOR($iFlags, $CONNECT_REDIRECT)
    If BitAND($iOptions, 16) <> 0 Then $iFlags = BitOR($iFlags, $CONNECT_COMMANDLINE)
    If BitAND($iOptions, 32) <> 0 Then $iFlags = BitOR($iFlags, $CONNECT_CMD_SAVECRED)

    Local $tResource = DllStructCreate($tagNETRESOURCE)
    Local $pResource = DllStructGetPtr($tResource)
    DllStructSetData($tResource, "Type", $iType)
    DllStructSetData($tResource, "LocalName", $pLocalName)
    DllStructSetData($tResource, "RemoteName", $pRemoteName)

    Local $aResult = DllCall("mpr.dll", "dword", "WNetAddConnection2W", "ptr", $pResource, "ptr", $pPassWord, "ptr", $pUserName, "dword", $iFlags)
    If @error Then Return SetError(@error, @extended, False)
    Return SetError($aResult[0], 0, $aResult[0] = 0)
EndFunc   ;==>_WinNet_AddConnection2

This function taken from WinNet.au3 wraps function parameters and passes pointers to them to the function.

This makes it seem to me that each function parameter can be wrapped as a separate dllstruct. That's what I was trying to do. Whatever it is that causes one of my snippets above to work and the other to not work must be something very basic that I am missing.

Something else basic that I am missing: why the "*" character crashes the dllcall. The header file indicates that the function wants 'double *pValue', and when hand-built it works according to my understanding of the documentation with 'double* 0' put in, but I can't figure out how to get the concept into the dllstruct. That's why I was trying to pass part of the parameters directly and others wrapped in a dllstruct.

I know you're not interested in my faulty logic, it only illustrates how dumb I am. But you did ask.

I read the documentation on MessageBox and MessageBoxIndirect. What I get from it is that one passes parameters directly and the other uses a pointer to a structure that contains the parameters.

I'm reading up on C++. Maybe I can get the basic context there.

I'm making it work without using dllstructs. Perhaps I am missing the entire reason they are used in the first place. The code examples the manufacturer provides for other languages use structures to pass info in and out. I thought it was a more efficient (faster execution) way to pass parameters in and get results back. Even that might not be true. See how dumb I am?

I code routinely in more primitive machine controllers. Some of them use programming languages that are barely a step above assembly code. I can use them to do basically whatever I want, because I know what is happening every step of the way. It's required in order to get deterministic behavior. High-level languages I struggle with because documentation on the underlying concepts, and the context they are intended for, is difficult for me to find.

As of right now, I've built three models (and sold many copies) of what I wanted to use this device for over the years using industrial microcontrollers (that aren't really designed for the job), and they are installed all over the continent. They work flawlessly and last indefinitely. The hardware costs more, so that's why I'm doing this. I have to do this in my spare time though, because if I spent this many of my working-time hours on it, I would've burned through any savings by now. All previous attempts by others in my field to do this task with Windows have resulted in unreliable performance (probably because they all used vb dot net - ugh).

I've used AutoIT to get some wondrous things done before (like moving data in and out of these controllers using their custom comm protocols). Yeah, it took me awhile to figure that out. I'll keep trying to figure this out too until I get to the bottom of it. That way I own the code and it isn't unreliable bloatware.

Every programming language brings a mountain to climb. The chicken-egg problem is the cost of learning it to find out if it performs the tasks you need, and whether there the time invested in learning can ever be paid back compared to alternative ways of getting the task done.

I know your time is valuable, so thanks for taking the time to reply.

--97T--

Share this post


Link to post
Share on other sites

#13 ·  Posted (edited)

WNetAddConnection2W function takes pointer to something as first parameter. That's what you pass, pointer to something. You don't pass separate elements of that structure as separate parameters to the function. Why? Because the function wouldn't know what to do with them. Same thing goes for every function. If it requires pointer you pass pointer, if it requires separate parameters you pass them. There is no mixing allowed (unless really allowed).

For example Beep function. It's defined as:

BOOL WINAPI Beep(
  __in  DWORD dwFreq,
  __in  DWORD dwDuration
);

So it requires two dword values to be passed as separate parameters. In return it will do somethig and return boll value.

Out of that, constructing the call in AutoIt, it would be:

DllCall("kernel32.dll", "bool", "Beep", "dword", 800, "dword", 1000)

That's it. Nothing more, nothing less.

This would be wrong:

$tParam1 = DllStructCreate("dword")
DllStructSetData($tParam1, 1, 800)
$pParam1 = DllStructGetPtr($tParam1)

$tParam2 = DllStructCreate("dword")
DllStructSetData($tParam2, 1, 1000)
$pParam2 = DllStructGetPtr($tParam2)

DllCall("kernel32.dll", "bool", "Beep", "ptr", $pParam1, "ptr", $pParam2)

Why? Because Beep said it will take dwords. It didn't say pointer to dwords. The outcome of that call will be some strange long lasting beep on some super high frequency if any. Function will interpret those pointers as dwords and when you read them as dwords you get some rather high numbers. So, your call that you think it's working, is not really working as you think since you're passing wrong parameter value (reinterpreted pointer).

This is wrong too (this is the same thing you think you can do with your dll):

$tParams = DllStructCreate("dword;dword")
DllStructSetData($tParams, 1, 800)
DllStructSetData($tParams, 1, 1000)
$pParams = DllStructGetPtr($tParams)

DllCall("kernel32.dll", "bool", "Beep", "ptr", $pParams)

What will happen now? Unless Beep function is doing extensive error and all kind of other checking it would just crash your app. Primarily because you aren't passing enough parameters and that's causing different problems with stack.

To end this... You have this:

$aCall = DllCall($hDLL, "none", "eGet", "handle", $hLabJack, "long", 1001, "long", 12, "double*", 0, "long", 0)
    $nVal = $aCall[4]

That's the same as:

$tDouble = DllStructCreate("double")
$pDouble = DllStructGetPtr($tDouble)

$aCall = DllCall($hDLL, "none", "eGet", "handle", $hLabJack, "long", 1001, "long", 12, "ptr", $pDouble, "long", 0)
$nVal = DllStructGetData($tDouble)

And that's all that you can do with structures here. But don't. It's better to leave that job to AutoIt to do it internally because it wouldn't fuck up like you did ;).

Good luck.

Edited by trancexx

♡♡♡

.

eMyvnE

Share this post


Link to post
Share on other sites

#14 ·  Posted (edited)

Ahh, OK. You can't use a dllstruct unless the function parameter you are calling asks for a pointer. That's it. I guess I did fuck it up, guessing all that stuff, hehe.

Thanks a bunch for spelling that out for me.

Edited by NinerSevenTango

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0