刚去的新公司分配了我一个项目需求,将PPT文件(包括*.ppt和*.pptx)转换成多张png图片。由于以前只有native C++的经验,在网上逛了多圈后,发现都是使用C#实现这个功能的,被这个需求折磨了几天后,今天终于把这个问题解决了。所以在此记录下解决这个项目需求的总结。
C#的程序代码在非托管的C++环境中使用有三种方式:1.平台调用技术(P/Invoke)。2. C++ Interop。3.COM Interop。
我使用的是C++ Interop方法,也就是使用C++/CLI语法将C#的动态库封装成C++/CLI的动态库,然后在native C++程序中就可以调用C++/CLI的动态库了。以下是我的一些做法:
1.我使用的IDE工具是VS2015,从网上找到了一份PPT转化成图片的C#动态库。链接如下:https://www.cnblogs.com/kksguijiao/articles/9099103.html
将这份C#的代码进行了一些简单的修改符合了我的需求。修改后的代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Aspose.Cells;
using ESBasic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
//using System.Threading.Tasks;
// 这个是去掉水印的
using System.Runtime.CompilerServices;
using System.Reflection;
using System.Xml;
namespace LicenseHelper
{
public static class ModifyInMemory
{
private static string AsposeList = "Aspose.3D.dll, Aspose.BarCode.dll, Aspose.BarCode.Compact.dll, Aspose.BarCode.WPF.dll, Aspose.Cells.GridDesktop.dll, Aspose.Cells.GridWeb.dll, Aspose.CAD.dll, Aspose.Cells.dll, Aspose.Diagram.dll, Aspose.Email.dll, Aspose.Imaging.dll, Aspose.Note.dll, Aspose.OCR.dll, Aspose.Pdf.dll, Aspose.Slides.dll, Aspose.Tasks.dll, Aspose.Words.dll";
public static void ActivateMemoryPatching()
{
Assembly[] arr = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in arr)
{
if (AsposeList.IndexOf(assembly.FullName.Split(',')[0] + ".dll") != -1)
ActivateForAssembly(assembly);
}
AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(ActivateOnLoad);
}
private static void ActivateOnLoad(object sender, AssemblyLoadEventArgs e)
{
if (AsposeList.IndexOf(e.LoadedAssembly.FullName.Split(',')[0] + ".dll") != -1)
ActivateForAssembly(e.LoadedAssembly);
}
private static void ActivateForAssembly(Assembly assembly)
{
MethodInfo miLicensed1 = typeof(ModifyInMemory).GetMethod("InvokeMe1", BindingFlags.NonPublic | BindingFlags.Static);
MethodInfo miLicensed2 = typeof(ModifyInMemory).GetMethod("InvokeMe2", BindingFlags.NonPublic | BindingFlags.Static);
MethodInfo miEvaluation = null;
Dictionary miDict = new Dictionary()
{
{"System.DateTime" , miLicensed1},
{"System.Xml.XmlElement", miLicensed2}
};
Type[] arrType = null;
bool isFound = false;
int nCount = 0;
try
{
arrType = assembly.GetTypes();
}
catch (ReflectionTypeLoadException err)
{
arrType = err.Types;
}
foreach (Type type in arrType)
{
if (isFound) break;
if (type == null) continue;
MethodInfo[] arrMInfo = type.GetMethods(BindingFlags.NonPublic | BindingFlags.Static);
foreach (MethodInfo info in arrMInfo)
{
if (isFound) break;
try
{
string strMethod = info.ToString();
if ((strMethod.IndexOf("(System.Xml.XmlElement, System.String)") > 0) && (miDict.ContainsKey(info.ReturnType.ToString())))
{
miEvaluation = info;
MemoryPatching(miEvaluation, miDict[miEvaluation.ReturnType.ToString()]);
nCount++;
if (((assembly.FullName.IndexOf("Aspose.Pdf") == -1) && (nCount == 2)) ||
((assembly.FullName.IndexOf("Aspose.Pdf") != -1) && (nCount == 6)))
{
isFound = true;
break;
}
}
}
catch
{
throw new InvalidOperationException("MemoryPatching for \"" + assembly.FullName + "\" failed !");
}
}
}
String[] aParts = assembly.FullName.Split(',');
string fName = aParts[0];
if (fName.IndexOf("Aspose.BarCode.") != -1)
fName = "Aspose.BarCode";
else if (fName.IndexOf("Aspose.3D") != -1)
fName = "Aspose.ThreeD";
try
{
Type type2 = assembly.GetType(fName + ".License");
MethodInfo mi = type2.GetMethod("SetLicense", new Type[] { typeof(Stream) });
string LData = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPExpY2Vuc2U+CiAgPERhdGE+CiAgICA8TGljZW5zZWRUbz5MaWNlbnNlZTwvTGljZW5zZWRUbz4KICAgIDxFbWFpbFRvPmxpY2Vuc2VlQGVtYWlsLmNvbTwvRW1haWxUbz4KICAgIDxMaWNlbnNlVHlwZT5EZXZlbG9wZXIgT0VNPC9MaWNlbnNlVHlwZT4KICAgIDxMaWNlbnNlTm90ZT5MaW1pdGVkIHRvIDEwMDAgZGV2ZWxvcGVyLCB1bmxpbWl0ZWQgcGh5c2ljYWwgbG9jYXRpb25zPC9MaWNlbnNlTm90ZT4KICAgIDxPcmRlcklEPjc4NDM3ODU3Nzg1PC9PcmRlcklEPgogICAgPFVzZXJJRD4xMTk3ODkyNDM3OTwvVXNlcklEPgogICAgPE9FTT5UaGlzIGlzIGEgcmVkaXN0cmlidXRhYmxlIGxpY2Vuc2U8L09FTT4KICAgIDxQcm9kdWN0cz4KICAgICAgPFByb2R1Y3Q+QXNwb3NlLlRvdGFsIFByb2R1Y3QgRmFtaWx5PC9Qcm9kdWN0PgogICAgPC9Qcm9kdWN0cz4KICAgIDxFZGl0aW9uVHlwZT5FbnRlcnByaXNlPC9FZGl0aW9uVHlwZT4KICAgIDxTZXJpYWxOdW1iZXI+e0YyQjk3MDQ1LTFCMjktNEIzRi1CRDUzLTYwMUVGRkExNUFBOX08L1NlcmlhbE51bWJlcj4KICAgIDxTdWJzY3JpcHRpb25FeHBpcnk+MjA5OTEyMzE8L1N1YnNjcmlwdGlvbkV4cGlyeT4KICAgIDxMaWNlbnNlVmVyc2lvbj4zLjA8L0xpY2Vuc2VWZXJzaW9uPgogIDwvRGF0YT4KICA8U2lnbmF0dXJlPlFYTndiM05sTGxSdmRHRnNJRkJ5YjJSMVkzUWdSbUZ0YVd4NTwvU2lnbmF0dXJlPgo8L0xpY2Vuc2U+";
Stream stream = new MemoryStream(Convert.FromBase64String(LData));
stream.Seek(0, SeekOrigin.Begin);
mi.Invoke(Activator.CreateInstance(type2, null), new Stream[] { stream });
}
catch
{
//throw new InvalidOperationException("SetLicense for \"" + assembly.FullName + "\" failed !");
}
}
private static DateTime InvokeMe1(XmlElement element, string name)
{
return DateTime.MaxValue;
}
private static XmlElement InvokeMe2(XmlElement element, string name)
{
if (element.LocalName == "License")
{
string License64 = "PERhdGE+PExpY2Vuc2VkVG8+R3JvdXBEb2NzPC9MaWNlbnNlZFRvPjxMaWNlbnNlVHlwZT5TaXRlIE9FTTwvTGljZW5zZVR5cGU+PExpY2Vuc2VOb3RlPkxpbWl0ZWQgdG8gMTAgZGV2ZWxvcGVyczwvTGljZW5zZU5vdGU+PE9yZGVySUQ+MTMwNzI0MDQwODQ5PC9PcmRlcklEPjxPRU0+VGhpcyBpcyBhIHJlZGlzdHJpYnV0YWJsZSBsaWNlbnNlPC9PRU0+PFByb2R1Y3RzPjxQcm9kdWN0PkFzcG9zZS5Ub3RhbDwvUHJvZHVjdD48L1Byb2R1Y3RzPjxFZGl0aW9uVHlwZT5FbnRlcnByaXNlPC9FZGl0aW9uVHlwZT48U2VyaWFsTnVtYmVyPjliNTc5NTAxLTUyNjEtNDIyMC04NjcwLWZjMmQ4Y2NkZDkwYzwvU2VyaWFsTnVtYmVyPjxTdWJzY3JpcHRpb25FeHBpcnk+MjAxNDA3MjQ8L1N1YnNjcmlwdGlvbkV4cGlyeT48TGljZW5zZVZlcnNpb24+Mi4yPC9MaWNlbnNlVmVyc2lvbj48L0RhdGE+PFNpZ25hdHVyZT5udFpocmRoL3I0QS81ZFpsU2dWYnhac0hYSFBxSjZ5UVVYa0RvaW4vS2lVZWhUUWZET0lQdHdzUlR2NmRTUVplOVdXekNnV3RGdkdROWpmR2QySmF4YUQvbkx1ZEk2R0VVajhqeVhUMG4vbWRrMEF1WVZNYlBXRjJYd3dSTnFlTmRrblYyQjhrZVFwbDJ2RzZVbnhxS2J6VVFxS2Rhc1pzZ2w1Q0xqSFVEWms9PC9TaWduYXR1cmU+";
element.InnerXml = new UTF8Encoding().GetString(Convert.FromBase64String(License64));
}
if (element.LocalName == "BlackList")
{
string BlackList64 = "PERhdGE+PC9EYXRhPjxTaWduYXR1cmU+cUJwMEx1cEVoM1ZnOWJjeS8vbUVXUk9KRWZmczRlY25iTHQxYlNhanU2NjY5RHlad09FakJ1eEdBdVBxS1hyd0x5bmZ5VWplYUNGQ0QxSkh2RVUxVUl5eXJOTnBSMXc2NXJIOUFyUCtFbE1lVCtIQkZ4NFMzckFVMnd6dkxPZnhGeU9DQ0dGQ2UraTdiSHlGQk44WHp6R1UwdGRPMGR1RTFoRTQ5M1RNY3pRPTwvU2lnbmF0dXJlPg==";
element.InnerXml = new UTF8Encoding().GetString(Convert.FromBase64String(BlackList64));
}
XmlNodeList elementsByTagName = element.GetElementsByTagName(name);
if (elementsByTagName.Count <= 0)
{
return null;
}
return (XmlElement)elementsByTagName[0];
}
private static unsafe void MemoryPatching(MethodBase miEvaluation, MethodBase miLicensed)
{
IntPtr IntPtrEval = GetMemoryAddress(miEvaluation);
IntPtr IntPtrLicensed = GetMemoryAddress(miLicensed);
if (IntPtr.Size == 8)
*((long*)IntPtrEval.ToPointer()) = *((long*)IntPtrLicensed.ToPointer());
else
*((int*)IntPtrEval.ToPointer()) = *((int*)IntPtrLicensed.ToPointer());
}
private static unsafe IntPtr GetMemoryAddress(MethodBase mb)
{
RuntimeHelpers.PrepareMethod(mb.MethodHandle);
if ((Environment.Version.Major >= 4) || ((Environment.Version.Major == 2) && (Environment.Version.MinorRevision >= 3053)))
{
return new IntPtr(((int*)mb.MethodHandle.Value.ToPointer() + 2));
}
UInt64* location = (UInt64*)(mb.MethodHandle.Value.ToPointer());
int index = (int)(((*location) >> 32) & 0xFF);
if (IntPtr.Size == 8)
{
ulong* classStart = (ulong*)mb.DeclaringType.TypeHandle.Value.ToPointer();
ulong* address = classStart + index + 10;
return new IntPtr(address);
}
else
{
uint* classStart = (uint*)mb.DeclaringType.TypeHandle.Value.ToPointer();
uint* address = classStart + index + 10;
return new IntPtr(address);
}
}
}
}
namespace pptToImage
{
public class Class1
{
///
/// 将PPT、Excel、Txt文档转换为图片的方法
///
// ppt文件路径
// 图片输出路径,如果为空,默认值为pdf所在路径
// 从PDF文档的第几页开始转换,如果为0,默认值为1
// 从PDF文档的第几页开始停止转换,如果为0,默认值为pdf总页数
// 设置图片的像素,数字越大越清晰,如果为0,默认值为128,建议最大值不要超过1024
public void ConvertToImage_PPT(string originFilePath)
{
string imageOutputDirPath = string.Empty;
int startPageNum = 0, endPageNum = 0, resolution = 0;
string Format = Path.GetExtension(originFilePath).ToLower();
try
{
//先将文件转换为pdf临时文件
string tmpPdfPath = originFilePath.Replace(Format, ".pdf");
if (Format == ".xls" || Format == ".xlsx" || Format == ".txt")
{
Workbook wb = new Workbook(originFilePath);
wb.Save(tmpPdfPath, SaveFormat.Pdf);
}
else
{
Aspose.Slides.Presentation doc = new Aspose.Slides.Presentation(originFilePath);
if (doc == null)
{
throw new Exception("ppt文件无效或者ppt文件被加密!");
}
if (imageOutputDirPath.Trim().Length == 0)
{
imageOutputDirPath = Path.GetDirectoryName(originFilePath);
}
if (!Directory.Exists(imageOutputDirPath))
{
Directory.CreateDirectory(imageOutputDirPath);
}
if (startPageNum <= 0)
{
startPageNum = 1;
}
if (endPageNum > doc.Slides.Count || endPageNum <= 0)
{
endPageNum = doc.Slides.Count;
}
if (startPageNum > endPageNum)
{
int tempPageNum = startPageNum; startPageNum = endPageNum; endPageNum = startPageNum;
}
if (resolution <= 0)
{
resolution = 128;
}
if (doc != null)
{
doc.Save(tmpPdfPath, Aspose.Slides.Export.SaveFormat.Pdf);
}
}
//再将pdf转换为图片
ConvertToImage_PDF(tmpPdfPath, imageOutputDirPath, startPageNum, endPageNum, resolution);
//删除pdf临时文件
File.Delete(tmpPdfPath);
return ;
}
catch (Exception ex)
{
//ErrorHandler.WriteError(ex);
//IErrorHandler.WriteError(ex);
//IErrorHandler.WriteError(ex);
Console.WriteLine(ex);
return ;
throw;
}
}
///
/// 将pdf文档转换为图片的方法
///
// pdf文件路径
// 图片输出路径,如果为空,默认值为pdf所在路径
// 从PDF文档的第几页开始转换,如果为0,默认值为1
// 从PDF文档的第几页开始停止转换,如果为0,默认值为pdf总页数
// 设置图片的像素,数字越大越清晰,如果为0,默认值为128,建议最大值不要超过1024
public void ConvertToImage_PDF(string originFilePath, string imageOutputDirPath, int startPageNum, int endPageNum, int resolution)
{
try
{
Aspose.Pdf.Document doc = new Aspose.Pdf.Document(originFilePath);
if (doc == null)
{
throw new Exception("pdf文件无效或者pdf文件被加密!");
}
if (imageOutputDirPath.Trim().Length == 0)
{
imageOutputDirPath = Path.GetDirectoryName(originFilePath);
}
if (!Directory.Exists(imageOutputDirPath))
{
Directory.CreateDirectory(imageOutputDirPath);
}
if (startPageNum <= 0)
{
startPageNum = 1;
}
if (endPageNum > doc.Pages.Count || endPageNum <= 0)
{
endPageNum = doc.Pages.Count;
}
if (startPageNum > endPageNum)
{
int tempPageNum = startPageNum; startPageNum = endPageNum; endPageNum = startPageNum;
}
if (resolution <= 0)
{
resolution = 128;
}
string imageNamePrefix = Path.GetFileNameWithoutExtension(originFilePath).ToLower();
string imgName = string.Empty;
string newPath = Path.Combine(imageOutputDirPath, imageNamePrefix) + "/";
// 创建目录路径
if (!Directory.Exists(newPath))
{
Directory.CreateDirectory(newPath);
}
for (int i = startPageNum; i <= endPageNum; i++)
{
MemoryStream stream = new MemoryStream();
//string imgPath = Path.Combine(imageOutputDirPath, imageNamePrefix) + "_" + i.ToString("000") + ".jpg";
string imgPath = newPath + (i-1).ToString("0") + ".png";
// imgName += imgUrl + "/" + imageNamePrefix + "_" + i.ToString("000") + ".jpg|";
imgName += newPath + (i-1).ToString("0") + ".png|";
Aspose.Pdf.Devices.Resolution reso = new Aspose.Pdf.Devices.Resolution(resolution);
Aspose.Pdf.Devices.JpegDevice jpegDevice = new Aspose.Pdf.Devices.JpegDevice(reso, 100);
jpegDevice.Process(doc.Pages[i], stream);
Image img = Image.FromStream(stream);
Bitmap bm = ESBasic.Helpers.ImageHelper.Zoom(img, 0.6f);
bm.Save(imgPath, ImageFormat.Png);
img.Dispose();
stream.Dispose();
bm.Dispose();
System.Threading.Thread.Sleep(200);
}
if (!string.IsNullOrEmpty(imgName))
{
imgName = imgName.Substring(0, imgName.Length - 1);
}
return ;
}
catch (Exception ex)
{
// ErrorHandler.WriteError(ex);
Console.WriteLine(ex);
throw;
}
}
}
}
在上面链接中下载Aspose的dll动态库,把这些动态库和我修改的代码一起编译,生成pptToImage.dll的C#动态库。
2. 新建C++/CLI的类库项目。截图如下:
我的C++/CLI的类库代码如下:
// ToCppDll_CsharpDll.h
#pragma once
//namespace ToCppDll_CsharpDll {
//
// public ref class Class1
// {
// // TODO: 在此处添加此类的方法。
//
// };
//}
extern "C" __declspec(dllexport)
void ConvertToImage_PPT(char* originFilePath);
// 这是主 DLL 文件。
#include "stdafx.h"
#include "ToCppDll_CsharpDll.h"
#include
using namespace System;
using namespace Runtime::InteropServices;
using namespace pptToImage;
using namespace LicenseHelper;
__declspec(dllexport)
void ConvertToImage_PPT(char* originFilePath)
{
LicenseHelper::ModifyInMemory::ActivateMemoryPatching();
Class1 ^cls = gcnew Class1();
String ^inputPath = gcnew String(originFilePath);
cls->ConvertToImage_PPT(inputPath);
}
编译以上代码,会生成ToCppDll_CsharpDll.dll,ToCppDll_CsharpDll.lib的动态库,在非托管 C++的环境中就可以使用这两个库了,当然上面生成的C#动态库和Aspose的动态库也都是需要。这个C++/CLI的ToCppDll_CsharpDll库相当于一个桥梁的作用,连接非托管的C++环境和.net 应用程序环境。这一步也是对C#的动态库进行封装,封装成了一个C++/CLI的动态库,非托管的C++程序中能够像调用普通的C/C++库一样调用这个库。
3. 这个是测试主程序了,不需要设置/clr。代码如下:
#include
#include
#include "../CppCliInclude/ToCppDll_CsharpDll.h"
//#pragma comment(lib,"ToCppDll_CsharpDll.lib")
int main(int argc, char *argv[])
{
char *pTemp = "C:/Users/tz100/Desktop/title.ppt";
ConvertToImage_PPT(pTemp);
getchar();
return 0;
}
由于是刚刚接触这些C#,托管C++/CLI语言和VS IDE环境,导致我走了太多的弯路,浪费了许多的时间,心累了多次,终于有了上面的一点点收获,这或许是对自己最大的勉励吧。
注意事项:
1> 编译程序的时候注意引用C#动态库,C++/CLI库。
2> 如果还有问题先自己上网查查,实在解决不了再留言吧。