Results 1 to 12 of 12

Thread: LoadImage issue

  1. #1

    Thread Starter
    Lively Member
    Join Date
    Sep 2009
    Posts
    95

    Exclamation LoadImage issue

    Hi all,

    I wrote an Icon class to easily handle 32bit icons in VB6. I used following project as example: Systray Icon
    The problem, which I am facing is, that in my class one icon cannot be loaded - but in the example it is! And I dont see a difference between both projects...
    Here you can download my Demo Class.

    The icon which is not loaded is the second icon in the resource file (ICON_0). But both projects are using the same resource file.
    The LoadImage function is used for resource file icons in the compiled project only. So you should compile it to see the problem.
    I added the LoadImage call twice: the first call uses the W-variant of the API, the second the A-variant, like it is done in the example.
    Both calls fail in my demo for the second icon. But the first icon (INT resource) is loaded correctly - and in the example this second icon is loaded too.
    No dll error occurs - the returned handle is just zero.

    Can you reproduce this behavoir?
    Do you see the problem source?

    Here you can see the loaded icon in the example:
    Name:  loaded_icon.png
Views: 1815
Size:  37.5 KB

    And here the missing one in my demo:
    Name:  missing_icon.png
Views: 1713
Size:  94.2 KB

    While writing the class, I found two other APIs: LoadIcon and LoadIconMetric. Can somebody tell me the differences/advantages/disadvantages of these two compared to LoadImage?
    Last edited by NeedHelp!; Jan 3rd, 2013 at 06:54 AM.

  2. #2
    Default Member Bonnie West's Avatar
    Join Date
    Jun 2012
    Location
    InIDE
    Posts
    4,060

    Re: LoadImage issue

    In my XP system, your code works as expected. I see the icon that's missing from your screenshot whether I run from the IDE or compiled. You might want to try this modification to your LoadFromResource routine.

    Code:
     
    Public Function LoadFromResource(ByRef ResName As Variant) As Boolean
       'Resource file has to be added to this library project!
       'pic has to be static, to not be destroyed if the routine quits
        Static Pic As StdPicture
        Dim lpszName As Long
    
       'The icon will be destroyed if you load a new one or if the class is terminated
        Call FreeIconMemory
    
        If App.LogMode Then
            Select Case VarType(ResName)
                Case vbString:  lpszName = StrPtr(ResName)
                Case vbInteger: lpszName = CLng(ResName)
                Case Else:      Err.Raise 5& 'Invalid procedure call or argument
            End Select
    
            m_hIcon = LoadImage(App.hInstance, lpszName, IMAGE_ICON, 32&, 32&, LR_SHARED)
        Else
            On Error Resume Next
            Set Pic = LoadResPicture(ResName, vbResIcon)
            On Error GoTo 0
    
            If Pic Is Nothing Then m_hIcon = 0& Else m_hIcon = Pic.Handle
        End If
    
        LoadFromResource = m_hIcon <> 0&
    End Function
    According to MSDN, the MAKEINTRESOURCE macro returns the specified value in the low-order word and zero in the high-order word. Although it returns a C/C++ string, it is technically not the same as a VB string (LPTSTR vs BSTR). However, both data types are really just pointers to the real strings. Since 32-bit pointers are of the same size as a Long data type, what MAKEINTRESOURCE(intResID) return is the same as MAKELONG(intResID, 0), which can be further simplified as just intResID. A positive Integer coerced to a Long will contain 0 in the high word.

    LoadIcon is an older and simpler API than LoadImage. LoadIconMetric, on the other hand, is not available on systems earlier than Vista. LoadImage is the most versatile of the three and is the one recommended by Microsoft.
    Last edited by Bonnie West; Jan 3rd, 2013 at 11:47 AM.
    On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
    Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)

  3. #3

    Thread Starter
    Lively Member
    Join Date
    Sep 2009
    Posts
    95

    Re: LoadImage issue

    Quote Originally Posted by Bonnie West View Post
    In my XP system, your code works as expected.
    Interesting. It seems, that on Win7 the 16x16 pixel icon cannot be loaded in this particular project, if the LR_SHARED flag is used.

    I see the icon that's missing from your screenshot whether I run from the IDE or compiled.
    Running it from the IDE does show the icon, because in this case the VB-LoadResPicture function is used.

    You might want to try this modification to your LoadFromResource routine.

    Code:
     
    Public Function LoadFromResource(ByRef ResName As Variant) As Boolean
       'Resource file has to be added to this library project!
       'pic has to be static, to not be destroyed if the routine quits
        Static Pic As StdPicture
        Dim lpszName As Long
    
       'The icon will be destroyed if you load a new one or if the class is terminated
        Call FreeIconMemory
    
        If App.LogMode Then
            Select Case VarType(ResName)
                Case vbString:  lpszName = StrPtr(ResName)
                Case vbInteger: lpszName = CLng(ResName)
                Case Else:      Err.Raise 5& 'Invalid procedure call or argument
            End Select
    
            m_hIcon = LoadImage(App.hInstance, lpszName, IMAGE_ICON, 32&, 32&, LR_SHARED)
        Else
            On Error Resume Next
            Set Pic = LoadResPicture(ResName, vbResIcon)
            On Error GoTo 0
    
            If Pic Is Nothing Then m_hIcon = 0& Else m_hIcon = Pic.Handle
        End If
    
        LoadFromResource = m_hIcon <> 0&
    End Function
    Yes, I was thinking about this. Normally I don't like Variant arguments, since the user has no information about what parameter types are allowed.
    But in this particular case, your suggestion would probably preferable.

    According to MSDN, the MAKEINTRESOURCE macro returns the specified value in the low-order word and zero in the high-order word. Although it returns a C/C++ string, it is technically not the same as a VB string (LPTSTR vs BSTR). However, both data types are really just pointers to the real strings. Since 32-bit pointers are of the same size as a Long data type, what MAKEINTRESOURCE(intResID) return is the same as MAKELONG(intResID, 0), which can be further simplified as just intResID. A positive Integer coerced to a Long will contain 0 in the high word.
    I wanted to stay close the the MSDN recommendation. I copied the makro from somewhere else and did not fully understand its function:
    Code:
    '—— Makro ———————————————————————————————————————————————————————————————————————
    Private Function LOWORD(ByVal dwValue As Long) As Long
        Call CopyMemory(LOWORD, dwValue, 2)
    End Function
    
    Private Function MAKELONG(ByVal wLow As Long, ByVal wHi As Long) As Long
        If (wHi And &H8000&) Then
            MAKELONG = (((wHi And &H7FFF&) * 65536) Or (wLow And &HFFFF&)) Or &H80000000
        Else
            MAKELONG = LOWORD(wLow) Or (&H10000 * LOWORD(wHi))
        End If
    End Function
    
    Private Function MAKEINTRESOURCE(ByVal lID As Long) As String
        MAKEINTRESOURCE = "#" & CStr(MAKELONG(lID, 0))
    End Function
    '————————————————————————————————————————————————————————————————————————————————
    Maybe it is giving some security, if the user is passing a value exeeding the range of an Integer type...
    But the makro is also adding a pound sign, so your code should not work. - Did you test it?

    EDIT:
    Yes, your code is working. - But I don't understand why, since the MSDN is telling:
    If IS_INTRESOURCE(lpszName) is TRUE, then lpszName specifies the integer identifier of the given resource. Otherwise, it is a pointer to a null- terminated string. If the first character of the string is a pound sign (#), then the remaining characters represent a decimal number that specifies the integer identifier of the resource. For example, the string "#258" represents the identifier 258.


    LoadIcon is an older and simpler API than LoadImage. LoadIconMetric, on the other hand, is not available on systems earlier than Vista. LoadImage is the most versatile of the three and is the one recommended by Microsoft.
    Ok, thanks. I agree with you.
    Since my class is loading the SM_CXSMICON sized icon if a size of -1 and the SM_CXICON if a size of 0 got specified, it also has the advantages of LoadIconMetric.
    Do you think, that there might be the need for loading non-square icons - my class is currently not able to load a 15x16 pixel icon for instance.
    Btw: what is the LoadImage API doing, if a requested size is not available?
    Last edited by NeedHelp!; Jan 3rd, 2013 at 05:38 PM.

  4. #4
    Default Member Bonnie West's Avatar
    Join Date
    Jun 2012
    Location
    InIDE
    Posts
    4,060

    Re: LoadImage issue

    Quote Originally Posted by NeedHelp! View Post
    It seems, that on Win7 the 16x16 pixel icon cannot be loaded in this particular project, if the LR_SHARED flag is used.
    I don't think that flag is the culprit. It may be something else.

    Quote Originally Posted by NeedHelp! View Post
    Normally I don't like Variant arguments, since the user has no information about what parameter types are allowed.
    It was meant to provide a syntax similar to LoadResPicture, so the programmer is probably already aware of what to pass.

    Quote Originally Posted by NeedHelp! View Post
    I wanted to stay close the the MSDN recommendation. I copied the makro from somewhere else and did not fully understand its function:
    MAKEINTRESOURCE is defined in Winuser.h as:

    Code:
     
    #define MAKEINTRESOURCEA(i) ((LPSTR)((ULONG_PTR)((WORD)(i))))
    #define MAKEINTRESOURCEW(i) ((LPWSTR)((ULONG_PTR)((WORD)(i))))
    I'm not well-versed with C/C++, but I think that what that macro does is cast the Integer (Word in C/C++) i to a Long (ULONG_PTR) and then cast it again to a C/C++ string (LPSTR/LPWSTR). So, essentially, MAKEINTRESOURCE takes an Integer and puts it in the low word of a Long.

    Quote Originally Posted by NeedHelp! View Post
    ... But I don't understand why, since the MSDN is telling:

    If IS_INTRESOURCE(lpszName) is TRUE, then lpszName specifies the integer identifier of the given resource. Otherwise, it is a pointer to a null-terminated string. If the first character of the string is a pound sign (#), then the remaining characters represent a decimal number that specifies the integer identifier of the resource. For example, the string "#258" represents the identifier 258.
    Code:
     
    #define IS_INTRESOURCE(_r) ((((ULONG_PTR)(_r)) >> 16) == 0)
    IS_INTRESOURCE examines the high word of the pointer variable to see if it is 0. I think pointer values usually start at &H10000. So, if the passed pointer value doesn't have any of its high word bits set, then it is assumed to be an integer identifier for a resource.

    You've probably overlooked "Otherwise, it is a pointer to a null-terminated string. If the first character of the string is a pound sign (#), ..."

    The lpszName parameter accepts two kinds of input. First, as its prefix implies, it accepts a pointer to a string. That string may either be the name of an image resource or a filename. Second, lpszName also accepts either the image ordinal or an OEM image (both are Integer/Word). MSDN specified that the MAKEINTRESOURCE macro should be used to convert the passed Integer value to the data type of the lpszName argument (which is a pointer). MAKEINTRESOURCE takes an Integer but it does not turn that into a pointer to a string. Rather, it places the Integer in the low word of the pointer value output.

    Thus, the best way of declaring the LoadImage API in VB is by using the Unicode version. You'll get the flexibility of accepting either a String or Integer. Also, you won't need the MAKEINTRESOURCE macro anymore, as I've demonstrated in post #2.

    Quote Originally Posted by NeedHelp! View Post
    Do you think, that there might be the need for loading non-square icons - my class is currently not able to load a 15x16 pixel icon for instance.
    If you get it to work properly, then why not?

    Quote Originally Posted by NeedHelp! View Post
    Btw: what is the LoadImage API doing, if a requested size is not available?
    Testing showed that it scaled the image.
    On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
    Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)

  5. #5

    Thread Starter
    Lively Member
    Join Date
    Sep 2009
    Posts
    95

    Re: LoadImage issue

    Quote Originally Posted by Bonnie West View Post
    I don't think that flag is the culprit. It may be something else.
    But in fact it was the evildoer:

    The example project used following LR_SHARED definition:
    Code:
    Private Const LR_SHARED As Long = &H8000&
    And the ApiViewer gave me this LR_SHARED definition (without the ampersand at the end):
    Code:
    Private Const LR_SHARED As Long = &H8000
    Thankfully, I got the new VBFusion addin today, which did call attention to this problem by providing following code alert:
    Attachment 94841




    It was meant to provide a syntax similar to LoadResPicture, so the programmer is probably already aware of what to pass.
    Sure, but with a Variant we can get a runtime error, which we would get at compile time, if we use the actual needed type - I think this is preferable.
    But here the Variant type is needed, because we could have a INT-resource 101 and a non-INT-resource named "101" at the same time for instance.
    With using a String as parameter type, we can load only one of these two resources - with a Variant its no problem.



    IS_INTRESOURCE examines the high word of the pointer variable to see if it is 0. I think pointer values usually start at &H10000. So, if the passed pointer value doesn't have any of its high word bits set, then it is assumed to be an integer identifier for a resource.

    You've probably overlooked "Otherwise, it is a pointer to a null-terminated string. If the first character of the string is a pound sign (#), ..."

    The lpszName parameter accepts two kinds of input. First, as its prefix implies, it accepts a pointer to a string. That string may either be the name of an image resource or a filename. Second, lpszName also accepts either the image ordinal or an OEM image (both are Integer/Word). MSDN specified that the MAKEINTRESOURCE macro should be used to convert the passed Integer value to the data type of the lpszName argument (which is a pointer). MAKEINTRESOURCE takes an Integer but it does not turn that into a pointer to a string. Rather, it places the Integer in the low word of the pointer value output.
    Thus, the best way of declaring the LoadImage API in VB is by using the Unicode version. You'll get the flexibility of accepting either a String or Integer. Also, you won't need the MAKEINTRESOURCE macro anymore, as I've demonstrated in post #2.
    So LoadImage lpszName parameter can can be:
    1.) a pointer to a null-terminated string, containing the resource file path,
    2.) a pointer to a null-terminated string, containing the resource name,
    3.) a pointer to a null-terminated string, containing the resource ordinal with prefixed pound sign and
    4.) a Long value with the resources ordinal in the low word
    => Basically two methods for passing the ordinal information. Correct?



    If you get it to work properly, then why not?
    Getting it to work should not be the problem, but we need another size parameter for this, which makes the whole thing not so clear for the user, because with the current size parameter it is possiible to give following information instead of the acutal icon size (changed it recently):
    IS_First = 0 -> loads the first icon, independently from its size (used this as default)
    IS_Small = -1 -> loads the icon with system small size (usuallly 16x16)
    IS_Large = -2 -> loads the icon with system large size (usually 32x32)

    If we have a second such parameter, we can get in conflict:
    Code:
    Ico.LoadFromResource("Icon_0", IS_First, IS_Large)
    => What icon to load here? - The first or the system large icon?
    Ok, an error can be raised in this situation, but I thought, I can simplify it with just one parameter, since I never saw a non-square icon in practice.
    I was wondering, if you know a case, where a non-square icon got used.



    Testing showed that it scaled the image.
    Yes, but I couldn't see, if the function takes
    - the largest icon and scales it down
    - the next larger icon and scales it down
    - the most similar sized icon and scales it up or down
    - the last/first icon and scales it up/down
    - or a random icon and scales it up/down

    In most cases the difference would probably not very visible, but the various icons of an icon set can have completely different images, which makes this information interesting.
    To check this, I have to create a special icon set with different icons - so I am asking first. (But from your last answer I see, that you have to test it too.)



    PS:
    How do you make the VB code format using the typical colors?

  6. #6
    Default Member Bonnie West's Avatar
    Join Date
    Jun 2012
    Location
    InIDE
    Posts
    4,060

    Re: LoadImage issue

    Quote Originally Posted by NeedHelp! View Post
    And the ApiViewer gave me this LR_SHARED definition (without the ampersand at the end):
    For all values from &H8000 to &HFFFF, the literal must be closed off with the type-declaration character for Long (& ampersand) to explicitly type it as such, lest it will be treated as a negative Integer.

    Quote Originally Posted by NeedHelp! View Post
    Sure, but with a Variant we can get a runtime error, which we would get at compile time, if we use the actual needed type
    Using a data type other than a Variant doesn't mean you're safe from implicit type conversion. Consider this:

    Code:
     
    Private Sub Form_Load()
        Call Routine("101", 101%)
    End Sub
    
    Private Sub Routine(ByVal nParam As Integer, ByRef sParam As String)
        Stop: End      'Add both Param arguments to the Watch Window
    End Sub            'Note their values and types
    Quote Originally Posted by NeedHelp! View Post
    Basically two methods for passing the ordinal information. Correct?
    Correct!

    Quote Originally Posted by NeedHelp! View Post
    ... since I never saw a non-square icon in practice.
    Me neither.
    On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
    Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)

  7. #7

    Thread Starter
    Lively Member
    Join Date
    Sep 2009
    Posts
    95

    LoadImage issue #2

    Quote Originally Posted by Bonnie West View Post
    For all values from &H8000 to &HFFFF, the literal must be closed off with the type-declaration character for Long (& ampersand) to explicitly type it as such, lest it will be treated as a negative Integer.
    Yes, normally I do add the ampersand to all hex values to ensure, that it is not a negative Integer.
    But I used the ApiViewer to generate the LR_SHARED declaration and just pasted it without any change...

    Using a data type other than a Variant doesn't mean you're safe from implicit type conversion.
    That is true - but at least I can see the needed type by pressing Ctrl+i.




    Unfortunatelly I have another issue regarding LoadImage:

    In both projects (linked in my first post), the code is using the API ExtractIconEx to get an icon from an EXE or DLL file:
    Code:
    Public Function LoadFromLibrary(ByVal Library As String, ByVal lIconIndex As Long) As Boolean
        Call FreeIconMemory
        Call ExtractIconEx(StrPtr(Library), lIconIndex, m_hIcon, ByVal 0&, 1)
        LoadFromLibrary = CBool(m_hIcon)
    End Function
    But with this API we can get either the system large or system small icon only.
    And we cannot use a resource name or ordinal to retrive the right icon - we have to use the index instead.
    Additionally this API is not capable to load bitmap resources like LoadImage.
    Therefore I wanted to apply LoadImage here too. - But following code is not working:
    Code:
    Public Function LoadFromLibrary2(ByVal Library As String, _
                                     ByRef ResNameOrOrdinal As Variant, _
                                     Optional Size As IconSizeEnum = IS_First) As Boolean
    
        ' Destroy the previous Icon, if a new one is loaded
        Call FreeIconMemory
        
        Dim hMod As Long
        hMod = LoadLibrary(StrPtr(Library))
        Call MsgBox("hMod             = " & hMod & vbCrLf & _
                    "Err.LastDllError = " & Err.LastDllError)
        hMod = GetModuleHandle(StrPtr(Library))     'no FreeLibrary!
        Call MsgBox("hMod             = " & hMod & vbCrLf & _
                    "Err.LastDllError = " & Err.LastDllError)
        
        Dim lpszName As Long
        Select Case VarType(ResNameOrOrdinal)
            Case vbString:  lpszName = StrPtr(ResNameOrOrdinal)
            Case vbInteger: lpszName = CLng(ResNameOrOrdinal)
            Case Else:      Call Err.Raise(5&)  'Invalid procedure call or argument
        End Select
            
        Dim tSize As Size
        tSize = GetSize(Size)
        
        ' Do not use LR_SHARED for images that have non-standard sizes,
        ' that may change after loading, or that are loaded from a file.
        m_hIcon = LoadImage(hMod, _
                            lpszName, _
                            IMAGE_ICON, _
                            tSize.cx, _
                            tSize.cy, _
                            LR_DEFAULTCOLOR)
    
        Call MsgBox("m_hIcon          = " & m_hIcon & vbCrLf & _
                    "Err.LastDllError = " & Err.LastDllError)
    
        Call FreeLibrary(hMod)
    
        LoadFromLibrary2 = CBool(m_hIcon)
    End Function
    I tried it with GetModuleHandle, like recommended in the MSDN article to LoadImage and with LoadLibrary, in case the module has not been loaded yet.
    Both APIs return the same handle, but the first call to one of them (it doesn't matter if GetModuleHandle or LoadLibrary is called first) does also show a dll error: 1402
    The LoadImage API then returns 0 and gives the dll error 1813. (I tried it with "shell32.dll" for Library and the value 130 for ResNameOrOrdinal)

    Maybe there is no icon with ordinal 130... How can I check, which icons are in there?
    With ResHacker I am just seeing lots of folders under "Icon" with a number as name containing an icon with the name "1033".
    Under "Icon Group" I can find some ordinal information, but I still see the above mentiond result, if I use them.

    I don't see my mistake - I did everything like stated in the MSDN article (at least I think so)...
    Can you please help me here too?
    Last edited by NeedHelp!; Jan 5th, 2013 at 06:10 AM.

  8. #8
    Default Member Bonnie West's Avatar
    Join Date
    Jun 2012
    Location
    InIDE
    Posts
    4,060

    Re: LoadImage issue #2

    Quote Originally Posted by NeedHelp! View Post
    And we cannot use a resource name or ordinal to retrive the right icon - we have to use the index instead.
    nIconIndex [in]
    Type: int

    . . .

    If this value is a negative number and either phiconLarge or phiconSmall is not NULL, the function begins by extracting the icon whose resource identifier is equal to the absolute value of nIconIndex. For example, use -3 to extract the icon whose resource identifier is 3.


    Quote Originally Posted by NeedHelp! View Post
    Therefore I wanted to apply LoadImage here too. - But following code is not working:
    The following alterations worked for me (not too different from yours, really):

    Code:
     
    Private Declare Function FormatMessage Lib "kernel32.dll" Alias "FormatMessageW" (ByVal dwFlags As Long, ByVal lpSource As Long, ByVal dwMessageId As Long, ByVal dwLanguageId As Long, ByVal lpBuffer As Long, ByVal nSize As Long, Optional ByVal Arguments As Long) As Long
    Private Declare Sub SetLastError Lib "kernel32.dll" (ByVal dwErrCode As Long)
    
    Public Function LoadFromLibrary2(ByRef Library As String, _
                                     ByRef ResNameOrOrdinal As Variant, _
                            Optional ByVal Size As IconSizeEnum = IS_First) As Boolean
       'Always pass Strings ByRef, unless intentionally modifying them. Passing ByVal
       'makes a copy, slowing down code. Variants are more efficiently passed ByRef too.
       'For most other data types (Byte, Integer, Long, Single, etc.), ByVal is always faster.
    
       'Local variables are still allocated whether they were used or not
       'Might as well declare them all in one place
        Dim hLib As Long, hMod As Long, lpszName As Long, tSize As Size
    
        Call FreeIconMemory    'Destroy the previous Icon, if a new one is loaded
    
        Call SetLastError(0&)  'Reset LastDllError to avoid retrieving previous error code
        hLib = LoadLibrary(StrPtr(Library))
        MsgBox """" & GetErrorMsg(Err.LastDllError) & """", vbInformation, "hLib = &H" & Hex$(hLib)
    
        Call SetLastError(0&)  'SetLastError used only for debugging; may be removed if desired
        hMod = GetModuleHandle(StrPtr(Library))     'Don't FreeLibrary!
        MsgBox """" & GetErrorMsg(Err.LastDllError) & """", vbInformation, "hMod = &H" & Hex$(hMod)
    
        Select Case VarType(ResNameOrOrdinal)
            Case vbString:  lpszName = StrPtr(ResNameOrOrdinal)
            Case vbInteger: lpszName = CLng(ResNameOrOrdinal)
            Case Else:      FreeLibrary hLib    'Free the module first
                            Call Err.Raise(5&)  'Invalid procedure call or argument
        End Select
    
        tSize = GetSize(Size)
    
       'Some API's don't reset LastDllError upon success
        Call SetLastError(0&)
       'Do not use LR_SHARED for images that have non-standard sizes,
       'that may change after loading, or that are loaded from a file.
        m_hIcon = LoadImage(hMod, lpszName, IMAGE_ICON, tSize.cx, tSize.cy, LR_DEFAULTCOLOR)
        MsgBox """" & GetErrorMsg(Err.LastDllError) & """", vbInformation, "m_hIcon = &H" & Hex$(m_hIcon)
    
        hLib = FreeLibrary(hLib): Debug.Assert hLib 'If code stops here, the Library wasn't freed
    
        LoadFromLibrary2 = m_hIcon <> 0&     'An inline expression is faster than a function call
    End Function
    
    Private Function GetErrorMsg(ByVal ErrNum As Long) As String
        Const FORMAT_MESSAGE_FROM_SYSTEM = &H1000&, MAX_BUFFER = &H10000
    
        GetErrorMsg = Space$(MAX_BUFFER - 1&)
        GetErrorMsg = Left$(GetErrorMsg, FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, _
                      0&, ErrNum, 0&, StrPtr(GetErrorMsg), MAX_BUFFER) - 2&)
    End Function
    Code:
     
    Private Sub Form_Load()
    
        . . .
    
        With New Icon
            .LoadFromLibrary2 "shell32.dll", 167, SIZE_Large
            .DrawIcon hDC, ScaleX(Width, ScaleMode, vbPixels) - (.Width * 1.5!)
        End With
    End Sub
    Quote Originally Posted by NeedHelp! View Post
    I tried it with GetModuleHandle, like recommended in the MSDN article to LoadImage and with LoadLibrary, in case the module has not been loaded yet.
    Both APIs return the same handle, but the first call to one of them (it doesn't matter if GetModuleHandle or LoadLibrary is called first) does also show a dll error: 1402
    The LoadImage API then returns 0 and gives the dll error 1813. (I tried it with "shell32.dll" for Library and the value 130 for ResNameOrOrdinal)
    Reset the LastDllError (with SetLastError) prior to calling the desired API to make sure the error you're getting was set by it.

    Quote Originally Posted by NeedHelp! View Post
    Maybe there is no icon with ordinal 130...
    Indeed, there is no such icon resource in shell32.dll.

    Quote Originally Posted by NeedHelp! View Post
    How can I check, which icons are in there?
    With ResHacker I am just seeing lots of folders under "Icon" with a number as name containing an icon with the name "1033".
    Under "Icon Group" I can find some ordinal information, but I still see the above mentiond result, if I use them.
    Icon ordinals and names are listed under Icon Group. I believe 1033 is the English (US) language ID. Try Resource Hacker FX instead (unfortunately, the original site isn't available anymore). Scan the exe first on VirusTotal.
    Last edited by Bonnie West; Jan 5th, 2013 at 01:16 PM.
    On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
    Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)

  9. #9

    Thread Starter
    Lively Member
    Join Date
    Sep 2009
    Posts
    95

    Re: LoadImage issue #2

    Quote Originally Posted by Bonnie West View Post
    The following alterations worked for me (not too different from yours, really):
    Yes, with ordinal 167 also my code works. - But thank you for your exploratory additions. They are very helpful.


    Code:
     
    Private Sub Form_Load()
    
        . . .
    
        With New Icon
            .LoadFromLibrary2 "shell32.dll", 167, SIZE_Large
            .DrawIcon hDC, ScaleX(Width, ScaleMode, vbPixels) - (.Width * 1.5!)
        End With
    End Sub
    Here I would set the ScaleMode of the Form to pixels and use its ScaleWidth property instead:
    Code:
        With New Icon
            Call .LoadFromLibrary2("shell32.dll", 167, IS_Large)
            Call .DrawIcon(Me.hDC, Me.ScaleWidth - .Width)
        End With

    Reset the LastDllError (with SetLastError) prior to calling the desired API to make sure the error you're getting was set by it.
    Ok, this is an important information.
    Someone told me, to use Err.LastDllError instead of the API GetLastError in VB6, because the VB runtime is calling APIs itself, which may influence the error code returned form GetLastError.
    Maybe its not very likely, but this would also mean, that after setting the error code to zero with Call SetLastError(0&), the VB runtime can create another error by calling an API before I do call my desired API from VB6. Or is this impossible, if I call SetLastError directly before my API call?

    Anyway... if I use Err.LastDllError, it would be nice to clear the dll error without APIs too. Unfortunatelly Err.Clear is not doing the job - these are my reproducable results from my tests:
    Code:
        Call Err.Clear                          ' Does not clear error 1402
        Err.Number = 0                          ' Does not clear error 1402
        Dim s As String: s = Err.LastDllError   ' Does clear the error
        Err.Description = ""                    ' Does clear the error
    But is it safe to do this?



    Indeed, there is no such icon resource in shell32.dll.
    Ok, this explains the result of my code. But not following screen shots:
    Name:  Res167.png
Views: 1787
Size:  180.6 KB
    Here we see the working icon with identifier 167, but the tool does also show a resource for ID 300:
    Name:  Res300.png
Views: 1702
Size:  161.6 KB
    I don't understand this... Do you know the reason for this?


    Icon ordinals and names are listed under Icon Group.
    What is the difference between Icon and Icon Group?
    Resource Hacker does show both, XN Resource Editor just Icon Group and Melanders Resource Editor Icon only.
    Does this mean, that only Resource Hacker is showing all icons? - Or can both icon types be shown under one category too?
    I am wondering this, because the MSDN is speaking about icon group too: "If the file is an executable file or DLL, the return value is the number of RT_GROUP_ICON resources."

    I believe 1033 is the English (US) language ID.
    Yes, I saw "English (USA)" mentioned for these icons in the Resoure Editor
    But how can an icon be connected to a language? - They are to small to show text...

    Try Resource Hacker FX instead (unfortunately, the original site isn't available anymore).
    Resource Hacker FX is just a patcher for Resource Hacker v3.5.2 beta. But on the Resource Hacker site we can get version 3.6.0 only, to which the patcher is not compatible.
    It also says, that Resource Hacker will not be continued or made open source. Instead it suggests to use either the open source XN Resource Editor (which fails to downlaod on that site), or Melanders Resource Editor. Since XN Resource Editor does also show the icons in folders, I choose Melander Resource Editor. It does even not need to be installed: just run the executable. It crashed directly after the very first start, but is doing its job well since then. It is a little confusing to arrange the different views in the right way, but it is way more powerful than Resource Hacker. You might to try it out. - But there is one downside: both alternatives are not as fast as Resource Hacker! For just a quick look into the resouces without editing, it is still the best. Would you mind to upload your patched executable for me?

    Scan the exe first on VirusTotal.
    All said, it is clean - just my anti virus (comodo) did alert maleware :|

  10. #10
    Default Member Bonnie West's Avatar
    Join Date
    Jun 2012
    Location
    InIDE
    Posts
    4,060

    Re: LoadImage issue #2

    Quote Originally Posted by NeedHelp! View Post
    Someone told me, to use Err.LastDllError instead of the API GetLastError in VB6, because the VB runtime is calling APIs itself, which may influence the error code returned form GetLastError.
    Maybe its not very likely, but this would also mean, that after setting the error code to zero with Call SetLastError(0&), the VB runtime can create another error by calling an API before I do call my desired API from VB6. Or is this impossible, if I call SetLastError directly before my API call?
    Whenever you call an API that was declared through the Declare statement, VB immediately calls the GetLastError function afterwards and stores the return value in the Err object's LastDllError property. API functions that do not return a value are not expected to fail, hence they do not set LastDllError. It's possible for VB to internally call an API that fails and thus erases the previous error code. For example, you SetLastError to 0 and then you call VB's MsgBox function (MessageBox API) which somehow fails. Afterwards, you then call another API (which succeeds) and check Err.LastDllError. The value you'll get may either be 0 (if the API set the last error code on success) or the MsgBox's last error. Therefore, you should call SetLastError just before the API whose LastDllError value you would like to test. Also, LastDllError is usually only inspected when an API function failed.

    Quote Originally Posted by NeedHelp! View Post
    Anyway... if I use Err.LastDllError, it would be nice to clear the dll error without APIs too. Unfortunatelly Err.Clear is not doing the job - these are my reproducable results from my tests:

    . . .

    But is it safe to do this?
    SetLastError and SetLastErrorEx are the official functions for setting the last error code. Dimensioning a String calls one of the String Manipulation Functions which most likely reset the last error code when it succeeded. Interestingly, Err.Clear does seem to reset the last error code. Here are my tests:

    Code:
     
    Private Sub Main()
        Debug.Print Err.LastDllError
        SetLastError 100&
        Debug.Print Err.LastDllError
        Err.Clear
        Debug.Print GetLastError
        Debug.Print Err.LastDllError
    End Sub
    Code:
     
     0 
     100 
     0 
     0
    SetLastError and GetLastError were defined in a Type Library so as to directly call them and prevent VB from calling GetLastError again. But even if they were declared by the Declare statement, the results were still the same.

    Quote Originally Posted by NeedHelp! View Post
    Here we see the working icon with identifier 167, but the tool does also show a resource for ID 300:

    I don't understand this... Do you know the reason for this?
    In my system, Resource Hacker confirms that shell32.dll has an icon resource with ID 300. It looks almost the same as in your screenshot.

    Quote Originally Posted by NeedHelp! View Post
    What is the difference between Icon and Icon Group?
    Resource Hacker does show both, XN Resource Editor just Icon Group and Melanders Resource Editor Icon only.
    Does this mean, that only Resource Hacker is showing all icons? - Or can both icon types be shown under one category too?
    I am wondering this, because the MSDN is speaking about icon group too: "If the file is an executable file or DLL, the return value is the number of RT_GROUP_ICON resources."
    From Icons:

    The term icon can refer to either of the following:

    • A single icon image. This is a resource of type RT_ICON.
    • A group of images, from which the system or an application can choose the most appropriate icon based on size and color depth. This is a resource of type RT_GROUP_ICON.
    Quote Originally Posted by NeedHelp! View Post
    But how can an icon be connected to a language? - They are to small to show text...
    Well, sometimes it's useful to have different images for different locales.

    Quote Originally Posted by NeedHelp! View Post
    You might to try it out.
    I will!

    Quote Originally Posted by NeedHelp! View Post
    Would you mind to upload your patched executable for me?
    Sure! Download the original installer here. This is the exe's latest scan by VirusTotal.
    On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
    Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)

  11. #11

    Thread Starter
    Lively Member
    Join Date
    Sep 2009
    Posts
    95

    Re: LoadImage issue #2

    Quote Originally Posted by Bonnie West View Post
    Whenever you call an API that was declared through the Declare statement, VB immediately calls the GetLastError function afterwards and stores the return value in the Err object's LastDllError property. API functions that do not return a value are not expected to fail, hence they do not set LastDllError. It's possible for VB to internally call an API that fails and thus erases the previous error code. For example, you SetLastError to 0 and then you call VB's MsgBox function (MessageBox API) which somehow fails. Afterwards, you then call another API (which succeeds) and check Err.LastDllError. The value you'll get may either be 0 (if the API set the last error code on success) or the MsgBox's last error. Therefore, you should call SetLastError just before the API whose LastDllError value you would like to test. Also, LastDllError is usually only inspected when an API function failed.
    Thanks for the explaination.

    SetLastError and SetLastErrorEx are the official functions for setting the last error code. Dimensioning a String calls one of the String Manipulation Functions which most likely reset the last error code when it succeeded. Interestingly, Err.Clear does seem to reset the last error code. Here are my tests:
    Code:
     
    Private Sub Main()
        Debug.Print Err.LastDllError    ' 0 
        SetLastError 100&
        Debug.Print Err.LastDllError    ' 100 
        Err.Clear
        Debug.Print GetLastError        ' 0 
        Debug.Print Err.LastDllError    ' 0 
    End Sub
    I tried your code and I can confirm: Err.Clear does delete the 100, Debug.Print Err.LastDllError does not.
    But now compare with my screen shots (it shows always the first message box):
    Name:  Err.Clear.png
Views: 1571
Size:  44.1 KB
    Name:  Err.LastllError.png
Views: 1646
Size:  49.2 KB
    Very strange! Another thing, which is not explainable on first sight... ;(
    Maybe the behavoir depends on the error code.
    But it might answer my question, if it is save to use such a line instead of SetLastError.


    SetLastError and GetLastError were defined in a Type Library so as to directly call them and prevent VB from calling GetLastError again. But even if they were declared by the Declare statement, the results were still the same.
    Do I have to add this Type Library additionally? - I had to declare SetLastError.


    In my system, Resource Hacker confirms that shell32.dll has an icon resource with ID 300. It looks almost the same as in your screenshot.
    Sorry, my bad! - Icon #130 was the one, which does not exist. I thought it was 300 and was wondering, why it could be loaded this time, even that there was no big change in the code.


    Well, sometimes it's useful to have different images for different locales.
    Ok, I am convinced: flag icons for instance. - Will LoadImage automatically load the right icon, or have all icons of an icon group the same local setting always?


    Ok, now I have almost everything:
    - The first icon load problem occured because of a wrong LR_SHARED declaration.
    - The second icon load problem was actually none: I mixed #300 with #130...
    Actually all no big deals. But there was a lot new information for me from you. So thanks again!

    There is just ome more question:
    It does not make sence to call both: LoadLibrary and GetModuleHandle.
    I suppose GetModuleHandle does only work for system libraries, which are loaded already - but the function has no clue, from what kind of libraries the user wants to extract icons.
    I do also suppose, that calling LoadLibrary is quite more costly than to call GetModuleHandle.
    => Therefore I would call GetModuleHandle first - and try it with LoadLibrary, if GetModuleHandle fails. Then free the library again in case it got loaded before exit.
    Would you agree with this?

  12. #12
    Default Member Bonnie West's Avatar
    Join Date
    Jun 2012
    Location
    InIDE
    Posts
    4,060

    Re: LoadImage issue #2

    Quote Originally Posted by NeedHelp! View Post
    But it might answer my question, if it is safe to use such a line instead of SetLastError.
    I highly recommend that you use only SetLastError or SetLastErrorEx when explicitly setting the last error value. Any other means is not officially documented and probably unintended.

    Quote Originally Posted by NeedHelp! View Post
    Do I have to add this Type Library additionally? - I had to declare SetLastError.
    You don't have to. I only tried to eliminate the middleman (the Declare statement) to allow calling GetLastError meaningfully. SetLastError can be safely called with the Declare statement.

    Quote Originally Posted by NeedHelp! View Post
    Will LoadImage automatically load the right icon, or have all icons of an icon group the same local setting always?
    You don't have to worry about the right Language ID when using resources. Windows takes care of loading the appropriate Language ID for a resource based on the current locale.

    Quote Originally Posted by NeedHelp! View Post
    There is just ome more question:
    It does not make sence to call both: LoadLibrary and GetModuleHandle.
    I suppose GetModuleHandle does only work for system libraries, which are loaded already - but the function has no clue, from what kind of libraries the user wants to extract icons.
    I do also suppose, that calling LoadLibrary is quite more costly than to call GetModuleHandle.
    => Therefore I would call GetModuleHandle first - and try it with LoadLibrary, if GetModuleHandle fails. Then free the library again in case it got loaded before exit.
    Would you agree with this?
    Trial and error is one way of finding out if your method will work. The documentation for GetModuleHandle does not mention whether LoadLibrary needs to be called for modules not yet loaded in the address space of the calling process, so I don't know if it's really needed.
    On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
    Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width