Abstract
This article explains how inline assembly programming by linking or invoking CPU-dependent native assembly 32-bit code to C# managed code. The .NET Framework, in fact, doesn't support assembly code execution explicitly in a CLR compiler because it JITs the IL code to native code, there is no provision for assembly code in the .NET Framework. But the VC++ inline assembler can integrate any of C or C++ methods in its scope due to its convenient nature. Despite the complicated nature of this process, it is very useful in some scenarios where faster-optimized code matters because a larger calculation must be completed fast and C++ or C# code is relatively slow to process a complicated algorithm. Apart from program improvement, inline assembly code also aids in controlling hardware and reducing memory leaks in a better way rather than .NET or VC++ language. We shall show assembly code invoked by genuine C# code by presenting a GUI application that renders some math and CPU information gathering functionality by calling an ASM code library.
Prerequisite
The example scenario illustrated in the article requires a comprehensive working experience for manipulating both a managed and unmanaged DLL, moreover the knowledge of MASM programming code syntax because the DLL will contain methods defined in the form of Assembly language. Our workstation must be configured with this software as in the following:
- Visual C++
- Visual C#
- MASM (optional)
The mechanics
Invoking hardcore Assembly code into managed .NET code is a rather complicated task because the CLR typically doesn't execute ASM instructions. But as we know, C# can consume the methods of an unmanaged code that could be located in any VC++ or C++ DLL, and more interestingly, C++ or VC++ can execute or integrate Assembly code. So here is the hack, VC++ can become the medium between Assembly code and C# source code and we can invoke the ASM code in C# code indirectly. However, we first write some functionality for specific operations in the form of assembly code and export them by marking _declspec in VC++ code that produces an unmanaged DLL library that has the definition for all the methods, and finally, we import these methods into the C# code file to consume the built-in methods into the DLL as described in the following figure.
Inline ASM code
Here, we are integrating an Assembly code routine that later will be called in the C# program. This mechanism is doing some typical mathematics operations as well as calculates CPU accuracy by writing logic in ASM code. This section is illustrating the process of a Win32 VC++ DLL that has an inline Assembly code. The following code segment is a header file that contains the exported routine definition.
#include <windows.h>
extern "C" __declspec(dllexport) int __stdcall Addition(int a, int b);
extern "C" __declspec(dllexport) int __stdcall Substraction(int a, int b);
extern "C" __declspec(dllexport) int __stdcall Multiply(int a, int b);
extern "C" __declspec(dllexport) int __stdcall Division(int a, int b);
extern "C" __declspec(dllexport) int __stdcall CPUSpeed();
extern "C" __declspec(dllexport) char* __stdcall CPUType();
extern "C" __declspec(dllexport) int __stdcall CPUFamily();
extern "C" __declspec(dllexport) int __stdcall CPUModel();
#endif
Now let's come to one of the routines that add two integers using assembly code. This article doesn't intend to teach assembly programming or there is no need to dive deeper into the syntax, just use simple opcodes that add two numbers and store the results into an integer type variable using an _asm block that invokes the inline assembler and can appear in a C or C++ statement anywhere as in the following.
int Addition(int a, int b) {
int result;
__asm {
mov eax, a
add eax, b
mov result, eax
}
return result;
}
The method CPUSpeed is coded for calculating the CPU actual speed using an inline assembly code instruction with VC++ as in the following.
extern "C" __declspec(dllexport) int __stdcall CPUSpeed() {
LARGE_INTEGER ulFreq, ulTicks, ulValue, ulStartCounter, ulEAX_EDX, ulResult;
QueryPerformanceFrequency(&ulFreq);
QueryPerformanceCounter(&ulTicks);
// calculate one second interval
ulValue.QuadPart = ulTicks.QuadPart + ulFreq.QuadPart;
__asm {
rdtsc
mov ulEAX_EDX.LowPart, EAX
mov ulEAX_EDX.HighPart, EDX
}
ulStartCounter.QuadPart = ulEAX_EDX.QuadPart;
do {
QueryPerformanceCounter(&ulTicks);
} while (ulTicks.QuadPart <= ulValue.QuadPart);
__asm {
rdtsc
mov ulEAX_EDX.LowPart, EAX
mov ulEAX_EDX.HighPart, EDX
}
// calculate result
ulResult.QuadPart = ulEAX_EDX.QuadPart - ulStartCounter.QuadPart;
return (int)ulResult.QuadPart / 1000000;
}
Once we have done the coding for all the routines, debug the program, but before this, it is necessary to do some slight configuration. Since it is a DLL file, it doesn't have an entry point so we need to tell the compiler manually that it's a library project. The second important setting is that all VC++ DLLs are compiled with the /clr option by default but that can cause loader lock issues and a deadlock situation could occur non-deterministically. The loaderLock MDA typically identifies efforts to execute managed code on a thread that holds the Microsoft OS loader lock and utilizing the DLL before its initialization by the OS is illegitimate and could lead to deadlock situations. If this option is enabled, our DLL does not even compile successfully but instead fails because other Win32 functions also require the loader lock when programming inside the OS loader lock. Hence we need to disable this option and this can be done using solution properties, in the General section make the following changes:
Finally, compile this project and AsmCodeLib.dll is created in the solution Debug folder that will be referenced in the C# GUI application in the next section.
Note. The _asm block in a C/C++ program upsets optimization because the compiler doesn't try to optimize the __asm block itself and it also affects register variable storage.
Invoking ASM code
Congratulations! The unmanaged DLL is finally done by inline assembly coding with VC++. This library contains a couple of routine definitions to perform a specific operation that will be going live using the C# Windows application. We can confirm with the methods definition details accompanied in the DLL using the Dumpbin.exe utility as in the following that enumerates the routine and other metadata as in the following:
Perhaps you might be thinking that this is an unmanaged DLL and it would be referenced in the C# solution using Add Reference like we did earlier a couple of times but the important point to note is that it is neither a COM library nor a genuine VC++ unmanaged DLL. However, in this scenario, we can't add its reference into the solution because it is not a managed assembly and Regsvr32.exe too can't save us, because it is not a COM DLL. Hence, we shall proceed with another approach in which we just provide the full path of the DLL in the DllImport attribute where it is located as in the following.
[DllImport(@"FULL-PATH \AsmCodeLib.dll")]
static extern int Addition(int x, int y);
[DllImport(@"FULL-PATH \AsmCodeLib.dll")]
static extern int Substraction(int x, int y);
[DllImport(@"FULL-PATH \AsmCodeLib.dll")]
static extern int Multiply(int x, int y);
[DllImport(@"FULL-PATH \AsmCodeLib.dll")]
static extern string CPUType();
[DllImport(@"FULL-PATH \AsmCodeLib.dll")]
static extern int CPUFamily();
[DllImport(@"FULL-PATH \AsmCodeLib.dll")]
static extern int CPUModel();
After making the entry for each DLL method in the DllImport attribute in the C# file, invoke each corresponding routine related to the math operation and CPU by calling the method name directly and store the return value on the designated windows form text box as in the following.
try
{
string CPU_Type = CPUType();
txtType.Text = CPU_Type.ToString();
// Other operations...
int CPU_Speed = CPUSpeed();
txtSpeed.Text = CPU_Speed.ToString();
// Other operations...
int res = Addition(Convert.ToInt32(txtX.Text), Convert.ToInt32(txtY.Text));
txtRes.Text = res.ToString();
}
catch (DllNotFoundException err)
{
// Handle the exception...
}
Finally, run the project and a Windows form will appear as in the following, in which two sections are highlighted, one for doing the math operation and the other is for retrieving CPU information. So, enter any two integer type values and perform any operation, such as addition, division, and so on, and in the other box, we will obtain significant information about the CPU such as speed, type, and model by hitting the Retrieve button.
Final note
So we have discovered a special mechanism in which we can invoke the Assembly code in a C# source code file even if not supported by the CLR. In this voyage, we have relied on a Win32 VC++ DLL that has an inline definition for assembly code because VC++ supports (or can execute) ASM code, unlike .NET code. A VC++ unmanaged DLL can be invoked in C# code. So VC++ bridges the gap between Assembly language and C# to get this functionality. Such an implementation can happen using the MASM ml.exe, the VC++ cl.exe, and the C# csc.exe in which each file is compiled and debugged in an isolated manner and later linked together but this process is considered to be cumbersome rather than by opting to use Visual Studio itself. Hopefully, it shall be explained in forthcoming articles soon.