Serialize file content in model 3rd party storage using SOLIDWORKS API
This example demonstrates how to use 3rd Party Storage in SOLIDWORKS API to embed and extract the file content directly into the model stream.
Example SOLIDWORKS add-in is built using the SwEx.AddIn framework but it could work with any other methods of creating the add-ins.
Add-in adds two buttons in the menu and toolbar and provides two handlers correspondingly:
{ width=400 }
- AddFile - asynchronous method to store the embed file data in the stream. This method asks user to select the file, reads its content and serializes it into a file stream.
- LoadFile - loads the embedded file from the stream and prompts user to select the file path to store the content. The file name is prepopulated based on the embedded file name
{ width=550 }
Usage Instructions
- Open any model (model must be saved to a disk)
- Click "AddFile" button. File browse dialog is displayed. Select any file. File data is serialized into the model and message box is displayed.
- You can close the model and SOLIDWORKS
- Reopen the model and click "LoadFile". File data is deserialized from the model and File Save As dialog is displayed (name is populated based on the embedded file name). File is saved to the selected location
EmbedFileAddIn.vb
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Windows.Forms
Imports CodeStack.SwEx.AddIn
Imports CodeStack.SwEx.AddIn.Attributes
Imports SolidWorks.Interop.swconst
<ComVisible(True), Guid("E54E85ED-B8AE-434D-B616-7D691527A429")>
<AutoRegister("EmbedFileAddIn", "Sample Demonstrating use of 3rd party store")>
Partial Public Class EmbedFile
Inherits SwAddInEx
Private Const STREAM_NAME As String = "CodeStack.EmbedFile"
<CodeStack.SwEx.Common.Attributes.Title("Embed File")>
Public Enum Commands_e
AddFile
LoadFile
End Enum
Public Overrides Function OnConnect() As Boolean
AddCommandGroup(Of Commands_e)(AddressOf OnButtonClick)
Return True
End Function
Private Async Sub OnButtonClick(ByVal cmd As Commands_e)
Select Case cmd
Case Commands_e.AddFile
Await SaveFile()
Case Commands_e.LoadFile
LoadFile()
End Select
End Sub
Private Async Function SaveFile() As Task
Try
Dim fileToSave As String = BrowseFile(True)
If Not String.IsNullOrEmpty(fileToSave) Then
Dim embedData As New EmbedFileData()
embedData.FileName = Path.GetFileName(fileToSave)
embedData.Content = File.ReadAllBytes(fileToSave)
Await SaveDataToDocument(App.IActiveDoc2, embedData)
App.SendMsgToUser2("Data saved", swMessageBoxIcon_e.swMbInformation, swMessageBoxBtn_e.swMbOk)
End If
Catch ex As Exception
App.SendMsgToUser2(ex.Message, swMessageBoxIcon_e.swMbStop, swMessageBoxBtn_e.swMbOk)
End Try
End Function
Private Sub LoadFile()
Try
Dim embedData = ReadDataFromDocument(App.IActiveDoc2)
Dim fileToSave As String = BrowseFile(False, embedData.FileName)
If Not String.IsNullOrEmpty(fileToSave) Then
File.WriteAllBytes(fileToSave, embedData.Content)
End If
Catch ex As Exception
App.SendMsgToUser2(ex.Message, swMessageBoxIcon_e.swMbStop, swMessageBoxBtn_e.swMbOk)
End Try
End Sub
Function BrowseFile(isOpen As Boolean, Optional fileName As String = "") As String
Dim fileDlg As FileDialog
If isOpen Then
fileDlg = New OpenFileDialog()
Else
fileDlg = New SaveFileDialog()
End If
fileDlg.Title = "Select File"
fileDlg.Filter = "All files (*.*)|*.*"
fileDlg.FileName = fileName
If fileDlg.ShowDialog() = DialogResult.OK Then
Return fileDlg.FileName
Else
Return ""
End If
End Function
End Class
Structure used for serialization contains the content of the file and file name
EmbedFileData.vb
Public Class EmbedFileData
Public Property FileName As String
Public Property Content As Byte()
End Class
For simplicity IStream com stream is wrapped into the System.IO.Stream type.
ComStream.vb
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Runtime.InteropServices.ComTypes
Public Class ComStream
Inherits Stream
Private ReadOnly m_ComStream As IStream
Private ReadOnly m_Commit As Boolean
Private m_IsWritable As Boolean
Public Sub New(ByRef comStream As IStream, writable As Boolean, Optional commit As Boolean = True)
If comStream Is Nothing Then
Throw New ArgumentNullException(NameOf(comStream))
End If
m_ComStream = comStream
m_IsWritable = writable
m_Commit = commit
End Sub
Public Overrides ReadOnly Property CanRead() As Boolean
Get
Return True
End Get
End Property
Public Overrides ReadOnly Property CanSeek() As Boolean
Get
Return True
End Get
End Property
Public Overrides ReadOnly Property CanWrite() As Boolean
Get
Return m_IsWritable
End Get
End Property
Public Overrides ReadOnly Property Length As Long
Get
Const STATSFLAG_NONAME As Integer = 1
Dim stats As ComTypes.STATSTG = Nothing
m_ComStream.Stat(stats, STATSFLAG_NONAME)
Return stats.cbSize
End Get
End Property
Public Overrides Property Position() As Long
Get
Return Seek(0, SeekOrigin.Current)
End Get
Set(ByVal Value As Long)
Seek(Value, SeekOrigin.Begin)
End Set
End Property
Public Overrides Sub Flush()
If m_Commit Then
Const STGC_DEFAULT As Integer = 0
m_ComStream.Commit(STGC_DEFAULT)
End If
End Sub
Public Overrides Sub SetLength(ByVal Value As Long)
m_ComStream.SetSize(Value)
End Sub
Public Overrides Sub Write(buffer() As Byte, offset As Integer, count As Integer)
If offset <> 0 Then
Dim bufferSize As Integer
bufferSize = buffer.Length - offset
Dim tmpBuffer(bufferSize) As Byte
Array.Copy(buffer, offset, tmpBuffer, 0, bufferSize)
m_ComStream.Write(tmpBuffer, bufferSize, Nothing)
Else
m_ComStream.Write(buffer, count, Nothing)
End If
End Sub
Public Overrides Function Read(buffer() As Byte, offset As Integer, count As Integer) As Integer
Dim bytesRead As Integer = 0
Dim boxBytesRead As Object = bytesRead
Dim hObject As GCHandle
Try
hObject = GCHandle.Alloc(boxBytesRead, GCHandleType.Pinned)
Dim pBytesRead As IntPtr = hObject.AddrOfPinnedObject()
If offset <> 0 Then
Dim tmpBuffer(count - 1) As Byte
m_ComStream.Read(tmpBuffer, count, pBytesRead)
bytesRead = CInt(boxBytesRead)
Array.Copy(tmpBuffer, 0, buffer, offset, bytesRead)
Else
m_ComStream.Read(buffer, count, pBytesRead)
bytesRead = CInt(boxBytesRead)
End If
Finally
If hObject.IsAllocated Then
hObject.Free()
End If
End Try
Return bytesRead
End Function
Public Overrides Function Seek(offset As Long, origin As SeekOrigin) As Long
Dim curPosition As Long = 0
Dim boxCurPosition As Object = curPosition
Dim hObject As GCHandle
Try
hObject = GCHandle.Alloc(boxCurPosition, GCHandleType.Pinned)
Dim pCurPosition As IntPtr = hObject.AddrOfPinnedObject()
m_ComStream.Seek(offset, origin, pCurPosition)
curPosition = CLng(boxCurPosition)
Finally
If hObject.IsAllocated Then
hObject.Free()
End If
End Try
Return curPosition
End Function
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing Then
m_IsWritable = False
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub
Protected Overrides Sub Finalize()
Dispose(False)
End Sub
End Class
Serialization and deserialization routine utilizing the XmlSerializer class, but any other serialization methods could be used.
EmbedFile.vb
Imports System.Runtime.InteropServices.ComTypes
Imports System.Xml.Serialization
Imports SolidWorks.Interop.sldworks
Imports SolidWorks.Interop.swconst
Partial Public Class EmbedFile
Public Class ThirdPartyStreamNotFoundException
Inherits Exception
End Class
Private Async Function SaveDataToDocument(ByVal model As IModelDoc2, ByVal data As EmbedFileData) As Task
Dim err As Integer = -1
Dim warn As Integer = -1
model.SetSaveFlag()
Const S_OK As Integer = 0
Dim result As Boolean? = Nothing
Dim onSaveToStorageNotifyFunc = Function()
Try
StoreData(model, data, STREAM_NAME)
result = True
Catch
result = False
End Try
Return S_OK
End Function
Select Case CType(model.[GetType](), swDocumentTypes_e)
Case swDocumentTypes_e.swDocPART
AddHandler TryCast(model, PartDoc).SaveToStorageNotify, onSaveToStorageNotifyFunc
Case swDocumentTypes_e.swDocASSEMBLY
AddHandler TryCast(model, AssemblyDoc).SaveToStorageNotify, onSaveToStorageNotifyFunc
Case swDocumentTypes_e.swDocDRAWING
AddHandler TryCast(model, DrawingDoc).SaveToStorageNotify, onSaveToStorageNotifyFunc
End Select
If Not model.Save3(CInt(swSaveAsOptions_e.swSaveAsOptions_Silent), err, warn) Then
Throw New InvalidOperationException($"Failed to save the model: {CType(err, swFileSaveError_e)}")
End If
Await Task.Run(Sub()
While Not result.HasValue
Threading.Thread.Sleep(10)
End While
End Sub)
Select Case CType(model.GetType(), swDocumentTypes_e)
Case swDocumentTypes_e.swDocPART
RemoveHandler TryCast(model, PartDoc).SaveToStorageNotify, onSaveToStorageNotifyFunc
Case swDocumentTypes_e.swDocASSEMBLY
RemoveHandler TryCast(model, AssemblyDoc).SaveToStorageNotify, onSaveToStorageNotifyFunc
Case swDocumentTypes_e.swDocDRAWING
RemoveHandler TryCast(model, DrawingDoc).SaveToStorageNotify, onSaveToStorageNotifyFunc
End Select
If Not result.Value Then
Throw New Exception("Failed to store the data")
End If
End Function
Private Function ReadDataFromDocument(ByVal model As IModelDoc2) As EmbedFileData
Return ReadData(Of EmbedFileData)(model, STREAM_NAME)
End Function
Private Sub StoreData(Of T)(ByVal model As IModelDoc2, ByVal data As T, ByVal streamName As String)
Try
Dim stream = TryCast(model.IGet3rdPartyStorage(streamName, True), IStream)
Using comStr = New ComStream(stream, True, False)
comStr.Seek(0, IO.SeekOrigin.Begin)
Dim ser = New XmlSerializer(GetType(T))
ser.Serialize(comStr, data)
End Using
Catch
Throw
Finally
model.IRelease3rdPartyStorage(streamName)
End Try
End Sub
Private Function ReadData(Of T)(ByVal model As IModelDoc2, ByVal streamName As String) As T
Try
Dim stream = TryCast(model.IGet3rdPartyStorage(streamName, False), IStream)
If stream IsNot Nothing Then
Using comStr = New ComStream(stream, False)
comStr.Seek(0, IO.SeekOrigin.Begin)
Dim ser = New XmlSerializer(GetType(T))
Return CType(ser.Deserialize(comStr), T)
End Using
Else
Throw New ThirdPartyStreamNotFoundException()
End If
Catch
Throw
Finally
model.IRelease3rdPartyStorage(streamName)
End Try
End Function
End Class