Author Topic: C wrapper API - Call a CPP method From Assembly Language  (Read 627 times)

Vortex

  • Member
  • *****
  • Posts: 2556
Re: C wrapper API - Call a CPP method From Assembly Language
« Reply #15 on: April 24, 2021, 07:54:49 PM »
Hi LiaoMi,

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

hutch--

  • Administrator
  • Member
  • ******
  • Posts: 8326
  • Mnemonic Driven API Grinder
    • The MASM32 SDK
Re: C wrapper API - Call a CPP method From Assembly Language
« Reply #16 on: April 24, 2021, 08:17:13 PM »
> 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.
hutch at movsd dot com
http://www.masm32.com    :biggrin:  :skrewy:

Vortex

  • Member
  • *****
  • Posts: 2556
Re: C wrapper API - Call a CPP method From Assembly Language
« Reply #17 on: April 24, 2021, 09:08:57 PM »
Hello,

Code modified to use new and delete :

Code: [Select]
#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

Code: [Select]
cl /MD ClassSample.cpp
ClassSample.exe : 5632 bytes

Code: [Select]
\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

  • Member
  • *****
  • Posts: 2147
    • https://github.com/nidud/asmc
Re: C wrapper API - Call a CPP method From Assembly Language
« Reply #18 on: April 24, 2021, 11:19:32 PM »
Here's a Asmc version using the objconv trick.

This implements the class in assembly and call it from C++.

Code: [Select]
public Rect

.class Shape

  width  dd ?
  height dd ?

    setWidth  proc :dword
    setHeight proc :dword
    .ends

.class Rectangle : public Shape

    getArea proc

    .ends

    .code

Shape::setWidth proc Width:dword

    mov [rcx].Shape.width,edx
    ret

Shape::setWidth endp

Shape::setHeight proc Height:dword

    mov [rcx].Shape.height,edx
    ret

Shape::setHeight endp

Rectangle::getArea proc

    mov eax,[rcx].Shape.width
    mul [rcx].Shape.height
    ret

Rectangle::getArea endp

    .data

    rc_vtable RectangleVtbl {
            { Shape_setWidth, Shape_setHeight }, Rectangle_getArea
        }
    rectangle Rectangle {
            { rc_vtable, 0, 0 }
        }
    Rect ptr Rectangle rectangle

    end

This is a copy of the Inheritance sample.

Code: [Select]
#include <stdio.h>

class Shape {
   public:
      void setWidth(int);
      void setHeight(int);

   protected:
      int width;
      int height;
};

class Rectangle: public Shape {
   public:
      int getArea();
};

extern class Rectangle *Rect;

int mainCRTStartup(void) {

   Rect->setWidth(5);
   Rect->setHeight(7);

   printf("Total area: %d\n",  Rect->getArea() );

   return 0;
}

The make process is a bit clunky but it works.

Code: [Select]
main.exe:
    asmc64 -Cp -frame -nolib shape.asm
    cl /c /MD main.cpp
    objconv -v0 -nr:?setWidth@Shape@@QEAAXH@Z:Shape_setWidth main.obj main2.obj
    objconv -v0 -nr:?setHeight@Shape@@QEAAXH@Z:Shape_setHeight main2.obj main.obj
    objconv -v0 -nr:?getArea@Rectangle@@QEAAHXZ:Rectangle_getArea main.obj main2.obj
    objconv -v0 -nr:?Rect@@3PEAVRectangle@@EA:Rect main2.obj main.obj
    link /subsystem:console main.obj shape.obj msvcrt.lib
    $@
    pause
    del *.obj

LiaoMi

  • Member
  • ****
  • Posts: 845
Re: C wrapper API - Call a CPP method From Assembly Language
« Reply #19 on: May 03, 2021, 01:40:51 AM »
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:

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

Code: [Select]
00000001400010D9                                                                                             | F2:0F100D 871F0000                 | MOVSD   XMM1, [b]QWORD[/b] PTR DS:[<__real@404e000000000000>]                                  |
C Language
Code: [Select]
__real@404e000000000000 dq 404E000000000000hAsm
Code: [Select]
__real@404e000000000000 dq -60.0; Float
Quote
Move 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;
}


Code: [Select]

    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

  • Regular Member
  • *
  • Posts: 37
Re: C wrapper API - Call a CPP method From Assembly Language
« Reply #20 on: May 03, 2021, 03:41:39 AM »
DWORD != QWORD -> Float INIT

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

    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

  • Member
  • ****
  • Posts: 845
Re: C wrapper API - Call a CPP method From Assembly Language
« Reply #21 on: May 03, 2021, 06:23:21 AM »
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

  • Regular Member
  • *
  • Posts: 37
Re: C wrapper API - Call a CPP method From Assembly Language
« Reply #22 on: May 05, 2021, 10:51:27 AM »
Hi LiaoMi,

The following code compiles correctly under MSVC 19.28.29914.0:

Code: [Select]
#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:

Code: [Select]
                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

  • Member
  • ****
  • Posts: 845
Re: C wrapper API - Call a CPP method From Assembly Language
« Reply #23 on: May 05, 2021, 07:33:47 PM »
Hi LiaoMi,

The following code compiles correctly under MSVC 19.28.29914.0:

Code: [Select]
#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:

Code: [Select]
                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

Code: [Select]
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

  • Member
  • ****
  • Posts: 845
Re: C wrapper API - Call a CPP method From Assembly Language
« Reply #24 on: May 05, 2021, 08:44:32 PM »
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.

Code: [Select]
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;
}

Code: [Select]
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.

Code: [Select]
#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).
Code: [Select]
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

Code: [Select]
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