Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
en:multiasm:papc:chapter_6_9 [2026/01/01 20:49] – [Merging of the High-Level Languages and Assembler Code] pczekalskien:multiasm:papc:chapter_6_9 [2026/02/27 02:50] (current) – [Merging of the High-Level Languages and Assembler Code] jtokarz
Line 3: Line 3:
 The integration of assembler code with applications written in high-level languages brings benefits in particular scenarios, such as implementing complex mathematical algorithms and real-time tasks that require efficient, compact code. No one uses an assembler to implement a graphical user interface (GUI) anymore, as there is no reason to do so. Modern desktop operating systems are designed to provide a rich user experience, supporting languages such as C#, C++, and Python for implementing user interfaces (UIs) through libraries. While those UI generation functions can be executed from the assembler level, there is virtually no reason to do it. A more effective approach is to have the main application is written in a high-level language and execute assembly code as needed to perform backend operations efficiently. The integration of assembler code with applications written in high-level languages brings benefits in particular scenarios, such as implementing complex mathematical algorithms and real-time tasks that require efficient, compact code. No one uses an assembler to implement a graphical user interface (GUI) anymore, as there is no reason to do so. Modern desktop operating systems are designed to provide a rich user experience, supporting languages such as C#, C++, and Python for implementing user interfaces (UIs) through libraries. While those UI generation functions can be executed from the assembler level, there is virtually no reason to do it. A more effective approach is to have the main application is written in a high-level language and execute assembly code as needed to perform backend operations efficiently.
  
-In the case of multi-tier web applications, assembly code is usually hidden in the backend, oriented towards efficient computation, and it is wrapped in a high-level API library, such as. e.g. ASP.NET Core Web API or many other REST libraries.+In the case of multi-tier web applications, assembly code is usually hidden in the backend, oriented towards efficient computation, and it is wrapped in a high-level API library. e.g. ASP.NET Core Web API or many other REST libraries
 + 
 +It is possible to merge assembler code with high-level languages either as: 
 +    * static, where assembler code is compiled as a library object file and merged with the code during linking (figure {{ref>staticlinking}}), or 
 +    * dynamic, where the assembler code library is loaded during runtime (figure {{ref>dynamiclinking}}). 
 + 
 +<figure staticlinking> 
 +{{ :en:multiasm:papc:static_linking.png?600 |Static merging (linking) of the assembler code and high-level application}} 
 +<caption>Static merging (linking) of the assembler code and high-level application</caption> 
 +</figure> 
 +<figure dynamiclinking> 
 +{{ :en:multiasm:papc:dynamic_linking.png?600 |Dynamic merging (loading) of the assembler code and high-level application}} 
 +<caption>Dynamic merging (loading) of the assembler code and high-level application</caption> 
 +</figure> 
 +Dynamic code loading is considered an advantage because the original application does not contain the assembler binary executable; it is kept in a separate file and loaded on demand, allowing it to be compiled and exchanged independently. On the other hand, it raises several challenges, such as versioning, compatibility, and the time required to load the library from the file system before the first call to its contents.
 ===== Programming in Assembler for Windows ===== ===== Programming in Assembler for Windows =====
 Windows OS has historically supported unmanaged code written primarily in C++. This kind of code runs directly on the CPU, but divergence in hardware platforms, such as the introduction of ARM-core-based platforms running Windows, causes incompatibility issues. Since the introduction of the .NET framework, Windows has provided developers with a safer way to execute their code, called "managed code". The difference is that managed code, typically written in C#, is executed by a .NET framework interpreter rather than being compiled into machine code, as unmanaged code is. The use of managed code brings multiple advantages for developers, including automated memory management and code isolation from the operating system. This, however, raises several challenges when integrating managed code and assembly code. In any case, the integration model is common: the assembler implements functions (usually stateless) that are later called from the high-level language and return data to it (figure {{ref>masmintegration1}}). Windows OS has historically supported unmanaged code written primarily in C++. This kind of code runs directly on the CPU, but divergence in hardware platforms, such as the introduction of ARM-core-based platforms running Windows, causes incompatibility issues. Since the introduction of the .NET framework, Windows has provided developers with a safer way to execute their code, called "managed code". The difference is that managed code, typically written in C#, is executed by a .NET framework interpreter rather than being compiled into machine code, as unmanaged code is. The use of managed code brings multiple advantages for developers, including automated memory management and code isolation from the operating system. This, however, raises several challenges when integrating managed code and assembly code. In any case, the integration model is common: the assembler implements functions (usually stateless) that are later called from the high-level language and return data to it (figure {{ref>masmintegration1}}).
Line 17: Line 31:
  
 ==== Dynamic memory management considerations ==== ==== Dynamic memory management considerations ====
-Using dynamic memory management at the level of the assembler code is troublesome: allocating and releasing memory require calls to the hosting operating system. It is possible, but complex. Moreover, there is no dynamic, automated memory management, as in .NET, Java, and Python, so the developer is on their own, similar to programming in C++. For this reason, it is common to allocate adequate memory resources on the high-level code, e.g., the GUI front-end and pass them to the assembler code as pointers. Note, however, that for some higher-level languages, such as C#, it is necessary to follow a strict pattern to ensure correct and persistent memory allocation, as described in the following sections.+Using dynamic memory management at the assembler level is troublesome: allocating and releasing memory require calls to the host operating system. It is possible, but complex. Moreover, there is no dynamic, automated memory management, as in .NET, Java, and Python, so the developer is on their own, much like in C++. For this reason, it is common to allocate adequate memory resources on the high-level code, e.g., the GUI front-end and pass them to the assembler code as pointers (figure {{ref>dynamicmemory}}). Note, however, that for some higher-level languages, such as C#, it is necessary to follow a strict pattern to ensure correct and persistent memory allocation, as described in the following sections.
  
 <note tip>Using dynamic memory management at the level of the assembler code is troublesome. Common practice is to dynamically allocate memory resources in the scope of the calling (high-level) application and pass them to the assembler code via pointers.</note> <note tip>Using dynamic memory management at the level of the assembler code is troublesome. Common practice is to dynamically allocate memory resources in the scope of the calling (high-level) application and pass them to the assembler code via pointers.</note>
  
 +<figure dynamicmemory>
 +{{ :en:multiasm:papc:hll_and_assembler-dynamic_memory_allocation.drawio.png?600 | Dynamic Memory Allocation Model for Assembler Code Integration}}
 +<caption>Dynamic Memory Allocation Model for Assembler Code Integration</caption>
 +</figure>
 ==== Pure Assembler Applications for Windows CMD ==== ==== Pure Assembler Applications for Windows CMD ====
 It is possible to write an application for Windows solely in assembler. While the reason to do it is doubtful, some hints presented below, such as calling system functions, may be helpful. It is possible to write an application for Windows solely in assembler. While the reason to do it is doubtful, some hints presented below, such as calling system functions, may be helpful.
Line 86: Line 104:
 **Programming for applications written in unmanaged code** **Programming for applications written in unmanaged code**
  
-In the case of the unmanaged code, integration is straightforward. Assembler code is encapsulated in the DLL library (libraries). It is possible to merge assembler code either as: +In the case of the unmanaged code, integration is straightforward. Assembler code is usually encapsulated in the DLL library (or multiple libraries). 
-    * staticwhere assembler code is compiled as .lib file and merged with the code during linking, or +Below is a sample dummy assembler function that returns an integer (no parameters)along with relevant C++ code that dynamically loads DLL, including the full library-loading lifecycle.
-    * dynamic, where assembler code is loaded during runtime.+
  
-The "static" method is considered less flexible and uses a common pattern shared with many C++ libraries. Below is a sample dummy assembler function that returns an integer (no parameters), along with relevant C++ code that dynamically loads a DLL, including the full library-loading lifecycle. +Assembler code (source for DLL):
- +
-Assembler code (DLL):+
 <code cpp AssemblerDll.asm> <code cpp AssemblerDll.asm>
 .code .code
Line 101: Line 116:
 MyAsmProc endp MyAsmProc endp
 end end
 +</code>
 +
 +Relevant definition file:
 +<code cpp AssemblerDll.def>
 +LIBRARY AssemblerDll
 +EXPORTS MyAsmProc
 </code> </code>
 C++ application, dynamically loading a DLL and dynamically obtaining the assembler function's handler (address): C++ application, dynamically loading a DLL and dynamically obtaining the assembler function's handler (address):
-<code cpp>+<code cpp WindowsCmdX64.cpp>
 #include <Windows.h> #include <Windows.h>
 #include <iostream> #include <iostream>
Line 111: Line 132:
 int main() int main()
 { {
- dllHandle = LoadLibrary(TEXT("AssemblerDll.dll")); +    dllHandle = LoadLibrary(TEXT("AssemblerDll.dll")); 
- if (!dllHandle) +    if (!dllHandle) 
-+    
- std::cerr << "Failed to load DLL library\n"; +        std::cerr << "Failed to load DLL library\n"; 
- return 1; +        return 1; 
-+    
- MyProc myAsmProcedure = (MyProc)GetProcAddress(dllHandle, "MyAsmProc"); +    MyProc myAsmProcedure = (MyProc)GetProcAddress(dllHandle, "MyAsmProc"); 
- if (!myAsmProcedure) +    if (!myAsmProcedure) 
-+    
- std::cerr << "Failed to find assembler procedure\n"; +        std::cerr << "Failed to find assembler procedure\n"; 
- FreeLibrary(dllHandle); +        FreeLibrary(dllHandle); 
- return 2; +        return 2; 
-+    
- std::cout << myAsmProcedure(); +    std::cout << myAsmProcedure(); 
- FreeLibrary(dllHandle); +    FreeLibrary(dllHandle); 
- return 0;+    return 0;
 } }
  
Line 134: Line 155:
  
 **Programming for applications written in managed code** **Programming for applications written in managed code**
 +
 In the case of managed code, things get more complex. The .NET framework features automated memory management that releases unused memory (e.g., objects for which there are no more references) and optimises variable locations to improve performance. It is known as a .NET Garbage Collector (GC). GC instantly traces references and, in the event of an object relocation in memory, updates all references accordingly. It also releases memory (objects) that are no longer referenced. This automated mechanism, however, applies only across managed code apps. The problem arises when developers integrate a front-end application written in managed code with assembler libraries written in unmanaged code. All pointers and references passed to the assembler code are not automatically traced by the GC. Using dynamically allocated variables on the .NET side and accessing them from the assembler code is a very common scenario. GC cannot "see" any reference to the object (variable, memory) made in unmanaged code; thus, it may release or relocate memory without updating the reference address on the assembler side. It causes very hard-to-debug errors that occur randomly and are very serious (e.g. null pointer exception). Possible reference cases are presented visually in the: In the case of managed code, things get more complex. The .NET framework features automated memory management that releases unused memory (e.g., objects for which there are no more references) and optimises variable locations to improve performance. It is known as a .NET Garbage Collector (GC). GC instantly traces references and, in the event of an object relocation in memory, updates all references accordingly. It also releases memory (objects) that are no longer referenced. This automated mechanism, however, applies only across managed code apps. The problem arises when developers integrate a front-end application written in managed code with assembler libraries written in unmanaged code. All pointers and references passed to the assembler code are not automatically traced by the GC. Using dynamically allocated variables on the .NET side and accessing them from the assembler code is a very common scenario. GC cannot "see" any reference to the object (variable, memory) made in unmanaged code; thus, it may release or relocate memory without updating the reference address on the assembler side. It causes very hard-to-debug errors that occur randomly and are very serious (e.g. null pointer exception). Possible reference cases are presented visually in the:
   * figure {{ref>csharp1}} - proper reference, initially OK untill GC runs,    * figure {{ref>csharp1}} - proper reference, initially OK untill GC runs, 
Line 193: Line 215:
  
 ===== Programming in Assembler for Linux ===== ===== Programming in Assembler for Linux =====
 +Principles for composing assembler code and high-level language into a single application on Linux OSes are similar to those on Windows; dynamic loading is more complex. Thus, we consider only static linking of the code. The most common use of C++ is as a high-level application. Still other options are possible, such as Python.
 +
 +Linux provides more parameters passed via registers in its x64 standard calls (up to 6) than Windows (only up to 4). Refer to the chapter [[en:multiasm:papc:chapter_6_8|]] for details.
 +
 +A common scenario is to use the [[https://man7.org/linux/man-pages/man1/g++.1.html|g++]] compiler to compile high-level applications and [[https://www.nasm.us/|nasm]] to compile assembler code. It is also common to help compose a heterogeneous project using makefiles, as presented below.
 +
 +The sample project is composed of the ''main.cpp'' file (main file with high-level, C++ application), ''asmfunc.asm'' containing the assembler source code and the aforementioned ''Makefile''.
 +
 +The ''Makefile'' contains definitions of the compilation and linking of the **main** application and also a definition of the cleanup commands ("clean" section):
 +<code ini Makefile>
 +all: main
 +
 +main: main.o asmfunc.o
 + g++ -o main main.o asmfunc.o
 +
 +main.o: main.cpp
 + g++ -c -g -F dwarf main.cpp
 +
 +asmfunc.o: asmfunc.asm
 + nasm -g -f elf64 -F dwarf asmfunc.asm -l asmfunc.lst
 +
 +clean:
 + rm -f ./main || true 
 + rm -f ./main.o || true
 + rm -f ./asmfunc.o || true
 + rm -f ./asmfunc.lst || true
 +</code>
 +
 +<note important>It is essential to remember that in Linux OSes, indentation whitespaces in ''Makefile'' must be created using TABs rather than SPACEs.</note>
 +
 +Assembler code exposes functions to the linker using the ''global'' directive. Without it, assembler functions remain "private" and cannot be called, so linking won't succeed if there is a reference to the function from the high-level language part of the code. The following code presents a dummy function that performs integer addition of two arguments. Directives "section" are optional in this example.
 +
 +<code assembler asmfunc.asm>
 +section .data
 +section .bss
 +section .text
 +
 +global addInAsm
 +
 +addInAsm:
 + nop
 + mov rax, rsi
 + add rax, rdi
 + ret
 +</code>
 +
 +Finally, the calling side (C++ application) uses the ''extern'' directive to inform the linker about the external function, written in assembler.
 +<code cpp main.cpp>
 +#include <iostream>
 +
 +extern "C" {long long int addInAsm(long long, long long);}
 +
 +long long a=10;
 +long long b=7;
 +long long returnValue;
 +
 +int main() {
 +    std::cout << "Hello, Assembler!" << std::endl;
 +    returnValue = addInAsm(a,b);
 +    std::cout << "Sum of " << a << " and " << b << " is " << returnValue 
 +              << std::endl;
 +    return 0;
 +}
 +</code>
en/multiasm/papc/chapter_6_9.1767293341.txt.gz · Last modified: by pczekalski
CC Attribution-Share Alike 4.0 International
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0