- 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:
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:
- 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
- oder die DLL zu einer 64Bit DLL umwandeln und kompilieren
Links