Как начать разработку расширений для Internet Explorer?
Кто-нибудь здесь имеет опыт разработки / разработки расширений IE, которые могут поделиться своими знаниями? Это будет включать примеры кода, или ссылки на хорошие, или документацию по процессу, или что-нибудь еще.
Я действительно хочу сделать это, но я попадаю в гигантскую стену с паршивой документацией, паршивым кодом / примером кода / его отсутствием. Любая помощь / ресурсы, которые вы можете предложить, будет принята с благодарностью.
В частности, я хотел бы начать с того, как получить доступ к DOM / манипулировать им из расширения IE.
РЕДАКТИРОВАТЬ, даже больше деталей:
В идеале я хотел бы установить кнопку на панели инструментов, чтобы при нажатии на нее появлялось меню, содержащее ссылки на внешние сайты. Я также хотел бы получить доступ к DOM и установить JavaScript на странице в зависимости от некоторых условий.
Каков наилучший способ сохранить информацию в расширении IE? В Firefox/Chrome/ большинстве современных браузеров вы используете window.localStorage
, но, очевидно, с IE8/IE7, это не вариант. Может быть, БД SQLite или такой? Можно предположить, что.NET 4.0 будет установлен на компьютере пользователя?
Я не хочу использовать Spice IE, так как хочу создать тот, который совместим также с IE9. Я также добавил тег C++ к этому вопросу, потому что, если его лучше создать в C++, я могу это сделать.
10 ответов
Человек... это было много работы! Мне было так интересно, как это сделать, что я сделал это сам.
Прежде всего... кредит не весь мой. Это компиляция того, что я нашел на этих сайтах:
- Статья CodeProject, как сделать BHO;
- 15 секунд, но это не было 15 секунд, это заняло около 7 часов;
- Учебник Microsoft, помог мне добавить командную кнопку.
- И эта тема для social.msdn, которая помогла мне понять, что сборка должна быть в GAC.
- Это недавнее сообщение в блоге MSDN содержит полностью рабочий пример
- много других сайтов, в процессе открытия...
И, конечно же, я хотел, чтобы в моем ответе были функции, которые вы спрашивали:
- Обход DOM, чтобы найти что-то;
- кнопка, которая показывает окно (в моем случае для настройки)
- сохранить конфигурацию (для этого я буду использовать regitry)
- и, наконец, выполнить JavaScript.
Я опишу это шаг за шагом, как мне удалось это сделать, работая с Internet Explorer 8, в Windows 7 x64... заметьте, что я не смог протестировать в других конфигурациях. Надеюсь, вы понимаете =)
Создание рабочего Internet Explorer 8 Addon
Я использую Visual Studio 2010, C# 4, .Net Framework 4, поэтому некоторые из этих шагов могут немного отличаться для вас.
Создана библиотека классов. Я назвал мой InternetExplorerExtension.
Добавьте эти ссылки в проект:
- Interop.SHDocVw
- Microsoft.mshtml
Примечание. Эти ссылки могут находиться в разных местах на каждом компьютере.
это то, что мой раздел ссылок в 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" />
Создайте следующие файлы:
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);
}
}
и, наконец, форма, которую мы будем использовать для настройки параметров. В этой форме поместите TextBox
и хорошо Button
, Установите для DialogResult кнопки значение Ok. Поместите этот код в код формы:
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; }
}
}
}
В свойствах проекта сделайте следующее:
- Подписать сборку с помощью сильной клавиши;
- На вкладке "Отладка" установите для параметра " Запустить внешнюю программу" значение
C:\Program Files (x86)\Internet Explorer\iexplore.exe
- На вкладке "Отладка" установите для аргументов командной строки значение
http://msdn.microsoft.com/en-us/library/ms976373.aspx#bho_getintouch
На вкладке "События сборки" установите для командной строки событий после сборки значение:
"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" / отменить регистрацию "$(TargetDir)$(TargetFileName)" "C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" "$(TargetDir)$(TargetFileName)"
Внимание: поскольку у меня компьютер x64, на пути к исполняемому файлу gacutil на моем компьютере есть определенный x64, который может отличаться на вашем.
64-битному IE требуется 64-битная компиляция и 64-битная регистрация BHO. Используйте 64-битный RegAsm.exe (обычно находится в C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe)
Как работает этот аддон
Он пересекает все дерево DOM, заменяя текст, настроенный с помощью кнопки, на желтый фон. Если вы щелкнете по пожелтевшему тексту, он вызовет функцию JavaScript, которая была вставлена на страницу динамически. Слово по умолчанию - "браузер", так что оно соответствует большинству из них! РЕДАКТИРОВАТЬ: после изменения строки, которая будет выделена, вы должны нажать на поле URL и нажать Enter... F5 не будет работать, я думаю, что это потому, что F5 рассматривается как "навигация", и это потребует прослушивания события навигации (может быть). Я постараюсь исправить это позже.
Теперь пришло время идти. Я очень устал. Не стесняйтесь задавать вопросы... возможно, я не смогу ответить, так как я собираюсь в путешествие... через 3 дня я вернусь, но я постараюсь приехать сюда тем временем.
Еще один крутой подход - проверить:
Это основа, основанная на JS с jquery, которая позволяет вам разрабатывать расширения для браузеров для IE, FF и Chrome, используя единый общий код JS. По сути, фреймворк выполняет всю отвратительную работу, и у вас остается написание кода приложений.
Состояние расширений IE на самом деле довольно печальное. У вас есть старая модель IE5 Browser Helper Object (да, те печально известные BHO, которые все любили блокировать в прошлом), панели инструментов и новые ускорители для IE. Даже тогда совместимость иногда будет нарушаться. Раньше я поддерживал расширение для IE6, которое порвалось с IE7, поэтому некоторые вещи изменились. По большей части, насколько я знаю (я не касался BHOs в течение многих лет), вам все еще нужно кодировать их с использованием Active Template Libraries (вроде STL для Microsoft COM), а также только для C++. Вы могли бы сделать COM Interop с C# и уйти с этим в C#, но это, вероятно, будет слишком сложно для того, чтобы оно того стоило. В любом случае, если вы заинтересованы в кодировании своего собственного расширения для IE (что вполне вероятно, если вы хотите, чтобы ваши расширения были доступны во всех основных браузерах), здесь приведены официальные ресурсы Microsoft.
http://msdn.microsoft.com/en-us/library/aa753587(v=vs.85).aspx
А для ускорителей, которые являются новыми в IE8, вы можете проверить это.
http://msdn.microsoft.com/en-us/library/cc289775(v=vs.85).aspx
Я согласен, что документация ужасна, а API довольно устарели. Тем не менее, я надеюсь, что это помогает.
РЕДАКТИРОВАТЬ: Я думаю, что я могу добавить один последний источник информации здесь. Я просматривал свои записи назад, когда работал над BHO. И эта статья заставила меня начать с них. Он довольно старый, но имеет хорошее объяснение интерфейсов ATL, которые вы будете использовать при работе с IE BHO (например, IObjectWithSite). Я думаю, что это довольно хорошо объяснено и помогло мне много тогда. http://msdn.microsoft.com/en-us/library/bb250436.aspx Я также проверил пример, который опубликовал GregC. Он работает как минимум с IE8 и совместим с VS 2010, поэтому, если вы хотите использовать C#, вы можете начать там и взглянуть на книгу Джона Скита. (C# в Depth 2nd edition) Глава 13 содержит много информации о новых функциях в C# 4, которые вы можете использовать для улучшения взаимодействия с COM. (Я все еще рекомендовал бы вам делать надстройку в C++)
Разработка C# BHOs - это боль в заднице. Он включает в себя много неприличного COM-кода и вызовов p/invoke.
У меня есть в основном законченный C# BHO, который вы можете использовать как угодно. Я говорю "в основном", потому что я так и не понял, как сохранить данные приложения в защищенном режиме IE.
Я работаю с браузерным браузером IE уже много лет, и в ходе одного из них снова и снова появляется одно имя с полезными сообщениями: Игорь Тандетник
Если бы я разрабатывал расширение, я бы нацелился на BHO и начал поискать в Google:
БХО Игорь Тандетник
ИЛИ ЖЕ
Browser Helper Object Игорь Тандетник
Его сообщения часто очень подробны, и он знает, о чем говорит.
Вы найдете себя в ушах в программировании COM и ATL. Для ознакомления с образцом ознакомьтесь с: http://msdn.microsoft.com/en-us/library/ms976373.aspx
Если вы не пытаетесь изобретать велосипед, вы можете попробовать добавить In Express для IE. Я использовал продукт для материала VSTO, и он довольно хорош. Также у них есть полезный форум и быстрая поддержка.
Я согласен с Робертом Харви, в C# 4.0 улучшено взаимодействие с COM. Вот немного старого кода C#, остро нуждающегося в переписывании.
http://www.codeproject.com/KB/cs/Attach_BHO_with_C_.aspx
Это попытка упростить вещи, избегая ATL и переходя на Spartan COM:
Это явно решаемо, но для других пользователей я бы порекомендовал SpicIE framework. Я сделал свое собственное расширение на его основе. Он официально поддерживает только Internet Explorer 7/8, но я проверил это на Internet Explorer 6-10 (от Windows XP до Windows 8 Consumer Preview) и работает нормально. К сожалению, в последнем выпуске были некоторые ошибки, поэтому мне пришлось их исправлять и сделать свой собственный выпуск: http://archive.msdn.microsoft.com/SpicIE/Thread/View.aspx?ThreadId=5251
Вопрос с 2013 года, сейчас 2020 год, но он может быть полезен для будущих посетителей.
Я попытался реализовать ответ @Miguel Angelo, сначала это не сработало.
Есть еще некоторые настройки, которые вам предстоит определить.
в Internet Explorer (я использую IE-11) перейдите в Tools-->Internet Options-->Advanced
:
Также см. Этот вопрос SO и этот пример из github
Сердечно предлагаю вам этот пост Павла Зольникова, опубликованный в 2002 году!
http://www.codeproject.com/Articles/2219/Extending-Explorer-with-Band-Objects-using-NET-and
Он основан на использовании объектов Band и скомпилирован с использованием.Net 2.0. Исходный код предоставляется, открывается и хорошо компилируется с Visual Studio 2013. Как вы прочтете в комментариях к посту, он отлично работает для IE 11, Windows 7 и Windows 10. Он отлично работает для меня в Windows 7 + SP1 и IE. 11 Наслаждайтесь!
На вкладке "События сборки" установите для командной строки событий после сборки значение: (x64), приведенное ниже.
"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)"
Я хочу, чтобы на вкладке "События сборки" была установлена командная строка "События после сборки" (32-разрядная операционная система)
"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)"