Skip to main content

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:

Add-in menu{ 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

Browse for save file path{ 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