Unmanaged DLL dynamisch laden

Links

Code

// LoadLibrary, GetProcAddress, FreeLibrary aus der kernel32.dll laden
static class DLLBaseFunc
{
        [DllImport("kernel32.dll")]
        public static extern IntPtr LoadLibrary(string dllToLoad);

        [DllImport("kernel32.dll")]
        public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);

        [DllImport("kernel32.dll")]
        public static extern bool FreeLibrary(IntPtr hModule);
}

class Program
{
    // delegat-Funktion definieren, die eine DLL-Funktion abbildet
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int AddFct(int a, int b);

    static void Main(string[] args)
    {
        // DLL laden
        IntPtr pDll = DLLBaseFunc.LoadLibrary("c:\\temp\\meine.dll");

        if (pDll != IntPtr.Zero)
        {
            // Funktionszeiger holen
            IntPtr pAdd = DLLBaseFunc.GetProcAddress(pDll, "Add");
            
            if (pAdd != IntPtr.Zero)
            {
                // Funktionszeiger in delegat-Typ wandeln
                AddFct Add = (AddFct)Marshal.GetDelegateForFunctionPointer(pAdd, typeof(AddFct));

                // delegat-Funktion (DLL-Funktion) aufrufen
                int theResult = Add(10, 20);
            }
            
            // DLL entladen
            if (DLLBaseFunc.FreeLibrary(pDll) == false)
            {
                // error
            }
        }
    }
}

Managed Code DLL über COM in unmanaged Code laden

  • managed DLL mit COM-Interface am Beispiel: ComTest.dll (managed DLL in C#) und ComTestClient.exe (unmanaged C++ Exe)
  • weiterführende Infos: Link
// Code für die ComTest.dll (managed Code)
using System;
using System.Runtime.InteropServices; // wichtig

namespace ComTest
{
    [ComVisible(true)] // wichtig
    [Guid("F3C44592-37E2-4a36-AABA-21527D3F750B")] // kann über Menü->Extras->GUID erstellen erzeugt werden

    // Interface-Klasse deklarieren
    public interface IClass1
    {
        int Add(int i1, int i2);
        int Sub(int i1, int i2);
        string AddString(string sZeichenfolge);
    }

    [ClassInterface(ClassInterfaceType.AutoDispatch)] // wichtig
    public class Class1: IClass1
    {
        public int Add(int i1, int i2)
        {
            return i1 + i2;
        }
        public int Sub(int i1, int i2)
        {
            return i1 - i2;
        }

        public string AddString(string sZeichenfolge)
        {
            return sZeichenfolge + " " + sZeichenfolge;
        }
    }
}
  • Projekt->Eigenschaften->Anwendung->Assemblyinformationen->Haken bei „Assembly COM-sichbar machen“
  • Projekt->Eigenschaften->Erstellen->Haken bei „Für COM-Interop registrieren“
  • DLL kompilieren -> ComTest.dll und ComTest.tlb (Interfaceinformationen) werden erstellt
  • Soll die DLL auf einem anderen Rechner eingesetzt werden, müssen dort die Interfaceklassen unbedingt mit „RegAsm.exe ComTest.dll /codebase“ registriert werden.
  • ComTest.tlb und ComTest.dll in das Quellverzeichnis der ComTestClient.exe kopieren, bei Veränderungen am Quellcode der DLL diesen Vorgang wiederholen 🙂
// ComTestClient.exe (unmanaged Code mit COM-Interface)
#include "stdafx.h"
#import "ComTest.tlb" no_namespace //TLB mit Interface und Funktionen der ComTest.dll importieren

int _tmain(int argc, _TCHAR* argv[])
{
  long iRes=0;
  HRESULT hr = ::CoInitialize (NULL);

  if (hr == S_OK)
  {
    // Zeiger auf lokales COM-Objekt der Klasse Class1 holen
    IClass1Ptr pClass1 (__uuidof(Class1));

    // Funktion Add() aufrufen
    if (pClass1)
    {
        long i = pClass1->Add(1, 5);
        pClass1->Release();
    }
  }

  ::CoUninitialize();

  return 0;
}
  • Eine zweite Möglichkeit ein COM-Objekt zu erstellen wird über CoCreateInstance() ermöglicht.
  • Der Vorteil dabei ist, dass man einen global verwendbaren Zeiger erhält.
// ComTestClient.exe (unmanaged Code mit COM-Interface)
#include "stdafx.h"
#import "ComTest.tlb" no_namespace //TLB mit Interface und Funktionen der ComTest.dll importieren

int _tmain(int argc, _TCHAR* argv[])
{
  long iRes=0;

  IClass1 * pInterfacePtr = NULL; // Zeiger auf das Interface

  HRESULT hr = ::CoInitialize (NULL);

  if (hr == S_OK)
  {
    if (SUCCEEDED(CoCreateInstance(__uuidof(Class1), NULL, CLSCTX_ALL, __uuidof(IClass1), (LPVOID *)&pInterfacePtr)))
    {
        if (pInterfacePtr)
        {
            // Add aufrufen
            long i = pInterfacePtr->Add(1, 5);
            pInterfacePtr->Release();
        }
    }
  }

  ::CoUninitialize();

  return 0;
}
  • Will man ein COM-Objekt aus einer managed DLL innerhalb einer unmanaged DLL benutzen, darf die verwaltete Ausführung (__uuidof(Class1)) nicht in der DLLMain() [InitInstance(), ExitInstance()] stattfinden, sondern nur innerhalb einer selber geschriebenen DLL-Funktion. Es kommt sonst zur Auslösung eines Haltepunktes und die Anwendung friert ein.
  • Folgender Meldungstext wird dann im Ausgabefenster von VS angezeigt:


Unmanaged DLL per P/Invoke importieren

Funktionsimport in eine Klasse einbinden

public partial class MyClass
{
        // Funktion _Initialize() aus mytest.dll importieren
        // C/C++: int _Initialize(char *cpFileName);
        [DllImport("mytest.dll", EntryPoint="_Initialize", ExactSpelling=false, CallingConvention=CallingConvention.Cdecl)]
        private static extern int _Initialize(string sFileName);

        // Funktion _Add() aus mytest.dll importieren
        // C/C++: int _Add(int a, int b);
        [DllImport("mytest.dll", EntryPoint="_Add", ExactSpelling=false, CallingConvention=CallingConvention.Cdecl)]
        private static extern int _Add(int a, int b);

        public int Initialize(string sFileName)
        {
            return _Initialize(sFileName);
        }

        public int Add(int a, int b)
        {           
            return _Add(a, b);
        }
}

// Aufruf der DLLFunktion über das Objekt
MyClass DLLObject = new MyClass();
int iResult = DLLObject.Initialize("test.ini");
int iSum = DLLObject.Add(1, 2);

besondere Typen (z.B. structe) übergeben

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public int left;
    public int top;
    public int right;
    public int bottom;
}

public partial class Win32ImportClass
{
        // in C/C++
        // RECT rc;
        // HWND hwnd = FindWindow("main", NULL);
        // ::GetWindowRect(hwnd, &rc);

        [DllImport("user32.dll")]
        public static extern int GetWindowRect(int hwnd, ref RECT rc);
        // bei Klassenstrukturen anstelle von Structen -> Marshallen:
        // public static extern int GetWindowRect(int hwnd, [MarshalAs(UnmanagedType.LPStruct)] RECT rc);
}

// Anwendung
RECT rc = new RECT();
int hwnd = // hier noch das Handle holen ...
Win32ImportClass.GetWindowRect(hwnd, ref rc);

Hinweis

Sobald das Hostprojekt mit Plattformtyp „AnyCPU“ compiliert wird, wird diese auf einem 64Bit-System (z.B. Windows 7 64Bit) auch als 64Bit-Anwendung ausgeführt. Wird nun auf diesem 64Bit-System eine 32Bit unmanaged DLL in die 64Bit-Hostanwendung geladen, führt dies zu einer System.BadImageFormatException. Es gibt zwei Möglichkeiten dies zu umgehen:

  1. Hostanwendung als Plattformtyp „x86“ (32Bit) kompilieren (Projekt->Eigenschaften->Erstellen->Zielplattform->x86), so dass auch unter einem 64Bit-System die Anwendung zwangsweise als 32Bit ausgeführt wird
  2. oder die DLL zu einer 64Bit DLL umwandeln und kompilieren

Links