Announcement

Collapse
No announcement yet.

PB conversion of C/C++ SDK

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

  • PB conversion of C/C++ SDK

    I am converting a C/C++ SDK to PowerBASIC. This will be released to others
    wishing to use PB to develop add-ons for the latest version of Visual Cadd.
    For stand-alone applications, they are now recommending explicit linking be
    used for this and future versions, to insure that their DLLs get loaded in the
    correct order and properly initialized. This requires writing a procedure for
    each of the 1490 calls in the Visual Cadd API similar to:

    '------------------------------------------------------------------------------
    FUNCTION VCGethWnd ALIAS "VCGethWnd" (BYVAL hW AS LONG) EXPORT AS LONG
    '------------------------------------------------------------------------------
    LOCAL fp AS DWORD
    LOCAL lReturn AS LONG

    IF hInstVCMAIN32 THEN
    fp = GetProcAddress (hInstVCMAIN32, "VCGethWnd")
    IF fp THEN
    CALL DWORD fp USING VCGethWnd (hW) TO lReturn
    END IF
    END IF
    FUNCTION = lReturn

    END FUNCTION

    Is it correct to drop the BYVAL in the CALL DWORD parameter list as shown here?

    The C/C++ version of this VCLink32.dll is 184,410 bytes
    The PB/DLL version is 266, 240 bytes (272,384 bytes if #REGISTER NONE is used)

    I have been promoting PB based on 'smaller' DLLs for almost two years. Now that
    they want to include a PB version in this latest release of the SDK, I am sure that
    I will be asked why the 44% - 48% increase over the C/C++ equivalent!

    What answer can I give? Is it due in part to the ALIAS requirements to maintain the
    proper case?

    With approx. 1500 procedures, the source code exceeds the maximum lines allowed
    by PB in one file, but I understand the reason for this....

    TIA


    ------------------

  • #2
    Why are you converting from C/C++ if there is a PB-version?

    Regards
    Peter

    P.S.
    Isn't this better?:
    Code:
    DECLARE FUNCTION Proto_VCGethWnd (BYVAL hW AS LONG) AS LONG   
     
    FUNCTION VCGethWnd ALIAS "VCGethWnd" (BYVAL hW AS LONG) EXPORT AS LONG
     
        STATIC fp AS DWORD
        LOCAL lReturn AS LONG
     
        IF hInstVCMAIN32 = 0 THEN EXIT FUNCTION
        IF fp = 0 then fp = GetProcAddress (hInstVCMAIN32, "VCGethWnd")
        IF fp THEN CALL DWORD fp USING Proto_VCGethWnd (hW) TO lReturn
     
        FUNCTION = lReturn
     
    END FUNCTION

    ------------------




    [This message has been edited by Peter P Stephensen (edited November 08, 2000).]
    [email protected]
    www.dreammodel.dk

    Comment


    • #3
      With as many as 10 versions of this program in use, with as many
      versions of the C/C++ VCLink32.dll, there are many incompatibilities
      with add-ons developed with C/C++. Visual Basic has many drawbacks
      as well, with all the support modules required and the inability to handle
      callbacks directly from the Visual Cadd program. The most reliable
      add-ons have been done with Delphi EXEs which do not use VCLink32.dll,
      but include .pas files with explicit linking as shown in the above example.

      Many users would like to develop their own add-ons, but don't have the
      time or inclination to learn Visual Basic, Delphi or C/C++, but most have
      some knowledge of DOS Basic. This is an ideal place to promote PB/DLL
      as the tool of choice. Very little knowledge of Windows programming is
      required as the GUI is handled by the Visual Cadd application. PB has the
      the required data types to be directly compatible and handles the callbacks
      properly. I have a work-around that can pass the Visual Cadd UDTs by value.
      All that is left is to properly convert the explicit linking to included .bas files,
      for use with single EXE stand-alone (Visual Cadd not running) applications.

      The PB version of the Visual Cadd SDK is not yet available. I am the one they asked
      to prepare it for their next SDK and API documentation release....



      [This message has been edited by Bob Benson (edited November 08, 2000).]

      Comment


      • #4
        Do you really need to create 1490 separate hard-coded named functions or can you not just create one function that receives the function name and resolves the address?

        Alternativelty, create another function that handes the GetProcAddress() and CALL DWORD function, and reduce each of the other functions to a call to this function...

        With regard to why the DLL created with PB is larger than the C version - it is hard to comment without actually being able to see the source code to both DLL's.

        Dropping the BYVAL is probably *not* a good idea if the calling code is passing the parameter BYVAL - BYVAL is the default passing technique for C/C++. BYVAL parameters are placed directly on the stack frame when the function is called, whereas a BYREF means that the address must be loaded and the the target value must be loaded... BYVAL will render this out this "additional" layer of indirection, and probably reduce the instruction count by one (ok, so I'm talking in fairly broad terms here!)

        The ALIAS simply tells the compiler to substitute the ALIAS name into the export table in the EXE rather than the true function name, so unless the ALIAS name is physically longer then the "true" name, then there should be no perceptable size difference in your EXE.

        Also, remember that EXE sizes grow/shrink in "chunks", so for an "accurate" comparison of the DLL's, you really need to compare the "memory image" size rather than the absolute disk file size of the DLL's. Out of interest, which has more runtime memory requirements? Your PB DLL or the C DLL?

        Finally, Peter's suggestion should improve performance for subsequent calls to the function, but the slightly larger code code size will make the DLL larger when multiplied by 1490 Since the target DLL is dynamically loaded, is there no chance that it may be unloaded and reloaded at runtime? If so, the target function address *may* change, so keeping it completly dynamic may be the best overall strategy in spite of performance.

        However, if Peters suggestion is plausable, then why not create a GLOBAL DWORD array and enumerate the entire 1490 addresses during LIBMAIN, and then reduce the 1490 functions into a simple CALL DWORD (after verifying that the array subscript is valid, of course!). You could keep the the entire set of function names in a DATA array, and use an equate for each index into the array...

        ie:
        Code:
        'psuedocode only!
        %nVCGethWnd = 1
        ...etc
        function libmain(...)
          DIM targetaddr(1 : DATACOUNT) AS GLOBAL DWORD
          FOR x& = 1 TO DATACOUNT
            targetaddr(x&) = GetProcAddress(hlibrary, READ$(x&))
          NEXT x&
          function = 1
          DATA vcGethWnd,...
        end function
         
        function vcGethWnd(byval hw as long) export as long
          if targetaddr(%nVCGethWnd) then CALL DWORD targetaddr(%nVCGethWnd) USING...
        end function
        Anyway, it's just an idea.... best of luck!

        ------------------
        Lance
        PowerBASIC Support
        mailto:[email protected][email protected]</A>
        Lance
        mailto:[email protected]

        Comment


        • #5
          Bob;

          There is one explanation for why a C++ DLL may be smaller than
          its PB counterpart:

          Many C++ EXE's and DLL's require the VC++ Runtime DLL
          (MSVCRT10.DLL or MSVCRT20.DLL). Its kind of like a VB
          app which requires the VB runtime. Since much of the code
          is in the runtime, the EXE or DLL is smaller in size.

          You can use the depends.exe program found on the PB web site to
          see what dependencies the C version you mentioned has.

          Now, some OS DLLs also link to the VC++ runtimes (like OLEDLG.DLL),
          and this will show up in the depends program, but you should see
          the VC++ runtimes listed at the top level in the depends program, if
          the C dll links to it directly.

          By using a runtime, C EXE's and DLL's can be much smaller and this
          fools you into thinking the compiler did a great job of producing
          a small app.

          I am not saying that a C compiler couldn't produce a small executable
          than PB, but this is one explanation of how it does at times.


          ------------------
          Chris Boss
          Computer Workshop
          Developer of "EZGUI"
          http://cwsof.com
          http://twitter.com/EZGUIProGuy

          Comment


          • #6
            Lance --

            > Do you really need to create 1490 separate functions?

            The intention is to have the calls in procedural code be the
            same whether implicit or explicit linking is used for the
            Visual Cadd DLLs. This is typically what has been done in the
            past with the C/C++ and Delphi versions. The Delphi SDK only
            uses explicit linking. It is said that this is due to problems
            with using implicit linking with NT4, but this was back when
            Visual Cadd was being done with Borland C/C++. The latest version
            has been with MSVC++.

            > Alternatively, create another function that handles the
            > GetProcAddress() and CALL DWORD functions....

            After asking for the latest VCLink32 SDK, I see that is what
            they have done this time. They are using generic wrapper functions
            overloaded for all possible combinations of parameters passed.
            This appears to be 192 combinations (SUBs, FUNCTION returns, and
            parameter lists) that would require as many additional procedures
            with PB. I may work with this later, but for now I will stay with
            the individual procedures. I am using a code generator to write
            the code, and those procedures would become more complex.

            > hard to comment without being able to see the source code...

            Previous versions:
            Code:
            //============== VCGethWnd ==============
            typedef long (CALLBACK* VCGethWndFN) (WORLDHANDLE);
            
            long WINAPI VCGethWnd (WORLDHANDLE hW)
            {
            	long lRet;
            	lRet = 0;
            
            	FARPROC fp = GetProcAddress(hInstVCMAIN32 , (LPSTR)"VCGethWnd");
            	if (fp)
            	{
            		return ((VCGethWndFN)fp)(hW);
            	}
            	return lRet;
            }
            Latest version:
            Code:
            long WINAPI VCGethWnd(WORLDHANDLE hW)
            {
            	return intFunction(hInstVCMAIN32, "VCGethWnd", hW);
            }
            
            
            int WINAPI intFunction(HMODULE hMod, const char* szFunction, long v1)
            {
            	typedef int (CALLBACK* GenericProc) (long);
            	int vReturn = NULL;
            
            	if (hMod)
            	{
            		FARPROC fp = GetProcAddress(hMod, szFunction);
            		if (fp)
            		{
            			vReturn = ((GenericProc)fp)(v1);
            		}
            	}
            
            	return vReturn;
            }
            > Dropping the BYVAL is *not* a good idea...

            So, in all cases the CALL DWORD USING parameter list should
            be EXACTLY the same as the parameter list defined in the
            SUB (or FUNCTION) above, right?

            > The ALIAS simply tells the compiler to substitute ....

            The names are the same, ALIAS is used to only maintain the
            proper case.

            > Which has more runtime memory requirements?

            How do I determine this for the C/C++ DLL?

            > Peter's suggestion should improve performance for subsequent
            > calls........keeping it completely dynamic may be the best
            > strategy in spite of performance.

            I agree, as this appears to be the recomendation for this
            latest version.

            > However, if Peter's suggestion is plausabe, then ....

            I have a macro converter that uses a similar method, but it
            is less than 300 tokens. Thanks for the example!

            Chris --

            Depends shows that the C/C++ version only uses KERNEL32.dll.

            PowerBASIC version requires:
            ADVAPI32.dll
            GDI32.dll
            KERNEL32.dll
            OLE32.dll
            OLEAUT32.dll
            USER32.dll

            What is the deal with this? dynamic string capabilities?

            Thanks for the help, guys!!

            -- Bob

            ------------------


            [This message has been edited by Bob Benson (edited November 09, 2000).]

            Comment


            • #7
              From frequently asked questions:
              -----
              Following on, PowerBASIC does not support passing or receiving
              UDT's by value (BYVAL). However, passing or receiving a structure
              by value is sometimes required when calling DLL's written in C/C++.
              The solution is to use inline assembler to PUSH the elements that
              comprise the structure onto the stack, and then call the DLL
              function directly using inline assembler code. To receive a
              structure by value, use the reverse process (POP the elements
              from the stack).
              -----

              Could someone offer some examples? So far, I have been able to
              pass the UDT elements to the C/C++ procedure without exceeding
              the 16 parameter limit, but I need to receive the UDT by-value
              as well. Most of the UDT's are a number of DOUBLEs or LONGs.

              In the past, I have also been able to do this by changing the
              C/C++ function to a PB SUB and placing the UDT as the first
              element in the parameter list.

              TIA

              Bob

              Comment


              • #8
                Bob,

                I find it HARD to believe that modern coding in C/C++ is passing
                structures BYVAL. Passing a structure BYVAL is very inefficient.
                The best way of dealing with structres is with POINTERS going
                and coming.

                I am not familiar with VisualCADD but in AutoCAD ObjectARX world
                you just don't pass structures BYVAL.

                Could you post an example of where you NEED this!!!!

                Thanx,
                Cecil

                ------------------

                Comment


                • #9
                  Sadly enough, the Windows API itself has a few routines that expect UDTs BYVAL. So, you will run across these things on occasion.

                  ------------------
                  Tom Hanlin
                  PowerBASIC Staff

                  Comment


                  • #10
                    Bob,

                    Passing structures has a bit to do with what you need to do with them,
                    if you need to pass the address of a structure so that it can receive
                    multiple parameters, (RECT is a good example) you need to pull the
                    structure apart at the receiving end, fill the members with the the
                    values you require and exit the function with whatever return value
                    you need. This is passing an address ByVal.

                    lpStruct = VarPtr(MyStruct.FirstMember)

                    retval = MyFunctionCall(par1,par2,lpStruct)

                    The other method is to use the structure as a data type in the receiving
                    function prototype.

                    FUNCTION MyFunctionCall(ByVal par1 as DWORD, _
                    ByVal par2 as DWORD, _
                    lpStruct as MyStruct) as WHATEVER

                    Then use the passed structure with its normal members. This method is
                    useful where you have to pass a large number of parameters but only
                    receive a single return value.

                    Regards,

                    [email protected]

                    ------------------
                    hutch at movsd dot com
                    The MASM Forum

                    www.masm32.com

                    Comment


                    • #11
                      Yes, there are BP equivalents for most of the C/C++ calls, but not all.
                      As far as passing UDT's to the Visual Cadd API, this is the worst case:
                      Code:
                      extern "C" void WINAPI VCAddEllipticalArcEntity(short* iError,
                       short iSymbolIndex, Point2D dpP0, Point2D dpP1, Point2D dpP2,
                       Point2D dpP3, Point2D dpP4, Point2D dpP5, Point2D dpP6);
                      Point2D has two elements which are doubles. For this procedure,
                      passing the individual elements of the UDTs will result in 16 values
                      passed. But, when using generic wrapper functions for the explicit
                      linking (GetProcAddress), the 'handle' and 'function name' are added
                      as well, which then results in 18 values to be passed ........

                      A typical example of receiving a UDT by value is:
                      Code:
                      extern "C" Point2D WINAPI VCGetSymScale(short* iError);
                      In this case, writing the procedure as a SUB instead of a FUNCTION
                      and putting the UDT as the first element of the parameter list will work,
                      but I don't know why??
                      Code:
                      DECLARE SUB VCGetSymScale LIB "VCMAIN32.DLL" ALIAS "VCGetSymScale" _
                                  (pRet AS Point2D, iError AS INTEGER)

                      ------------------


                      [This message has been edited by Bob Benson (edited November 21, 2000).]

                      Comment


                      • #12
                        Bob,

                        Man, I see the predicament you are in. First of all, on the
                        first function you mention, these should have been rolled into
                        a resbuf with linked list capability in lieu of passing each
                        individual point. Once you setup a linked list, assign a variable
                        to that type, then pass the address to the function.
                        Since this DLL is already written, is it possible to get VisualCADD
                        to change it? This is a VERY inefficient way to do this in C.

                        Function 2 works in PB because of the following:

                        extern "C" Point2D WINAPI VCGetSymScale(short* iError);

                        1. The Point2D in C is a return type for the function. You are
                        pushing the address of this variable on the stack in your PB
                        declare, since the PB default is BYREF unless otherwise specified.

                        2. In fact, both args in the PB declare are passed BYREF. The
                        prototype (short* iError); is an integer pointer in C and your
                        PB declare is passing the address of iError which is as it should
                        be.

                        Apparently the sequence of args on the stack match the C calling
                        convention specified in the original DLL.

                        As far as pushing all the elements on the stack in the first Fn
                        using assembly, that's out of my league. Hutch would be the
                        man to help you here. Hutch, are you out there?

                        Cheers,
                        Cecil

                        ------------------

                        Comment


                        • #13
                          Originally posted by Tom Hanlin:
                          Sadly enough, the Windows API itself has a few routines that expect UDTs BYVAL. So, you will run across these things on occasion.
                          This being so, maybe the win32api.inc file should include the necessary inline assembler referred to in the FAQ, as quoted by Bob.

                          ------------------
                          --Dan
                          Dan

                          Comment


                          • #14
                            In the cases I know about, the structures are DWORD or QUAD sized, making it more convenient to deal with the issue by copying from the structure to an integer type. It would be nice to have an example of the proper asm syntax, though.

                            ------------------
                            Tom Hanlin
                            PowerBASIC Staff

                            Comment


                            • #15
                              Cecil,

                              I confess that my C is getting very rusty but if I understand the question,
                              doing a manual call in assembler using STDCALL is reasonably simple to do.

                              Addressing in win 32 is all unsigned 32 bit integer (DWORD) so if the required
                              addresses are done as DWORD, the rest is very simple.

                              In PowerBASIC, I use LoadLibrary() and test the return value to make sure it
                              returns a valid handle, then use GetProcAddress() to get the address of the
                              function being called. The parameters are then pushed in reverse order as is
                              normal in STDCALL and then the call to the function is made.

                              Code:
                                    hDLL = LoadLibrary("libname.dll")
                                
                                    If hDLL <> %NULL Then
                                      lpFunction = GetProcAddress(hDLL,"YourProcName")
                                        If lpFunction = %NULL Then
                                          MsgBox "Can't find function"
                                          FUNCTION = 0
                                          Exit FUNCTION
                                        End If
                                    Else
                                      MsgBox "Can't find DLL"
                                      FUNCTION = 0
                                      Exit FUNCTION
                                    End If
                                
                                    ! push par10
                                    ! push par9
                                    ! push par8
                                    ! push par7
                                    ! push par6
                                    ! push par5
                                    ! push par4
                                    ! push par3
                                    ! push par2
                                    ! push par1
                                    ! call lpFunction
                              
                                    ' return value usually in EAX
                              This type of code will do normal register size data using STDCALL
                              convention OK.

                              If the called function is written in C calling, I forget how to do the
                              stack cleanup but that info should be around somewhere. It can start to get
                              messy when you need to pass bigger than DWORD size on the stack, it usually
                              means getting the high and low DWORD size values of the QUAD and pushing
                              them in order and this of course will depend on how the DLL functions are
                              written.

                              I don't know if this is any use to Bob but its at least normal stuff with
                              manual calling of a function.

                              Regards,

                              [email protected]

                              ------------------
                              hutch at movsd dot com
                              The MASM Forum

                              www.masm32.com

                              Comment


                              • #16
                                Steve,

                                I see what you mean. My guess is that it's about 99% sure that
                                the function calls in Bob's DLL is __cdecl. My knowledge of assembly
                                is very weak but I would assume the first action before pushing
                                the args on the stack would be to get the stackpointer location.
                                Then after the function call , reset the stackpointer,
                                thus poping all the args that were added off the stack.

                                Your right about the function. Without knowing the definition
                                of how it's implemented, one is "shooting in the dark".

                                This DLL function is in dire need of a rewrite. The Point2D is a
                                struct defined as having two doubles, x and y in it. How do you
                                pass double values in assembly?

                                Cheers,
                                Cecil



                                ------------------

                                Comment


                                • #17
                                  Cecil --

                                  My next question as well!
                                  And returning the value from the C/C++ function?

                                  All calls are STDCALL (WINAPI)

                                  Thanks for all the input guys!!

                                  -- Bob

                                  Comment


                                  • #18
                                    Bob,

                                    Back from the Thanksgiving holidays. Is it possible to get a
                                    code snippet of the fn VCAddEllipticalArcEntity()? If we could
                                    see how the args are used, maybe this would simplify how we go
                                    about pushing them on the stack.

                                    Cheers,
                                    Cecil

                                    ------------------


                                    [This message has been edited by Cecil Williams (edited November 27, 2000).]

                                    Comment


                                    • #19
                                      extern "C" void WINAPI VCAddEllipticalArcEntity(short* iError, short iSymbolIndex,
                                      Point2D dpP0, Point2D dpP1, Point2D dpP2, Point2D dpP3, Point2D dpP4,
                                      Point2D dpP5, Point2D dpP6);

                                      If used with the generic wrapper functions for GetProcAddress,
                                      the parameter list would be:

                                      par1 LONG handle to appropriate VCadd DLL
                                      par2 DWORD pointer to ASCIIZ function name BYVAL VARPTR (szFnName)
                                      par3 DWORD pointer to iError BYVAL VARPTR(iError)
                                      par4 INTEGER BYVAL iSymbolIndex
                                      par5 DOUBLE BYVAL dp0.x
                                      par6 DOUBLE BYVAL dp0.y
                                      par7 DOUBLE BYVAL dp1.x
                                      par8 DOUBLE BYVAL dp1.y
                                      par9 DOUBLE BYVAL dp2.x
                                      par10 DOUBLE BYVAL dp2.y
                                      par11 DOUBLE BYVAL dp3.x
                                      par12 DOUBLE BYVAL dp3.y
                                      par13 DOUBLE BYVAL dp4.x
                                      par14 DOUBLE BYVAL dp4.y
                                      par15 DOUBLE BYVAL dp5.x
                                      par16 DOUBLE BYVAL dp5.y
                                      par17 DOUBLE BYVAL dp6.x
                                      par18 DOUBLE BYVAL dp6.y

                                      This is a SUB (void function) so no parameters are returned.

                                      I can put the GetProcAddress code in this procedure and not use their
                                      'generic wrapper functions' and then use only the required 16 parameters.
                                      IMHO, using a wrapper for this does not make sense, as this is the only
                                      call in the API that uses this particular parameter list anyway...

                                      I was just asking for a good example of using assembly, as noted in
                                      'Frequently Asked Questions', with type conversions, etc.

                                      It also does not make sense to me to GetProcAddress on all 1490 procedures
                                      when building an app that may only call a small number of these, but that is the
                                      way their Delphi SDK .pas files are set up, so I will do the PB SDK in a similar
                                      manner. I have experimented with a version that uses conditional compilation
                                      based on use, DLL or EXE, exported or not and actual list of procedures used.
                                      This takes quite a bit longer to compile, but is what I would use for my own
                                      'smaller' EXEs. I am glad that our apps are all add-on DLLs that run within the
                                      Visual Cadd environment and can be dynamically linked!!

                                      Thanks,
                                      Bob

                                      Comment


                                      • #20
                                        Cecil,

                                        I don't have any of the Visual Cadd source code. Sorry!

                                        Thanks,
                                        Bob


                                        [This message has been edited by Bob Benson (edited November 27, 2000).]

                                        Comment

                                        Working...
                                        X