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

1 Like

Hi guys,

I need your help one more time, not sure if i should create another post, but it is related, it’s not really an issue, but it is related to variable declaration.

This is c++ code to call a Windows API method from Psapi.dll

HMODULE lphModule;
DWORD lpcbNeeded;
EnumProcessModules( hProcess, &lphModule, sizeof(lphModule), 
             &lpcbNeeded)

Here is the method signature

BOOL EnumProcessModules(
  HANDLE  hProcess,
  HMODULE *lphModule,
  DWORD   cb,
  LPDWORD lpcbNeeded // Here, LPDWORD == *DWORD, that's why &cdNeeded is passed above
);

Great, i managed to reproduce the same call in Julia like so

const HANDLE = Ptr{Cvoid}
const HMODULE = Ptr{HANDLE}

const DWORD = Clong
const LPDWORD = Ptr{DWORD}

# the [] acts like a pointer for some reason, it was totally accidental, it worked
init(t::Type{LPDWORD}) = [zero(DWORD)] # the [] acts like a pointer for some reason
init(t::Type{DWORD}) = [zero(DWORD)] # the [] acts like a pointer for some reason
init(t::Type{HMODULE}) = [C_NULL]

lphModule        = init(HMODULE)      # HMODULE <=> *HANDLE
lpcbNeeded    = init(LPDWORD)     # LPDWORD <=> *DWORD
hProcess = # this HANDLE is being received from other method call
ccall((:EnumProcessModules, "Psapi"), stdcall, Cint, 
                            (HANDLE, HMODULE, DWORD, LPDWORD), 
                            hProcess, lphModule, sizeof(lphModule), lpcbNeeded)

The code above works great, i get the lphModule::HMODULE value and can do stuff with it, the Vector syntax […] acts like a pointer for some reason, it makes sense of course, but i really don’t like how i am declaring lphModule and lpcbNeeded having to use this kind of syntax, and i couldn’t find any other way to do so.

lphModule = Ptr{HANDLE}(C_NULL) # this doesn't work
lpcbNeeded = Ptr{DWORD}(zero(DWORD)) # or this

if you know an elegant way to declare variables in Julia like it was done in c++ i would really appreciate your help.

So, in summary, i really want to do this

HMODULE lphModule;
DWORD lpcbNeeded;

In Julia, but in a more elegant way.

Thank you guys.

It doesn’t act as a pointer and you don’t want a pointer. You want a piece of memory to get the address of. That’s what you do with

in C. That’s why using array works, since the array contains the memory you need but it’s not the best way.

Please read the document.

Note that there’s no way you can define lphModule = <initial NULL pointer value> and expect it to be assigned the pointer value later. It is strictly forbidden in julia and will never be possible to do. If you are looking for a ccall syntax so that you can do that then you’ll not find it. You can only find alternative to using a vector (see the doc link above). There’s also no reason to use your init. It doesn’t really make it more clear especially for anyone that have done similar ccalls. You are much better off leaving the creation of the memory as is.

1 Like

Hi @yuyichao, thank you for your reply, you helped me a lot.

i made it work using Ref{} like so

const HANDLE = Ptr{Cvoid}
const HMODULE = Ptr{HANDLE}

const DWORD = Clong
const LPDWORD = Ref{DWORD}

lpcbNeeded = LPDWORD(0)     # LPDWORD <=> *DWORD
lphModule = Ref{HMODULE}(0)      # HMODULE <=> *HANDLE

ccall((:EnumProcessModules, "Psapi"), stdcall, Cint, 
                            (HANDLE, Ref{HMODULE}, DWORD, LPDWORD), 
                            hProcess, lphModule, sizeof(lphModule.x), lpcbNeeded)

It looks much better, but i’m not sure about the correctness of this code.

You should never use lphModule.x. You should at least replace it with lphModule[]. And in this case you should replace it with the size of the type instead.

1 Like

Done, it works fine and looks much better, thank you :slight_smile: