user113670
user113670

Reputation: 29

How can C# call a ml64 dll directly without an intermediate C++ layer?

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

Answers (3)

user113670
user113670

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

Alejandro
Alejandro

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

Unsigned
Unsigned

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

Related Questions