9 Nisan 2018

C# ile Yazılan Kütüphaneyi C++ ile dışarı açmak.

Amaç: Varolan C# projesini C++ ile diğer dillere açmak.

1) Visual Studio ile Class Library projesi oluşturalım. Ben projemin adını MyLibrary verdim.

2) https://www.nuget.org/packages/UnmanagedExports adresinde ki paketi C# projemize kuralım.

Install-Package UnmanagedExports -Version 1.2.7

3) Solution Explorer üzerinden projemize sağ tıkla "Properties" içerisinden
a) "Build" sekmesi içersinde
aşağıda ki ayarları yapalım.

Platform target: x86 ,
Allow unsafe code: true,
Register for COM interop: true

b) "Build Events" içerisinde  aşağıdaki komutu yazarak dll i her build edildiğinde çıkan dosyaları C++ projesinin build edildiği klasöre ekliyoruz. Proje boyunca release modunda çalışıyorum. bu yüzden Release seçtim.

Post-build event command line:
xcopy /y "$(ProjectDir)$(OutDir)*" "$(SolutionDir)Release"

4) Export.cs sınıfını oluşturalım.

using RGiesecke.DllExport;
using System.Runtime.InteropServices;

namespace MyLibrary
{
    public class Export
    {
        [DllExport("SayMyName", CallingConvention = CallingConvention.Cdecl)]
        [return: MarshalAs(UnmanagedType.LPWStr)]
        public static string SayMyName([MarshalAs(UnmanagedType.LPWStr)]string name)
        {
            if (name == "Heisenberg")
                return "You're Goddamn Right";
            else return "Say my name!";
        }

        [DllExport("Sum", CallingConvention = CallingConvention.Cdecl)]
        public static int Sum(int x, int y) => x + y;
    }
}

5) Yeni bir C++ projesi oluşturalım. Solution Explorer üzerinden Yeni Proje ekleyelim.

Other Languages ( Diğer Diller) => Visual C++ => Win32 Project seçelim. Proje adını MyLibrary.Native verdim.
!Win32 Application Wizard'da Applcation Type: DLL seçerek devam edelim.

6) C++ projesine sağ tıkla properties içerisinde "Build Events" =>"Pre-Build Event" sekmesine komutlarını ekleyelim. Böylece build edilmeden önce C# dll lerini proje içerisine kopyalamış oluruz.

xcopy /y "$(SolutionDir)Release\MyLibrary.tlb" "$(ProjectDir)"
xcopy /y "$(SolutionDir)Release\MyLibrary.dll" "$(ProjectDir)"


7) C++ içerisinde Header Files klasörü içerisinde stdafx.h içerisinde yoksa aşağıdaki satırları da ekleyin.

#include <stdio.h>
#include <tchar.h>


8) MyLibrary.Native.cpp içerisini aşağıdaki gibi olacaktır.
//
// MyLibrary.Native.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"


extern "C" {
 //Dışarı verilecek metodlar
 __declspec(dllexport) wchar_t * __cdecl SayMyName(wchar_t * name);
 __declspec(dllexport) int __cdecl Sum(int x, int y);
}
#include <windows.h>

#import "MyLibrary.tlb" auto_rename

//MyLibrary.dll içerisinden okunacak methodların tipleri
//wchar_t* string'e karşılık geliyor.

typedef wchar_t* (*pSayMyName)(wchar_t* name);
typedef int (*pSum)(int x, int y);

wchar_t* __cdecl SayMyName(wchar_t* name) {
 pSayMyName sayMyName = NULL;
 HINSTANCE hDLL = 0;

 hDLL = ::LoadLibrary(L"MyLibrary.dll");
 ::CoInitialize(NULL);

 if (!hDLL) {
  return L"DLL Yüklenemedi";
 }

 //
 // TO DO: Add code here to get an instance of MyClass
 //
 sayMyName = (pSayMyName)GetProcAddress(hDLL, "SayMyName");

 if (!sayMyName)
 {
  return L"ERROR: Unable to find entry for SayMyName(name)\n";

 }
 return  sayMyName(name);
}

int __cdecl Sum(int x, int y) {
 pSum sum = NULL;
 HINSTANCE hDLL = 0;

 hDLL = ::LoadLibrary(L"MyLibrary.dll");
 ::CoInitialize(NULL);

 if (!hDLL) {
  printf("DLL Yüklenemedi");
 }

 //
 // TO DO: Add code here to get an instance of MyClass
 //
 sum = (pSum)GetProcAddress(hDLL, "Sum");

 if (!sum)
 {
  printf("ERROR: Unable to find entry for Sum(x,y)\n");

 }
 return  sum(x, y);
}
//
9)Sırasıyla MyLibrary ve MyLibrary.Native projelerini Release modunda build edelim. Ve Solution'ın bulunduğu klasör içerisindeki Release klasöründe bizim paketler hazırdır. Şimdi bunları önce Visul Studio Komut Satırı ("VS2015 x86 Native Tools Command Prompt") ile kontrol edelim.

Komut satırını açalım ve aşağıdaki komutu çalıştıralım. Bu bize dışarı açılan methodları listeleyecek.

Burada SayMyName ve Sum methodlarını her iki dll içinde de görmemiz lazım.

dumpbin "{proje yolu}\MyLibrary\Release\MyLibrary.dll" /exports
dumpbin "{proje yolu}\MyLibrary\Release\MyLibrary.Native.dll" /exports

Görüyorsanız İşin büyük bir bölümü bitiyor.


10) Son olarak C++ ile yazdığımız DLL'in çalışıp çalışmadığını C# Console Uygulaması üzerinden test edelim.

MyLibrary.Test adında bir C# konsol uygulaması ekleyelim.

11) MyLibrary.Test sağ tıkla

Properties => Build => içerisinde Platform target  x86 seçelim.
Properties => Build Events => Pre-build event command line içerisine aşağıdaki komutu yazalım. Böylece her build ettiğimizde projelerin son halini test projemize almış olacağız.

  xcopy /y "$(SolutionDir)Release" "$(ProjectDir)$(OutDir)"


12) MyLibrary.Test projesi içerisinde Program.cs aşağıdaki gibi extern methodlarını ekleyelim ve projeyi başlangıç projesi olarak seçelim. Ve testimizi yapalım.

using System;
using System.Runtime.InteropServices;

namespace MyLibrary.Test
{
    class Program
    {

        [DllImport("MyLibrary.Native.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SayMyName")]
        [return: MarshalAs(UnmanagedType.LPWStr)]
        public static extern string SayMyName([MarshalAs(UnmanagedType.LPWStr)]string name);

        [DllImport("MyLibrary.Native.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "Sum")]
        public static extern int Sum(int x, int y);

        static void Main(string[] args)
        {
            Console.WriteLine(SayMyName("Heisenberg"));
            Console.WriteLine(Sum(1, 2));
        }
    }
}
Bitmiş Proje Kodları:MyLibrary.dll