1

I'm trying to use an import library from a FASM-created DLL to visual studio, no matter what I try I get the unresolved external message.

; proc to export has name exp2
exp2:
....
;    
section '.edata' export readable
export 'fasmdll.dll',exp2,'exp2'

The DEF:

LIBRARY FASMDLL 
EXPORTS 
exp2@4

Then lib /def:fasmdll.def /out:fasmdll.lib /machine:x86

Then in C++

#pragma comment(lib,"fasmdll.lib")
extern "C" __declspec(dllimport) void  __stdcall exp2(DWORD);

The error dllmain.obj : error LNK2019: unresolved external symbol __imp__exp2@4 referenced in function _exp1@4

If I use __declspec(dllexport) instead I also get

dllmain.obj : error LNK2019: unresolved external symbol _exp2@4

No matter the combination, I can't make it working. Why? In standard Windows libs, the import library wors correctly... I suspect a problem with the DEF file?

Michael Chourdakis
  • 10,345
  • 3
  • 42
  • 78

1 Answers1

1

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

  •   export 'fasmdll.dll',
      .../INCLUDE/macro/export.inc [15] export [10]:
          dd RVA label
      processed: dd RVA
      error: invalid expression.
    
    You probably forgot a backslash after a comma in the export statement.
  • processed: push ebp. error: illegal instruction.: Don't include win32a.inc with format PE64.
  • processed: push rbp. error: illegal instruction.: Don't include win64a.inc with format PE.

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.
SWdV
  • 1,715
  • 1
  • 15
  • 36