Hallo Forum,
ich habe ein Projekt erstellt und nun meldet ein Anwender diesen Fehler.
Der Fehler tritt dann auf wenn mein Programm mit Hilfe der Windows Shell eine Zip Datei entpacken will.
Laut Internet Recherche liegt das daran das ich auf einem Windows 7 System entwickele und der Anwender noch WinXP benutzt. Bei WinXP sei eine ältere Shell32.dll dabei die nicht kompatibel sei. Zufällig habe ich eine Windows Server 2008 VM mit Visual Studio Express 2010 bei mir am Laufen. Das dort kompilierte Programm läuft dann auch auf der WinXP Maschine des Anwenders. Mir ist dieser Weg jedoch zu aufwendig.
Wie kann ich auf einer Windows 7 Maschine mit einem Visual Studio 2013 für eine alte Shell32.dll kompilieren? Ich habe schon versucht die alte Shell32.dll direkt in mein Projekt einzubinden (Add Reference+Browse) Das hat aber nicht funktioniert. Gibt es noch einen anderen Weg?
I solved it by extracting the interface declarations from Interop.Shell32.dll, and including only the ones I need, directly into the software. Wie könnte ich das machen?
Vielen Dank
Martin
Bitte keine Bilder von externen Filehostern einbinden, sondern Dateianhänge verwenden. Siehe [Hinweis] Wie poste ich richtig?, Punkt 6.1
Bin kein Freund davon sich für jeden Pups gleich was externes zu suchen, aber in diesem Fall würde ich mir .NET Code zum zip bearbeiten suchen.
"C# zip free" in deine Suchmaschine eintippen, sollte einiges finden.
Typischerweise über späte Bindung.
Type shellType = Type.GetTypeFromProgID("Shell.Application")
object shell = Activator.CreateInstance(shellType);
// usw...
Du gibt damit etwas Comfort ab, das ist aber bei nur 1 oder 2 genutzen Methoden/Eigenschaften mit einer eigenen Wrapper Klasse schnell gelöst.
Evtl. interessant für dich in dem Zusammenhang:
Allgemeiner COM-Wrapper für Späte Bindung
Ich möchte auch keine extra Dll ausliefern und ich möchte auch nicht das neueste .net Framework voraussetzen (wo Zip Funktionalität inkludiert ist). Ich würde gerne bei der Shell bleiben. Ich versuche den Weg von Sebastian zu gehen, so ganz funktioniert es aber noch nicht. Der Code wird ohne Fehler ausgeführt, jedoch wird nichts extrahiert.
Vorbereitungen:
-- Konsolenprojekt erstellen
--"Microsoft Shell Controls and Automation" als Referenz hinzufügen
-- Ein Zip Datei testweise nach C:\Temp\x.zip verschieben.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Shell32;
using System.Reflection;
using System.Runtime.InteropServices;
namespace ConsoleApplication1 {
class Program {
[STAThread]
static void Main(string[] args) {
// Pfad zu irgendeiner Zip Datei:
string DownloadPath = @"C:\Temp\x.zip";
// Datei soll im Temp Folder entpackt werden.
string ExtractedFileFolder = System.IO.Path.GetTempPath() + Guid.NewGuid().ToString();
System.IO.Directory.CreateDirectory(ExtractedFileFolder);
// Neue Methode:
ComObject Shell32 = new ComObject("Shell.Application");
ComObject TempFolder = Shell32.InvokeObjectReturningFunction("NameSpace", ExtractedFileFolder);
ComObject x = Shell32.InvokeObjectReturningFunction("NameSpace", DownloadPath);
ComObject FolderItems = x.InvokeObjectReturningFunction("Items");
TempFolder.InvokeFunction("CopyHere", FolderItems);
// Alte Methode
Shell32.Shell _shell = new Shell32.Shell();
Shell32.Folder _TempFolder = _shell.NameSpace(ExtractedFileFolder);
FolderItems _FolderItems = _shell.NameSpace(DownloadPath).Items();
_TempFolder.CopyHere(_FolderItems);
}
}
/// <summary>
/// Allgemeiner Wrapper, um spätgebunden mit COM-Objekten (bzw. ActiveX-Komponenten) zu arbeiten.
/// </summary>
public class ComObject : IDisposable {
// COM-Objekt
private object _realComObject = null;
/// <summary>
/// Übernimmt einen vorhanden Verweis auf ein COM-Objekt.
/// </summary>
/// <param name="injectedObject">Vorhandenes COM-Objekt</param>
public ComObject(object injectedObject) {
// Wenn kein Objekt angegeben wurde ...
if (injectedObject == null)
// Ausnahme werfen
throw new ArgumentNullException("injectedObject");
// Wenn das angegebene Objekt kein COM-Objekt ist ...
if (!injectedObject.GetType().IsCOMObject)
// Ausnahme werfen
throw new ArgumentException("Das angegebene Objekt ist kein COM-Objekt!", "injectedObject");
// Verweis übernehmen
_realComObject = injectedObject;
}
/// <summary>
/// Erzeugt ein neues COM-Objekt anhand einer COM ProgId.
/// </summary>
/// <param name="progId"></param>
public ComObject(string progId) {
// Wenn keine ProgId angegeben wurde ...
if (string.IsNullOrEmpty(progId))
// Ausnahme werfen
throw new ArgumentException();
// Typinformationen über die ProgId ermitteln
Type comType = Type.GetTypeFromProgID(progId);
// Wenn keine Typeninformationene gefunden wurden ...
if (comType == null)
// Ausnahme werfen
throw new TypeLoadException(string.Format("Fehler beim Laden der Typinformationen zu ProgId '{0}'.", progId));
// Instanz erzeugen
_realComObject = Activator.CreateInstance(comType);
}
/// <summary>
/// Ruft eine Funktion auf, die als Rückgabewert ein COM-Objekt (also keinen primitiven Datentyp) zurückgibt.
/// </summary>
/// <param name="functionName">Funktionsname</param>
/// <param name="parameters">Parameter</param>
/// <returns>Rückgabeobjekt</returns>
public ComObject InvokeObjectReturningFunction(string functionName, params object[] parameters) {
// Methode aufrufen
object result = _realComObject.GetType().InvokeMember(functionName, BindingFlags.InvokeMethod | BindingFlags.OptionalParamBinding, null, _realComObject, parameters);
// Wenn ein Objekt zurückgegeben wurde ...
if (result != null)
// Rückgabeobjekt in Wrapper einpacken und zurückgeben
return new ComObject(result);
// Nichts zurückgeben
return null;
}
/// <summary>
/// Ruft eine Funktion auf.
/// </summary>
/// <param name="functionName">Funktionsname</param>
/// <param name="parameters">Parameter</param>
/// <returns>Rückgabewert</returns>
public object InvokeFunction(string functionName, params object[] parameters) {
// Methode aufrufen und Rückgabewert zurückgeben
return _realComObject.GetType().InvokeMember(functionName, BindingFlags.InvokeMethod | BindingFlags.OptionalParamBinding, null, _realComObject, parameters);
}
/// <summary>
/// Ruft eine Prozedur auf.
/// </summary>
/// <param name="procedureName">Prozedurname</param>
/// <param name="parameters">Parameter</param>
public void InvokeProcedure(string procedureName, params object[] parameters) {
// Methode aufrufen
_realComObject.GetType().InvokeMember(procedureName, BindingFlags.InvokeMethod | BindingFlags.OptionalParamBinding, null, _realComObject, parameters);
}
/// <summary>
/// Ruft den Wert einer Eigenschaft ab.
/// </summary>
/// <param name="propertyName">Eigenschaftsname</param>
/// <returns>Rückgabewert</returns>
public object GetProperty(string propertyName) {
// Methode aufrufen und Rückgabewert zurückgeben
return _realComObject.GetType().InvokeMember(propertyName, BindingFlags.GetProperty | BindingFlags.OptionalParamBinding, null, _realComObject, new object[0]);
}
/// <summary>
/// Legt den Wert einer Eigenschaft fest.
/// </summary>
/// <param name="propertyName">Eigenschaftsname</param>
/// <param name="parameters">Parameter</param>
public void SetProperty(string propertyName, object value) {
// Methode aufrufen
_realComObject.GetType().InvokeMember(propertyName, BindingFlags.OptionalParamBinding | BindingFlags.SetProperty, null, _realComObject, new object[1] { value });
}
/// <summary>
/// Ruft den Wert einer Eigenschaft ab (für Eigenschaften, die Objekte zurückgeben).
/// </summary>
/// <param name="propertyName">Eigenschaftsname</param>
/// <returns>Rückgabeobjekt</returns>
public ComObject GetObjectReturningProperty(string propertyName) {
// Methode aufrufen und Rückgabewert zurückgeben
object result = _realComObject.GetType().InvokeMember(propertyName, BindingFlags.GetProperty | BindingFlags.OptionalParamBinding, null, _realComObject, new object[0]);
// Wenn ein Objekt zurückgegeben wurde ...
if (result != null)
// Rückgabeobjekt in Wrapper einpacken und zurückgeben
return new ComObject(result);
// Nichts zurückgeben
return null;
}
#region IDisposable Member
/// <summary>
/// Verwendete Ressourcen freigeben-
/// </summary>
public void Dispose() {
// Wenn das COM-Objekt nocht existiert ...
if (_realComObject != null) {
// COM-Objekt freigeben und entsorgen
Marshal.ReleaseComObject(_realComObject);
_realComObject = null;
}
}
#endregion
}
}
Ich möchte auch keine extra Dll ausliefern
Warum nicht? Was spricht dagegen? Ggf. mit ILMerge die DLLs mergen. Wobei das IMHO immer sehr unschön ist.
Das Problem welches du hast ist einfach, dass XP nun mal diese Funktionalität nicht bietet. Ob nun Late-Binding oder Peng. Was nicht da ist, kann nicht aufgerufen werden.
"Jedes Ding hat drei Seiten, eine positive, eine negative und eine komische." (Karl Valentin)
Die Funktionalität ist aber da. Wenn ich den Source der alten Methode unverändert auf einen Windows Server 2008 bringe und dort mit dem Visual Studio 2010 Express kompiliere und dieses Kompilat dann zum WindowsXP User schicke, dann wird die Datei über Shell32 entpackt. Die Funktionalität ist also da.
Es kann gut sein, dass Du bei solchen PInvokes pro Betriebssystem-Version eine extra Anpassung durchführen musst.
Es macht hier aber gut gemeint wirklich absolut keinen Sinn das Rad neu zu erfinden; gerade zum Thema Kompatibilität.
Am Markt gibt es auch ZIP Bibliotheken deren Lizenz das Einbetten erlaubt und nichts extra mitgeliefert werden muss.
Im Zweifel musst Du mit einer externen Lib sogar weniger mitliefern als pro Betriebssystem-Version eine extra DLL.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Der COM Wrapper(ComObject) ist nicht darauf vorbereitet das man ComObject direkt als Argument an eine Methode übergibst.
Er reicht das Argument 1:1 weiter und die Shell kennt ComObject natürlicht nicht. (Das kein TypeMismatch ausgelöst wird ist allerdings merkwürdig)
Der einfachste Weg wäre das private Feld "_realComObject" von ComObject hier mittels eines Properties "RealComObject" freizulegen und den Aufruf anzupassen. Also:
TempFolder.InvokeFunction("CopyHere", FolderItems.RealComObject);
Ginge natürlich noch schöner in dem man in die Invoke Methoden von ComObject auf die Verwendung von Arg=>ComObject vorbereitet.
Es funktioniert. Vielen Dank Sebastian 😃
Hier nochmal der Source. Einfach in ein Konsolenprojekt einfügen. Es läuft auf Windows Server 2008/2012 und Windows 7. XP konnte ich bis jetzt noch nicht testen, ich hoffe es läuft dort dann auch.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Shell32;
using System.Reflection;
using System.Runtime.InteropServices;
namespace ConsoleApplication1 {
class Program {
[STAThread]
static void Main(string[] args) {
// Pfad zu irgendeiner Zip Datei:
string DownloadPath = @"C:\Temp\a.zip";
// Datei soll im Temp Folder entpackt werden.
string ExtractedFileFolder = System.IO.Path.GetTempPath() + Guid.NewGuid().ToString();
System.IO.Directory.CreateDirectory(ExtractedFileFolder);
ComObject Shell32 = new ComObject("Shell.Application");
ComObject TempFolder = Shell32.InvokeObjectReturningFunction("NameSpace", ExtractedFileFolder);
ComObject x = Shell32.InvokeObjectReturningFunction("NameSpace", DownloadPath);
ComObject FolderItems = x.InvokeObjectReturningFunction("Items");
TempFolder.InvokeFunction("CopyHere", FolderItems.RealComObject);
}
}
/// <summary>
/// Allgemeiner Wrapper, um spätgebunden mit COM-Objekten (bzw. ActiveX-Komponenten) zu arbeiten.
/// </summary>
public class ComObject : IDisposable {
// COM-Objekt
private object _realComObject= null;
public object RealComObject {
get {
return _realComObject;
}
set {
_realComObject = value;
}
}
/// <summary>
/// Übernimmt einen vorhanden Verweis auf ein COM-Objekt.
/// </summary>
/// <param name="injectedObject">Vorhandenes COM-Objekt</param>
public ComObject(object injectedObject) {
// Wenn kein Objekt angegeben wurde ...
if (injectedObject == null)
// Ausnahme werfen
throw new ArgumentNullException("injectedObject");
// Wenn das angegebene Objekt kein COM-Objekt ist ...
if (!injectedObject.GetType().IsCOMObject)
// Ausnahme werfen
throw new ArgumentException("Das angegebene Objekt ist kein COM-Objekt!", "injectedObject");
// Verweis übernehmen
_realComObject = injectedObject;
}
/// <summary>
/// Erzeugt ein neues COM-Objekt anhand einer COM ProgId.
/// </summary>
/// <param name="progId"></param>
public ComObject(string progId) {
// Wenn keine ProgId angegeben wurde ...
if (string.IsNullOrEmpty(progId))
// Ausnahme werfen
throw new ArgumentException();
// Typinformationen über die ProgId ermitteln
Type comType = Type.GetTypeFromProgID(progId);
// Wenn keine Typeninformationene gefunden wurden ...
if (comType == null)
// Ausnahme werfen
throw new TypeLoadException(string.Format("Fehler beim Laden der Typinformationen zu ProgId '{0}'.", progId));
// Instanz erzeugen
_realComObject = Activator.CreateInstance(comType);
}
/// <summary>
/// Ruft eine Funktion auf, die als Rückgabewert ein COM-Objekt (also keinen primitiven Datentyp) zurückgibt.
/// </summary>
/// <param name="functionName">Funktionsname</param>
/// <param name="parameters">Parameter</param>
/// <returns>Rückgabeobjekt</returns>
public ComObject InvokeObjectReturningFunction(string functionName, params object[] parameters) {
// Methode aufrufen
object result = _realComObject.GetType().InvokeMember(functionName, BindingFlags.InvokeMethod | BindingFlags.OptionalParamBinding, null, _realComObject, parameters);
// Wenn ein Objekt zurückgegeben wurde ...
if (result != null)
// Rückgabeobjekt in Wrapper einpacken und zurückgeben
return new ComObject(result);
// Nichts zurückgeben
return null;
}
/// <summary>
/// Ruft eine Funktion auf.
/// </summary>
/// <param name="functionName">Funktionsname</param>
/// <param name="parameters">Parameter</param>
/// <returns>Rückgabewert</returns>
public object InvokeFunction(string functionName, params object[] parameters) {
// Methode aufrufen und Rückgabewert zurückgeben
return _realComObject.GetType().InvokeMember(functionName, BindingFlags.InvokeMethod | BindingFlags.OptionalParamBinding, null, _realComObject, parameters);
}
/// <summary>
/// Ruft eine Prozedur auf.
/// </summary>
/// <param name="procedureName">Prozedurname</param>
/// <param name="parameters">Parameter</param>
public void InvokeProcedure(string procedureName, params object[] parameters) {
// Methode aufrufen
_realComObject.GetType().InvokeMember(procedureName, BindingFlags.InvokeMethod | BindingFlags.OptionalParamBinding, null, _realComObject, parameters);
}
/// <summary>
/// Ruft den Wert einer Eigenschaft ab.
/// </summary>
/// <param name="propertyName">Eigenschaftsname</param>
/// <returns>Rückgabewert</returns>
public object GetProperty(string propertyName) {
// Methode aufrufen und Rückgabewert zurückgeben
return _realComObject.GetType().InvokeMember(propertyName, BindingFlags.GetProperty | BindingFlags.OptionalParamBinding, null, _realComObject, new object[0]);
}
/// <summary>
/// Legt den Wert einer Eigenschaft fest.
/// </summary>
/// <param name="propertyName">Eigenschaftsname</param>
/// <param name="parameters">Parameter</param>
public void SetProperty(string propertyName, object value) {
// Methode aufrufen
_realComObject.GetType().InvokeMember(propertyName, BindingFlags.OptionalParamBinding | BindingFlags.SetProperty, null, _realComObject, new object[1] { value });
}
/// <summary>
/// Ruft den Wert einer Eigenschaft ab (für Eigenschaften, die Objekte zurückgeben).
/// </summary>
/// <param name="propertyName">Eigenschaftsname</param>
/// <returns>Rückgabeobjekt</returns>
public ComObject GetObjectReturningProperty(string propertyName) {
// Methode aufrufen und Rückgabewert zurückgeben
object result = _realComObject.GetType().InvokeMember(propertyName, BindingFlags.GetProperty | BindingFlags.OptionalParamBinding, null, _realComObject, new object[0]);
// Wenn ein Objekt zurückgegeben wurde ...
if (result != null)
// Rückgabeobjekt in Wrapper einpacken und zurückgeben
return new ComObject(result);
// Nichts zurückgeben
return null;
}
#region IDisposable Member
/// <summary>
/// Verwendete Ressourcen freigeben-
/// </summary>
public void Dispose() {
// Wenn das COM-Objekt nocht existiert ...
if (_realComObject != null) {
// COM-Objekt freigeben und entsorgen
Marshal.ReleaseComObject(_realComObject);
_realComObject = null;
}
}
#endregion
}
}
Du hast Recht. Ich hatte es hingeschrieben ohne nachzudenken. Ich habe es editiert.