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 ?
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.