Search code examples
c++assemblydllvisual-c++-2012

Is it possible to dllexport (and then dllimport) function written in assembly?


I have large C++ project, containing few modules - all of them are compiled to dynamic libraries. I target multiple platforms, including Windows, Linux and MacOSX.

Profiling tests revealed some critical points, in which I was able to get huge performance gain, for example: hash computing, some vector operations etc. I implemented this functionality in assembly using SSE/MMX.

Everything was fine, until I switched back to x64 target in Visual C++, where inline assembly is not permitted. And I'm stuck. Also, these functions are used in other modules as well.

Basically, what I am trying to achieve, is to implement some functions, that reside inside DLL in assembly. I tried this:

Api.h

extern "C" void DLL_API __stdcall sample_fun(/*args*/);

Api.asm

sample_fun PROC public ;args
.....
sample_fun ENDP

This obviously will not work, because of name mangling.

I also tried this:

Api.h

void DLL_API sample_fun(/*args*/);

Api.cpp

extern "C" __stdcall sample_fun_impl(/*args*/).

void DLL_API sample_fun(/*args*/)
{
  return sample_fun_impl(/*args*/);
}

Api.asm

sample_fun_impl PROC public ;args
.....
sample_fun_impl ENDP

In this case, I am still getting linker error about unresolved external symbol (sample_fun_impl), which is weird, because it is actually a private function, called only from within the DLL.

Is it possible to do what I am trying to ?


Solution

  • So, the problem has been solved. Here is a minimal example of what I wanted with some explanations:

    Asx.h

    namespace Asx
    {
    #if ASX_PLATFORM_IS64BIT //This is resolved using 'ifdef _M_X64'
        extern ASX_DLL_API ULONGLONG roundup_pow2_64(ULONGLONG value);
    #else
        extern ASX_DLL_API DWORD roundup_pow2_32(DWORD value);
    #endif
    }
    

    Asx.cpp

    #include "Asx.h"
    
    #if ASX_PLATFORM_IS64BIT
    extern "C" ULONGLONG __cdecl roundup_pow2_64_impl(ULONGLONG value);
    #else
    extern "C" DWORD __cdecl roundup_pow2_32_impl(DWORD value);
    #endif
    
    namespace Asx
    {
    
    #if ASX_PLATFORM_IS64BIT
        ULONGLONG roundup_pow2_64(ULONGLONG value)
        {
            return roundup_pow2_64_impl(value);
        }
    #else
        DWORD roundup_pow2_32(DWORD value)
        {
            return roundup_pow2_32_impl(value);
        }
    #endif
    
    }
    

    Asx_asm_impl.asm

    IFNDEF ASX_PLATFORM_IS64BIT
    .686P
    .MODEL FLAT, C
    .XMM
    ENDIF
    
    .CODE
    
    IFDEF ASX_PLATFORM_IS64BIT
    
    roundup_pow2_64_impl PROC public, value:QWORD
    //Implementation
    roundup_pow2_64_impl ENDP
    
    ELSE
    
    roundup_pow2_32_impl PROC public, value:DWORD
    //Implementation
    roundup_pow2_32_impl ENDP
    
    ENDIF
    
    END
    

    What was wrong?

    1) I did not take into account, that calling conventions are treated differently in x64, however accidentally this didn't cause any problems.

    2) At some point, I noticed, that functions marked __cdecl are searched by linker using their name prepended with an underscore. I made dumpbin of problematic DLL and it was there - but indeed with an underscore at the beginning! So I left its declaration as it was and changed its name from roundup_pow2_32_impl to _roundup_pow2_32_impl and at the same time, I added MODEL FLAT, C.

    3) I used IFDEF/IFNDEF inside .asm file. But I assumed, that all defines visible to cl will be also visible to ml/ml64. Wrong. Only after manually adding required constants everything started to work (.asm file properties -> Microsoft Macro Assembler -> General -> Preprocessor Definitions).

    I guess after trying and trying many different solutions, everything turned into one, big mess. Clean setup worked perfectly:

    Main.cpp

    #include "../Asx/Header.h"
    
    int main(int argc, char** argv)
    {
    #if ASX_PLATFORM_IS64BIT
        ULONGLONG v = Asx::roundup_pow2_64(4000);
    #else
        DWORD v = Asx::roundup_pow2_32(4000);
    #endif
    
        return 0;
    }
    

    Result in both Win32 and x64: 4096.

    And big thanks for bogdan! Without his hint about calling convention specifiers on x64, I wouldn't solve this.