c# - una - ¿Cómo empezar a desarrollar extensiones de Internet Explorer?
crear proyecto asp.net visual studio 2017 (10)
¿Alguien aquí tiene experiencia con / en el desarrollo de extensiones de IE que pueden compartir sus conocimientos? Esto incluiría ejemplos de código, o enlaces a buenos, o documentación sobre el proceso, o cualquier cosa.
Tengo muchas ganas de hacer esto, pero estoy golpeando una pared gigante con documentación pésima, código pésimo / código de ejemplo / falta de eso. Cualquier ayuda / recursos que pueda ofrecer serían muy apreciados.
Específicamente, me gustaría comenzar con cómo obtener acceso a / manipular el DOM desde una extensión de IE.
EDITAR, incluso más detalles:
Idealmente, me gustaría plantar un botón de la barra de herramientas que, cuando se hace clic en él, aparece un menú que contiene enlaces a sitios externos. También me gustaría acceder al DOM y plantar JavaScript en la página dependiendo de algunas condiciones.
¿Cuál es la mejor manera de persistir la información en una extensión de IE? En Firefox / Chrome / la mayoría de los navegadores modernos, usa window.localStorage
, pero obviamente con IE8 / IE7, esa no es una opción. Tal vez una base de datos SQLite o tal? ¿Está bien asumir que .NET 4.0 se instalará en la computadora de un usuario?
No quiero usar Spice IE, ya que también quiero crear uno compatible con IE9. También he agregado la etiqueta C ++ a esta pregunta, porque si es mejor crear una en C ++, puedo hacerlo.
¡Te sugiero encarecidamente este post de Pavel Zolnikov publicado en 2002!
http://www.codeproject.com/Articles/2219/Extending-Explorer-with-Band-Objects-using-NET-and
Se basa en el uso de objetos de banda y se compila utilizando .Net 2.0. Se proporciona el código fuente y se abre y compila bien con Visual Studio 2013. Como leerá en los comentarios posteriores, funciona perfectamente bien para IE 11 y en Windows 7 y Windows 10. Funcionó perfectamente bien para mí en Windows 7 + SP1 e IE 11 Disfruta!
El desarrollo de C # BHOs es un dolor en el culo. Se trata de un montón de código COM icky y p / invocar llamadas.
here tengo un C # BHO en su mayoría terminado, en el cual usted es libre de usar la fuente para lo que quiera. Digo "principalmente" , porque nunca descubrí cómo guardar los datos de aplicación en el Modo protegido de IE .
El estado de las extensiones de IE es bastante triste. Tiene el viejo modelo de IE5 Browser Helper Object (sí, esos BHO infames que a todos les gustaba bloquear en el día), barras de herramientas y los nuevos aceleradores para IE. Incluso entonces, la compatibilidad se romperá a veces. Solía mantener una extensión para IE6 que se rompió con IE7, así que hay algunas cosas que han cambiado. En su mayor parte, por lo que sé (no he tocado los BHO en años), todavía necesita codificarlos utilizando las bibliotecas de plantillas activas (algo así como una STL para COM de Microsoft) y es solo para C ++. Podría hacer interoperabilidad COM con C # y salirse con la suya en C #, pero probablemente será demasiado difícil para lo que vale. De todos modos, si está interesado en codificar su propia extensión para IE (lo cual es plausible si desea que sus extensiones estén disponibles en todos los principales navegadores), aquí están los recursos oficiales de Microsoft.
http://msdn.microsoft.com/en-us/library/aa753587(v=vs.85).aspx
Y para los aceleradores que son nuevos en IE8 puedes marcar este.
http://msdn.microsoft.com/en-us/library/cc289775(v=vs.85).aspx
Estoy de acuerdo en que la documentación es terrible, y las API están bastante desactualizadas. Todavía espero que esto ayude.
EDITAR: Supongo que puedo lanzar una última fuente de información aquí. Estaba revisando mis notas de cuando estaba trabajando en BHOs. Y este es el artículo que me hizo comenzar con ellos. Es un poco antiguo, pero tiene una buena explicación de las interfaces ATL que utilizará cuando trabaje con IE BHOs (IObjectWithSite, por ejemplo). Creo que está bastante bien explicado y me ayudó mucho en aquel entonces. http://msdn.microsoft.com/en-us/library/bb250436.aspx También verifiqué el ejemplo que GregC publicó. Funciona con al menos IE8 y es compatible con VS 2010, por lo que si desea hacer C #, puede comenzar allí y echar un vistazo al Libro de Jon Skeet. (C # en la segunda edición en profundidad) El Capítulo 13 contiene una gran cantidad de información sobre las nuevas funciones en C # 4 que puede utilizar para hacer que la interacción con COM sea más agradable. (Todavía te recomendaría hacer tu complemento en C ++)
Estoy de acuerdo con Robert Harvey, C # 4.0 cuenta con una interoperabilidad COM mejorada. Aquí hay un poco de código C # más antiguo, que necesita desesperadamente una reescritura.
http://www.codeproject.com/KB/cs/Attach_BHO_with_C_.aspx
Este es un intento de simplificar las cosas al evitar ATL e ir con Spartan COM:
He estado trabajando con el control del navegador web de IE durante años, y en el transcurso de ellos, un nombre aparece una y otra vez con publicaciones útiles: Igor Tandetnik
Si estuviera desarrollando una extensión, apuntaría a un BHO y comenzaría a buscar en Google para:
BHO Igor Tandetnik
O
Navegador ayudante objeto Igor Tandetnik
Sus publicaciones son a menudo muy detalladas, y él sabe de lo que está hablando.
Usted se encontrará hasta sus oídos en la programación COM y ATL. Para obtener un tutorial de muestra, visite: http://msdn.microsoft.com/en-us/library/ms976373.aspx
Hombre ... esto ha sido mucho trabajo! Tenía tanta curiosidad acerca de cómo hacer esto, que lo hice yo mismo.
En primer lugar ... el crédito no es todo mío. Esta es una compilación de lo que encontré, en estos sitios:
- Artículo de CodeProject , cómo hacer un BHO;
- 15 segundos, pero no fue de 15 segundos, tardó aproximadamente 7 horas;
- Microsoft tutorial , me ayudó a agregar el botón de comando.
- Y este tema social.msdn , que me ayudó a entender que la asamblea debe estar en el GAC.
- Esta reciente publicación del blog de MSDN contiene un ejemplo completamente funcional.
- Muchos otros sitios, en el proceso de descubrimiento ...
Y, por supuesto, quería que mi respuesta tuviera las características que me pediste:
- Traversal DOM para encontrar algo;
- un botón que muestra una ventana (en mi caso para configurar)
- persistir en la configuración (usaré regitry para eso)
- y finalmente ejecutar javascript.
Lo describiré paso a paso, cómo logré hacerlo trabajando con Internet Explorer 8 , en Windows 7 x64 ... tenga en cuenta que no podría probar en otras configuraciones. Espero que entiendas =)
Creación de un complemento de Internet Explorer 8 en funcionamiento
Estoy usando Visual Studio 2010 , C # 4 , .Net Framework 4 , por lo que algunos de estos pasos pueden ser ligeramente diferentes para usted.
Creé una biblioteca de clases. Llamé a la mía InternetExplorerExtension .
Añade estas referencias al proyecto:
- Interop.SHDocVw
- Microsoft.mshtml
Nota: Estas referencias pueden estar en diferentes lugares en cada computadora.
esto es lo que contiene mi sección de referencias en csproj:
<Reference Include="Interop.SHDocVw, Version=1.1.0.0, Culture=neutral, PublicKeyToken=90ba9c70f846762e, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<EmbedInteropTypes>True</EmbedInteropTypes>
<HintPath>C:/Program Files (x86)/Microsoft Visual Studio 9.0/Common7/IDE/PrivateAssemblies/Interop.SHDocVw.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.mshtml, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<EmbedInteropTypes>True</EmbedInteropTypes>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
Crea los siguientes archivos:
IEAddon.cs
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Microsoft.Win32;
using mshtml;
using SHDocVw;
namespace InternetExplorerExtension
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("D40C654D-7C51-4EB3-95B2-1E23905C2A2D")]
[ProgId("MyBHO.WordHighlighter")]
public class WordHighlighterBHO : IObjectWithSite, IOleCommandTarget
{
const string DefaultTextToHighlight = "browser";
IWebBrowser2 browser;
private object site;
#region Highlight Text
void OnDocumentComplete(object pDisp, ref object URL)
{
try
{
// @Eric Stob: Thanks for this hint!
// This will prevent this method being executed more than once.
if (pDisp != this.site)
return;
var document2 = browser.Document as IHTMLDocument2;
var document3 = browser.Document as IHTMLDocument3;
var window = document2.parentWindow;
window.execScript(@"function FncAddedByAddon() { alert(''Message added by addon.''); }");
Queue<IHTMLDOMNode> queue = new Queue<IHTMLDOMNode>();
foreach (IHTMLDOMNode eachChild in document3.childNodes)
queue.Enqueue(eachChild);
while (queue.Count > 0)
{
// replacing desired text with a highlighted version of it
var domNode = queue.Dequeue();
var textNode = domNode as IHTMLDOMTextNode;
if (textNode != null)
{
if (textNode.data.Contains(TextToHighlight))
{
var newText = textNode.data.Replace(TextToHighlight, "<span style=''background-color: yellow; cursor: hand;'' onclick=''javascript:FncAddedByAddon()'' title=''Click to open script based alert window.''>" + TextToHighlight + "</span>");
var newNode = document2.createElement("span");
newNode.innerHTML = newText;
domNode.replaceNode((IHTMLDOMNode)newNode);
}
}
else
{
// adding children to collection
var x = (IHTMLDOMChildrenCollection)(domNode.childNodes);
foreach (IHTMLDOMNode eachChild in x)
{
if (eachChild is mshtml.IHTMLScriptElement)
continue;
if (eachChild is mshtml.IHTMLStyleElement)
continue;
queue.Enqueue(eachChild);
}
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
#endregion
#region Load and Save Data
static string TextToHighlight = DefaultTextToHighlight;
public static string RegData = "Software//MyIEExtension";
[DllImport("ieframe.dll")]
public static extern int IEGetWriteableHKCU(ref IntPtr phKey);
private static void SaveOptions()
{
// In IE 7,8,9,(desktop)10 tabs run in Protected Mode
// which prohibits writes to HKLM, HKCU.
// Must ask IE for "Writable" registry section pointer
// which will be something like HKU/S-1-7***/Software/AppDataLow/
// In "metro" IE 10 mode, tabs run in "Enhanced Protected Mode"
// where BHOs are not allowed to run, except in edge cases.
// see http://blogs.msdn.com/b/ieinternals/archive/2012/03/23/understanding-ie10-enhanced-protected-mode-network-security-addons-cookies-metro-desktop.aspx
IntPtr phKey = new IntPtr();
var answer = IEGetWriteableHKCU(ref phKey);
RegistryKey writeable_registry = RegistryKey.FromHandle(
new Microsoft.Win32.SafeHandles.SafeRegistryHandle(phKey, true)
);
RegistryKey registryKey = writeable_registry.OpenSubKey(RegData, true);
if (registryKey == null)
registryKey = writeable_registry.CreateSubKey(RegData);
registryKey.SetValue("Data", TextToHighlight);
writeable_registry.Close();
}
private static void LoadOptions()
{
// In IE 7,8,9,(desktop)10 tabs run in Protected Mode
// which prohibits writes to HKLM, HKCU.
// Must ask IE for "Writable" registry section pointer
// which will be something like HKU/S-1-7***/Software/AppDataLow/
// In "metro" IE 10 mode, tabs run in "Enhanced Protected Mode"
// where BHOs are not allowed to run, except in edge cases.
// see http://blogs.msdn.com/b/ieinternals/archive/2012/03/23/understanding-ie10-enhanced-protected-mode-network-security-addons-cookies-metro-desktop.aspx
IntPtr phKey = new IntPtr();
var answer = IEGetWriteableHKCU(ref phKey);
RegistryKey writeable_registry = RegistryKey.FromHandle(
new Microsoft.Win32.SafeHandles.SafeRegistryHandle(phKey, true)
);
RegistryKey registryKey = writeable_registry.OpenSubKey(RegData, true);
if (registryKey == null)
registryKey = writeable_registry.CreateSubKey(RegData);
registryKey.SetValue("Data", TextToHighlight);
if (registryKey == null)
{
TextToHighlight = DefaultTextToHighlight;
}
else
{
TextToHighlight = (string)registryKey.GetValue("Data");
}
writeable_registry.Close();
}
#endregion
[Guid("6D5140C1-7436-11CE-8034-00AA006009FA")]
[InterfaceType(1)]
public interface IServiceProvider
{
int QueryService(ref Guid guidService, ref Guid riid, out IntPtr ppvObject);
}
#region Implementation of IObjectWithSite
int IObjectWithSite.SetSite(object site)
{
this.site = site;
if (site != null)
{
LoadOptions();
var serviceProv = (IServiceProvider)this.site;
var guidIWebBrowserApp = Marshal.GenerateGuidForType(typeof(IWebBrowserApp)); // new Guid("0002DF05-0000-0000-C000-000000000046");
var guidIWebBrowser2 = Marshal.GenerateGuidForType(typeof(IWebBrowser2)); // new Guid("D30C1661-CDAF-11D0-8A3E-00C04FC9E26E");
IntPtr intPtr;
serviceProv.QueryService(ref guidIWebBrowserApp, ref guidIWebBrowser2, out intPtr);
browser = (IWebBrowser2)Marshal.GetObjectForIUnknown(intPtr);
((DWebBrowserEvents2_Event)browser).DocumentComplete +=
new DWebBrowserEvents2_DocumentCompleteEventHandler(this.OnDocumentComplete);
}
else
{
((DWebBrowserEvents2_Event)browser).DocumentComplete -=
new DWebBrowserEvents2_DocumentCompleteEventHandler(this.OnDocumentComplete);
browser = null;
}
return 0;
}
int IObjectWithSite.GetSite(ref Guid guid, out IntPtr ppvSite)
{
IntPtr punk = Marshal.GetIUnknownForObject(browser);
int hr = Marshal.QueryInterface(punk, ref guid, out ppvSite);
Marshal.Release(punk);
return hr;
}
#endregion
#region Implementation of IOleCommandTarget
int IOleCommandTarget.QueryStatus(IntPtr pguidCmdGroup, uint cCmds, ref OLECMD prgCmds, IntPtr pCmdText)
{
return 0;
}
int IOleCommandTarget.Exec(IntPtr pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
{
try
{
// Accessing the document from the command-bar.
var document = browser.Document as IHTMLDocument2;
var window = document.parentWindow;
var result = window.execScript(@"alert(''You will now be allowed to configure the text to highlight...'');");
var form = new HighlighterOptionsForm();
form.InputText = TextToHighlight;
if (form.ShowDialog() != DialogResult.Cancel)
{
TextToHighlight = form.InputText;
SaveOptions();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return 0;
}
#endregion
#region Registering with regasm
public static string RegBHO = "Software//Microsoft//Windows//CurrentVersion//Explorer//Browser Helper Objects";
public static string RegCmd = "Software//Microsoft//Internet Explorer//Extensions";
[ComRegisterFunction]
public static void RegisterBHO(Type type)
{
string guid = type.GUID.ToString("B");
// BHO
{
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(RegBHO, true);
if (registryKey == null)
registryKey = Registry.LocalMachine.CreateSubKey(RegBHO);
RegistryKey key = registryKey.OpenSubKey(guid);
if (key == null)
key = registryKey.CreateSubKey(guid);
key.SetValue("Alright", 1);
registryKey.Close();
key.Close();
}
// Command
{
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(RegCmd, true);
if (registryKey == null)
registryKey = Registry.LocalMachine.CreateSubKey(RegCmd);
RegistryKey key = registryKey.OpenSubKey(guid);
if (key == null)
key = registryKey.CreateSubKey(guid);
key.SetValue("ButtonText", "Highlighter options");
key.SetValue("CLSID", "{1FBA04EE-3024-11d2-8F1F-0000F87ABD16}");
key.SetValue("ClsidExtension", guid);
key.SetValue("Icon", "");
key.SetValue("HotIcon", "");
key.SetValue("Default Visible", "Yes");
key.SetValue("MenuText", "&Highlighter options");
key.SetValue("ToolTip", "Highlighter options");
//key.SetValue("KeyPath", "no");
registryKey.Close();
key.Close();
}
}
[ComUnregisterFunction]
public static void UnregisterBHO(Type type)
{
string guid = type.GUID.ToString("B");
// BHO
{
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(RegBHO, true);
if (registryKey != null)
registryKey.DeleteSubKey(guid, false);
}
// Command
{
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(RegCmd, true);
if (registryKey != null)
registryKey.DeleteSubKey(guid, false);
}
}
#endregion
}
}
Interop.cs
using System;
using System.Runtime.InteropServices;
namespace InternetExplorerExtension
{
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("FC4801A3-2BA9-11CF-A229-00AA003D7352")]
public interface IObjectWithSite
{
[PreserveSig]
int SetSite([MarshalAs(UnmanagedType.IUnknown)]object site);
[PreserveSig]
int GetSite(ref Guid guid, [MarshalAs(UnmanagedType.IUnknown)]out IntPtr ppvSite);
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct OLECMDTEXT
{
public uint cmdtextf;
public uint cwActual;
public uint cwBuf;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public char rgwz;
}
[StructLayout(LayoutKind.Sequential)]
public struct OLECMD
{
public uint cmdID;
public uint cmdf;
}
[ComImport(), ComVisible(true),
Guid("B722BCCB-4E68-101B-A2BC-00AA00404770"),
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleCommandTarget
{
[return: MarshalAs(UnmanagedType.I4)]
[PreserveSig]
int QueryStatus(
[In] IntPtr pguidCmdGroup,
[In, MarshalAs(UnmanagedType.U4)] uint cCmds,
[In, Out, MarshalAs(UnmanagedType.Struct)] ref OLECMD prgCmds,
//This parameter must be IntPtr, as it can be null
[In, Out] IntPtr pCmdText);
[return: MarshalAs(UnmanagedType.I4)]
[PreserveSig]
int Exec(
//[In] ref Guid pguidCmdGroup,
//have to be IntPtr, since null values are unacceptable
//and null is used as default group!
[In] IntPtr pguidCmdGroup,
[In, MarshalAs(UnmanagedType.U4)] uint nCmdID,
[In, MarshalAs(UnmanagedType.U4)] uint nCmdexecopt,
[In] IntPtr pvaIn,
[In, Out] IntPtr pvaOut);
}
}
Y finalmente un formulario, que utilizaremos para configurar las opciones. En esta forma coloque un TextBox
y un Button
Aceptar. Establezca el DialogResult del botón en Ok . Coloque este código en el código del formulario:
using System.Windows.Forms;
namespace InternetExplorerExtension
{
public partial class HighlighterOptionsForm : Form
{
public HighlighterOptionsForm()
{
InitializeComponent();
}
public string InputText
{
get { return this.textBox1.Text; }
set { this.textBox1.Text = value; }
}
}
}
En las propiedades del proyecto, haga lo siguiente:
- Firma la asamblea con una tecla fuerte;
- En la pestaña Depuración, establezca Iniciar programa externo en
C:/Program Files (x86)/Internet Explorer/iexplore.exe
- En la pestaña Depuración, configure Argumentos de línea de comando en
http://msdn.microsoft.com/en-us/library/ms976373.aspx#bho_getintouch
En la pestaña Eventos de compilación, configure la línea de comandos de los eventos posteriores a la compilación en:
"C:/Program Files (x86)/Microsoft SDKs/Windows/v7.0A/Bin/NETFX 4.0 Tools/x64/gacutil.exe" /f /i "$(TargetDir)$(TargetFileName)" "C:/Windows/Microsoft.NET/Framework/v4.0.30319/RegAsm.exe" /unregister "$(TargetDir)$(TargetFileName)" "C:/Windows/Microsoft.NET/Framework/v4.0.30319/RegAsm.exe" "$(TargetDir)$(TargetFileName)"
Atención: como mi computadora es x64, hay una x64 específica dentro de la ruta del ejecutable gacutil en mi máquina que puede ser diferente en la suya.
64bit IE necesita BHO compilado de 64 bits y registrado de 64 bits. Use 64bit RegAsm.exe (normalmente vive en C: / Windows / Microsoft.NET / Framework64 / v4.0.30319 / RegAsm.exe)
Cómo funciona este complemento
Atraviesa todo el árbol DOM, reemplazando el texto, configurado usando el botón, por sí mismo con un fondo amarillo. Si hace clic en los textos amarillos, llama a una función javascript que se insertó en la página de forma dinámica. La palabra predeterminada es ''navegador'', ¡para que coincida con muchos de ellos! EDITAR: después de cambiar la cadena a resaltar, debe hacer clic en el cuadro de URL y presionar Intro ... F5 no funcionará, creo que se debe a que F5 se considera como "navegación" y sería necesario escuchar el evento de navegación (tal vez). Intentaré arreglar eso más tarde.
Ahora, es hora de irse. Estoy muy cansado. Siéntase libre de hacer preguntas ... puede ser que no podré responder ya que me voy de viaje ... en 3 días estoy de vuelta, pero intentaré venir aquí mientras tanto.
Obviamente está resuelto, pero para los otros usuarios, recomendaría el marco SpicIE . He hecho mi propia extensión basada en ella. Solo es compatible con Internet Explorer 7/8 oficialmente, pero lo probé en Internet Explorer 6-10 (desde Windows XP a Windows 8 Consumer Preview) y funciona bien . Desafortunadamente, hubo algunos errores en la última versión, así que tuve que corregirlos e hice mi propia versión: http://archive.msdn.microsoft.com/SpicIE/Thread/View.aspx?ThreadId=5251
Otro enfoque genial sería revisar:
Es un marco basado en JS con jquery que le permite desarrollar extensiones de navegadores para IE, FF y Chrome utilizando un único código JS común. Básicamente, el marco hace todo el trabajo desagradable y te queda escribir el código de tus aplicaciones.
Si no está intentando reinventar la rueda, puede probar Add In Express para IE . He usado el producto para las cosas de VSTO , y es bastante bueno. También tienen un foro útil y un soporte rápido.
En la pestaña Eventos de compilación, configure la línea de comando de eventos posteriores a la compilación en: (x64) se lista a continuación
"C:/Program Files (x86)/Microsoft SDKs/Windows/v7.0A/Bin/NETFX 4.0 Tools/x64/gacutil.exe" /if "$(TargetDir)$(TargetFileName)"
"C:/Windows/Microsoft.NET/Framework64/v4.0.30319/RegAsm.exe" /u "$(TargetDir)$(TargetFileName)"
"C:/Windows/Microsoft.NET/Framework64/v4.0.30319/RegAsm.exe" "$(TargetDir)$(TargetFileName)"
Quiero que la pestaña Build Events, establezca la línea de comandos de Post-build events en (sistema operativo de 32 bits)
"C:/Program Files/Microsoft SDKs/Windows/v7.1/Bin/gacutil.exe" /if "$(TargetDir)$(TargetFileName)"
"C:/Windows/Microsoft.NET/Framework/v4.0.30319/RegAsm.exe" /u "$(TargetDir)$(TargetFileName)"
"C:/Windows/Microsoft.NET/Framework/v4.0.30319/RegAsm.exe" "$(TargetDir)$(TargetFileName)"