Reputation: 29
Is there a simple way to create a Visual Studios solution having two projects - the first a ml64 assembly program that creates a .dll - and a second C# program that invokes a method in the .dll?
For example:
ml64 code file div.asm:
.code
; ulong Divide (ref ulong rem, ulong dividend_low, ulong divisor)
; sets rem to (rem*2^64 + dividend_low) mod divisor
; return floor((rem*2^64 + dividend_low) / divisor)
; assume &rem in rcx, dividend_low in rdx, divisor in r8
Divide proc
mov rax,rdx
mov rdx,qword ptr [rcx]
cmp rdx,r8
jae ovfl
div r8
mov qword ptr [rcx],rdx
ovfl:
ret
Divide endp
end
C# code file main.cs:
using System;
using System.Runtime.InteropServices; // DLL support
class HelloWorld
{
[DllImport("div.dll")]
public static extern ulong Divide (ref ulong rem, ulong dividend_low,
ulong divisor);
static void Main ()
{
Console.WriteLine ("This is C# program");
long quot, rem = 3, dividend_low = 1, divisor = 20;
Console.WriteLine("divide (3 * 2^64 + 1) by 20");
quot = Divide(ref rem, dividend_low, divisor);
Console.WriteLine("quotient = {0}, remainder = {1}", quot, rem);
}
}
I would like to do this without recourse to an intermediate layer in C++ if possible. What settings need be set in VS? Does the C# code need to use PInvoke? If so how?
Upvotes: 2
Views: 536
Reputation: 29
It works! Here are the details for those interested:
Assembly ml64:
.code
; ulong Divide (ref ulong rem, ulong dividend_low, ulong divisor)
; on entry rem is dividend_high
; sets rem to (dividend_high*2^64 + dividend_low) mod divisor
; return floor((dividend_high*2^64 + dividend_low) / divisor)
; assume &rem in rcx, dividend_low in rdx, divisor in r8
Divide proc
mov rax,rdx
mov rdx,qword ptr [rcx]
cmp rdx,r8
jae ovfl ; overflow if divisor <= dividend_high (includes divide by 0)
div r8 ; dividend_high >= 0 && divisor > dividend_high => divisor > 0
mov qword ptr [rcx],rdx
ovfl:
ret ; ((ß-2)*ß + (ß-1))/(ß-1) = ((ß-1)*ß - 1)/(ß-1) = ß - 1/(ß-1) < ß
Divide endp
end
Managed C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices; // DLL support
namespace Troy5
{
class Program
{
[DllImport(@"C:\CSharp\Troy5\x64\Debug\Div.dll")]
public static extern ulong Divide (ref ulong rem, ulong dividend_low,
ulong divisor);
static void Main (string[] args)
{
Console.WriteLine("This is C# program");
ulong quot, rem = 3, dividend_low = 1, divisor = 20;
Console.WriteLine("divide (3 * 2^64 + 1) by 20");
quot = Divide(ref rem, dividend_low, divisor);
Console.WriteLine("quotient = {0}, remainder = {1}", quot, rem);
}
}
}
The trick was to explicitly place '/EXPORT Divide' in the link command of the assembler and to NOT try to include the dll in the References of the C# program. Instead I just put the full path of the dll in the program. The rest of the settings can be inferred from the following command lines:
ml64.exe /c /nologo /Sa /Sg /Zi /Fo"C:\CSharp\Troy5\x64\Debug\div.obj"
/Fl"C:\CSharp\Troy5\Div\div.lst" /W3 /errorReport:none /Ta
LINK /OUT:"C:\CSharp\Troy5\x64\Debug\Div.dll" /NOLOGO /DLL /DEBUG
/PDB:"c:\CSharp\Troy5\Div\x64\Debug\Div.pdb" /MAP":c:\CSharp\Troy5\Div\div.map"
/MAPINFO:EXPORTS /ASSEMBLYDEBUG /NOENTRY /MACHINE:X64
Be sure to set up the build dependencies properly.
Thanks to Svick and Alejandro for their help!
Upvotes: 2
Reputation: 7819
Your approach is correct, you just create a DLL in whatever unmanaged language you like (assembler in your case) and then use P/Invoke from the C# side to call it, there is no wrapper needed to do such a thing, as C# can call directly into unmanaged code.
Two things to watch out for. On the DLL side, make sure you export your function entry point so that external callers can use it (not sure here how MASM handles this, possibly you must put some entry indicating that, but not sure really). Another thing on the C# side is that the project must be forced into compiling for x64 (AnyCPU or x86 aren't valid here), because you're calling unmanaged x64 code explicitly.
Other than that, [DllImport]
is the way to go.
Upvotes: 2
Reputation: 9916
Create an empty Visual C++ DLL project and insert your .asm
file, and compile it. Then use DllImport
to call the exported method from your C# project.
Upvotes: 0