Error when loading a C submodule from dll

161 views
Skip to first unread message

Jasper Klein

unread,
Dec 27, 2025, 2:57:39 PM12/27/25
to lua-l
I'm making a C module with submodules so I can load libraries with 'foo.bar' or 'foo.baz'.

This works fine on my Linux PC but on a Windows VM Lua fails to load the dll.
The following error is shown in the console:

Y:\PG1003\foo>.\..\x64\Debug\Lua54.exe .\test\main.lua
Running tests...
.\..\x64\Debug\Lua54.exe: error loading module 'foo.bar' from file './.\test\main.lua':
        %1 is not a valid Win32 application.


stack traceback:
        [C]: in ?
        [C]: in function 'require'
        ./.\test\tests/foo.lua:2: in main chunk
        [C]: in function 'require'
        ./.\test\test.lua:108: in function 'test.run_test_modules'
        (...tail calls...)
        .\test\main.lua:37: in main chunk
        [C]: in ?


The line './.\test\tests/foo.lua:2: in main chunk' is the call with the require for the submodule.
Also Windows shows an error messagebox that contains error status 0xc000012f.

This is with Lua 5.4.7.
I didn't try to reproduce the issue with older or newer versions of Lua.

Has anyone else encountered the same problem?
Can I fix this in the C module?
Or is this a bug in Lua?

-- Jasper

Spar

unread,
Dec 27, 2025, 3:40:43 PM12/27/25
to lua-l
You need to provide info about your compilation command. Do you export luaopen?
--
You received this message because you are subscribed to the Google Groups "lua-l" group.
To unsubscribe from this group and stop receiving emails from it, send an email to lua-l+un...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/lua-l/934a2e2e-22f4-495c-afeb-3c217871e9can%40googlegroups.com.

Jasper Klein

unread,
Dec 27, 2025, 5:05:35 PM12/27/25
to lua-l
Op zaterdag 27 december 2025 om 21:40:43 UTC+1 schreef Spar:
You need to provide info about your compilation command. Do you export luaopen?

The Lua executable is linked to a Lua dll of the same version.
I use Visual Studio 2022 version 17.13 and the executable, dll and module are in the same solution and use the same compiler, the same language version and are build as debug.
The module is build with warning level 4 while Lua is build with warning level 3.

The luaopen_ functions are exported.
There are no issues when the functions are registered like a regular module.

This works on Windows and Linux
local foo = require( "foo" )  -- luaopen_foo

This fails for foo.dll on Windows but works for foo.so on Linux
local bar = require( "foo.bar" )  -- luaopen_foo_bar 

Sewbacca

unread,
Dec 27, 2025, 6:48:11 PM12/27/25
to lu...@googlegroups.com
gnu/clang export all symbols per default that's why it probably works on Linux. If luaopen_foo_bar is not found on Windows it is not exported.

If you think it is exported we'll need more info about your specific module. And it's compilation process.

~ Sewbacca

Jasper Klein

unread,
Dec 28, 2025, 10:04:06 AM12/28/25
to lua-l
Okay, here are more details of the implementation and build process.

I'm implementing a hashing module for my own private project.
The hashing algoritms are categorized by using submodules.
To quote the manual about submodules:
"For instance, when requiring a.b.c, it will search for a C library for a. If found, it looks into it for an open function for the submodule; in our example, that would be luaopen_a_b_c. With this facility, a package can pack several C submodules into one single library, with each submodule keeping its original open function."

The hashing module is written in C++.
I've my own small header only library to help binding functions to the Lua world and to reduce boilerplate when dealing with userdata.

These compiler and linker flags are used by Visual Studio when building binaries for debug mode:

Lua dll

Compile flags:
/JMC /permissive- /ifcOutput "x64\Debug\" /GS /W3 /Zc:wchar_t /ZI /Gm- /Od /sdl /Fd"Y:\PG1003\x64\Debug\Lua54.pdb" /Zc:inline /fp:precise /D "LUA_BUILD_AS_DLL" /D "_CRT_SECURE_NO_WARNINGS" /D "_DEBUG" /D "_CONSOLE" /D "_WINDLL" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX /Zc:forScope /RTC1 /Gd /MDd /std:c++23preview /FC /Fa"x64\Debug\" /EHsc /nologo /Fo"x64\Debug\" /Fp"x64\Debug\Lua54.pch" /diagnostics:column

Linker flags:
/OUT:"Y:\PG1003\x64\Debug\Lua54.dll" /MANIFEST /NXCOMPAT /PDB:"Y:\PG1003\x64\Debug\Lua54.pdb" /DYNAMICBASE "kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" /IMPLIB:"Y:\PG1003\x64\Debug\Lua54.lib" /DEBUG /DLL /MACHINE:X64 /INCREMENTAL /PGD:"Y:\PG1003\x64\Debug\Lua54.pgd" /SUBSYSTEM:WINDOWS /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /ManifestFile:"x64\Debug\Lua54.dll.intermediate.manifest" /LTCGOUT:"x64\Debug\Lua54.iobj" /ERRORREPORT:PROMPT /ILK:"x64\Debug\Lua54.ilk" /NOLOGO /TLBID:1

Lua exe

Compile flags:
/JMC /permissive- /ifcOutput "x64\Debug\" /GS /W4 /Zc:wchar_t /I"Y:\PG1003\LibLua54" /ZI /Gm- /Od /sdl /Fd"x64\Debug\vc143.pdb" /Zc:inline /fp:precise /D "LUA_BUILD_AS_DLL" /D "_CRT_SECURE_NO_WARNINGS" /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX /Zc:forScope /RTC1 /Gd /MDd /std:c++23preview /FC /Fa"x64\Debug\" /EHsc /nologo /Fo"x64\Debug\" /Fp"x64\Debug\Lua54.pch" /diagnostics:column

Linker flags:
/OUT:"Y:\PG1003\x64\Debug\Lua54.exe" /MANIFEST /NXCOMPAT /PDB:"Y:\PG1003\x64\Debug\Lua54.pdb" /DYNAMICBASE "Lua54.lib" "kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" /DEBUG /MACHINE:X64 /INCREMENTAL /PGD:"Y:\PG1003\x64\Debug\Lua54.pgd" /SUBSYSTEM:CONSOLE /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /ManifestFile:"x64\Debug\Lua54.exe.intermediate.manifest" /LTCGOUT:"x64\Debug\Lua54.iobj" /ERRORREPORT:PROMPT /ILK:"x64\Debug\Lua54.ilk" /NOLOGO /LIBPATH:"Y:\PG1003\x64\Debug\" /TLBID:1

Module

Compile flags:
/JMC /permissive- /ifcOutput "x64\Debug\" /GS /W4 /Zc:wchar_t /I"Y:\PG1003\LibLua54" /ZI /Gm- /Od /sdl /Fd"x64\Debug\vc143.pdb" /Zc:inline /fp:precise /D "LUA_BUILD_AS_DLL" /D "_CRT_SECURE_NO_WARNINGS" /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX /Zc:forScope /RTC1 /Gd /MDd /std:c++23preview /FC /Fa"x64\Debug\" /EHsc /nologo /Fo"x64\Debug\" /Fp"x64\Debug\Lua54.pch" /diagnostics:column

Linker flags:
/OUT:"Y:\PG1003\x64\Debug\hash.dll" /MANIFEST /NXCOMPAT /PDB:"Y:\PG1003\x64\Debug\hash.pdb" /DYNAMICBASE "Lua54.lib" "kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" /IMPLIB:"Y:\PG1003\x64\Debug\hash.lib" /DEBUG /DLL /MACHINE:X64 /INCREMENTAL /PGD:"Y:\PG1003\x64\Debug\hash.pgd" /SUBSYSTEM:WINDOWS /MANIFESTUAC:NO /ManifestFile:"x64\Debug\hash.dll.intermediate.manifest" /LTCGOUT:"x64\Debug\hash.iobj" /ERRORREPORT:PROMPT /ILK:"x64\Debug\hash.ilk" /NOLOGO /LIBPATH:"Y:\PG1003\x64\Debug\" /TLBID:1

The Lua executable and the module are dynamically linked to Lua54.dll.
These 3 projects (as they are called in Visual Studio) compile without errors and warnings.


The following is part of the source code.
To repeat myself; there are no errors thrown by Lua or Windows when `luaopen_hash_md5` is renamed to `luaopen_hash` and `require( "hash" )` is called instead of `require( "hash.md5" )`.

//
// Includes
//
// ...
//

#if defined( _WIN32 )
# define EXPORT __declspec( dllexport )
#else
# define EXPORT
#endif

//
// Implementation
//
// ...
//

static constexpr const luaL_Reg crc_functions[] =
{
    { "crc32", crc32::calculate_crc32 },
    { NULL,    NULL }
};

static constexpr const luaL_Reg md5_functions[] =
{
    { "md5",     md5::calculate_md5 },
    { "digest",  md5::make_digest },
    { NULL,      NULL }
};

extern "C"
{

EXPORT int luaopen_hash_crc( lua_State * const L ) noexcept
{
    luaL_checkversion( L );

    lua_newtable( L );
    luaL_setfuncs( L, crc_functions, 0 );

    return 1;
}

EXPORT int luaopen_hash_md5( lua_State * const L ) noexcept
{
    luaL_checkversion( L );

    register_metatable< md5::digest >( L );

    lua_newtable( L );
    luaL_setfuncs( L, md5_functions, 0 );

    return 1;
}

}


Augusto Goulart

unread,
Dec 28, 2025, 11:01:47 AM12/28/25
to lu...@googlegroups.com
Do you **need** to use MSVS? I've made a CRC32 module and this is the build script (with MinGW):

targ='libluacrc32'

gcc -Wall -Wextra -Wpedantic -Wshadow -Wformat=2 \
    -fPIC $CFLAGS --std=c11 -O3 -c lookup.c $targ.c

if [ "$(printenv 'OS')" = 'Windows_NT' ]; then
  gcc -shared -lm -o $targ.dll $targ.o lookup.o $LFLAGS -llua55
else
  gcc -shared -lm -o $targ.so $targ.o lookup.o $LFLAGS
fi

It doesn't have submodules, so it might not be helpful to you, but here's the code: https://github.com/oAGoulart/libluacrc32/tree/master

Augusto

--
You received this message because you are subscribed to the Google Groups "lua-l" group.
To unsubscribe from this group and stop receiving emails from it, send an email to lua-l+un...@googlegroups.com.

Jasper Klein

unread,
Dec 28, 2025, 11:26:52 AM12/28/25
to lua-l
Op zondag 28 december 2025 om 17:01:47 UTC+1 schreef Augusto Goulart:
Do you **need** to use MSVS? I've made a CRC32 module and this is the build script (with MinGW):

 I've no experience using gcc on Windows.
All my projects run on Linux and Windows.
For Linux I use gcc and for Windows MSVC and for some projects also clang via Visual Studio.

Each compiler outputs different warnings and catches issues that other compilers do not catch.
This made my code more portable and better.
Also it helps me to track down the origin of compile errors when the error message is hard to understand.

-- Jasper

Sewbacca

unread,
Dec 28, 2025, 11:33:15 AM12/28/25
to lu...@googlegroups.com
What do the lua commands

print(assert(package.loadlib(path, "luaopen_hash_crc"))())
print(assert(package.loadlib(path, "luaopen_hash_md5"))())

yield?

What does the cmd shell command:

dumpbin /SYMBOLS path | findstr luaopen_

yield?

You need to replace path with the path to your dll file.

All in all this looks correctly configured.

~ Sewbacca

Luau Project

unread,
Dec 28, 2025, 11:38:19 AM12/28/25
to lu...@googlegroups.com
Around a year ago, I coded a hashing module like this (registered on LuaRocks under the name "lua-hash"), chaining "different classes" inside a main module.

Basically, I tried to follow the idea of OpenSSL + Windows bcrypt API. In short, I wrapped a few classes to open different algorithms (md5, sha256, ...), allowed the user to feed a context with data up to some point, then finalize the message digest.

If you "relax definitions" and consider userdata as a special table, you may have your main module exported as userdata "a", then set a submodule "b" as a field of "a". You can check the sources of my module to have ideas how I chained such submodules ( https://github.com/luau-project/lua-hash )

Jasper Klein

unread,
Dec 28, 2025, 4:26:17 PM12/28/25
to lua-l
Op zondag 28 december 2025 om 17:33:15 UTC+1 schreef Sewbacca:
What do the lua commands

print(assert(package.loadlib(path, "luaopen_hash_crc"))())
print(assert(package.loadlib(path, "luaopen_hash_md5"))())

yield?

These print tables:
table: 0000024D0C8B4EE0
table: 0000024D0C8B3C60

When iterating over the md5 table I get:
md5     function: 00007FFE02666429
digest  function: 00007FFE02666154

 
What does the cmd shell command:

dumpbin /SYMBOLS path | findstr luaopen_

yield?
 
Instead of /SYMBOLS I used 'dumpbin /EXPORTS' and got the following result:

Microsoft (R) COFF/PE Dumper Version 14.43.34808.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file Y:\PG1003\x64\Debug\hash.dll

File Type: DLL

  Section contains the following exports for hash.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           2 number of functions
           2 number of names

    ordinal hint RVA      name

          1    0 00036861 luaopen_hash_crc = @ILT+2140(luaopen_hash_crc)
          2    1 000367E9 luaopen_hash_md5 = @ILT+2020(luaopen_hash_md5)

  Summary

        1000 .00cfg
        1000 .data
        2000 .idata
        1000 .msvcjmc
        6000 .pdata
        D000 .rdata
        1000 .reloc
        1000 .rsrc
       6F000 .text
       35000 .textbss

 

Jasper Klein

unread,
Dec 28, 2025, 4:46:46 PM12/28/25
to lua-l
Op zondag 28 december 2025 om 17:38:19 UTC+1 schreef Luau Project:
...
If you "relax definitions" and consider userdata as a special table, you may have your main module exported as userdata "a", then set a submodule "b" as a field of "a".

I may consider that, depending on outcome of this thread.
But I prefer this inconsistency of loading submodules between Linux and Windows to be fixed.

-- Jasper

Sewbacca

unread,
Dec 28, 2025, 7:52:03 PM12/28/25
to lu...@googlegroups.com
Okay so compilation is okay, there might be an issue with your package setup, what is your cpath and where does your dll lie relative to your cpath?

Luiz Henrique de Figueiredo

unread,
Dec 28, 2025, 8:39:19 PM12/28/25
to lu...@googlegroups.com
> I coded a hashing module like this (registered on LuaRocks under the name "lua-hash"), chaining "different classes" inside a main module.

See also my digest at https://web.tecgraf.puc-rio.br/~lhf/ftp/lua/#ldigest

Jasper Klein

unread,
Dec 29, 2025, 1:05:33 PM12/29/25
to lua-l
Op maandag 29 december 2025 om 01:52:03 UTC+1 schreef Sewbacca:
Okay so compilation is okay, there might be an issue with your package setup, what is your cpath and where does your dll lie relative to your cpath?
 
You are right, the bug was in the test framework of the build system. 

local short_src  = debug.getinfo( 1 ).short_src
local test_path  = string.gsub( short_src, "main.lua$", "?.lua" )
local tests_path = string.gsub( short_src, "main.lua$", "tests/?.lua" )
local build_path = string.gsub( short_src, "test/main.lua$", "build/?.so" )  -- No substitution on Windows, a lua file was added to package.cpath.
                                                                             -- Also the extension is wrong for Windows (although the build subdir
                                                                             -- is not available in the setup for Visual Studio).

package.path     = package.path .. ";".. test_path .. ";".. tests_path
package.cpath    = package.cpath .. ";".. build_path


The bug is fixed by making the substitution a bit smarter.

local tests_path = string.gsub( short_src, "([\\/])main.lua$", "%1tests%1?.lua" )
local build_path = string.gsub( short_src, "test([\\/])main.lua$",
    function( separator )
        return separator == "/" and "build/?.so" or "build\\?.dll"
    end )


So the first conclusion is that there was indeed a bug in the buildsystem.
The second  one is that submodules are not used often as some of you in the thread suggested to used nested classes.
Finally, everyone have to write a hashing library at some point.

Thank you all.
-- Jasper
Reply all
Reply to author
Forward
0 new messages