News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

C wrapper API - Call a CPP method From Assembly Language

Started by LiaoMi, April 23, 2021, 03:43:38 AM

Previous topic - Next topic

Vortex

Hi LiaoMi,

Nice work. Inline functions are useful to avoid function calling overhead. It's an optimization method to build faster code.

hutch--

> then I don't understand why they inline functions that can be found in standard libraries

This one is simple, inlining some functions can be more efficient than calling external library modules.

Vortex

Hello,

Code modified to use new and delete :

#include <stdio.h>

class formula {

    public:
       
        int calc(int, int, int, int, int);
        void GetResult(int);
};

int formula::calc(int a, int b, int c, int d, int e)
{
    return a * b * c * d * e;
}

void formula::GetResult(int t)
{
    printf("The result is %u\n",t);
}


int main()
{
    int result=0;
    formula *f = new formula();

    result = f->calc(2,4,6,8,10);
    f->GetResult(result);

    delete f;

    return 0;

}


An alternative :

Quote/MD link with MSVCRT.LIB

cl /MD ClassSample.cpp

ClassSample.exe : 5632 bytes

\PellesC\bin\podump.exe /imports ClassSample.exe | find ".dll"
        MSVCR90.dll
        KERNEL32.dll


The attached example imports new and delete from msvcrt.dll , not msvcr90.dll  The size of the executable is 3072 bytes.

nidud

#18
deleted

LiaoMi

There was little time to answer on time, sorry  :undecided:

@Vortex

Hi Vortex,

another great example, thank you!  :thup:

@Hutch

Hi Hutch,

perfect strategy until 400kb of useless code is added  :biggrin: Thanks!

@nidud

Hi Nidud,

the reverse technique is also quite powerful, thanks!  :thumbsup:

@New

I made an example on templates, but it does not work for float numbers, maybe I missed something, or there is an error in the compiler - https://developercommunity.visualstudio.com/t/c-compiler-bug-when-using-fpfast-and-o2/155625



I tried different methods, the code is not generated correctly  :dazzled:

0000000140001390 <main_c_20210502_173122.public: void __cdecl MyClass<float>::template_set(float) __ptr64>   | F3:0F1109                          | MOVSS   DWORD PTR DS:[RCX], XMM1                                                        |

0000000140001350 <main_c_20210502_173122.public: float __cdecl MyClass<float>::template_get(void) __ptr64>   | F3:0F1001                          | MOVSS   XMM0, DWORD PTR DS:[RCX]                                                        |

DWORD != QWORD -> Float INIT

00000001400010D9                                                                                             | F2:0F100D 871F0000                 | MOVSD   XMM1, [b]QWORD[/b] PTR DS:[<__real@404e000000000000>]                                  |

C Language
__real@404e000000000000 dq 404E000000000000h
Asm
__real@404e000000000000 dq -60.0; Float

QuoteMove Scalar Single-Precision Floating-Point Values
Opcode   Mnemonic   Description
F3 0F 10 /r   MOVSS xmm1, xmm2/m32   Move scalar single-precision floating-point value from xmm2/m32 to xmm1 register.
F3 0F 11 /r   MOVSS xmm2/m32, xmm1   Move scalar single-precision floating-point value from xmm1 register to xmm2/m32.
Description
Moves a scalar single-precision floating-point value from the source operand (second operand) to the destination operand (first operand). The source and destination operands can be XMM registers or 32-bit memory locations. This instruction can be used to move a single-precision floating-point value to and from the low doubleword of an XMM register and a 32-bit memory location, or to move a single-precision floating-point value between the low doublewords of two XMM registers. The instruction cannot be used to transfer data between memory locations.

When the source and destination operands are XMM registers, the three high-order doublewords of the destination operand remain unchanged. When the source operand is a memory location and destination operand is an XMM registers, the three high-order doublewords of the destination operand are cleared to all 0s.

Operation
//MOVSS instruction when source and destination operands are XMM registers:
if(IsXMM(Source) && IsXMM(Destination)) Destination[0..31] = Source[0..31];
//Destination[32..127] remains unchanged
//MOVSS instruction when source operand is XMM register and destination operand is memory location:
else if(IsXMM(Source) && IsMemory(Destination)) Destination = Source[0..31];
//MOVSS instruction when source operand is memory location and destination operand is XMM register:
else {
   Destination[0..31] = Source;
   Destination[32..127] = 0;
}



    int In = 56;
        float flo = 60.0f; <- QWORD
        double doub = 3.14159265358979;
    long long ll = 3.14159265358971;
     
        struct float_attrstr* f = newMyClassFloat();
        MyClass_float_set(f, 60.0f);
        //using %f format specifier
        printf("Value of float = %f\n", MyClass_float_get(f)); <- DWORD
        printf("Value of float = %f\n", flo);  <- QWORD
        deleteMyClassFloat(f);
       
 


The main procedure for tests is not correct, because I think the float should be DWORD (32 bits).

tenkey

Quote from: LiaoMi on May 03, 2021, 01:40:51 AM
DWORD != QWORD -> Float INIT

00000001400010D9                                                                                             | F2:0F100D 871F0000                 | MOVSD   XMM1, [b]QWORD[/b] PTR DS:[<__real@404e000000000000>]                                  |

C Language
__real@404e000000000000 dq 404E000000000000h


    int In = 56;
        float flo = 60.0f; <- QWORD
        double doub = 3.14159265358979;
    long long ll = 3.14159265358971;
     
        struct float_attrstr* f = newMyClassFloat();
        MyClass_float_set(f, 60.0f);
        //using %f format specifier
        printf("Value of float = %f\n", MyClass_float_get(f)); <- DWORD
        printf("Value of float = %f\n", flo);  <- QWORD
        deleteMyClassFloat(f);
       
 


The main procedure for tests is not correct, because I think the float should be DWORD (32 bits).

Hi LiaoMi,

The code is correct. printf() has varargs. Also, %f handles only QWORDs. flo is converted to double (REAL8/QWORD) because printf() value argument is untyped. I think you have optimization enabled, so compiler eliminated flo, and loaded QWORD 60.0 directly from constant memory.

LiaoMi

Hi tenkey,

to see the error, you need to run the file and look at the template. At this point, there may be no error, but in the class itself, the float is treated as a dword, i.e. value is truncated, the supplied qword does not retain its value, as you can see in the picture above. The value was read in the same way, this value is also not correct. I tried both with and without optimization, there are both versions in the archive above. I tried converting a variable from qword to dword, but nothing good came out.

MyClass_float_set(f, 60.0f);  <-  60.0f =QWORD IN - here we need a DWORD, but the input is QWORD
printf("Value of float = %f\n", MyClass_float_get(f)); <- DWORD out

Value of int = 56
Value of int = 56
Value of double = 3.141593
Value of double = 3.141593
Value of float = 0.000000 <- Bug
Value of float = 60.000000
Value of long long = 1431655765
Value of long long = 1431655765

tenkey

Hi LiaoMi,

The following code compiles correctly under MSVC 19.28.29914.0:

#include <stdio.h>

extern struct float_attrstr*  newMyClassFloat();
extern void deleteMyClassFloat(struct float_attrstr *f);
extern void MyClass_float_set(struct float_attrstr* f, float v);
extern float MyClass_float_get(struct float_attrstr* f);

int main(int argc, char** argv)
{
    int In = 56;
    float flo = 60.0f;
    double doub = 3.14159265358979;
    long long ll = 3.14159265358971;

    struct float_attrstr* f = newMyClassFloat();
    MyClass_float_set(f, 60.0f);                            // DWORD constant
    //using %f format specifier
    printf("Value of float = %f\n", MyClass_float_get(f));  // returned DWORD, converted to QWORD
    printf("Value of float = %f\n", flo);                   // QWORD constant
    deleteMyClassFloat(f);
}


Important differences between main_c.asm and the MSVC 19.28.29914.0 .asm output:

                call    newMyClassFloat
                movsd   xmm1, __real@404e000000000000   ; not VC - does not load a QWORD constant
movss xmm1, DWORD PTR __real@42700000         ; VC - loads a DWORD constant

                movsxd  rbx, eax        ; not VC - newMyClassFloat() does not return a 32-bit pointer
mov rbx, rax                ; VC - newMyClassFloat() returns a 64-bit pointer
                movq    rdx, xmm1
                mov     rcx, rbx
                call    MyClass_float_set
                mov     rcx, rbx
                call    MyClass_float_get
               
                PrologueRsp40
                mov     edx, eax        ; not VC - 32-bit float is not returned in eax
cvtss2sd xmm1, xmm0             ; VC - 32-bit float is returned in xmm0, converted to 64-bit float
movq rdx, xmm1               ; VC - printf() has varargs, all floats must be at least 64-bit
                lea     rcx, Format3          ; "Value of float = %lf\n"
                call    printf
                EpilogueRsp40


LiaoMi

Quote from: tenkey on May 05, 2021, 10:51:27 AM
Hi LiaoMi,

The following code compiles correctly under MSVC 19.28.29914.0:

#include <stdio.h>

extern struct float_attrstr*  newMyClassFloat();
extern void deleteMyClassFloat(struct float_attrstr *f);
extern void MyClass_float_set(struct float_attrstr* f, float v);
extern float MyClass_float_get(struct float_attrstr* f);

int main(int argc, char** argv)
{
    int In = 56;
    float flo = 60.0f;
    double doub = 3.14159265358979;
    long long ll = 3.14159265358971;

    struct float_attrstr* f = newMyClassFloat();
    MyClass_float_set(f, 60.0f);                            // DWORD constant
    //using %f format specifier
    printf("Value of float = %f\n", MyClass_float_get(f));  // returned DWORD, converted to QWORD
    printf("Value of float = %f\n", flo);                   // QWORD constant
    deleteMyClassFloat(f);
}


Important differences between main_c.asm and the MSVC 19.28.29914.0 .asm output:

                call    newMyClassFloat
                movsd   xmm1, __real@404e000000000000   ; not VC - does not load a QWORD constant
movss xmm1, DWORD PTR __real@42700000         ; VC - loads a DWORD constant

                movsxd  rbx, eax        ; not VC - newMyClassFloat() does not return a 32-bit pointer
mov rbx, rax                ; VC - newMyClassFloat() returns a 64-bit pointer
                movq    rdx, xmm1
                mov     rcx, rbx
                call    MyClass_float_set
                mov     rcx, rbx
                call    MyClass_float_get
               
                PrologueRsp40
                mov     edx, eax        ; not VC - 32-bit float is not returned in eax
cvtss2sd xmm1, xmm0             ; VC - 32-bit float is returned in xmm0, converted to 64-bit float
movq rdx, xmm1               ; VC - printf() has varargs, all floats must be at least 64-bit
                lea     rcx, Format3          ; "Value of float = %lf\n"
                call    printf
                EpilogueRsp40


Hi tenkey,

thanks a lot  :thup: :thup: :thup: :thumbsup:, I completely forgot that I had to describe the prototypes in the C example, I didn't even notice it  :biggrin:. Now the results are all correct

Value of int = 56
Value of int = 56
Value of double = 3.141593
Value of double = 3.141593
Value of float = 60.000000
Value of float = 60.000000
Value of long long = 1431655765
Value of long long = 1431655765


The update can be downloaded below  :arrow_down:

Assembly stages
1. cl -c /D /x64 /doc /O2 MyWrapper.cc
2. cl -c /D /x64 /doc /O2 MyClass.cc
3. Build_uasm64_Link_x64_Debug.bat - link /OPT:NOREF /FIXED /MAP:%appname%_%stamp%.map /entry:%entryOEP% /DEBUG /debugtype:cv /VERBOSE /NODEFAULTLIB:libcmt.lib /pdb:%appname%_%stamp%.pdb /SUBSYSTEM:Console,5.0 /VERSION:4.0 /MACHINE:X64 /RELEASE %appname%.obj MyWrapper.obj MyClass.obj >> %appname%_%stamp%.assemblylog.txt

LiaoMi

Hi,

I updated the first post with this content:

Part 1: Using a simple class - http://masm32.com/board/index.php?topic=9317.msg102309#msg102309
Part 2: Using a class template - http://masm32.com/board/index.php?topic=9317.msg102551#msg102551
Part 3: Advanced techniques for addressing functions in C++ from an assembler program
Part 4: Using dynamic variables and class initialization from assembler
Part 5: Defining a class in another class
Part 6: I haven't thought about that yet  :tongue:

Now we need to think about how to convert the classes into a ready-made wrapper object in assembler, so that this is not the same as I did by compiling an example in the C language. Each type in the template will need its own wrapper function. To do this, we will take classes and templates that are already familiar to us.

class MyClass {
        private:
                int m_i;
        public:
                void int_set(int i);

                int int_get();
};

void MyClass::int_set(int i) {
        m_i = i;
}

int MyClass::int_get() {
        return m_i;
}


template<typename T>
class MyClass {
        private:
                T m_i;
        public:
                void template_set(T i);
                T template_get();
};

template <typename T>
void MyClass<T>::template_set(T i)
{
m_i = i;
}

template <typename T>
T MyClass<T>::template_get()
{
return m_i;
}

template class MyClass<int>;
template class MyClass<double>;
template class MyClass<float>;
template class MyClass<long long>;


The structure of the wrapper remains the same for us, only the types will change.

#ifdef __cplusplus
extern "C" {
#endif

typedef struct MyClass MyClass;

MyClass* newMyClass();

void MyClass_int_set(MyClass* v, int i);

int MyClass_int_get(MyClass* v);

void deleteMyClass(MyClass* v);

#ifdef __cplusplus
}
#endif


It should be something like this (this is an incomplete conversion, partly pseudocode).
MyClass TYPEDEF MyClass
newMyClass PROTO
MyClass_int_set PROTO v:XMASM ,i:DWORD
MyClass_int_get PROTO v:XMASM
deleteMyClass PROTO v:XMASM

MyClass STRUCT DEFALIGNMASM
m_i DWORD ?
void int_set(int i)
int int_get()
MyClass ENDS


or

MyClass STRUCT DEFALIGNMASM
m_i DWORD ?
int_set QWORD ?
int_get QWORD ?
MyClass ENDS


For those who want to dive deeper into the topic:
(Addison-Wesley professional computing series) David R. Hanson - C Interfaces and Implementations Techniques for Creating Reusable Software-Addison-Wesley Professional (1996) - https://archive.org/details/cinterfacesimple0000hans
Source Code - https://github.com/drh/cii or https://github.com/drh/cii/archive/refs/heads/master.zip