Announcement

Collapse
No announcement yet.

Calling PowerBasic Function from DLL Library Route written in "C"

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Calling PowerBasic Function from DLL Library Route written in "C"

    Dear Sirs,

    I'm attempting to configure a function/subroutine written in PB/Win 8.04 as a "callback function" to an "event scheduler" in the "Universal Library" from "Measurement Computing" (which is a DLL and appears to be written in C/C++). The event scheduler calls the PB routine whenever a certain event occurs, such as when a measurement module completes a scan, experiences an error, or transfers a certain quantity of measurements to a "Windows Global Memory Buffer" (which has already been established by another routine, with great help from individuals on this forum. )

    To initialize the event scheduler, we call "cbEnableEvent" in the Universal Library, and associate an event with a user-written routine (my-routine) by passing a description of the event (EventType, defined as an equate) along with a pointer to my-routine. Other parameters which we pass define the measurement-board-number, a pointer to data I am passing to my-routine, and a more detailed definition of the event (Count), such as the amount of data to transfere before calling my-routine. The header file in C/C++ is the following:
    int EXTCCONV cbEnableEvent(int BoardNum, unsigned EventType, unsigned Count, EVENTCALLBACK CallbackFunc, void *UserData);
    I've created the following declaration for PowerBasic:
    DECLARE FUNCTION cbEnableEvent LIB "cbw32.dll" ALIAS "cbEnableEvent" ( BYVAL BoardNum&, _
    BYVAL EventType AS DWORD, _
    BYVAL Count AS DWORD, _
    BYVAL CallBack_Function_Pointer AS DWORD, _
    BYREF UserData_FirstElement AS ANY ) AS LONG 'Could also be BYVAL UserData_Pointer to first element of array or user defined type.
    Then, in my PowerBasic program, I call cbEnableEvent and associate a specific event (defined elsewhere) to myRoutine and pass myParam as follows:
    LOCAL myParam AS LONG
    LOCAL myRoutinePtr AS DWORD

    myRoutinePtr = CODEPTR( myRoutine )
    ErrorCode_cbEnableEvent = cbEnableEvent( BoardNum, Event, EventParam, BYVAL myRoutinePtr, BYREF myParam )
    The documentation for cbEnableEvent says the following about the parameter which defines the CallbackFunc (my-routine):
    The address of or pointer to the user-defined callback function to handle the above event type(s). This function must be defined using the standard call (__stdcall) calling convention. Consequently, Visual Basic programs must define their callback functions in standard modules (.bas) and cannot be object methods. On the other hand, C++ programs can define this callback function as either a global function or as a static member function of a class (however, beware that static members do NOT have access to instance specific data).

    As for the Callback Function (my-routine), the documentation says the callback function must have the following prototype in C/C++:
    void __stdcall CallbackFunc( int BoardNum, unsigned EventType, unsigned EventData, void* UserData );
    I've defined a subroutine in the main program module as follows:
    SUB myRoutine STDCALL ALIAS "myRoutine" ( BYVAL BoardNum AS LONG, BYVAL EventType AS DWORD, BYVAL EventData AS DWORD, BYREF PassedParameter AS LONG ) EXPORT
    I don't see any evidence that myRoutine is being called no matter which event I initiate into cbEnableEvent, yet there is no error code returned and no evidence of a link failure when running the PB exe.

    1. Am I connecting my-routine (written in PowerBasic) correctly to cbEnableEvent?
    2. I'm passing a pointer-to-myRoutine (which has a non-zero value) BYVAL to a DLL, but can a DLL access a Function/Sub in the main program source file by means of a pointer?
    3. Does PowerBasic implement the "global function" concept mentioned in the documentation?



    Sincerely Yours,
    John Harvill
    Last edited by John Harvill; 4 Feb 2008, 09:45 PM. Reason: Correct spelling in title

  • #2
    >ErrorCode_cbEnableEvent = cbEnableEvent( BoardNum, Event......

    I assume this is returning success. (code not shown)

    As far as your questions....
    1. As far as I can tell you are doing everything right.
    2. Sure, no problem. The the calling function happens to be located in a DLL is immaterial, given that.......
    3. I don't know what "global function" means but as long as your pass the address of the function to the cb_EnableEvent function correctly, the function IS visible anywhere within the process.

    I might try (after testing the return of the registration function for success) changing the callback function to a FUNCTION rather than a SUB, but I don't do C/C++ so that for me would be a shot in the dark.

    MCM
    Michael Mattias
    Tal Systems (retired)
    Port Washington WI USA
    [email protected]
    http://www.talsystems.com

    Comment


    • #3
      A VOID procedure in C directly translates to a PowerBASIC SUB. I would declare UserData as BYVAL DWORD and use VARPTR(), as it looks like a pointer to a memory block. Also you don't have to assign the CODEPTR to a variable, just pass it inline without the BYVAL (the parameter is BYVAL already).
      kgpsoftware.com | Slam DBMS | PrpT Control | Other Downloads | Contact Me

      Comment


      • #4
        I would declare UserData as BYVAL DWORD and use VARPTR(), as it looks like a pointer to a memory block.
        Actually what it looks like is what is pretty much standard for callback registration functions: an integer for application use, so it can be whatever the programmer wants it to be.

        A commented demo dealing with writing and using callback functions...

        PB/WIN: Write your own ENUM functions to callback September 23, 2002

        MCM
        Michael Mattias
        Tal Systems (retired)
        Port Washington WI USA
        [email protected]
        http://www.talsystems.com

        Comment


        • #5
          Originally posted by Michael Mattias View Post
          Actually what it looks like is what is pretty much standard for callback registration functions: an integer for application use, so it can be whatever the programmer wants it to be.

          A commented demo dealing with writing and using callback functions...

          PB/WIN: Write your own ENUM functions to callback September 23, 2002

          MCM
          I know what a user defined value is, the code "void* UserData " explicitly states it is a memory buffer. Read a book on C.
          kgpsoftware.com | Slam DBMS | PrpT Control | Other Downloads | Contact Me

          Comment


          • #6
            You need to read in context, sir. The variable appears both in the registration function and the callback function's header.

            But I do need to thank you for your comment, as it allows to me to say once again:

            " Blindly performing type-for-type, verb-for-verb, function-for-function conversions from one source code language to another is a stone loser."


            MCM
            Michael Mattias
            Tal Systems (retired)
            Port Washington WI USA
            [email protected]
            http://www.talsystems.com

            Comment


            • #7
              You've said yourself "I don't do C\C++", and in this thread we are helping with a translation from the C language.

              The parameter in question is a pointer to an undefined memory block, Ala BYVAL DWORD. If it is BYREF, then the code in the function must use VARPTR everytime the pointer needs to be accessed, unless it is a single DWORD value.
              kgpsoftware.com | Slam DBMS | PrpT Control | Other Downloads | Contact Me

              Comment


              • #8
                Thankyou for taking the time to read this long question I posed.

                The cbEnableEvent is returning a value of zero which indicates success (no error).

                According to the documentation, the last parameter is a pointer to a block of memory containing the data to be passed to the callback function. I'm taking advantage of PowerBasic's easy syntax for passing the pointer by passing a single-variable argument BYREF.

                As I understand, for PowerBasic, that passing an argument "BYREF variable" is identical to passing "BYVAL varptr( variable )."

                Kev, are you saying that the following call
                ErrorCode_cbEnableEvent = cbEnableEvent( BoardNum, Event, EventParam, BYVAL myRoutinePtr, BYREF myParam )
                can be simplified to be
                ErrorCode_cbEnableEvent = cbEnableEvent( BoardNum, Event, EventParam, myRoutine, BYREF myParam )
                and that your ultimate suggestion is

                DECLARE FUNCTION cbEnableEvent LIB "cbw32.dll" ALIAS "cbEnableEvent" ( BYVAL BoardNum&, _
                BYVAL EventType AS DWORD, _
                BYVAL Count AS DWORD, _
                BYVAL CallBack_Function_Pointer AS DWORD, _
                BYVAL UserData_Pointer AS DWORD ) AS LONG
                and

                ErrorCode_cbEnableEvent = cbEnableEvent( BoardNum, Event, EventParam, myRoutiner, BYVAL varptr( myParam ) )
                Sincerely,
                John Harvill
                Last edited by John Harvill; 4 Feb 2008, 11:35 AM.

                Comment


                • #9
                  Yes, but without the parameter overrides, like this:

                  Code:
                  ErrorCode_cbEnableEvent = cbEnableEvent(BoardNum, Event, EventParam, CODEPTR(myRoutine), VARPTR(myParam) )
                  I note that the code explictly defines the callback as STDCALL (which is PB's default), What about cbEnableEvent? Does the project settings for the original code stipulate CDECL or STDCALL? If cbEnableEvent is CDECL, this will explain why it doesn't work!
                  kgpsoftware.com | Slam DBMS | PrpT Control | Other Downloads | Contact Me

                  Comment


                  • #10
                    What exactly is the parameter you want to pass? An integer variable? A UDT?

                    This is a very common property of third-party libraries... and also of the standard Windows API: you register a callback function by address, passing a "user value" integer; then when that function is called it always passes as a parameter that same "user value" integer to the callback.

                    "How" you send (register) that user value and how you read it in your callback function is simply a matter of choosing compatible syntax. Remember, the library call to your callback function does not have so much as a clue as to what that fourth parameter means or how you have it defined in your source code. All it knows is, "every time I call the callback function, pass the registered 'user value' to that function.

                    How do you GET that user integer value?

                    Well, let's see..

                    It could be a handle (to a window, to a file, to a GDI object, to a global memory block)
                    It could an address (a VARPTR or CODEPTR)
                    It could be a value (1, 2,3 , like an 'action code')

                    What to use depends on what info you need in your callback function which is NOT provided by the library itself... you pick a value which means something to you.

                    MCM
                    Michael Mattias
                    Tal Systems (retired)
                    Port Washington WI USA
                    [email protected]
                    http://www.talsystems.com

                    Comment


                    • #11
                      The instructions for passing pointers in PowerBasic Function calls says

                      When a Function definition specifies either a BYREF parameter or a pointer variable parameter, the calling code may freely pass a BYVAL DWORD or a Pointer instead. While the use of the explicit BYVAL override in the calling code is optional, it is recommended for clarity.
                      The AS clause is required when declaring a pointer as a parameter. Each pointer parameter must be declared to be passed BYVAL. The use of the BYVAL keyword in the calling code is optional but recommended for clarity:
                      ' Integer Pointer (passed by value)
                      FUNCTION Test(BYVAL A AS INTEGER PTR) AS LONG
                      Which is why I specify "BYVAL" for the pointers in my call to cbEnableEvent.

                      Do you think I'm getting caught by the "AS ANY" type declaration for the pointer-to-user-parameter in the declaration?

                      I don't know whether the overall library uses CDECL or STDCALL. I have been successful calling other functions in the library using PowerBasic's default convention SDECL. I've also been following the documentation for interfacing to Visual Basic, which (according to what I've read so far) doesn't require any special link settings to work with Visual Basic.

                      Sincerely,
                      John Harvill
                      Last edited by John Harvill; 4 Feb 2008, 05:00 PM.

                      Comment


                      • #12
                        Code:
                        cbEnableEvent(int BoardNum, unsigned EventType, unsigned Count, EVENTCALLBACK CallbackFunc, void *UserData);
                        .. might just have easily been provided to you as ...
                        Code:
                        cbEnableEvent(int BoardNum, unsigned EventType, unsigned Count, EVENTCALLBACK CallbackFunc, unsigned UserData);
                        ...in which case the callback function would have been provided as ....
                        Code:
                        void __stdcall CallbackFunc( int BoardNum, unsigned EventType, unsigned EventData, unsigned UserData );
                        .. and in point of fact, you could use these definitions as everything will work exactly the same.

                        These are all "hows" - what you should be concerned with is the "what" and the "what" is "integer with meaning to the programmer."

                        MCM
                        Michael Mattias
                        Tal Systems (retired)
                        Port Washington WI USA
                        [email protected]
                        http://www.talsystems.com

                        Comment


                        • #13


                          It works now.
                          All the suggestions work.
                          Even my original code works, with one minor change.

                          Time for me to fess-up to what I really did wrong. I terminated the background process (running the measurement scan) too soon, before the callback function had a chance to run.

                          I called the scan function (cbAIScan) with the "option variable" set to run the scan as a background process (which most likely causes the measurement board and windows to interact in a parallel thread, I guess, since this all happens inside the Universal Library). While the scan was running in the background, I monitored the progress of the scans in a loop, reading its progress every 10ms (using sleep 10 with every pass thru the loop). When the required number of measurements had been taken, I terminated the background process.

                          This apparently caused a race condition. I know the scanning had completed before I terminated the background process because one of the monitored parameters indicated that the scan had completed while still in the loop. I expected the completion to immediately trigger the callback function. However, it appears I didn't leave enough time for the callback function to run. As a fix, to prevent the race condition, I entered "sleep 100" before terminating the background process, which left enough time for the callback function to run.

                          All the suggested solutions work. My original code works with the original declare ("BYREF userdata AS ANY"), and the other suggested declare works ("BYVAL pointer_to_data AS DWORD"). My sub definition works with the user data passed "BYREF variable AS LONG" and "BYVAL pointer_to_variable AS LONG POINTER." The call to ebEnableEvent works with the user data passed "BYREF myParam" and "BYVAL VARPTR( myParam )." Finally, the sub does not require the "STDCALL" or "ALIAS" or the "EXPORT" keywords. I can simply use

                          SUB myRoutine ( BYVAL BoardNum AS LONG, BYVAL EventType AS DWORD, BYVAL EventData AS DWORD, BYREF myParam AS LONG )
                          or
                          SUB myRoutine ( BYVAL BoardNum AS LONG, BYVAL EventType AS DWORD, BYVAL EventData AS DWORD, BYVAL myParamPtr AS LONG POINTER )
                          whichever is most convenient. PowerBasic is turning out to be flexible in how it can pass the user data.

                          Thankyou for taking the time to explain the rules for calling routines in PowerBasic from "C/C++" libraries. I'm sorry for the wild-goose-chase. Nevertheless, this discussion is valuable for clarifying the rules for PowerBasic in these situations.

                          Sincerely,
                          John Harvill
                          Last edited by John Harvill; 4 Feb 2008, 02:01 PM.

                          Comment

                          Working...
                          X