目前现在很多大型的网站或是某些项目需要让用户上传的文档可以在线预览。这个目前我所了解到的有以下几种情况:
1、pdf 在线阅读
2、office 在线阅读
对于pdf目前有很多解决方案了,可以参考 http://www.open-open.com/news/view/1fc3e18 ,对于office在线阅读,目前。我的办法是,先转换为PDF,然后使用pdf在线阅读的方式进行浏览。目前好像很多站点都是这么做的。
然而,我在项目中,这类文档在线阅读方案是这样的。OFFICE --> PDF ---> SWF --> PNG 然后用户通过预览图片的方式去查看文档。
这样做的目的就是,可以让尽可能多的平台直接观看文档,因为任何一个平台都能很好的支持PNG图片预览,但这样做,又产生了新的问题。
1、office 转成PDF 需要用安装office2007 + SaveAsPDFandXPS加载项 亦或是使用office 2010 。后则直接集成了这个加载项。让一个服务器安装一套office 然后功能仅仅用到的是将office转换为pdf。这个代价是比较大的。office还是不便宜的。
2、pdf 转换为 swf ,好在目前有现成的工具。(不需要UI界面)
3、swf 转换为 png ,目前也有现场的工具。(不需要UI界面)
4、因为这一系列过程才能正常的转换一个文档。而这样的工具,最适合的形态是windows 服务。这样不管如何,只要用户上传了文档,我们就可以进行转换。然而,理想是美好的。当我尝试将这个过程变成服务的过程中,遇到了新的问题。windows xp之后,服务是不能直接调用Application(ExE) 的。而将office转换为pdf,其原理是打开文档,使用另存为按钮,将它保存为pdf。而service 不支持图形界面的操作。在此,笔者查尽了资料。最后在CodeProject上找到了答案。原文我就不列举了,因为懒所以没有做笔记的习惯。代码如下:
1 public class ProcessStarter : IDisposable 2 { 3 #region Import Section 4 5 private static uint STANDARD_RIGHTS_REQUIRED = 0x000F0000; 6 private static uint STANDARD_RIGHTS_READ = 0x00020000; 7 private static uint TOKEN_ASSIGN_PRIMARY = 0x0001; 8 private static uint TOKEN_DUPLICATE = 0x0002; 9 private static uint TOKEN_IMPERSONATE = 0x0004; 10 private static uint TOKEN_QUERY = 0x0008; 11 private static uint TOKEN_QUERY_SOURCE = 0x0010; 12 private static uint TOKEN_ADJUST_PRIVILEGES = 0x0020; 13 private static uint TOKEN_ADJUST_GROUPS = 0x0040; 14 private static uint TOKEN_ADJUST_DEFAULT = 0x0080; 15 private static uint TOKEN_ADJUST_SESSIONID = 0x0100; 16 private static uint TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY); 17 private static uint TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID); 18 19 private const uint NORMAL_PRIORITY_CLASS = 0x0020; 20 21 private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400; 22 23 24 private const uint MAX_PATH = 260; 25 26 private const uint CREATE_NO_WINDOW = 0x08000000; 27 28 private const uint INFINITE = 0xFFFFFFFF; 29 30 [StructLayout(LayoutKind.Sequential)] 31 public struct SECURITY_ATTRIBUTES 32 { 33 public int nLength; 34 public IntPtr lpSecurityDescriptor; 35 public int bInheritHandle; 36 } 37 38 public enum SECURITY_IMPERSONATION_LEVEL 39 { 40 SecurityAnonymous, 41 SecurityIdentification, 42 SecurityImpersonation, 43 SecurityDelegation 44 } 45 46 public enum TOKEN_TYPE 47 { 48 TokenPrimary = 1, 49 TokenImpersonation 50 } 51 52 public enum WTS_CONNECTSTATE_CLASS 53 { 54 WTSActive, 55 WTSConnected, 56 WTSConnectQuery, 57 WTSShadow, 58 WTSDisconnected, 59 WTSIdle, 60 WTSListen, 61 WTSReset, 62 WTSDown, 63 WTSInit 64 } 65 66 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 67 public struct STARTUPINFO 68 { 69 public Int32 cb; 70 public string lpReserved; 71 public string lpDesktop; 72 public string lpTitle; 73 public Int32 dwX; 74 public Int32 dwY; 75 public Int32 dwXSize; 76 public Int32 dwYSize; 77 public Int32 dwXCountChars; 78 public Int32 dwYCountChars; 79 public Int32 dwFillAttribute; 80 public Int32 dwFlags; 81 public Int16 wShowWindow; 82 public Int16 cbReserved2; 83 public IntPtr lpReserved2; 84 public IntPtr hStdInput; 85 public IntPtr hStdOutput; 86 public IntPtr hStdError; 87 } 88 89 [StructLayout(LayoutKind.Sequential)] 90 internal struct PROCESS_INFORMATION 91 { 92 public IntPtr hProcess; 93 public IntPtr hThread; 94 public int dwProcessId; 95 public int dwThreadId; 96 } 97 98 [StructLayout(LayoutKind.Sequential)] 99 private struct WTS_SESSION_INFO 100 { 101 public Int32 SessionID; 102 103 [MarshalAs(UnmanagedType.LPStr)] 104 public String pWinStationName; 105 106 public WTS_CONNECTSTATE_CLASS State; 107 } 108 109 [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 110 static extern uint WTSGetActiveConsoleSessionId(); 111 112 [DllImport("wtsapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] 113 static extern bool WTSQueryUserToken(int sessionId, out IntPtr tokenHandle); 114 115 [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] 116 public extern static bool DuplicateTokenEx(IntPtr existingToken, uint desiredAccess, IntPtr tokenAttributes, SECURITY_IMPERSONATION_LEVEL impersonationLevel, TOKEN_TYPE tokenType, out IntPtr newToken); 117 118 [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] 119 static extern bool CreateProcessAsUser(IntPtr token, string applicationName, string commandLine, ref SECURITY_ATTRIBUTES processAttributes, ref SECURITY_ATTRIBUTES threadAttributes, bool inheritHandles, uint creationFlags, IntPtr environment, string currentDirectory, ref STARTUPINFO startupInfo, out PROCESS_INFORMATION processInformation); 120 121 [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 122 static extern bool CloseHandle(IntPtr handle); 123 124 [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 125 static extern int GetLastError(); 126 127 [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 128 static extern int WaitForSingleObject(IntPtr token, uint timeInterval); 129 130 [DllImport("wtsapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] 131 static extern int WTSEnumerateSessions(System.IntPtr hServer, int Reserved, int Version, ref System.IntPtr ppSessionInfo, ref int pCount); 132 133 [DllImport("userenv.dll", CharSet = CharSet.Auto, SetLastError = true)] 134 static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit); 135 136 [DllImport("wtsapi32.dll", ExactSpelling = true, SetLastError = false)] 137 public static extern void WTSFreeMemory(IntPtr memory); 138 139 [DllImport("userenv.dll", SetLastError = true)] 140 [return: MarshalAs(UnmanagedType.Bool)] 141 static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment); 142 143 #endregion 144 145 public ProcessStarter() 146 { 147 148 } 149 150 public ProcessStarter(string processName, string fullExeName) 151 { 152 processName_ = processName; 153 processPath_ = fullExeName; 154 } 155 public ProcessStarter(string processName, string fullExeName, string arguments) 156 { 157 processName_ = processName; 158 processPath_ = fullExeName; 159 arguments_ = arguments; 160 } 161 162 public static IntPtr GetCurrentUserToken() 163 { 164 IntPtr currentToken = IntPtr.Zero; 165 IntPtr primaryToken = IntPtr.Zero; 166 IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero; 167 168 int dwSessionId = 0; 169 IntPtr hUserToken = IntPtr.Zero; 170 IntPtr hTokenDup = IntPtr.Zero; 171 172 IntPtr pSessionInfo = IntPtr.Zero; 173 int dwCount = 0; 174 175 WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref dwCount); 176 177 Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO)); 178 179 Int32 current = (int)pSessionInfo; 180 for (int i = 0; i < dwCount; i++) 181 { 182 WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTS_SESSION_INFO)); 183 if (WTS_CONNECTSTATE_CLASS.WTSActive == si.State) 184 { 185 dwSessionId = si.SessionID; 186 break; 187 } 188 189 current += dataSize; 190 } 191 192 WTSFreeMemory(pSessionInfo); 193 194 bool bRet = WTSQueryUserToken(dwSessionId, out currentToken); 195 if (bRet == false) 196 { 197 return IntPtr.Zero; 198 } 199 200 bRet = DuplicateTokenEx(currentToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out primaryToken); 201 if (bRet == false) 202 { 203 return IntPtr.Zero; 204 } 205 206 return primaryToken; 207 } 208 209 public void Run() 210 { 211 212 IntPtr primaryToken = GetCurrentUserToken(); 213 if (primaryToken == IntPtr.Zero) 214 { 215 return; 216 } 217 STARTUPINFO StartupInfo = new STARTUPINFO(); 218 processInfo_ = new PROCESS_INFORMATION(); 219 StartupInfo.cb = Marshal.SizeOf(StartupInfo); 220 221 SECURITY_ATTRIBUTES Security1 = new SECURITY_ATTRIBUTES(); 222 SECURITY_ATTRIBUTES Security2 = new SECURITY_ATTRIBUTES(); 223 224 string command = """ + processPath_ + """; 225 if ((arguments_ != null) && (arguments_.Length != 0)) 226 { 227 command += " " + arguments_; 228 } 229 230 IntPtr lpEnvironment = IntPtr.Zero; 231 bool resultEnv = CreateEnvironmentBlock(out lpEnvironment, primaryToken, false); 232 if (resultEnv != true) 233 { 234 int nError = GetLastError(); 235 } 236 237 CreateProcessAsUser(primaryToken, null, command, ref Security1, ref Security2, false, CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT, lpEnvironment, null, ref StartupInfo, out processInfo_); 238 239 DestroyEnvironmentBlock(lpEnvironment); 240 CloseHandle(primaryToken); 241 } 242 243 public void Stop() 244 { 245 Process[] processes = Process.GetProcesses(); 246 foreach (Process current in processes) 247 { 248 if (current.ProcessName == processName_) 249 { 250 current.Kill(); 251 } 252 } 253 } 254 255 public int WaitForExit() 256 { 257 WaitForSingleObject(processInfo_.hProcess, INFINITE); 258 int errorcode = GetLastError(); 259 return errorcode; 260 } 261 262 #region IDisposable Members 263 264 public void Dispose() 265 { 266 } 267 268 #endregion 269 270 private string processPath_ = string.Empty; 271 private string processName_ = string.Empty; 272 private string arguments_ = string.Empty; 273 private PROCESS_INFORMATION processInfo_; 274 275 public string ProcessPath 276 { 277 get 278 { 279 return processPath_; 280 } 281 set 282 { 283 processPath_ = value; 284 } 285 } 286 287 public string ProcessName 288 { 289 get 290 { 291 return processName_; 292 } 293 set 294 { 295 processName_ = value; 296 } 297 } 298 299 public string Arguments 300 { 301 get 302 { 303 return arguments_; 304 } 305 set 306 { 307 arguments_ = value; 308 } 309 } 310 }
其调用方式,如下:
1 ProcessStarter ps = new ProcessStarter(RunProcessName, "Worker.exe"); 2 ps.Run();
第一个参数,是外壳程序,也就是说,当系统登录的时候一定会存在的进程。也就是Login之后,当前用户一定会存在的进程。
第二个参数,是我们需要运行的Application。
这样就可以在windows service中Call到Application了。
到了这里问题好像解决了。但是,我发现服务器必须需要一个用户登录进去,Office 才能转换为PDF。分析了很久的原因。后来突然有一天发现。以为没有Login之前。是没有UI界面的。在没有UI界面之前是不可能打开Office的。所以此路不通!!!
于是乎,继续寻找解决办法。就在昨天,我发现了新的解决办法。但还没有去尝试。如果有人有现场的解决办法,希望能拿出来分享下。
1、Aspose.words.dll 听说这玩意儿可以在不安装OFFICE的情况下将word转换为pdf。目前我没有验证。但,仅仅只能将word转换为pdf还是不够的,我们还有 ppt excel。再者版本问题也是个问题。是否支持 Office 2003 和 Office 2007以上版本。
2、OpenOffice 这个家伙是阿帕奇搞的。听说可以不依靠office的情况下处理这些。但是太大了。我也没来得及去详细的查阅资料。
最后,期待第二篇日志可以解决以上问题。让office文档能在脱离安装office的情况下完美转换。