Wie mache ich aus einer C # -DLL eine COM-Interop-DLL, die von einer VB6-Anwendung verwendet werden kann?
Wie mache ich aus einer C # -DLL eine COM-Interop-DLL, die von einer VB6-Anwendung verwendet werden kann?
Antworten:
Dies ist die Antwort, die ich in StackOverflow finden wollte, aber nicht konnte. Es stellt sich als ziemlich einfach heraus, eine einfache C # -Dll in eine COM-DLL umzuwandeln.
Erstellen Sie eine Lösung mit einem C # -Klassenprojekt. Die Klasse sollte eine Schnittstelle für die Eigenschaften / Methoden und eine Schnittstelle für die Ereignisse haben. Weisen Sie der Klasse und den Schnittstellen GUID-Attribute zu, wie in MSDN - Beispiel für eine COM-Klasse (C # -Programmierhandbuch) beschrieben . Siehe auch: MSDN - Gewusst wie: Auslösen von Ereignissen, die von einer COM-Senke verarbeitet werden .
Aktivieren Sie unter Projekteigenschaften> Registerkarte Anwendung> Schaltfläche Baugruppeninformationen die Option "Baugruppe COM-sichtbar machen". Dadurch werden alle öffentlichen Methoden in der Klasse COM sichtbar.
Klicken Sie unter Projekteigenschaften> Registerkarte Erstellen auf "Plattformziel" auf x86.
Das ist alles, was Sie tun müssen, um die DLL zu erstellen. Um die DLL aufzurufen, müssen Sie sie registrieren.
Sie können die DLL auf eine der folgenden Arten registrieren:
Registrieren Sie die DLL manuell bei RegAsm. Auf diese Weise können Sie die DLL im Verzeichnis Ihrer Wahl und nicht im Erstellungsverzeichnis registrieren. Dies ist die Methode, die ich verwendet habe.
Öffnen Sie eine Befehlsshell mit Administratorrechten und geben Sie ein
RegAsm.exe -tlb -codebase mydll.dll
RegAsm.exe befindet sich unter "C: \ Windows \ Microsoft.NET \ Framework \ v2.0.50727", während "mydll.dll" der Name Ihrer DLL ist. tlb
bedeutet "Typbibliothek erstellen";
codebase
bedeutet "Schreiben Sie den Verzeichnisspeicherort in die Registrierung, vorausgesetzt, er wird nicht im GAC abgelegt".
RegAsm zeigt eine Warnung an, dass die Assembly einen starken Namen haben sollte. Sie können es ignorieren.
Zu diesem Zeitpunkt sollten Sie in der Lage sein, einen Verweis auf die COM-DLL in VB6 hinzuzufügen, ihn mit Intellisense anzuzeigen und wie eine normale COM-DLL auszuführen.
Wenn Sie InstallShield verwenden, um die DLL zusammen mit dem Rest Ihrer Anwendung zu installieren, gehen Sie wie folgt vor.
Fügen Sie in InstallShield der Komponentenliste eine neue Komponente hinzu. Denken Sie daran, die Komponente einem Feature zuzuordnen. Setzen Sie die Komponenteneigenschaft ".NET COM Interop" auf "Ja".
Fügen Sie die DLL-Datei zum Abschnitt "Dateien" der Komponente hinzu. Überprüfen Sie nicht die Eigenschaft "Selbstregistrierung". Klicken Sie mit der rechten Maustaste auf die DLL-Datei und wählen Sie "Set Key File".
Fügen Sie die TLB-Datei zum Abschnitt "Dateien" der Komponente hinzu. Überprüfen Sie die Eigenschaft "Selbstregistrierung".
Die richtige Version des .Net Framework muss auf dem Ziel-PC vorhanden sein.
Das ist es.
-tlb -codebase
. Ich habe mir ein paar Stunden lang den Kopf darüber kaputt gemacht, bis ich das gefunden habe. Vielen Dank!
Als Erweiterung zu @Kieren Johnstones Antwort ein praktisches Codebeispiel müssen:
Von:
public class ApiCaller
{
public DellAsset GetDellAsset(string serviceTag, string apiKey)
{
....
}
}
public class DellAsset
{
public string CountryLookupCode { get; set; }
public string CustomerNumber { get; set; }
public bool IsDuplicate { get; set; }
public string ItemClassCode { get; set; }
public string LocalChannel { get; set; }
public string MachineDescription { get; set; }
public string OrderNumber { get; set; }
public string ParentServiceTag { get; set; }
public string ServiceTag { get; set; }
public string ShipDate { get; set; }
}
Zu:
[Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F")]
[ComVisible(true)]
public interface IComClassApiCaller
{
IComClassDellAsset GetDellAsset(string serviceTag, string apiKey);
}
[Guid("7BD20046-DF8C-44A6-8F6B-687FAA26FA71"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface IComClassApiCallerEvents
{
}
[Guid("0D53A3E8-E51A-49C7-944E-E72A2064F938"),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(IComClassApiCallerEvents))]
[ComVisible(true)]
[ProgId("ProgId.ApiCaller")]
public class ApiCaller : IComClassApiCaller {
public IComClassDellAsset GetDellAsset(string serviceTag, string apiKey)
{
.....
}
}
[Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83E")]
[ComVisible(true)]
public interface IComClassDellAsset
{
string CountryLookupCode { get; set; }
string CustomerNumber { get; set; }
bool IsDuplicate { get; set; }
string ItemClassCode { get; set; }
string LocalChannel { get; set; }
string MachineDescription { get; set; }
string OrderNumber { get; set; }
string ParentServiceTag { get; set; }
string ServiceTag { get; set; }
string ShipDate { get; set; }
}
[Guid("7BD20046-DF8C-44A6-8F6B-687FAA26FA70"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface IComClassDellAssetEvents
{
}
[Guid("0D53A3E8-E51A-49C7-944E-E72A2064F937"),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(IComClassDellAssetEvents))]
[ComVisible(true)]
[ProgId("ProgId.DellAsset")]
public class DellAsset : IComClassDellAsset
{
public string CountryLookupCode { get; set; }
public string CustomerNumber { get; set; }
public bool IsDuplicate { get; set; }
public string ItemClassCode { get; set; }
public string LocalChannel { get; set; }
public string MachineDescription { get; set; }
public string OrderNumber { get; set; }
public string ParentServiceTag { get; set; }
public string ServiceTag { get; set; }
public string ShipDate { get; set; }
}
Hoffe das spart dir etwas Zeit
GetDellAsset
VB6 aufrufen, wenn Sie versuchen, über auf die Klasse zuzugreifen CreateObject
?
Die meisten Beispiele im Internet von COM-Servern enthalten nur eine CoClass, und es wird behauptet, dass diese CoClass einen öffentlichen Konstruktor haben muss. Dies ist in diesem Fall der Fall, aber normale Server haben mehr als eine CoClass, von denen nur eine erstellt werden kann, während die Instanzen der nicht erstellbaren CoClasses Eigenschaften der erstellbaren CoClass sind. Betrachten Sie beispielsweise das Word-Objektmodell mit der erstellbaren CoClass Application
, deren Documents
Eigenschaft wiederum aus Instanzen der CoClass besteht Document
. Der folgende Server verfügt über zwei CoClasses, eine mit einem öffentlichen Konstruktor und eine mit einem privaten Konstruktor.
Erstellen Sie eine Lösung für eine C # -Klassenbibliothek (.Net Framework), nicht für eine Klassenbibliothek (.Net Standard), und nennen Sie sie beispielsweise BankServerCSharp. Wählen Sie diesen Namen mit Bedacht aus, da er der Hauptteil der ProgIDs Ihrer CoClasses und des Namespace-Namens in C ++ ist. Dieser Name wird auch im Dialogfeld Referenzen von C # und VBA aufgeführt.
Löschen Sie den Boilerplate-Code und fügen Sie zwei Dateien Bank.cs und Account.cs hinzu. Geben Sie den folgenden Code ein:
//Account.cs
using System.Runtime.InteropServices;
namespace BankServerCSharp
{
[ComVisible(true)] // This is mandatory.
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IAccount
{
double Balance { get; } // A property
void Deposit(double b); // A method
}
[ComVisible(true)] // This is mandatory.
[ClassInterface(ClassInterfaceType.None)]
public class Account:IAccount
{
private double mBalance = 0;
private Account() { } // private constructor, coclass noncreatable
public static Account MakeAccount() { return new Account(); }
//MakeAccount is not exposed to COM, but can be used by other classes
public double Balance { get { return mBalance; } }
public void Deposit(double b) { mBalance += b; }
}
}
//Bank.cs
using System.Runtime.InteropServices;
namespace BankServerCSharp
{
[ComVisible(true)] // This is mandatory.
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IBank
{
string BankName { get; set; } // A property
IAccount FirstAccount { get; } // Another one of type IDispatch
}
[ComVisible(true)] // This is mandatory.
[ClassInterface(ClassInterfaceType.None)]
public class Bank:IBank
{
private string Name = "";
private readonly Account First;
public Bank() { First = Account.MakeAccount(); }
public string BankName {
get { return Name; }
set { Name= value; }
}
public IAccount FirstAccount {
get { return First; }
}
}
}
Erstellen Sie das Projekt mit der Konfiguration Release / Any CPU. Die Ausgabe ist die verwaltete DLL BankServerCSharp.dll im Ordner \ bin \ release.
Jetzt müssen Sie Ihre verwaltete COM-DLL registrieren. Versuchen Sie nicht regsvr32, es gibt ein spezielles Programm namens regasm für verwaltete COM-DLLs. Regasm hat eine Version für 32-Bit- und für 64-Bit-Apps. Öffnen Sie eine Eingabeaufforderung als Administrator und wechseln Sie zu C: \ Windows \ Microsoft.NET \ Framework \ v4.0.30319. Dieser Ordner enthält die regasm.exe-App zum Registrieren der verwalteten COM-DLL, als wäre es eine native 32-Bit-COM-DLL.
Typ RegAsm.exe /tlb /codebase path_to_your_bin_release_folder\BankServerCSharp.dll
. Sie müssen Ihre DLL auf diese Weise auf jedem Computer registrieren . Vergessen Sie nicht den Schalter / tlb, mit dem die Typbibliothek erstellt wird. Der Compiler kommentiert den Schalter / die Codebasis mit einigen Warnungen, die Sie ignorieren können. Die DLL ist im WoW64-Teil der Registrierung registriert und kann von nativen (nicht verwalteten) 32-Bit-Apps verwendet werden.
Wiederholen Sie nun die Registrierung für die Verwendung der verwalteten COM-DLL durch 64-Bit-Apps. Wechseln Sie zu C: \ Windows \ Microsoft.NET \ Framework 64 \ v4.0.30319 und geben Sie denselben Befehl wie zuvor ein.
Sie können die Registrierung auf Ihrem eigenen PC beschleunigen, indem Sie Visual Studio mit Administratorrechten ausführen und die folgenden Ereignisse nach dem Erstellen hinzufügen:
%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe /tlb /codebase "$(TargetPath)"
%SystemRoot%\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe /tlb /codebase "$(TargetPath)"
Sie können Ihre DLL jetzt wie eine native, nicht verwaltete COM-DLL verwenden. Testen Sie Ihre DLL mit VBA: Aktivieren Sie unter Tools / Referenzen das Kontrollkästchen BankServerCSharp. Wenn es nicht angezeigt wird, ist die Registrierung fehlgeschlagen. Ein einfaches Test-Sub:
Sub TestSOExampleNew()
On Error GoTo Oops
Dim BiBiBaBa As New BankServerCSharp.Bank 'New!
BiBiBaBa.BankName = "Big Bird Bad Bank"
Dim Account As BankServerCSharp.Account 'No New!
Set Account = BiBiBaBa.FirstAccount
Account.Deposit 2000
MsgBox BiBiBaBa.BankName & ". First client's balance: " & Account.Balance
Exit Sub
Oops:
MsgBox "Sorry, an unexpected error occurred!"
End Sub
Um Ihre verwaltete COM-DLL in C ++ zu testen, erstellen Sie eine neue Konsolenanwendung, fügen Sie den folgenden Code ein und erstellen Sie ihn als Release / x64 oder Release / x86:
#include "stdafx.h"
#import "D:\Aktuell\CSharpProjects\BankServerCSharp\BankServerCSharp\bin\Release\BankServerCSharp.tlb"
//this is the path of my C# project's bin\Release folder
inline void TESTHR(HRESULT x) { if FAILED(x) _com_issue_error(x); };
int main()
{
try
{
TESTHR(CoInitialize(0));
BankServerCSharp::IBankPtr BankPtr = nullptr;
TESTHR(BankPtr.CreateInstance("BankServerCSharp.Bank"));
BankPtr->BankName = L"Ernie First Global Bank";
BankServerCSharp::IAccountPtr AccountPtr = BankPtr->FirstAccount;
TESTHR(AccountPtr->Deposit(200.09));
wprintf(L"Name: %s, Balance: %.2f\n", (LPCWSTR)BankPtr->BankName, AccountPtr->Balance);
}
catch (const _com_error& e)
{
CStringW out;
out.Format(L"Exception occurred. HR = %lx, error = %s", e.Error(), e.ErrorMessage());
MessageBoxW(NULL, out, L"Error", MB_OK);
}
CoUninitialize();// Uninitialize COM
return 0;
}