我正在尝试运行Excel宏并在运行后关闭excel 不幸的是它不起作用。我尝试了Stackoverflow的所有解决方案,无法获得可靠的解决方案。请帮忙。 你可以看到我试图关闭,相当,释放COM对象,但似乎没有任何工作。
public static bool RunMacro(string Path, string MacroName, bool Close, ProgressForm ProgressForm, params object[] Arguments)
{
Microsoft.Office.Interop.Excel.Application aApplication = null;
bool aCloseApplication = true;
bool aResult = false;
try
{
aApplication = (Microsoft.Office.Interop.Excel.Application)Marshal.GetActiveObject("Excel.Application");
aCloseApplication = false;
}
catch (COMException aCOMException)
{
aApplication = new Microsoft.Office.Interop.Excel.Application();
aApplication.Visible = false;
}
if (aApplication != null)
{
aApplication.ScreenUpdating = false;
Microsoft.Office.Interop.Excel.Workbook aWorkbook = null;
Microsoft.Office.Interop.Excel.Worksheet aWorksheet = null;
bool aCloseWorkbook = true;
try
{
if (IsEdited(aApplication))
{
throw new Exception("Excel is in cell edit mode. Please stop editing cell and run import again");
}
else
{
for (int i = 0; i < aApplication.Workbooks.Count; i++)
if (aApplication.Workbooks[i + 1].FullName == Path)
{
aWorkbook = aApplication.Workbooks[i + 1];
aCloseWorkbook = false;
break;
}
if (aWorkbook == null)
aWorkbook = aApplication.Workbooks.Open(Path);
// Run macro here
aApplication.Run(string.Format("{0}!{1}", System.IO.Path.GetFileName(Path), MacroName), Arguments);
aResult = true;
}
}
finally
{
if (aWorksheet != null)
{
Marshal.ReleaseComObject(aWorksheet);
}
//does not work here!!! I want to close excel here
if (aWorkbook != null)
aWorkbook.Close();
aApplication.Quit();
Marshal.ReleaseComObject(aWorkbook);
Marshal.ReleaseComObject(aApplication);
}
}
return aResult;
}
答案 0 :(得分:3)
这是我在使用SSIS脚本任务刷新Excel文件时经常玩的东西。
我已经阅读了有关使用Marshal.ReleaseComObject
的混合内容,但我也发现它并非必要。对我来说,最终解决方案如下:
using xl = Microsoft.Office.Interop.Excel;
...
public void Main()
{
DoExcelWork();
GC.Collect();
GC.WaitForPendingFinalizers();
}
private void DoExcelWork()
{
xl.Application app = null;
xl.Workbooks books = null;
xl.Workbook book = null;
try
{
app = new xl.Application() { DisplayAlerts = false };
books = app.Workbooks;
book = books.Open("filename goes here");
book.RefreshAll();
// this is where you would do your Excel work
app.DisplayAlerts = false; // This is for reinforcement; the setting has been known to reset itself after a period of time has passed.
book.SaveAs("save path goes here");
app.DisplayAlerts = true;
book.Close();
app.Quit();
}
catch
{
if (book != null) book.Close(SaveChanges: false);
if (app != null) app.Quit();
}
}
我不确定您的应用程序是如何布局的,但在使用SSIS时,我发现有必要在声明Excel Interop对象的范围之外调用GC.Collect
,以避免使用Excel实例在某些情况下保持打开状态,因此有两种方法。
您应该能够调整此代码以满足您的要求。
答案 1 :(得分:0)
三件事:
手动或以编程方式关闭Excel。 Excel可能会弹出一个问题框,询问您是否要保存(即使Excel当前已隐藏)。实际上它必须关闭才能工作;如果您选择取消,它将不会被关闭。应该有一个重载,允许您忽略未保存的更改。
将引用设置为null
。
致电GC.Collect();
。
根据MSDN,一种阻止盒子阻止近距离尝试的方法是将DisplayAlerts设置为false
。 (虽然你会失去变化。)
据我所知,Marshall
你所做的事情是不必要的(根据this answer,它实际上很危险)。我特别做的是将interop对象包装在我自己的Excel
对象中,该对象实现IDisposable
。我的Dispose
方法基本上看起来像这样(虽然我也做了一些其他的事情,比如总是Show
Excel,如果它没有关闭,总是转回渲染和自动计算等。)
void Dispose() {
if (mExcel != null) {
mExcel = null;
GC.Collect();
}
}
并使用静态方法实现正确的使用模式:
public static void UseAndClose(Action<Excel> pAction) {
using (var excel = new Excel()) {
pAction(excel);
excel.Close(false); // closes all workbooks and then calls `Quit`
}
}
答案 2 :(得分:0)
您永远不应该在Marshal.ReleaseComObject
内调用Marshal.FinalReleaseComObject
和Excel Interop
方法;这是一种不好的做法。
没有人(包括Microsoft
)明确表示您必须手动释放COM
引用,因为您的应用程序和互操作库应该自动处理此问题。即使您想继续手动发布COM
个引用,一旦完成,您必须通过调用GC.Collect()
和GC.WaitForPendingFinalizers()
确保在流程结束时清除它们(在旁注中,如果从调试器运行方法,则GC
可能会失败,因为本地引用在方法结束之前一直处于kepy状态。)
这就是你要做的全部:
// let's make sure that the process doesn't hang because of alerts
aApplication.DisplayAlerts = false;
aWorkbook.Save();
aWorkbook.Close();
aApplication.Quit()
aApplication = null;
答案 3 :(得分:0)
如果你决定使用Marshal.ReleaseCOMObject
技术(而不是GC.Collect
),那么关键是确保你发布每一个RCW(COM对象),不只是一些。
一个例子,基于您的原始代码:
public static bool RunMacro(string Path, string MacroName, bool Close, params object[] Arguments)
{
Microsoft.Office.Interop.Excel.Application aApplication = null;
bool aCloseApplication = true;
bool aResult = false;
try
{
aApplication = (Microsoft.Office.Interop.Excel.Application)Marshal.GetActiveObject("Excel.Application");
aCloseApplication = false;
}
catch (COMException aCOMException)
{
aApplication = new Microsoft.Office.Interop.Excel.Application();
aApplication.Visible = false;
}
if (aApplication != null)
{
aApplication.ScreenUpdating = false;
Microsoft.Office.Interop.Excel.Workbook aWorkbook = null;
Microsoft.Office.Interop.Excel.Worksheet aWorksheet = null;
bool aCloseWorkbook = true;
try
{
if (IsEdited(aApplication))
{
throw new Exception("Excel is in cell edit mode. Please stop editing cell and run import again");
}
else
{
var workbooks = aApplication.Workbooks;
for (int i = 0; i < workbooks.Count; i++)
{
var workbook = aApplication.Workbooks.Item[i + 1];
if (workbook.FullName == Path)
{
aWorkbook = workbook;
aCloseWorkbook = false;
break;
}
else
{
Marshal.ReleaseComObject(workbook);
}
}
if (aWorkbook == null)
aWorkbook = workbooks.Open(Path);
Marshal.ReleaseComObject(workbooks);
// Run macro here
aApplication.Run(string.Format("{0}!{1}", System.IO.Path.GetFileName(Path), MacroName),
Arguments);
aResult = true;
}
}
finally
{
if (aWorksheet != null)
{
Marshal.ReleaseComObject(aWorksheet);
}
//does not work here!!! I want to close excel here
if (aWorkbook != null)
aWorkbook.Close();
aApplication.Quit();
Marshal.ReleaseComObject(aWorkbook);
Marshal.ReleaseComObject(aApplication);
}
}
return aResult;
}
请注意,我已经介绍了一些额外的变量。 workbooks
和workbook
指向原始代码中未RCO
的对象。
在决定使用此技术(使用ReleaseCOMObject
)和其他技术(使用GC.Collect
)方面,需要考虑以下因素:
ReleaseCOMObject
需要更多纪律(确保您发布所有内容,请考虑double dot rule,意识到application.Workbooks["something]"
实际上是application.Workbooks.Item["something"]
的简写(因此ReleaseCOMObject
等)等)GC.Collect()
会特别困难(因为您所需的生命周期基本上是函数范围 - 但它可以适用于某些场景)。正如two dots所述:
如果您是使用适量COM对象的客户端应用程序 在您的托管代码中自由传递,您不应该使用 ReleaseComObject的。
GC.Collect();
更简单(基本上只需致电GC.WaitForPendingFinalizers();
和Marshal.AreComObjectsAvailableForCleanup()
,直到false
返回GC.Collect
)。GC.Collect
通常较慢(因为它需要GC 所有内容而不仅仅是Chris Brumme所涉及的内容。)Marshal.AreComObjectsAvailableForCleanup
(更具体地说,true
)如果涉及多个线程可能会有问题(因为它可能会因其他RCW(来自其他线程)而返回GC.Collect
,而不仅仅是你在这个范围内特别感兴趣的那些)。GC.Collect
可能会降低您的整体系统效果,因为它可能会为您的所有对象提供RCWs(通过将它们推送到以后的GC代,而不是以其他方式)。 请注意,如果您决定使用srand()
,那么此处的其他答案非常适合提供有关如何执行此操作的一些指导。