Matlab’s built-in actxserver function provides access to COM/DCOM server applications on Microsoft Windows platforms. This enables us, for example, to open a Microsoft Office document programmatically, and modify it from within Matlab. This in turn can be used, for example, to prepare professional PDF reports, relying on Office’s ability to save documents in PDF format. Alternately, we could programmatically update cell formatting (colors, fonts, borders etc.) and embed images and graphs in an Excel workbook – something that the standard xlswrite cannot do (my officedoc utility does this, using the same COM interface).
Note: For some reason, COM servers are called an ActiveX server in Matlab, although the term ActiveX is normally used for clients (i.e., controls), for which we have the actxcontrol* set of built-in functions.
Today I will focus on two changes that I made to Matlab’s built-in actxserver function:
Reusing an active server (no pun intended…)
By default, actxserver starts a new instance of the specified COM server whenever it is called. Sometimes this is indeed useful. However, I find that in the vast majority of cases, I actually want to reuse an existing server instance if it is currently running. For example, it is much faster and more memory-efficient to open an Excel workbook file in an existing Excel process, than to open it in a new dedicated process.
In R2007a, Matlab introduced the actxGetRunningServer function. Unfortunately, it did not see fit to set it as the default behavior for actxserver, nor even as an optional additional parameter (although MathWorks *did* change the actxserver function in that very same release, to accept a custom interface parameter). Users need to change their programs to first call actxGetRunningServer, check whether it works or fails, and then call actxserver if it fails (meaning that a server process is not currently running and a new one needs to be started):
% Try to reuse an existing COM server instance if possible try hServer = actxGetRunningServer('excel.application'); % no crash so probably succeeded to connect to a running server catch % Never mind - continue normally to start the COM server and connect to it hServer = actxserver('excel.application'); end |
This is downright silly, I must say.
Moreover, all the existing user and Matlab code that uses actxserver will continue to start a new server instance rather than reuse existing ones. For example, the widely-used xlsread and xlswrite functions.
Instead of fixing Matlab’s installed actxserver.m file, which could be problematic when deploying applications to end-users, I created a copy of actxserver.m somewhere in my user folders that is high on the Matlab path. This way I can modify the file and bundle it with any application that I send to clients. The change to this file is simply to add a variant of the code above at the very top of the actxserver.m file, as follows (the new lines are highlighted):
function h = actxserver(progID, varargin) %ACTXSERVER Creates a COM Automation server. ... % Copyright 2006-2007 The MathWorks, Inc. % $Revision: 1.8.6.12 $ $Date: 2011/08/13 17:30:50 $ error(nargchk(1, 5, nargin, 'struct')); % Yair 17/5/2009: Try to reuse an existing COM server instance if possible try h = actxGetRunningServer(progID); return; % no crash so probably succeeded - returncatch % Never mind - continue normally to start the COM server and connect to itendmachinename = '';interface = 'IDispatch';... |
This simple change means that all exiting code, including Matlab’s built-in xlsread and xlswrite functions, now try to reuse a running COM server process if possible, starting a new one only if this fails. The code is fault-tolerant in that it also works on old Matlab releases where the actxGetRunningServer is not available.
Fix of the progID
The %matlabroot%/toolbox/matlab/winfun/private/newprogid.m function, a private function used by actxserver, normalizes COM program identifiers (progIDs), which is the string used to locate the COM server. Unfortunately, until R2011a (or specifically, when this was fixed by some MathWorker on June 10, 2010), this function had a bug that caused the normalization to fail. In order to correct this, I simply added the fixed function to the bottom of my modified actxserver.m file:
% Taken from: [matlabroot '\toolbox\matlab\winfun\private\newprogid.m'] function convertedProgID = newprogid(progID) % Copyright 1984-2004 The MathWorks, Inc. % $Revision: 1.1.8.5 $ $Date: 2004/04/15 00:07:00 $ convertedProgID = lower(progID); % Yair: in the new version (after 2010/06/10) this line is missing i.e. case-sensitive progID convertedProgID = regexprep(convertedProgID, '_', '__'); % Yair 17/5/2009: was progId - probably a bug convertedProgID = regexprep(convertedProgID, '-', '___'); convertedProgID = regexprep(convertedProgID, '\.', '_'); convertedProgID = regexprep(convertedProgID, ' ', '____'); convertedProgID = regexprep(convertedProgID, '&', '_____'); |
Note that when MathWorks fixed %matlabroot%/toolbox/matlab/winfun/private/newprogid.m in 2010, they removed the conversion of progID to lower-case, making it case-sensitive. In my modified version above, I have kept the older conversion to lowercase for case-insensitivity.
Public-service announcements
Readers in Israel are invited to attend a free training seminar that I will present on advanced Matlab topics in Herzliya, on Tuesday Jan 8, 2013. The seminar is free, but requires registration. Additional details here. I will speak in Hebrew, but the presentation will be in English and I will be happy to answer questions in English.
I wish to point readers’ attentions to the recent announcement by AccelerEyes and MathWorks, that they will now merge their parallel-processing solutions into Matlab’s product line. I assume this also means an out-of-court settlement of MathWorks’ patent-infringement lawsuit. On the one hand, this is bad news for competition, removing Matlab PCT’s major competitor from the market. On the other hand, it could mean improved PCT/DCS products, merging Jacket’s superior performance and functionality directly within Matlab. If I may venture a guess, 2013 will herald a better, faster, more integrated parallel computing solution for Matlab, but we should probably (and quite sadly) say goodbye to Jacket’s price levels. Such collaborations, generally portrayed as exciting for consumers, are typically much more exciting for the respective shareholders…
This blog will now take a short winter break, and will return following the New-Year. Happy Holidays/New-Year everyone! Let this upcoming year be another year filled with discoveries, innovation and success!
Is there a way to force actxserver to create two separate instances.
I have been trying to launch to separate COM instance of CQG COM object by calling actxserver(‘CQG.CQGCEL.4’) twice. This will only return one instance of the actxserver.
Is there a way to force 2 instances?
Tks
@Liam – I’m not aware of a way to do this.
doesn’t work, crashes out.
I had a program that reads two excel files (== two tables) and returns an excel with 10 sheets, 2 – 3 tables each sheet. I used WRITETABLE, I had to use that for every table, making the program a bit slow.
Even more, when I had to calculate hundreds of input file variation combinations, the run-time started slowing down progressively. This was due to the fact, that EXCEL instances were opened for each call to read/write EXCEL files, and they, not closing before the following loop, started stacking up. I solved this by closing all EXCEL instances after each reference to EXCEL files, side effect being not being able to use EXCEL while running the program (which I was too lazy to solve).
After implementing the try/catch posted here into actxserver.m, the program runs twice as fast without the side effect. Thank you kind sir.
Or maybe I was too joyful too quickly.
Apparently if there is an excel window open, I get an error and/or the window is closed.
Update:
Since “the one” used instance of EXCEL is closed inside other called functions (like xlsread/xlswrite), I had to hide some of the code there to prevent the programs from closing. I use readtable, which works good with this xlsread correction, but writetable has another Excel.Quit line, which I managed to find in write.m file. I disabled a few lines in writeXLSFile (which is called from within write.m), but not sure if they are of any significance, because my problem was fixed eventually after editing the write.m file. This however means that I will have to manually control the behaviour of how EXCEL workbooks are displayed, but this way my run-time was reduced from ~90 s to ~20 s.
HOWEVER, once compiled to an .exe, program seems to have a problem using actxGetRunningServer.m, which is the core to this ‘fix’. This meant that I had to change back all the functions to compile a version that’s usable with MCR.
At least I learnt something in the process. And even though my users get no merits from this, I will definitely use those fixes if I happen read/write hundreds of excel files again.
I noticed that xlsread will create a hidden and never-dying special server that always has priority when actxGetRunningServer is called.
So this cause a problem that no matter how many servers I re-create, the actxGetRunningServer is always connected to the hidden xlsread-generated server, which never dies even when I change the raw code by adding a Excel.Quit() to the private file of Matlab.
Ren – This is usually the expected behavior, which avoids unnecessary duplications of the Excel process in CPU/memory. If you want to kill the process you can always run
system('taskkill /im excel.exe 2> NUL')
orhExcel.Quit; delete(hExcel)
. (the delete(hExcel) part is important – without it the process will not actually quit).