The MASM Forum

64 bit assembler => UASM Assembler Development => Topic started by: LiaoMi on April 23, 2021, 03:43:38 AM

Title: C wrapper API - Call a CPP method From Assembly Language
Post by: LiaoMi on April 23, 2021, 03:43:38 AM
Hi,

How to call a C++ method from C - https://stackoverflow.com/questions/14815274/how-to-call-a-c-method-from-c
Tutorial: HowTo integrate a C++ library/class into a C programm - https://www.teddy.ch/c++_library_in_c/

I want to implement a universal translator for auto-generation of an interface, that describes a C++ class. The main goal is to be able to link C++ classes in an assembly program. But from the very beginning I have many problems. I have compiled object modules in visual studio, that do not exceed the size of 1 kb. Without the initial creation of a wrapper, I took the desired main function and formalized it as a main procedure. Then I tried to link all this using the linker from the visual studio, to my surprise, the 3 kb code increased itself to 400 kb, for the reason that there are two functions in the code responsible for creating an object and deleting it. Only two functions contributed so much shit to the code  :dazzled: :sad:

It seems that there is no other way out. It is necessary to change the libraries that support object-oriented code. Maybe then something will come out. So my idea failed before it even started  :greensml:

https://www.youtube.com/watch?v=iItfIP4PabU  :tongue:

Part 1: Using a simple class - http://masm32.com/board/index.php?topic=9317.msg102309#msg102309 (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 (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:

Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: jj2007 on April 23, 2021, 05:04:02 AM
All this looks unnecessarily complicated. Any chance to provide a tiny C++ "class" for testing how "this" etc are passed to C++?
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: LiaoMi on April 23, 2021, 05:58:22 AM
Quote from: jj2007 on April 23, 2021, 05:04:02 AM
All this looks unnecessarily complicated. Any chance to provide a tiny C++ "class" for testing how "this" etc are passed to C++?

Hi jj2007,

in the example, there is already an object module and source, CPP_All_in_one - here everything is collected in one object file without a wrapper. But the goal was to make executables small in size without rewriting everything.
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: jj2007 on April 23, 2021, 07:06:11 AM
Oops, doesn't work...
C:\Windows\ccut1p6K.o:Tmp.cpp:(.text.startup+0x16): undefined reference to `operator new(unsigned int)'
C:\Windows\ccut1p6K.o:Tmp.cpp:(.text.startup+0x24): undefined reference to `operator delete(void*)'
collect2.exe: error: ld returned 1 exit status
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: TimoVJL on April 23, 2021, 04:39:52 PM
Quote from: jj2007 on April 23, 2021, 05:04:02 AM
All this looks unnecessarily complicated. Any chance to provide a tiny C++ "class" for testing how "this" etc are passed to C++?
That hidden this comes in ecx / rcx ??int_set@MyClass@@QEAAXH@Z:
00000000  89542410                 mov dword ptr [rsp+10h], edx
00000004  48894C2408               mov qword ptr [rsp+8h], rcx
00000009  488B442408               mov rax, qword ptr [rsp+8h]
0000000E  8B4C2410                 mov ecx, dword ptr [rsp+10h]
00000012  8908                     mov dword ptr [rax], ecx
00000014  C3                       ret

?int_get@MyClass@@QEAAHXZ:
00000020  48894C2408               mov qword ptr [rsp+8h], rcx
00000025  488B442408               mov rax, qword ptr [rsp+8h]
0000002A  8B00                     mov eax, dword ptr [rax]
0000002C  C3                       ret
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: jj2007 on April 23, 2021, 06:07:38 PM
Quote from: TimoVJL on April 23, 2021, 04:39:52 PMThat hidden this comes in ecx / rcx ?

Yes indeed, at least in 64-bit code. Unfortunately, I haven't been able to make GetProcAddress work. Example:
  mov ebx, rv(LoadLibrary, "ureg")
  invoke GetProcAddress, ebx, Chr$("?SaveKeyToFile@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEBVWSTRING@@PEAK@Z")


The string is correct, and it should work (according to some sites), but it always returns zero. Plus, all that stuff is badly documented. Here is the output of podump.exe:
Dump of C:\Windows\System32\ureg.dll

File type: DLL

        Exported symbols for UREG.dll

               0 characteristics
        4A5BA291 time date stamp (Mon Jul 13 23:09:37 2009)
            0.00 version
               1 ordinal base
              19 number of functions
              19 number of names

        ordinal  hint  address           name
              1     0  000007FF701119A0  ??0REGISTRY@@QEAA@XZ
              2     1  000007FF701115A0  ??0REGISTRY_KEY_INFO@@QEAA@XZ
              3     2  000007FF70111348  ??0REGISTRY_VALUE_ENTRY@@QEAA@XZ
              4     3  000007FF70111A40  ??1REGISTRY@@UEAA@XZ
              5     4  000007FF70111E38  ?AddValueEntry@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEBVREGISTRY_VALUE_ENTRY@@EPEAK@Z
              6     5  000007FF701123D8  ?CreateKey@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@1PEAKE@Z
              7     6  000007FF7011266C  ?DeleteKey@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEBVWSTRING@@PEAK@Z
              8     7  000007FF7011272C  ?DeleteValueEntry@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEBVWSTRING@@PEAK@Z
              9     8  000007FF701128A4  ?DoesKeyExist@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEBVWSTRING@@1PEAK@Z
              A     9  000007FF70112900  ?DoesValueExist@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEBVWSTRING@@11PEAK@Z
              B     A  000007FF70114498  ?EnableRootNotification@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAXKE@Z
              C     B  000007FF70111AC4  ?Initialize@REGISTRY@@QEAAEPEBVWSTRING@@PEAK@Z
              D     C  000007FF70111740  ?Initialize@REGISTRY_KEY_INFO@@QEAAEPEBVWSTRING@@0K0PEAU_SECURITY_ATTRIBUTES@@@Z
              E     D  000007FF7011146C  ?Initialize@REGISTRY_VALUE_ENTRY@@QEAAEPEBVWSTRING@@KW4_REG_TYPE@@PEBEK@Z
              F     E  000007FF70114DC4  ?IsAccessAllowed@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@KPEAK@Z
             10     F  000007FF701148D0  ?LoadHive@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEBVWSTRING@@PEAK@Z
             11    10  000007FF70112A38  ?QueryKeyInfo@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEBVWSTRING@@1PEAVREGISTRY_KEY_INFO@@PEAK@Z
             12    11  000007FF70112E88  ?QueryKeySecurity@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEBVREGISTRY_KEY_INFO@@KPEAPEAXPEAK@Z
             13    12  000007FF70113018  ?QuerySubKeysInfo@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEBVWSTRING@@1PEAVARRAY@@PEAK@Z
             14    13  000007FF7011350C  ?QueryValues@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEBVWSTRING@@1PEAVARRAY@@PEAK@Z
             15    14  000007FF70114C84  ?RestoreKeyFromFile@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEBVWSTRING@@EPEAK@Z
             16    15  000007FF70114B60  ?SaveKeyToFile@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEBVWSTRING@@PEAK@Z
             17    16  000007FF70113B88  ?SetKeySecurity@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@KPEAXPEAKE@Z
             18    17  000007FF70114A50  ?UnLoadHive@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEAK@Z
             19    18  000007FF70113CC4  ?UpdateKeyInfo@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEAK@Z
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: Vortex on April 23, 2021, 08:04:51 PM
Hi LiaoMi,

The main problem is the complicated internals of C++ and the libraries associated with the compiler suite. Disassembling your object module with Agner Fog's objconv :



extern ??3@YAXPEAX_K@Z: near
extern ??2@YAPEAX_K@Z: near



main    LABEL NEAR
        mov     qword ptr [rsp+10H], rdx
        mov     dword ptr [rsp+8H], ecx
        push    rdi
        sub     rsp, 64
        mov     ecx, 4
        call    ??2@YAPEAX_K@Z
.
.
.
.
        mov     edx, 4
        mov     rcx, qword ptr [rsp+38H]
        call    ??3@YAXPEAX_K@Z
        xor     eax, eax
        add     rsp, 64
        pop     rdi
        ret                           



??3@YAXPEAX_K@Z and ??2@YAPEAX_K@Z are the two externals, the references to new and delete in your code :

#include "MyClass.h"
using namespace std;
int main(int argc, char* argv[]) {
MyClass *c = new MyClass();
c->int_set(3);
c->int_get();
delete c;
}


To get a smaller executable, the trick is to reduce the dependencies like new \ delete as much as possible. In our case, we are depending on the VC++ libraries LIBCMT.lib and OLDNAMES.lib This is why we get a bloated C++ executable for the obvious reasons.

To avoid the problematic C++ dependencies, I tried to replace the new & delete couple with pointers in my example :

#include <stdio.h>

class volume {

    public:
   
        int a;
        int b;
        int c;
        int d;
       
        int calc(void);
        void getvol(int);
};

int volume::calc(void)
{
    return a*b*c;
}

void volume::getvol(int v)
{
    printf("The volume is %u cm3\n",v);
}


int main()
{
    volume Box;
    volume *PtrBox;
    PtrBox = &Box;

    PtrBox->a = 2;
    PtrBox->b = 4;
    PtrBox->c = 6;

    PtrBox->d = PtrBox->calc();
    PtrBox->getvol(PtrBox->d);

return 0;

}


I am using VC++ Python to compile the code. It's and old compiler but very good for practical programming. Plus, you can make a portable package from the compiler components.

C:\Tools\VCForPython27>cl ClassBox.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 15.00.30729.01 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

ClassBox.cpp
Microsoft (R) Incremental Linker Version 9.00.30729.01
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:ClassBox.exe
ClassBox.obj


The executable's size is 51712 bytes.

Building the same project with a small C run-time library :

cl -c -Zl ClassSample.cpp
\masm32\bin64\link /SUBSYSTEM:CONSOLE /LARGEADDRESSAWARE /LIBPATH:\masm32\lib64 ClassSample.obj kernel32.lib msvcrt.lib crt0\crt0.lib


The result is 3072 bytes.

My method may not be suitable for complicated \ sophisticated C++ projects but it could be interesting to use it for small examples.
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: LiaoMi on April 23, 2021, 08:05:37 PM
Quote from: jj2007 on April 23, 2021, 07:06:11 AM
Oops, doesn't work...
C:\Windows\ccut1p6K.o:Tmp.cpp:(.text.startup+0x16): undefined reference to `operator new(unsigned int)'
C:\Windows\ccut1p6K.o:Tmp.cpp:(.text.startup+0x24): undefined reference to `operator delete(void*)'
collect2.exe: error: ld returned 1 exit status


Hi jj2007,

that's right, these are two functions that create and delete an object, they are in the visual studio libraries. These functions pull the entire 400kb of bloating code with them.

https://ghidra-sre.org/ghidra_9.2.3_PUBLIC_20210325.zip
https://download.java.net/java/early_access/jdk17/18/GPL/openjdk-17-ea+18_windows-x64_bin.zip

Windows: Extract the JDK distribution (.zip file) to your desired location and add the JDK's bin directory to your PATH:

Extract the JDK:

Right-click on the zip file and click Extract All...
Click Extract
Open Environment Variables window:

Windows 10: Right-click on Windows start button, and click System

Windows 7: Click Windows start button, right-click on Computer, and click Properties

Click Advanced system settings
Click Environment variables...
Add the JDK bin directory to the PATH variable:

Under System variables, highlight Path and click Edit...
At the end of the the Variable value field, add a semicolon followed by <path of extracted JDK dir>\bin
Click OK
Click OK
Click OK
Restart any open Command Prompt windows for changes to take effect

https://i.ibb.co/7Y8WGMz/2021-04-23-12-01-49-Code-Browser-Class-My-Wrapper-obj.png (https://i.ibb.co/7Y8WGMz/2021-04-23-12-01-49-Code-Browser-Class-My-Wrapper-obj.png)
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: Vortex on April 23, 2021, 09:21:27 PM
Quote from: jj2007 on April 23, 2021, 06:07:38 PM

Yes indeed, at least in 64-bit code. Unfortunately, I haven't been able to make GetProcAddress work. Example:
  mov ebx, rv(LoadLibrary, "ureg")
  invoke GetProcAddress, ebx, Chr$("?SaveKeyToFile@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEBVWSTRING@@PEAK@Z")


Hi Jochen,

It works. You need to try it with 64-bit code :

include \masm32\include64\masm64rt.inc

.data

func db '?SaveKeyToFile@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEBVWSTRING@@PEAK@Z',0

.code

start PROC

LOCAL hDLL:QWORD

    invoke  LoadLibrary,chr$("ureg.dll")
    mov     hDLL,rax

    invoke  GetProcAddress,rax,ADDR func

    invoke  vc_printf,\
            chr$("The address of the function = %llX"),\
            rax

    invoke  FreeLibrary,hDLL

    invoke  ExitProcess,0

start ENDP

END


Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: jj2007 on April 23, 2021, 09:33:31 PM
Quote from: Vortex on April 23, 2021, 09:21:27 PM
It works. You need to try it with 64-bit code

Hi Erol,

You are right! So by accident the 32-bit version of the DLL does not have that proc :cool:

So, now that we can access the proc with its address, can we identify "this", put it into rcx, and then use the proc?

We would need a crispy example of a C++ function that is
- available in System32\*.dll,
- does something useful and
- is documented :badgrin:
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: LiaoMi on April 23, 2021, 09:50:55 PM
Hi Vortex,

thanks for the great example  :thup: :thup: :thup:

From the technical point of view, we need to evaluate the library itself, how big it is in terms of dependencies, this is the main problem, even if I implement the function wrapper.

If we apply this logic to this example - Microsoft Cognitive Toolkit (CNTK) with ASMC - http://masm32.com/board/index.php?topic=9260.0, in this case there is a difference of 300 kb,

; 221  :     ::operator delete(_Ptr, _Bytes);
   call   ??3@YAXPEAX_K@Z            ; operator delete

; 221  :     ::operator delete(_Ptr, _Bytes);

call ??3@YAXPEAX_K@Z ; operator delete
; File C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.28.29910\include\xhash

; 322  :         _Mypair._Myval2._Myfirst = nullptr;

xor eax, eax
mov QWORD PTR [rbx], rax

; 323  :         _Mypair._Myval2._Mylast  = nullptr;

mov QWORD PTR [rbx+8], rax

; 324  :         _Mypair._Myval2._Myend   = nullptr;

mov QWORD PTR [rbx+16], rax



that based on all this, I concluded that it is impossible to do this. With a wrapper, the code can be called from assembly language programs, but its size will be the same as before  :tongue:
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: Vortex on April 24, 2021, 12:28:47 AM
Hi LiaoMi,

You are welcome. Here is another attempt :

#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);
}


QuoteSo, now that we can access the proc with its address, can we identify "this", put it into rcx, and then use the proc?

Hi Jochen,

Identifiying this as rcx and calling the functions from Masm :

include \masm32\include64\masm64rt.inc

EXTERN ?calc@formula@@QEAAHHHHHH@Z:PROC
calc TEXTEQU <?calc@formula@@QEAAHHHHHH@Z>

EXTERN ?GetResult@formula@@QEAAXH@Z:PROC
GetResult TEXTEQU <?GetResult@formula@@QEAAXH@Z>

dummy TEXTEQU <0>

.code

start PROC

;   rcx ->  this

    invoke  calc,dummy,2,4,6,8,10
    invoke  GetResult,dummy,rax

    invoke  ExitProcess,0

start ENDP

END


Of course, my example is very simple. C++ offers constructors, destructors, friend functions, overloading etc. All those features needs to be studied to see how they should be combined with asm.
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: Vortex on April 24, 2021, 04:35:26 AM
Here is the UASM version :

Option Stackbase:rsp
Option Frame:auto
Option Win64:11

?calc@formula@@QEAAHHHHHH@Z PROTO :QWORD,:QWORD,:QWORD,:QWORD,:QWORD,:QWORD
calc TEXTEQU <?calc@formula@@QEAAHHHHHH@Z>

?GetResult@formula@@QEAAXH@Z PROTO :QWORD,:QWORD
GetResult TEXTEQU <?GetResult@formula@@QEAAXH@Z>

ExitProcess PROTO :QWORD

dummy TEXTEQU <0>

.code

start PROC

;   rcx ->  this

    invoke  calc,dummy,2,4,6,8,10
    invoke  GetResult,dummy,rax

    invoke  ExitProcess,0

start ENDP

END
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: Vortex on April 24, 2021, 04:58:19 AM
Undecorating the C++ symbols with objconv :

\Tools\objconv\objconv.exe -nr:?calc@formula@@QEAAHHHHHH@Z:calc Class.obj Class2.obj
\Tools\objconv\objconv.exe -nr:?GetResult@formula@@QEAAXH@Z:GetResult Class2.obj Class.obj


Option Stackbase:rsp
Option Frame:auto
Option Win64:11

calc PROTO :QWORD,:QWORD,:QWORD,:QWORD,:QWORD,:QWORD
GetResult PROTO :QWORD,:QWORD
ExitProcess PROTO :QWORD

dummy TEXTEQU <0>

.code

start PROC

;   rcx ->  this

    invoke  calc,dummy,2,4,6,8,10
    invoke  GetResult,dummy,rax

    invoke  ExitProcess,0

start ENDP

END
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: LiaoMi on April 24, 2021, 11:18:40 AM
Hi Vortex and jj2007,

thanks for Your tips and help, I've finally managed to build a fully working version as planned in the first post. In the first steps, I wanted to replace all the functions that generate program bloat, so I parsed the libcmt.lib library, I took out object files from there and then pulled out the missing functions for integration in my code. It was planned to make a kind of stub function. Since I can make my own libraries that do not have any checks from the visual studio. After some experimentation, it turned out that both of these functions are exported in the msvcrt.lib library. The problem now is that each of these libraries that I have contain completely different functions with different types of checks, some of these libraries do not have exception checks and things like that.

From the library libcmt.lib library

; void *__fastcall operator new(size_t Size)
                ;public ??2@YAPEAX_K@Z
??2@YAPEAX_K@Z proc near
;??3@YAXPEAX_K@Z  proc near               ; DATA XREF: .pdata:$pdata$??2@YAPEAX_K@Z\u2193o
                push    rbx             ; $LN21
                sub     rsp, 20h
                mov     rbx, rcx
                jmp     short loc_1A
; ---------------------------------------------------------------------------

loc_B:                                  ; CODE XREF: operator new(unsigned __int64)+22\u2193j
                mov     rcx, rbx        ; Size
                call    _callnewh
                test    eax, eax
                jz      short loc_2A
                mov     rcx, rbx        ; Size

loc_1A:                                 ; CODE XREF: operator new(unsigned __int64)+9\u2191j
                call    malloc
                test    rax, rax
                jz      short loc_B
                add     rsp, 20h
                pop     rbx
                retn
; ---------------------------------------------------------------------------

loc_2A:                                 ; CODE XREF: operator new(unsigned __int64)+15\u2191j
                cmp     rbx, 0FFFFFFFFFFFFFFFFh
                jz      short loc_36
                ;call    ?__scrt_throw_std_bad_alloc@@YAXXZ ; __scrt_throw_std_bad_alloc(void)
                int     3               ; Trap to Debugger
; ---------------------------------------------------------------------------

loc_36:                                 ; CODE XREF: operator new(unsigned __int64)+2E\u2191j
                ;call    ?__scrt_throw_std_bad_array_new_length@@YAXXZ ; __scrt_throw_std_bad_array_new_length(void)
                int     3               ; Trap to Debugger
??2@YAPEAX_K@Z endp
;??3@YAXPEAX_K@Z  endp

; void __cdecl operator delete(void *Block)
                ;public ??3@YAXPEAX@Z
??3@YAXPEAX@Z   proc near
                jmp     free
??3@YAXPEAX@Z   endp


It will be interesting to see the assembly log in the archive .. look at the executable file in the debugger, it's extremely interesting  :eusa_dance:

UASM v2.52, Apr  2 2021, Masm-compatible assembler.
Portions Copyright (c) 1992-2002 Sybase, Inc. All Rights Reserved.
Source code is available under the Sybase Open Watcom Public License.

Fatal error A1106: Cannot open file: "C:\masm64\bin" [13]
main_c.asm(8) : Warning A4305: Stackbase automatically changed to RSP to support WIN64 options
Translated Windows SDK 10.0 64 bits
main_c.asm: 102 lines, 2 passes, 976 ms, 1 warnings, 0 errors
Microsoft (R) Incremental Linker Version 14.28.29337.0
Copyright (C) Microsoft Corporation.  All rights reserved.

Processed /NODEFAULTLIB:libcmt.lib
LINK : warning LNK4010: invalid subsystem version number 5.0; default subsystem version assumed

Starting pass 1
Processed /DEFAULTLIB:C:\masm64\lib64\msvcrt.lib
Processed /DEFAULTLIB:msvcrt.lib
Processed /DEFAULTLIB:D:\masm64\14.28.29910\lib\x64\msvcrt.lib
Processed /DEFAULTLIB:C:\masm64\m64lib\m64lib.lib
Processed /DEFAULTLIB:OLDNAMES

Searching libraries
    Searching C:\masm64\lib64\msvcrt.lib:
      Found printf
        Referenced in main_c.obj
        Loaded msvcrt.lib(msvcrt.dll)
      Found "void * __cdecl operator new(unsigned __int64)" (??2@YAPEAX_K@Z)
        Referenced in MyWrapper.obj
        Loaded msvcrt.lib(msvcrt.dll)
      Found __IMPORT_DESCRIPTOR_msvcrt
        Referenced in msvcrt.lib(msvcrt.dll)
        Referenced in msvcrt.lib(msvcrt.dll)
        Loaded msvcrt.lib(msvcrt.dll)
      Found __NULL_IMPORT_DESCRIPTOR
        Referenced in msvcrt.lib(msvcrt.dll)
        Loaded msvcrt.lib(msvcrt.dll)
      Found msvcrt_NULL_THUNK_DATA
        Referenced in msvcrt.lib(msvcrt.dll)
        Loaded msvcrt.lib(msvcrt.dll)
    Searching D:\masm64\14.28.29910\lib\x64\msvcrt.lib:
      Found "void __cdecl operator delete(void *,unsigned __int64)" (??3@YAXPEAX_K@Z)
        Referenced in MyWrapper.obj
        Loaded msvcrt.lib(delete_scalar_size.obj)
      Found "void __cdecl operator delete(void *)" (??3@YAXPEAX@Z)
        Referenced in msvcrt.lib(delete_scalar_size.obj)
        Loaded msvcrt.lib(delete_scalar.obj)
    Searching C:\masm64\m64lib\m64lib.lib:
    Searching D:\masm64\14.28.29910\lib\onecore\x64\OLDNAMES.lib:
    Searching C:\masm64\lib64\msvcrt.lib:
      Found free
        Referenced in msvcrt.lib(delete_scalar.obj)
        Loaded msvcrt.lib(msvcrt.dll)

Finished searching libraries

Finished pass 1

Unused libraries:
  C:\masm64\m64lib\m64lib.lib
  D:\masm64\14.28.29910\lib\onecore\x64\OLDNAMES.lib

Starting pass 2
     MyClass.obj
     MyWrapper.obj
     main_c.obj
     msvcrt.lib(msvcrt.dll)
     msvcrt.lib(msvcrt.dll)
     msvcrt.lib(msvcrt.dll)
     msvcrt.lib(msvcrt.dll)
     msvcrt.lib(msvcrt.dll)
     msvcrt.lib(msvcrt.dll)
     msvcrt.lib(delete_scalar.obj)
     msvcrt.lib(delete_scalar_size.obj)
Finished pass 2


From the log you can see that both functions were found in different libraries  :biggrin: What does it mean?!

At the moment we have a scheme -
C++ Object Modules <- C Language Wrapper <- Assembler program -> Wrapper generator to automate interface creation* (subsequent challenge)

As you said earlier, there are two ways out, a direct function call or the ability to create your own library with the necessary functions by examining libcmt.lib
Quote from: Vortex on April 24, 2021, 12:28:47 AM
Of course, my example is very simple. C++ offers constructors, destructors, friend functions, overloading etc. All those features needs to be studied to see how they should be combined with asm.

Quote from: jj2007 on April 23, 2021, 05:04:02 AM
Any chance to provide a tiny C++ "class" for testing how "this" etc are passed to C++?

The last question I would like to clarify, if all functions are exported from Microsoft libraries, then I don't understand why they inline functions that can be found in standard libraries ?! It will be very interesting to hear your opinion  :skrewy:
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: Vortex 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.
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: hutch-- 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.
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: Vortex on April 24, 2021, 09:08:57 PM
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.
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: nidud on April 24, 2021, 11:19:32 PM
deleted
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: LiaoMi 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

(https://i.ibb.co/vjDFkGX/main-c-20210502-170537-exe.png)

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).
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: tenkey on May 03, 2021, 03:41:39 AM
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.
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: LiaoMi 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
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: 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

Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: LiaoMi on May 05, 2021, 07:33:47 PM
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
Title: Re: C wrapper API - Call a CPP method From Assembly Language
Post by: LiaoMi 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 (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 (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