TL;DR 32-bit x86
Use ccall instead of stdcall, and add a relocations (fixups) section:
format PE DLL
include 'win32a.inc'
section '.text' code readable executable
exp2:
; First arg in [esp+4]
;...
ret
section '.edata' export readable
export 'fasmdll.dll',\
exp2,'exp2'
data fixups
end data
DEF:
LIBRARY fasmdll.dll
EXPORTS
exp2
lib /def:fasmdll.def /machine:x86
C++:
#pragma comment(lib, "fasmdll.lib")
extern "C" void exp2(DWORD);
Note: __declspec(dllimport)
is optional for functions.
Alternatively, use the stdcall code you had but with export 'fasmdll.dll', exp2,'exp2@4'
, but this may be a bit ugly.
TL;DR 64-bit x86
Just use the x64 calling convention:
format PE64 DLL
include 'win64a.inc'
section '.text' code readable executable
exp2:
; First arg in ecx
;...
ret
section '.edata' export readable
export 'fasmdll.dll',\
exp2,'exp2'
Fixups section doesn't seem to be necessary, but you can add one like above if you have problems.
DEF:
LIBRARY fasmdll.dll
EXPORTS
exp2
lib /def:fasmdll.def /machine:x64
C++:
#pragma comment(lib, "fasmdll.lib")
extern "C" void exp2(DWORD);
Full answer
I hope this solves your problem. Weirdly, I didn't get a linker error with your code, only a runtime error. I never got any errors referencing symbols starting with __imp__
Are you sure you posted all relevant code correctly?
If you want to use something other than ccall on 32-bit x86 it turns out to be very difficult to get it right. Apparently, LIB assumes you use ccall and uses the DEF file in this way. For each exported function fun
, it exports fun
as _fun
, such that MSVC can then import it as _fun
, by the ccall name mangling specification. See this SO answer on generating an import LIB for an stdcall DLL and this source it cites about DLLs & stdcall. The latter source also mentions how LIB ignores symbol aliases in DEF files (and in an /EXPORT:a=b
argument), even though the docs seem to suggest support. Hence, you have a problem when you want to export a function _fun
as _fun
.
If you want to use stdcall, what you can do is export label exp2
as exp2@4
(no underscore) and have entry exp2@4
in your DEF, such that LIB exports exp2@4
as _exp2@4
, which MSVC can then import:
export 'fasmdll.dll',\
exp2,'exp2@4'
However, the exported name exp2@4
does not follow the stdcall name mangling specification, which seems to require the leading underscore.
So how do you produce an import library for an existing DLL with an exported function _fun
? This was difficult to figure out, but it can be done. The source mentioned earlier suggests dlltool from the binutils package from MinGW. Make sure you get the 32-bit (i686) version. Now open the "MSYS2 MinGW 32-bit" shell, or make sure you call dlltool from the 32-bit folder mingw32\bin
. Prepare a DEF with exported symbol _fun
, now execute:
dlltool --input-def fasmdll.def --output-lib fasmdll.lib --no-leading-underscore
--no-leading-underscore
is necessary to prevent dlltool from prefixing every name with an underscore like LIB does. (This option is not supported by the related llvm-dlltool).
Now you can run cl
and the executable and it should work!
Note: like LIB, dlltool ignores aliases, so you cannot add a _fun = fun
entry.
If you don't want to use dlltool, you can use a hack: compile a C(++) file with __declspec(dllexport)
functions with arbitrary bodies, and compile with cl /LD
(possibly also pass a DEF file). Now use the produced LIB and discard the other files. If you pass a DEF file, aliases will be used, but only to define extra symbols in the DLL that you will not use, not aliases in the LIB file.
Full demo for ccall, stdcall, fastcall (32-bit)
Here we implement an increment
function using various calling conventions, with and without FASM's proc
macro. This is only applicable to 32-bit x86. For x86-64, just use the x64 calling convention as above.
fasmdll.asm
:
format PE DLL
include 'win32a.inc'
section '.text' code readable executable
increment_ccall:
mov eax, [esp+4]
inc eax
ret
proc increment_ccall_proc c, val
mov eax, [val]
inc eax
ret
endp
increment_stdcall:
mov eax, [esp+4]
inc eax
ret 4 ; pop 4 bytes
; stdcall is default, can be left out
proc increment_stdcall_proc stdcall, val
mov eax, [val]
inc eax
ret
endp
increment_fastcall:
mov eax, ecx
inc eax
ret
section '.edata' export readable
export 'fasmdll.dll',\
increment_ccall,'_increment_ccall',\
increment_ccall_proc,'_increment_ccall_proc',\
increment_stdcall,'_increment_stdcall@4',\
increment_stdcall_proc,'_increment_stdcall_proc@4',\
increment_fastcall,'@increment_fastcall@4'
data fixups
end data
fasm fasmdll.asm
fasmdll.def
:
LIBRARY fasmdll
EXPORTS
_increment_ccall
_increment_ccall_proc
_increment_stdcall@4
_increment_stdcall_proc@4
@increment_fastcall@4
mingw32\bin\dlltool --input-def fasmdll.def --output-lib fasmdll.lib --no-leading-underscore
test.cpp
:
#pragma comment(lib, "fasmdll")
extern "C" {
int /*implicit __cdecl*/ increment_ccall(int);
int /*implicit __cdecl*/ increment_ccall_proc(int);
int __stdcall increment_stdcall(int);
int __stdcall increment_stdcall_proc(int);
int __fastcall increment_fastcall(int);
}
#include <iostream>
int main() {
std::cout
<< increment_ccall(41) << '\n'
<< increment_ccall_proc(41) << '\n'
<< increment_stdcall(41) << '\n'
<< increment_stdcall_proc(41) << '\n'
<< increment_fastcall(41) << '\n'
;
}
cl test.cpp
(or use VS)
Output:
> .\test
42
42
42
42
42
Troubleshooting
First of all, I would recommend testing with a function with a different name than exp2
, because the compiler may fail to give an error when the function is not found in the LIB as there is a built-in function with the same name.
FASM errors
cl
linker errors
fasmdll.lib : warning LNK4272: library machine type 'x..' conflicts with target machine type 'x..'
: Make sure to use dlltool from the mingw folder for the right architecture, or to specify /machine:x..
to lib
, and to use cl
for the corresponding architecture.
test.obj : error LNK2019: unresolved external symbol _exp2 referenced in function _main
: The LIB does not export _exp2
. Check that you use lib
with export exp2
(without underscore), or dlltool --no-leading-underscore
with export _exp2
.
fasmdll.lib(fasmdll_lib_s00000.o) : error LNK2001: unresolved external symbol _head_fasmdll_lib
: You used the x64 version of dlltool, use the (32-bit) i636 version instead. Specifying --machine i363
is not enough for some reason, this might be a bug.
Executable crashes (exit code):
Look up at https://www.hresult.info/. To get the error message, run the executable not via the command line, but by double-clicking from explorer.
STATUS_ENTRYPOINT_NOT_FOUND
/ ERROR_PROC_NOT_FOUND
(-1073741511
): Function names that EXE imports and that DLL exports do not match. Look at output from dumpbin /imports *.exe
(or objdump
) (and dumpbin /imports *.dll
). Also make sure that you use the same architecture for the LIB and for cl
.
STATUS_INVALID_IMAGE_FORMAT
/ ERROR_BAD_EXE_FORMAT
(-1073741701
): DLL might have an empty .reloc
section, use data fixups
like above. Or DLL and executable may be built for different architectures (32-bit vs 64-bit). Make sure you have the correct format
in the FASM file and use the correct version of cl
: start it using the appropriate developer shell or select the right machine architecture in VS.
STATUS_CONFLICTING_ADDRESSES
(-1073741800
): DLL might lack a .reloc
section, add one with data fixups
like above.
STATUS_DLL_NOT_FOUND
/ ERROR_MOD_NOT_FOUND
(-1073741515
): Make sure the DLL is there and has the same name at the top of the LIB file.