Windows API help with CopyFileExA() and Callback to show Progress

Hello Mike,

I’ve actually got the callback working except that it GPF’s if I try to get to parameter values.

Thanks!!!

As mentioned on Skype
You need to use the 8 byte LARGE ( or ULARGE) instead of the 4 byte LONG

There’s an include, something like i64.inc that has the LARGE data type (sorry I’m away from my computer ATM so I can’t look it up for you)

I would urge you to try ShellFileOperation. I posted the prototypes back in my prior post. Its not a simple call but it provides its own window with progress and does all the calculations.

For CopyFileExA() to work your prototypes look mostly all wrong to me, but I could be wrong. It has some complex stuff I’m not sure about. The only way to make it work is to dive in and try so if you want it to work just keep going.

Here’s how I would write the prototypes (not tested). I’m not sure what that pbCancel is about.

CopyFileExA    PROCEDURE( |                            !BOOL WINAPI CopyFileExA(
    *CSTRING   lpExistingFileName   , |  !_In_ LPCSTR lpExistingFileName
    *CSTRING   lpNewFileName        , |  !_In_ LPCSTR lpNewFileName
    LONG       lpProgressRoutine=0  , |  !_In_opt_ LPPROGRESS_ROUTINE lpProgressRoutine  Address(CallBack) 
    UNSIGNED   lpData=0             , |  !_In_opt_ LPVOID lpData
    <*BOOL     pbCancel>            , |  !_Inout_opt_ LPBOOL pbCancel
    UNSIGNED   dwCopyFlags            |  !_In_ DWORD dwCopyFlags
    ),BOOL ,RAW,PASCAL,DLL(1),PROC,name('CopyFileExA')

LARGE_INTEGER_In  EQUATE(REAL)  !??? Not sure this is right ???

LPPROGRESS_ROUTINE    PROCEDURE( |      !DWORD WINAPI *LPPROGRESS_ROUTINE(
    LARGE_INTEGER_In   R_TotalFileSize           , |  !_In_ ??? LARGE_INTEGER TotalFileSize
    LARGE_INTEGER_In   R_TotalBytesTransferred   , |  !_In_ ??? LARGE_INTEGER TotalBytesTransferred
    LARGE_INTEGER_In   R_StreamSize              , |  !_In_ ??? LARGE_INTEGER StreamSize
    LARGE_INTEGER_In   R_StreamBytesTransferred  , |  !_In_ ??? LARGE_INTEGER StreamBytesTransferred
    UNSIGNED           dwStreamNumber            , |  !_In_ DWORD dwStreamNumber
    UNSIGNED           dwCallbackReason          , |  !_In_ DWORD dwCallbackReason
    SIGNED             hSourceFile               , |  !_In_ HANDLE hSourceFile
    SIGNED             hDestinationFile          , |  !_In_ HANDLE hDestinationFile
    <UNSIGNED          lpData>                     |  !_In_opt_ LPVOID lpData
    ) ,UNSIGNED,PASCAL

TotalFileSize   LIKE(INT64),OVER(R_TotalFileSize)   !??? Not sure this is right
Ditto for other 3 R_xxx

A LARGE_INTEGER is 64 bits, do you know how those work in Clarion and the i64.INC file?

I would have to look at some of my code for Int64 but IIRC an INT64 passed by value uses the 64-bit Floating Point registers so in Clarion need to prototype as a REAL. But its not a REAL so put an INT64 OVER() it.

1 Like

So I got it working.

Carl’s theory on using REAL’s is spot on.

MAP
    CopyProgressRoutine(REAL TotalFileSize,REAL TotalBytesTransferred,REAL StreamSize,REAL StreamBytesTransferred,ULONG dwStreamNumber,ULONG dwCallbackReason,Long hSourceFile,Long hDestinationFile,Long lpData),ULONG,PASCAL,name('LpprogressRoutine')
    MODULE('kernel32.dll') 
        drCopyFileExA(*CSTRING lpExistingFileName,*CSTRING lpNewFileName,ULONG lpProgressRoutine,Long lpData,BOOL pbCancel,DWORD dwCopyFlags),BOOL,PASCAL,RAW,name('CopyFileExA')
    END
END

The Callback:

CopyProgressRoutine Procedure(REAL TotalFileSize,REAL TotalBytesTransferred,REAL StreamSize,REAL StreamBytesTransferred,ULONG dwStreamNumber,ULONG dwCallbackReason,Long hSourceFile,Long hDestinationFile,Long lpData)

myBase dwrBase
st StringTheory
TransferredG Group(drINT64),Over(TotalBytesTransferred).
FileSizeG Group(drINT64),Over(TotalFileSize).

CODE
!Get the progress
myBase.SendDebug(myBase.GetProgress(TransferredG.lo,FileSizeG.lo))

…and the progress window

ScreenHunter 49

1 Like

Dang … I made my own little example to prove the REAL worked.

Note this is only for a LARGE_INTEGER passed by Value, that I think would be rare,
and not passed by address P LARGE_INTEGER e.g. see GetDiskFreeSpaceExA()


!CopyFileEx with Callback test by Carl Barnes - Copyright 2022 - Released under the MIT License
  PROGRAM

LARGE_INTEGER_In  EQUATE(REAL)  !64 bit INT passed by value Prototype as REAL

  MAP
TestCopy  PROCEDURE()  

CopyFileEx_PPROGRESS    PROCEDURE( |      !DWORD WINAPI *LPPROGRESS_ROUTINE(
    LARGE_INTEGER_In   R_TotalFileSize           , |  !_In_ LARGE_INTEGER TotalFileSize
    LARGE_INTEGER_In   R_TotalBytesTransferred   , |  !_In_ LARGE_INTEGER TotalBytesTransferred
    LARGE_INTEGER_In   R_StreamSize              , |  !_In_ LARGE_INTEGER StreamSize
    LARGE_INTEGER_In   R_StreamBytesTransferred  , |  !_In_ LARGE_INTEGER StreamBytesTransferred
    UNSIGNED           dwStreamNumber            , |  !_In_ DWORD dwStreamNumber
    UNSIGNED           dwCallbackReason          , |  !_In_ DWORD dwCallbackReason
    SIGNED             hSourceFile               , |  !_In_ HANDLE hSourceFile
    SIGNED             hDestinationFile          , |  !_In_ HANDLE hDestinationFile
    <UNSIGNED          lpData>                     |  !_In_opt_ LPVOID lpData
    ),UNSIGNED,PASCAL
  
      MODULE('api')
        OutputDebugString(*CSTRING cMsg),PASCAL,DLL(1),RAW,NAME('OutputDebugStringA')
        GetLastError(),LONG,PASCAL,DLL(1) 

CopyFileEx    PROCEDURE( |                            !BOOL WINAPI CopyFileExA(
    *CSTRING   lpExistingFileName   , |  !_In_ LPCSTR lpExistingFileName
    *CSTRING   lpNewFileName        , |  !_In_ LPCSTR lpNewFileName
    LONG       lpProgressRoutine=0  , |  !_In_opt_ LPPROGRESS_ROUTINE lpProgressRoutine  Address(CallBack) 
    UNSIGNED   lpData=0             , |  !_In_opt_ LPVOID lpData
    <*BOOL     pbCancel>            , |  !_Inout_opt_ LPBOOL pbCancel
    UNSIGNED   dwCopyFlags            |  !_In_ DWORD dwCopyFlags
    ),BOOL ,RAW,PASCAL,DLL(1),PROC,name('CopyFileExA')   !If the function succeeds, the return value is nonzero.
      END  !Module API
      
  END

!https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-copyfileexa
COPY_FILE_FAIL_IF_EXISTS    EQUATE(00000001h)   !The copy operation fails immediately if the target file already exists.
COPY_FILE_NO_BUFFERING      EQUATE(00001000h)   !The copy operation is performed using unbuffered I/O, bypassing system I/O cache resources. Recommended for very large file transfers.
PROGRESS_CANCEL     EQUATE(1)   !Cancel the copy operation and delete the destination file.
PROGRESS_CONTINUE   EQUATE(0)   !Continue the copy operation.
PROGRESS_QUIET      EQUATE(3)   !Continue the copy operation, but stop invoking CopyProgressRoutine to report progress.
PROGRESS_STOP       EQUATE(2)   !Stop the copy operation. It can be restarted at a later time.

  CODE
  TestCopy()
  
TestCopy  PROCEDURE() 
ExistingFileName    CSTRING('XXX.txt') 
NewFileName         CSTRING('XXX_Copy.txt')
ProgressRoutine     LONG 
lpData              LONG 
boolCancel          BOOL 
CopyFlags           LONG 
RetCopy             BOOL 
    CODE
    IF ~EXISTS(ExistingFileName) THEN COPY('CopyFileExTest.clw',ExistingFileName).
    IF ~EXISTS(ExistingFileName) THEN 
        Message('You need to make a file to test copy named: ' & ExistingFileName, 'CopyFileEx Test File')
        RETURN
    END
    REMOVE(NewFileName)
    lpData=8312022
    ProgressRoutine = ADDRESS( CopyFileEx_PPROGRESS )
    RetCopy = CopyFileEx( |         
            ExistingFileName , |    !  *CSTRING   lpExistingFileName   , |  !_In_ LPCSTR lpExistingFileName
            NewFileName      , |    !  *CSTRING   lpNewFileName        , |  !_In_ LPCSTR lpNewFileName
            ProgressRoutine  , |    !  LONG       lpProgressRoutine=0  , |  !_In_opt_ LPPROGRESS_ROUTINE lpProgressRoutine  Address(CallBack) 
            lpData     , |          !  UNSIGNED   lpData=0             , |  !_In_opt_ LPVOID lpData
            boolCancel , |          ! <*BOOL     pbCancel>            , |  !_Inout_opt_ LPBOOL pbCancel
            CopyFlags  )            !  UNSIGNED   dwCopyFlags            |  !_In_ DWORD dwCopyFlags

    Message('CopyFileEx return='& RetCopy & CHOOSE(~RetCopy,'  FAILED','  Success!') & |
            '  <9>LastError='& GetLastError() &|
           '||Copy From: <9>'& ExistingFileName & |
            '|Copy To:    <9>'& NewFileName & |
            '','CopyFileEx Test')
    RETURN

!================= 
! https://docs.microsoft.com/en-us/windows/win32/api/winbase/nc-winbase-lpprogress_routine 

CopyFileEx_PPROGRESS    PROCEDURE( |      !DWORD WINAPI *LPPROGRESS_ROUTINE(
    LARGE_INTEGER_In   R_TotalFileSize           , |  !_In_ LARGE_INTEGER TotalFileSize
    LARGE_INTEGER_In   R_TotalBytesTransferred   , |  !_In_ LARGE_INTEGER TotalBytesTransferred
    LARGE_INTEGER_In   R_StreamSize              , |  !_In_ LARGE_INTEGER StreamSize
    LARGE_INTEGER_In   R_StreamBytesTransferred  , |  !_In_ LARGE_INTEGER StreamBytesTransferred
    UNSIGNED           dwStreamNumber            , |  !_In_ DWORD dwStreamNumber
    UNSIGNED           dwCallbackReason          , |  !_In_ DWORD dwCallbackReason
    SIGNED             hSourceFile               , |  !_In_ HANDLE hSourceFile
    SIGNED             hDestinationFile          , |  !_In_ HANDLE hDestinationFile
    <UNSIGNED          lpData>                   )    !_In_opt_ LPVOID lpData       ) !,UNSIGNED ,RAW,PASCAL

!Note: LARGE_INTEGER_In  EQUATE(REAL)  !64 bit INT passed by value Prototype as REAL
TotalFileSize           LIKE(INT64),OVER(R_TotalFileSize)       !Passed REAL but its really 64 bit INT
TotalBytesTransferred   LIKE(INT64),OVER(R_TotalBytesTransferred) 
StreamSize              LIKE(INT64),OVER(R_StreamSize)   
StreamBytesTransferred  LIKE(INT64),OVER(R_StreamBytesTransferred)   
ReturnProgress  LONG 
    CODE
    CASE Message('In callback lpData=' & lpData  & |
            '||TotalFileSize = '          & TotalFileSize.lo & |
            ' <9>TotalBytesTransferred = '& TotalBytesTransferred.lo & |
            '|StreamSize = '                & StreamSize.lo & |
            ' <9>StreamBytesTransferred = ' & StreamBytesTransferred.lo & |
            '||dwStreamNumber = ' & dwStreamNumber & |
            '|dwCallbackReason = '& dwCallbackReason & |
            '|hSourceFile = '& hSourceFile & |
            ' <9>hDestinationFile = '& hDestinationFile & |
            '','CopyFileEx_PPROGRESS',ICON:Copy,'Continue|Quite|Cancel Copy')
    OF 1 ; ReturnProgress = PROGRESS_CONTINUE          
    OF 2 ; ReturnProgress = PROGRESS_QUIET           
    OF 3 ; ReturnProgress = PROGRESS_CANCEL 
    END 
    RETURN ReturnProgress

CopyFileExTestProject.zip (2.5 KB)

3 Likes

Its the same trick I’m using here. :wink:

TLDR, the compiler isnt too fussy about the data types used in api declarations in clarion map modules() and windows isnt either provided the number of bytes of each parameter matches up in the right order.

A Real is an 8byte data type, so it gets its bytes and only later in the runtime does the code to process a Real get used.

A Real could be used where ever an 8byte parameter is specified, which would be alot of the 64bit api’s.

I should add, where you need to match the calling convention like whats detailed here x86 calling conventions - Wikipedia
just reverse the order of the parameters like I did, so these are the same

  map
  module('win32')
  Pragma ('call(c_conv=> on)')
  someWinAPIusingC(MatchingByteLengthDataType param1, MatchingByteLengthDataType param2),C
  Pragma ('call(c_conv=> off)')
  end
  end

  map
  module('win32')
  someWinAPIusingC(MatchingByteLengthDataType param2, MatchingByteLengthDataType param1)
  end
  end

I think its a few levels of progression up from a vintage tupperware shape sorter ball. :nerd_face:

Hi ,

may it is a silly question but when I tried to compile the example in Clarion 9 I get these errors:
2022-09-03 02 50 37

Thanks

You’ll have to add these types at the top. I think they were added in C11.

INT64    GROUP,TYPE
lo         ULONG
hi         LONG
         END

UINT64   GROUP,TYPE
lo         ULONG
hi         ULONG
         END
1 Like

Sorry … feeling stupid today :roll_eyes:

got this error !

image

In C9 you must link win32ext.lib.

1 Like

Which can do in code with:

PRAGMA('link(win32ext.lib)')

I put those PRAGMA up just above the MAP. I like that it moves with the code. Can OMIT('***', _C110_) … or _c100_

Very good. thanks.

one last question, I tried to run it in clarion 6.3 and I got the same error “Unresolved External CopyFileExA in CopyFileExTest.obj” and I tried to link win32ext.lib but did not work.

thank you for your support.

Regards

The API doc shows it’s in shell32
So, using libmaker, create a .lib for this API

You can try adding the entire dll
But it’s likely that will lead to duplicates.

So delete everything you don’t need inside of libmaker before you save the .lib

Then link in the lib you created

1 Like

With the shipping LibMaker it is a PITA to have to carefully delete all but 1 function. With the " @ArnorBld + Mark + my " LibMaker you can Search and Tag 1+ Functions then write just Tagged in the Lib.

A unique feature … You can Subtract the current Clarion Win32.Lib so just have left everything missing and will not have duplicate errors. This allows opening a new release ClaRun.dll then subtract a prior release to see new exports that may reveal new RTL features.

All set and working fine.

thank you all and best regards

the conclusion that after creating the new .lib, I compiled the project in Clarion 6.3 under windows 7 32 bit.

the application works fine under window 10 64 bit but crashes in window 7 32 bit :frowning_face:

Hi,

Any suggestions to solve this issue; working on 64 bit but not 32 bit?

Regards

You’ve probably used the wrong copy of Shell32.DLL to create the .LIB file.

On a 64Bit system you must use the copy that’s in \Windows\SysWOW64 and not the copy that’s in \Windows\System32 (that, on a 64Bit system is 64Bit version)

Normally the 64Bit versions of DLLs won’t open with Clarion’s LibMaker, but in the case of Shell32 both 32Bit and 64Bit versions open.

Very cool! I’d forgotten I’d messed with that many years ago!

Thats a nice lib maker, the WinSxS search and load is a nice touch.

It is possible to work out the parameters and put them in the map automatically. Its a feature I have planned for my own apps which can make libs, which then makes searching for dll’s and producing libs a whole new game.

FYI.