Calling Windows API from Julia

Hi Julians,

I am trying to implement a script that will list all Fonts installed on Windows using Gdi32.dll.

First of all, here is the full script so you can make sense of what i’m about to write below: https://github.com/gabrielfreire/Fonts/blob/master/fonts.jl

All my C calls are working fine,

my problem is that when i receive ENUMLOGFONTA in the callback i pass to EnumFontFamiliesW and try to unsafe_string the lplf.elfFullName property it throws an EXCEPTION_ACCESS_VIOLATION exception, even though i am using Ref{ENUMLOGFONTA} to declare my callback @cfunction, i’m not sure if i understood the documentation correctly, but i believe Ref is supposed to avoid C from freeing that pointer and let me load it later, right?

same happens for lplf.elfLogFont.lfFaceName.

function enum_fam_callback(lplf::ENUMLOGFONTA, lpntm::NEWTEXTMETRICA, font_type::DWORD, afont_count::LPARAM)
    # Store true type fonts
    if Int32(font_type) == FontType.TrueType
        if lplf != C_NULL
            font = Dict(
                "name"=>unsafe_string(lplf.elfFullName), # this throws EXCEPTION_ACCESS_VIOLATION 
                "quality"=>lplf.elfLogFont.lfQuality, 
                "weight"=>lplf.elfLogFont.lfWeight,
                "faceName"=>lplf.elfLogFont.lfFaceName,
                "fontPitchAndFamily"=>lplf.elfLogFont.lfPitchAndFamily,
                "charSet"=>lplf.elfLogFont.lfCharSet
            )
            push!(fontnames, font)
        end
    end
    # dump(lplf)
    return convert(Cint, 1)::Cint
end

My Julia struct

struct LOGFONTA
    lfHeight::Clong
    lfWidth::Clong
    lfEscapement::Clong
    lfOrientation::Clong
    lfWeight::Clong
    lfItalic::UInt8
    lfUnderline::UInt8
    lfStrikeOut::UInt8
    lfCharSet::UInt8
    lfOutPrecision::UInt8
    lfClipPrecision::UInt8
    lfQuality::UInt8
    lfPitchAndFamily::UInt8
    lfFaceName::Cstring
end

struct ENUMLOGFONTA
    elfLogFont::LOGFONTA
    elfFullName::Cstring
    elfStyle::Cstring
end

Windows API C++ struct

typedef struct tagLOGFONTA {
  LONG lfHeight;
  LONG lfWidth;
  LONG lfEscapement;
  LONG lfOrientation;
  LONG lfWeight;
  BYTE lfItalic;
  BYTE lfUnderline;
  BYTE lfStrikeOut;
  BYTE lfCharSet;
  BYTE lfOutPrecision;
  BYTE lfClipPrecision;
  BYTE lfQuality;
  BYTE lfPitchAndFamily;
  CHAR lfFaceName[LF_FACESIZE];
} LOGFONTA, *PLOGFONTA, *NPLOGFONTA, *LPLOGFONTA;

typedef struct tagENUMLOGFONTA {
  LOGFONTA elfLogFont;
  BYTE     elfFullName[LF_FULLFACESIZE];
  BYTE     elfStyle[LF_FACESIZE];
} ENUMLOGFONTA, *LPENUMLOGFONTA;

I am pretty sure i am doing something wrong, but i can’t find where.
Please, help.

Full error message

Please submit a bug report with steps to reproduce this fault, and any error messages that follow (in their entirety). Thanks.
Exception: EXCEPTION_ACCESS_VIOLATION at 0x7ffeb535d4e0 -- strlen at C:\Windows\System32\msvcrt.dll (unknown line)
in expression starting at C:\Users\gabrielfreiredev\Documents\workspace\Fonts\fonts.jl:158
strlen at C:\Windows\System32\msvcrt.dll (unknown line)
jl_cstr_to_string at /home/Administrator/buildbot/worker/package_win64/build/src/home/Administrator/buildbot/worker/package_win64/build/src\array.c:496
unsafe_string at .\strings\string.jl:57 [inlined]
unsafe_string at .\c.jl:193 [inlined]
enum_fam_callback at C:\Users\gabrielfreiredev\Documents\workspace\Fonts\fonts.jl:134
unknown function (ip: 000000002936C362)
EnumFontFamiliesExW at C:\Windows\System32\gdi32full.dll (unknown line)
EnumFontFamiliesW at C:\Windows\System32\gdi32full.dll (unknown line)
top-level scope at C:\Users\gabrielfreiredev\Documents\workspace\Fonts\fonts.jl:158
jl_toplevel_eval_flex at /home/Administrator/buildbot/worker/package_win64/build/src/home/Administrator/buildbot/worker/package_win64/build/src\toplevel.c:808
jl_parse_eval_all at /home/Administrator/buildbot/worker/package_win64/build/src/home/Administrator/buildbot/worker/package_win64/build/src\ast.c:873
jl_load at /home/Administrator/buildbot/worker/package_win64/build/src/home/Administrator/buildbot/worker/package_win64/build/src\toplevel.c:878 [inlined]
jl_load_ at /home/Administrator/buildbot/worker/package_win64/build/src/home/Administrator/buildbot/worker/package_win64/build/src\toplevel.c:885
include at .\boot.jl:328 [inlined]
include_relative at .\loading.jl:1094
include at .\Base.jl:31
_jl_invoke at /home/Administrator/buildbot/worker/package_win64/build/src/home/Administrator/buildbot/worker/package_win64/build/src\gf.c:2043 [inlined]
jl_apply_generic at /home/Administrator/buildbot/worker/package_win64/build/src/home/Administrator/buildbot/worker/package_win64/build/src\gf.c:2213
exec_options at .\client.jl:295
_start at .\client.jl:464
_jl_invoke at /home/Administrator/buildbot/worker/package_win64/build/src/home/Administrator/buildbot/worker/package_win64/build/src\gf.c:2043 [inlined]
jl_apply_generic at /home/Administrator/buildbot/worker/package_win64/build/src/home/Administrator/buildbot/worker/package_win64/build/src\gf.c:2213
jl_apply at /home/Administrator/buildbot/worker/package_win64/build/ui/home/Administrator/buildbot/worker/package_win64/build/src\julia.h:1624 [inlined]
true_main at /home/Administrator/buildbot/worker/package_win64/build/ui/home/Administrator/buildbot/worker/package_win64/build/ui\repl.c:96
wmain at /home/Administrator/buildbot/worker/package_win64/build/ui/home/Administrator/buildbot/worker/package_win64/build/ui\repl.c:217
__tmainCRTStartup at /usr/src/debug/mingw64-x86_64-runtime-6.0.0-1/usr/src/debug/mingw64-x86_64-runtime-6.0.0-1/crt\crtexe.c:334
mainCRTStartup at /usr/src/debug/mingw64-x86_64-runtime-6.0.0-1/usr/src/debug/mingw64-x86_64-runtime-6.0.0-1/crt\crtexe.c:223
BaseThreadInitThunk at C:\Windows\System32\KERNEL32.DLL (unknown line)
RtlUserThreadStart at C:\Windows\SYSTEM32\ntdll.dll (unknown line)
Allocations: 2732207 (Pool: 2731174; Big: 1033); GC: 5

Thank you.

1 Like

The callback function should accept pointers as the first two arguments:

https://docs.microsoft.com/en-us/previous-versions//dd162621(v=vs.85)

(you could then use Base.fieldoffset to get the offset to pass to unsafe_load in order to dereference a given field – though there may be a simpler way these days)

1 Like

Thank you @ihnorton, i haven’t had the chance to properly try this yet, only tried once really quickly and didn’t manage to load the field, but i’ll try more and let you know.

Thanks =)

Hi @ihnorton, thank you again for your response, but i didn’t manage to make that work

the offset index for the elfFullName in ENUMLOGFONTA is 2.

i get this with fieldoffset

fieldoffset(ENUMLOGFONTA, 2) => 0x0000000000000028

unsafe_load does’t seem to be able to receive this kind of input, so i am using the field index

fullname = unsafe_load(lplf, 2)

But this returns the whole ENUMLOGFONTA object with the wrong values, maybe only right from elfFullName field forward, but not loaded, something like Cstring(0x0000000000610072)

and i keep getting EXCEPTION_ACCESS_VIOLATION when i try to dereference it using unsafe_load or unsafe_string

Thank you again, i hope you or somebody else can help me, i believe this should be very straight forward but i am doing something totally wrong.

P.S. i pushed the changes to github https://github.com/gabrielfreire/Fonts/blob/master/fonts.jl

I think your problem here is that the “string” parts of your C and julia structs don’t match. For example, in the C case, you’ve got

CHAR lfFaceName[LF_FACESIZE];

Which means that lfFaceName is an array of LF_FACESIZE windows CHARs which are embedded within the tagLOGFONTA struct. The equivalent thing on the julia side would be an NTuple of the appropriate character type (__wchar_t?)

    lfFaceName::NTuple{LF_FACESIZE,Cwchar_t}

You can then load this NTuple of windows characters which will then need to be converted to UTF-8 to create a valid String (presumably using transcode).

2 Likes

Hi @Chris_Foster, wooooow!! Thank you so much… it worked <3 <3
I haven’t updated github yet because i’m not at home, but the code changes were

types

const LF_FACESIZE = 32
const LF_FULLFACESIZE = 64

struct LOGFONTA
    lfHeight::Clong
    lfWidth::Clong
    lfEscapement::Clong
    lfOrientation::Clong
    lfWeight::Clong
    lfItalic::UInt8
    lfUnderline::UInt8
    lfStrikeOut::UInt8
    lfCharSet::UInt8
    lfOutPrecision::UInt8
    lfClipPrecision::UInt8
    lfQuality::UInt8
    lfPitchAndFamily::UInt8
    lfFaceName::NTuple{LF_FACESIZE, Cwchar_t}
end

struct ENUMLOGFONTA
    elfLogFont::LOGFONTA
    elfFullName::NTuple{LF_FULLFACESIZE, Cwchar_t}
    elfStyle::NTuple{LF_FACESIZE, Cwchar_t}
end

the callback stays the same

            fn = unsafe_load(lplf)
            font = Dict(
                "fullName"=>fn.elfFullName,
                "style"=>fn.elfStyle,
                "faceName"=>fn.elfLogFont.lfFaceName,
                "quality"=>fn.elfLogFont.lfQuality, 
                "weight"=>fn.elfLogFont.lfWeight,
                "fontPitchAndFamily"=>fn.elfLogFont.lfPitchAndFamily,
                "charSet"=>fn.elfLogFont.lfCharSet
            )

small function to decode stuff

using StringEncodings
function decode_str(encoded::NTuple)::String
    encoded_ = Vector{UInt8}([d for d in encoded])
    utf8enc = transcode(UInt8, encoded_)
    decoded = decode(utf8enc, enc"UTF-8")
    return replace(decoded, "\0"=>"")
end

and the display part looks like this

println("-----------------------------------------")
            @show decode_str(f["fullName"])
            @show decode_str(f["style"])
            @show decode_str(f["faceName"])
            haskey(FontPitchAndFamily, f["fontPitchAndFamily"]) && @show FontPitchAndFamily[f["fontPitchAndFamily"]]
            haskey(FontQuality, f["quality"])                   && @show FontQuality[f["quality"]]
            haskey(FontCharSet, f["charSet"])                   && @show FontCharSet[f["charSet"]]
            haskey(FontWeight, f["weight"])                     && @show FontWeight[f["weight"]]

Some output

-----------------------------------------
decode_str(f["fullName"]) = "Marlett"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Marlett"
FontPitchAndFamily[f["fontPitchAndFamily"]] = "VARIABLE_PITCH"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "SYMBOL_CHARSET"
FontWeight[f["weight"]] = "FW_MEDIUM"
-----------------------------------------
decode_str(f["fullName"]) = "Arial"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Arial"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "ANSI_CHARSET"
FontWeight[f["weight"]] = "FW_NORMAL"
-----------------------------------------
decode_str(f["fullName"]) = "Arial"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Arabic Transparent"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "ANSI_CHARSET"
FontWeight[f["weight"]] = "FW_NORMAL"
-----------------------------------------
decode_str(f["fullName"]) = "Arial"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Arial Baltic"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "BALTIC_CHARSET"
FontWeight[f["weight"]] = "FW_NORMAL"
-----------------------------------------
decode_str(f["fullName"]) = "Arial"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Arial CE"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "EASTEUROPE_CHARSET"
FontWeight[f["weight"]] = "FW_NORMAL"
-----------------------------------------
decode_str(f["fullName"]) = "Arial"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Arial CYR"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "RUSSIAN_CHARSET"
FontWeight[f["weight"]] = "FW_NORMAL"
-----------------------------------------
decode_str(f["fullName"]) = "Arial"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Arial Greek"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "GREEK_CHARSET"
FontWeight[f["weight"]] = "FW_NORMAL"
-----------------------------------------
decode_str(f["fullName"]) = "Arial"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Arial TUR"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "TURKISH_CHARSET"
FontWeight[f["weight"]] = "FW_NORMAL"
-----------------------------------------
decode_str(f["fullName"]) = "Arial Black"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Arial Black"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "ANSI_CHARSET"
FontWeight[f["weight"]] = "FW_HEAVY"
-----------------------------------------
decode_str(f["fullName"]) = "Calibri"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Calibri"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "ANSI_CHARSET"
FontWeight[f["weight"]] = "FW_NORMAL"
-----------------------------------------
decode_str(f["fullName"]) = "Calibri Light"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Calibri Light"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "ANSI_CHARSET"
FontWeight[f["weight"]] = "FW_LIGHT"
-----------------------------------------
decode_str(f["fullName"]) = "Cambria"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Cambria"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "ANSI_CHARSET"
FontWeight[f["weight"]] = "FW_NORMAL"
-----------------------------------------
decode_str(f["fullName"]) = "Cambria Math"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Cambria Math"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "ANSI_CHARSET"
FontWeight[f["weight"]] = "FW_NORMAL"
-----------------------------------------
decode_str(f["fullName"]) = "Candara"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Candara"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "ANSI_CHARSET"
FontWeight[f["weight"]] = "FW_NORMAL"
-----------------------------------------
decode_str(f["fullName"]) = "Comic Sans MS"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Comic Sans MS"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "ANSI_CHARSET"
FontWeight[f["weight"]] = "FW_NORMAL"
-----------------------------------------
decode_str(f["fullName"]) = "Consolas"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Consolas"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "ANSI_CHARSET"
FontWeight[f["weight"]] = "FW_NORMAL"
-----------------------------------------
decode_str(f["fullName"]) = "Constantia"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Constantia"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "ANSI_CHARSET"
FontWeight[f["weight"]] = "FW_NORMAL"
-----------------------------------------
decode_str(f["fullName"]) = "Corbel"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Corbel"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "ANSI_CHARSET"
FontWeight[f["weight"]] = "FW_NORMAL"
-----------------------------------------
decode_str(f["fullName"]) = "Courier New"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Courier New"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "ANSI_CHARSET"
FontWeight[f["weight"]] = "FW_NORMAL"
-----------------------------------------
decode_str(f["fullName"]) = "Courier New"
decode_str(f["style"]) = "Regular"
decode_str(f["faceName"]) = "Courier New Baltic"
FontQuality[f["quality"]] = "DRAFT_QUALITY"
FontCharSet[f["charSet"]] = "BALTIC_CHARSET"
FontWeight[f["weight"]] = "FW_NORMAL"

Thank you so much guys, really appreciate it.

1 Like

You’re welcome, glad it worked :smiley:

I think your decode_str would need tweaking for non-ascii chars, and the replace probably isn’t quite correct because there may be trailing garbage in the C buffer after the first null character. Perhaps the following would work:

function decode_str(encoded)
    firstnull = findfirst(c->c==0, encoded)
    transcode(String, collect(encoded[1:(firstnull !== nothing ? firstnull-1 : end)]))
end
1 Like

Wow, awesome, it worked perfectly indeed, thank you again @Chris_Foster, i’m very happy with Julia.

getting more familiar with it each day